Jelajahi Sumber

refactor: 落地 F2 UI DAO 去注入

5151561 3 bulan lalu
induk
melakukan
65424f81d0

+ 45 - 0
app/src/main/java/io/legado/app/data/repository/BookCacheManageRepository.kt

@@ -0,0 +1,45 @@
+package io.legado.app.data.repository
+
+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.entities.Book
+import io.legado.app.data.entities.BookGroup
+import io.legado.app.data.model.BookChapterCacheInfo
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.withContext
+
+class BookCacheManageRepository(
+    private val bookDao: BookDao,
+    private val bookChapterDao: BookChapterDao,
+    private val bookGroupDao: BookGroupDao,
+) {
+
+    fun flowBooks(): Flow<List<Book>> = bookDao.flowAll().flowOn(Dispatchers.IO)
+
+    fun flowSelectableGroups(): Flow<List<BookGroup>> =
+        bookGroupDao.flowSelect().flowOn(Dispatchers.IO)
+
+    suspend fun getAllBooks(): List<Book> = withContext(Dispatchers.IO) {
+        bookDao.all
+    }
+
+    suspend fun getBook(bookUrl: String): Book? = withContext(Dispatchers.IO) {
+        bookDao.getBook(bookUrl)
+    }
+
+    suspend fun getChapterCount(bookUrl: String): Int = withContext(Dispatchers.IO) {
+        bookChapterDao.getChapterCount(bookUrl)
+    }
+
+    suspend fun getVolumeCount(bookUrl: String): Int = withContext(Dispatchers.IO) {
+        bookChapterDao.getVolumeCount(bookUrl)
+    }
+
+    suspend fun getChapterCacheInfo(bookUrl: String): List<BookChapterCacheInfo> =
+        withContext(Dispatchers.IO) {
+            bookChapterDao.getChapterCacheInfoList(bookUrl)
+        }
+}

+ 6 - 0
app/src/main/java/io/legado/app/data/repository/BookRepository.kt

@@ -46,6 +46,12 @@ class BookRepository(
         }
         }
     }
     }
 
 
+    suspend fun getShelfBookConflict(name: String, author: String): Book? {
+        return withContext(Dispatchers.IO) {
+            bookDao.getShelfBookConflict(name, author)
+        }
+    }
+
     fun flowBookShelfByGroup(groupId: Long): Flow<List<BookShelfItem>> {
     fun flowBookShelfByGroup(groupId: Long): Flow<List<BookShelfItem>> {
         return bookDao.flowBookShelfByGroup(groupId)
         return bookDao.flowBookShelfByGroup(groupId)
     }
     }

+ 27 - 0
app/src/main/java/io/legado/app/data/repository/BookmarkRepository.kt

@@ -0,0 +1,27 @@
+package io.legado.app.data.repository
+
+import io.legado.app.data.dao.BookmarkDao
+import io.legado.app.data.entities.Bookmark
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.withContext
+
+class BookmarkRepository(
+    private val dao: BookmarkDao,
+) {
+
+    fun flowAll(): Flow<List<Bookmark>> = dao.flowAll().flowOn(Dispatchers.IO)
+
+    suspend fun getAll(): List<Bookmark> = withContext(Dispatchers.IO) {
+        dao.all
+    }
+
+    suspend fun save(bookmark: Bookmark) = withContext(Dispatchers.IO) {
+        dao.insert(bookmark)
+    }
+
+    suspend fun delete(bookmark: Bookmark) = withContext(Dispatchers.IO) {
+        dao.delete(bookmark)
+    }
+}

+ 54 - 40
app/src/main/java/io/legado/app/data/repository/ReplaceRuleRepository.kt

@@ -1,7 +1,7 @@
 package io.legado.app.data.repository
 package io.legado.app.data.repository
 
 
 import android.text.TextUtils
 import android.text.TextUtils
-import io.legado.app.data.appDb
+import io.legado.app.data.dao.ReplaceRuleDao
 import io.legado.app.data.entities.ReplaceRule
 import io.legado.app.data.entities.ReplaceRule
 import io.legado.app.utils.splitNotBlank
 import io.legado.app.utils.splitNotBlank
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Dispatchers
@@ -9,100 +9,110 @@ import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withContext
 
 
-class ReplaceRuleRepository {
+class ReplaceRuleRepository(
+    private val dao: ReplaceRuleDao,
+) {
 
 
     fun flowGroups(): Flow<List<String>> {
     fun flowGroups(): Flow<List<String>> {
-        return appDb.replaceRuleDao.flowGroups().flowOn(Dispatchers.IO)
+        return dao.flowGroups().flowOn(Dispatchers.IO)
     }
     }
 
 
     fun flowAll(): Flow<List<ReplaceRule>> {
     fun flowAll(): Flow<List<ReplaceRule>> {
-        return appDb.replaceRuleDao.flowAll().flowOn(Dispatchers.IO)
+        return dao.flowAll().flowOn(Dispatchers.IO)
     }
     }
 
 
     fun flowNoGroup(): Flow<List<ReplaceRule>> {
     fun flowNoGroup(): Flow<List<ReplaceRule>> {
-        return appDb.replaceRuleDao.flowNoGroup().flowOn(Dispatchers.IO)
+        return dao.flowNoGroup().flowOn(Dispatchers.IO)
     }
     }
 
 
     fun flowGroupSearch(key: String): Flow<List<ReplaceRule>> {
     fun flowGroupSearch(key: String): Flow<List<ReplaceRule>> {
-        return appDb.replaceRuleDao.flowGroupSearch(key).flowOn(Dispatchers.IO)
+        return dao.flowGroupSearch(key).flowOn(Dispatchers.IO)
     }
     }
 
 
     fun flowSearch(key: String): Flow<List<ReplaceRule>> {
     fun flowSearch(key: String): Flow<List<ReplaceRule>> {
-        return appDb.replaceRuleDao.flowSearch(key).flowOn(Dispatchers.IO)
+        return dao.flowSearch(key).flowOn(Dispatchers.IO)
+    }
+
+    suspend fun findById(id: Long): ReplaceRule? = withContext(Dispatchers.IO) {
+        dao.findById(id)
+    }
+
+    suspend fun getNextOrder(): Int = withContext(Dispatchers.IO) {
+        dao.maxOrder + 1
     }
     }
 
 
     suspend fun update(vararg rule: ReplaceRule) {
     suspend fun update(vararg rule: ReplaceRule) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            appDb.replaceRuleDao.update(*rule)
+            dao.update(*rule)
         }
         }
     }
     }
 
 
     suspend fun setEnabled(id: Long, enabled: Boolean) {
     suspend fun setEnabled(id: Long, enabled: Boolean) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            appDb.replaceRuleDao.updateEnabled(id, enabled)
+            dao.updateEnabled(id, enabled)
         }
         }
     }
     }
 
 
     suspend fun insert(vararg rule: ReplaceRule) {
     suspend fun insert(vararg rule: ReplaceRule) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            appDb.replaceRuleDao.insert(*rule)
+            dao.insert(*rule)
         }
         }
     }
     }
 
 
     suspend fun delete(rule: ReplaceRule) {
     suspend fun delete(rule: ReplaceRule) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            appDb.replaceRuleDao.delete(rule)
+            dao.delete(rule)
         }
         }
     }
     }
 
 
     suspend fun toTop(rule: ReplaceRule, isDesc: Boolean = false) {
     suspend fun toTop(rule: ReplaceRule, isDesc: Boolean = false) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
             if (isDesc) {
             if (isDesc) {
-                rule.order = appDb.replaceRuleDao.maxOrder + 1
+                rule.order = dao.maxOrder + 1
             } else {
             } else {
-                rule.order = appDb.replaceRuleDao.minOrder - 1
+                rule.order = dao.minOrder - 1
             }
             }
-            appDb.replaceRuleDao.update(rule)
+            dao.update(rule)
         }
         }
     }
     }
 
 
     suspend fun toBottom(rule: ReplaceRule, isDesc: Boolean = false) {
     suspend fun toBottom(rule: ReplaceRule, isDesc: Boolean = false) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
             if (isDesc) {
             if (isDesc) {
-                rule.order = appDb.replaceRuleDao.minOrder - 1
+                rule.order = dao.minOrder - 1
             } else {
             } else {
-                rule.order = appDb.replaceRuleDao.maxOrder + 1
+                rule.order = dao.maxOrder + 1
             }
             }
-            appDb.replaceRuleDao.update(rule)
+            dao.update(rule)
         }
         }
     }
     }
 
 
     suspend fun upOrder() {
     suspend fun upOrder() {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            val rules = appDb.replaceRuleDao.all
+            val rules = dao.all
             var normalOrder = 1
             var normalOrder = 1
             rules.forEach { rule ->
             rules.forEach { rule ->
                 if (rule.order >= 0) {
                 if (rule.order >= 0) {
                     rule.order = normalOrder++
                     rule.order = normalOrder++
                 }
                 }
             }
             }
-            appDb.replaceRuleDao.update(*rules.toTypedArray())
+            dao.update(*rules.toTypedArray())
         }
         }
     }
     }
 
 
     suspend fun addGroup(group: String) {
     suspend fun addGroup(group: String) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            val sources = appDb.replaceRuleDao.noGroup
+            val sources = dao.noGroup
             sources.forEach { source ->
             sources.forEach { source ->
                 source.group = group
                 source.group = group
             }
             }
-            appDb.replaceRuleDao.update(*sources.toTypedArray())
+            dao.update(*sources.toTypedArray())
         }
         }
     }
     }
 
 
     suspend fun upGroup(oldGroup: String, newGroup: String?) {
     suspend fun upGroup(oldGroup: String, newGroup: String?) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            val sources = appDb.replaceRuleDao.getByGroup(oldGroup)
+            val sources = dao.getByGroup(oldGroup)
             sources.forEach { source ->
             sources.forEach { source ->
                 source.group?.splitNotBlank(",")?.toHashSet()?.let {
                 source.group?.splitNotBlank(",")?.toHashSet()?.let {
                     it.remove(oldGroup)
                     it.remove(oldGroup)
@@ -111,61 +121,65 @@ class ReplaceRuleRepository {
                     source.group = TextUtils.join(",", it)
                     source.group = TextUtils.join(",", it)
                 }
                 }
             }
             }
-            appDb.replaceRuleDao.update(*sources.toTypedArray())
+            dao.update(*sources.toTypedArray())
         }
         }
     }
     }
 
 
     suspend fun delGroup(group: String) {
     suspend fun delGroup(group: String) {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
-            val sources = appDb.replaceRuleDao.getByGroup(group)
+            val sources = dao.getByGroup(group)
             sources.forEach { source ->
             sources.forEach { source ->
                 source.group?.splitNotBlank(",")?.toHashSet()?.let {
                 source.group?.splitNotBlank(",")?.toHashSet()?.let {
                     it.remove(group)
                     it.remove(group)
                     source.group = TextUtils.join(",", it)
                     source.group = TextUtils.join(",", it)
                 }
                 }
             }
             }
-            appDb.replaceRuleDao.update(*sources.toTypedArray())
+            dao.update(*sources.toTypedArray())
         }
         }
     }
     }
 
 
+    suspend fun clearGroups(groups: List<String>) = withContext(Dispatchers.IO) {
+        dao.clearGroups(groups)
+    }
+
     suspend fun enableByIds(ids: Set<Long>) =
     suspend fun enableByIds(ids: Set<Long>) =
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
             if (ids.isEmpty()) return@withContext
             if (ids.isEmpty()) return@withContext
-            appDb.replaceRuleDao.updateEnabled(ids.toList(), true)
+            dao.updateEnabled(ids.toList(), true)
         }
         }
 
 
     suspend fun disableByIds(ids: Set<Long>) =
     suspend fun disableByIds(ids: Set<Long>) =
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
             if (ids.isEmpty()) return@withContext
             if (ids.isEmpty()) return@withContext
-            appDb.replaceRuleDao.updateEnabled(ids.toList(), false)
+            dao.updateEnabled(ids.toList(), false)
         }
         }
 
 
     suspend fun deleteByIds(ids: Set<Long>) =
     suspend fun deleteByIds(ids: Set<Long>) =
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
             if (ids.isEmpty()) return@withContext
             if (ids.isEmpty()) return@withContext
 
 
-            val rules = appDb.replaceRuleDao.getByIds(ids)
-            appDb.replaceRuleDao.delete(*rules.toTypedArray())
+            val rules = dao.getByIds(ids)
+            dao.delete(*rules.toTypedArray())
         }
         }
 
 
     suspend fun topByIds(ids: Set<Long>, isDesc: Boolean = false) =
     suspend fun topByIds(ids: Set<Long>, isDesc: Boolean = false) =
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
             if (ids.isEmpty()) return@withContext
             if (ids.isEmpty()) return@withContext
-            val rules = appDb.replaceRuleDao.getByIds(ids)
+            val rules = dao.getByIds(ids)
             if (isDesc) {
             if (isDesc) {
-                var maxOrder = appDb.replaceRuleDao.maxOrder
+                var maxOrder = dao.maxOrder
                 val updated = rules.map {
                 val updated = rules.map {
                     maxOrder++
                     maxOrder++
                     it.copy(order = maxOrder)
                     it.copy(order = maxOrder)
                 }
                 }
-                appDb.replaceRuleDao.update(*updated.toTypedArray())
+                dao.update(*updated.toTypedArray())
             } else {
             } else {
-                var minOrder = appDb.replaceRuleDao.minOrder
+                var minOrder = dao.minOrder
                 val updated = rules.map {
                 val updated = rules.map {
                     minOrder--
                     minOrder--
                     it.copy(order = minOrder)
                     it.copy(order = minOrder)
                 }
                 }
-                appDb.replaceRuleDao.update(*updated.toTypedArray())
+                dao.update(*updated.toTypedArray())
             }
             }
         }
         }
 
 
@@ -173,21 +187,21 @@ class ReplaceRuleRepository {
         withContext(Dispatchers.IO) {
         withContext(Dispatchers.IO) {
             if (ids.isEmpty()) return@withContext
             if (ids.isEmpty()) return@withContext
 
 
-            val rules = appDb.replaceRuleDao.getByIds(ids)
+            val rules = dao.getByIds(ids)
             if (isDesc) {
             if (isDesc) {
-                var minOrder = appDb.replaceRuleDao.minOrder
+                var minOrder = dao.minOrder
                 val updated = rules.map {
                 val updated = rules.map {
                     minOrder--
                     minOrder--
                     it.copy(order = minOrder)
                     it.copy(order = minOrder)
                 }
                 }
-                appDb.replaceRuleDao.update(*updated.toTypedArray())
+                dao.update(*updated.toTypedArray())
             } else {
             } else {
-                var maxOrder = appDb.replaceRuleDao.maxOrder
+                var maxOrder = dao.maxOrder
                 val updated = rules.map {
                 val updated = rules.map {
                     maxOrder++
                     maxOrder++
                     it.copy(order = maxOrder)
                     it.copy(order = maxOrder)
                 }
                 }
-                appDb.replaceRuleDao.update(*updated.toTypedArray())
+                dao.update(*updated.toTypedArray())
             }
             }
         }
         }
 
 
@@ -198,7 +212,7 @@ class ReplaceRuleRepository {
                 val order = if (isDesc) size - index else index + 1
                 val order = if (isDesc) size - index else index + 1
                 rule.copy(order = order)
                 rule.copy(order = order)
             }
             }
-            appDb.replaceRuleDao.update(*updatedRules.toTypedArray())
+            dao.update(*updatedRules.toTypedArray())
         }
         }
     }
     }
 
 

+ 25 - 0
app/src/main/java/io/legado/app/data/repository/SearchRepository.kt

@@ -1,6 +1,7 @@
 package io.legado.app.data.repository
 package io.legado.app.data.repository
 
 
 import io.legado.app.data.AppDatabase
 import io.legado.app.data.AppDatabase
+import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.SearchKeyword
 import io.legado.app.data.entities.SearchKeyword
@@ -27,6 +28,10 @@ interface SearchRepository {
     suspend fun clearSearchKeywords()
     suspend fun clearSearchKeywords()
     suspend fun saveSearchBooks(books: List<SearchBook>)
     suspend fun saveSearchBooks(books: List<SearchBook>)
     suspend fun saveSearchBook(book: SearchBook)
     suspend fun saveSearchBook(book: SearchBook)
+    suspend fun findChangeSourceBooks(name: String, author: String): List<SearchBook>
+    suspend fun deleteSearchBooks(books: List<SearchBook>)
+    suspend fun getBookSourcePart(sourceUrl: String): BookSourcePart?
+    suspend fun getBookSource(sourceUrl: String): BookSource?
 }
 }
 
 
 class SearchRepositoryImpl(
 class SearchRepositoryImpl(
@@ -115,4 +120,24 @@ class SearchRepositoryImpl(
     override suspend fun saveSearchBook(book: SearchBook): Unit = withContext(Dispatchers.IO) {
     override suspend fun saveSearchBook(book: SearchBook): Unit = withContext(Dispatchers.IO) {
         appDb.searchBookDao.insert(book)
         appDb.searchBookDao.insert(book)
     }
     }
+
+    override suspend fun findChangeSourceBooks(
+        name: String,
+        author: String,
+    ): List<SearchBook> = withContext(Dispatchers.IO) {
+        appDb.searchBookDao.changeSourceByGroup(name, author, "")
+    }
+
+    override suspend fun deleteSearchBooks(books: List<SearchBook>): Unit =
+        withContext(Dispatchers.IO) {
+            if (books.isNotEmpty()) {
+                appDb.searchBookDao.delete(*books.toTypedArray())
+            }
+        }
+
+    override suspend fun getBookSourcePart(sourceUrl: String): BookSourcePart? =
+        withContext(Dispatchers.IO) {
+            appDb.bookSourceDao.getBookSourcePart(sourceUrl)
+        }
+
 }
 }

+ 5 - 1
app/src/main/java/io/legado/app/di/appModule.kt

@@ -19,7 +19,9 @@ import io.legado.app.data.repository.AppStartupRepository
 import io.legado.app.data.repository.AppUiConfigurationRepository
 import io.legado.app.data.repository.AppUiConfigurationRepository
 import io.legado.app.data.repository.BackupRestoreRepository
 import io.legado.app.data.repository.BackupRestoreRepository
 import io.legado.app.data.repository.BackupSettingsRepository
 import io.legado.app.data.repository.BackupSettingsRepository
+import io.legado.app.data.repository.BookCacheManageRepository
 import io.legado.app.data.repository.BookCacheCleanupRepository
 import io.legado.app.data.repository.BookCacheCleanupRepository
+import io.legado.app.data.repository.BookmarkRepository
 import io.legado.app.data.repository.BookContentProcessRepository
 import io.legado.app.data.repository.BookContentProcessRepository
 import io.legado.app.data.repository.BookDomainRepositoryImpl
 import io.legado.app.data.repository.BookDomainRepositoryImpl
 import io.legado.app.data.repository.BookExportSettingsRepository
 import io.legado.app.data.repository.BookExportSettingsRepository
@@ -291,6 +293,8 @@ val appModule = module {
     single<HomeDashboardGateway> { HomeDashboardRepository(get(), get()) }
     single<HomeDashboardGateway> { HomeDashboardRepository(get(), get()) }
     singleOf(::BookRepository)
     singleOf(::BookRepository)
     singleOf(::BookGroupRepository)
     singleOf(::BookGroupRepository)
+    singleOf(::BookmarkRepository)
+    singleOf(::BookCacheManageRepository)
     singleOf(::TagGroupRuleApplier)
     singleOf(::TagGroupRuleApplier)
     single<BookGroupMutationGateway> { BookGroupMutationRepository(get(), get()) }
     single<BookGroupMutationGateway> { BookGroupMutationRepository(get(), get()) }
     singleOf(::BookSourceRepository)
     singleOf(::BookSourceRepository)
@@ -643,7 +647,7 @@ val appModule = module {
     viewModel { (route: ReplaceEditRoute) ->
     viewModel { (route: ReplaceEditRoute) ->
         ReplaceEditViewModel(
         ReplaceEditViewModel(
             app = get(),
             app = get(),
-            replaceRuleDao = get(),
+            replaceRuleRepository = get(),
             route = route
             route = route
         )
         )
     }
     }

+ 6 - 6
app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkViewModel.kt

@@ -5,8 +5,8 @@ import android.net.Uri
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.Stable
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewModelScope
-import io.legado.app.data.dao.BookmarkDao
 import io.legado.app.data.entities.Bookmark
 import io.legado.app.data.entities.Bookmark
+import io.legado.app.data.repository.BookmarkRepository
 import io.legado.app.utils.FileDoc
 import io.legado.app.utils.FileDoc
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
 import io.legado.app.utils.createFileIfNotExist
 import io.legado.app.utils.createFileIfNotExist
@@ -79,7 +79,7 @@ sealed interface AllBookmarkEffect {
 
 
 class AllBookmarkViewModel(
 class AllBookmarkViewModel(
     application: Application,
     application: Application,
-    private val bookmarkDao: BookmarkDao
+    private val bookmarkRepository: BookmarkRepository,
 ) : AndroidViewModel(application) {
 ) : AndroidViewModel(application) {
 
 
     private val _searchQuery = MutableStateFlow("")
     private val _searchQuery = MutableStateFlow("")
@@ -91,7 +91,7 @@ class AllBookmarkViewModel(
     val uiState: StateFlow<BookmarkUiState> = combine(
     val uiState: StateFlow<BookmarkUiState> = combine(
         _searchQuery,
         _searchQuery,
         _collapsedGroups,
         _collapsedGroups,
-        bookmarkDao.flowAll()
+        bookmarkRepository.flowAll()
     ) { query, collapsed, allBookmarks ->
     ) { query, collapsed, allBookmarks ->
 
 
         val filteredList = if (query.isBlank()) {
         val filteredList = if (query.isBlank()) {
@@ -167,13 +167,13 @@ class AllBookmarkViewModel(
 
 
     fun updateBookmark(bookmark: Bookmark) {
     fun updateBookmark(bookmark: Bookmark) {
         viewModelScope.launch(Dispatchers.IO) {
         viewModelScope.launch(Dispatchers.IO) {
-            bookmarkDao.insert(bookmark)
+            bookmarkRepository.save(bookmark)
         }
         }
     }
     }
 
 
     fun deleteBookmark(bookmark: Bookmark) {
     fun deleteBookmark(bookmark: Bookmark) {
         viewModelScope.launch(Dispatchers.IO) {
         viewModelScope.launch(Dispatchers.IO) {
-            bookmarkDao.delete(bookmark)
+            bookmarkRepository.delete(bookmark)
         }
         }
     }
     }
 
 
@@ -188,7 +188,7 @@ class AllBookmarkViewModel(
                 val fileDoc = dirDoc.createFileIfNotExist(fileName)
                 val fileDoc = dirDoc.createFileIfNotExist(fileName)
 
 
                 fileDoc.openOutputStream().getOrThrow().use { outputStream ->
                 fileDoc.openOutputStream().getOrThrow().use { outputStream ->
-                    val allData = bookmarkDao.all
+                    val allData = bookmarkRepository.getAll()
                     if (isMarkdown) {
                     if (isMarkdown) {
                         writeMarkdown(outputStream, allData)
                         writeMarkdown(outputStream, allData)
                     } else {
                     } else {

+ 40 - 37
app/src/main/java/io/legado/app/ui/book/cache/manage/BookCacheManageViewModel.kt

@@ -3,13 +3,11 @@ package io.legado.app.ui.book.cache.manage
 import android.app.Application
 import android.app.Application
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewModelScope
 import io.legado.app.base.BaseViewModel
 import io.legado.app.base.BaseViewModel
-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.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.model.BookChapterCacheInfo
 import io.legado.app.data.model.BookChapterCacheInfo
+import io.legado.app.data.repository.BookCacheManageRepository
 import io.legado.app.domain.usecase.CacheBookChaptersUseCase
 import io.legado.app.domain.usecase.CacheBookChaptersUseCase
 import io.legado.app.domain.usecase.ClearBookCacheUseCase
 import io.legado.app.domain.usecase.ClearBookCacheUseCase
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.BookHelp
@@ -108,9 +106,7 @@ sealed interface BookCacheManageEffect {
 
 
 class BookCacheManageViewModel(
 class BookCacheManageViewModel(
     application: Application,
     application: Application,
-    private val bookDao: BookDao,
-    private val bookChapterDao: BookChapterDao,
-    private val bookGroupDao: BookGroupDao,
+    private val repository: BookCacheManageRepository,
     private val cacheBookChaptersUseCase: CacheBookChaptersUseCase,
     private val cacheBookChaptersUseCase: CacheBookChaptersUseCase,
     private val clearBookCacheUseCase: ClearBookCacheUseCase,
     private val clearBookCacheUseCase: ClearBookCacheUseCase,
 ) : BaseViewModel(application) {
 ) : BaseViewModel(application) {
@@ -164,12 +160,12 @@ class BookCacheManageViewModel(
     private fun initialize() {
     private fun initialize() {
         if (observeJob != null) return
         if (observeJob != null) return
         observeJob = viewModelScope.launch {
         observeJob = viewModelScope.launch {
-            bookDao.flowAll().collect { books ->
+            repository.flowBooks().collect { books ->
                 reloadAll(books = books, forceDatabase = false)
                 reloadAll(books = books, forceDatabase = false)
             }
             }
         }
         }
         viewModelScope.launch {
         viewModelScope.launch {
-            bookGroupDao.flowSelect().collect { groups ->
+            repository.flowSelectableGroups().collect { groups ->
                 _uiState.update { state ->
                 _uiState.update { state ->
                     state.copy(
                     state.copy(
                         groups = groups.filter { it.groupId > 0L }
                         groups = groups.filter { it.groupId > 0L }
@@ -238,7 +234,11 @@ class BookCacheManageViewModel(
         fullReloadJob = viewModelScope.launch {
         fullReloadJob = viewModelScope.launch {
             val expandedBookUrls = uiState.value.expandedBookUrls
             val expandedBookUrls = uiState.value.expandedBookUrls
             val result = withContext(Dispatchers.IO) {
             val result = withContext(Dispatchers.IO) {
-                val sourceBooks = if (forceDatabase) bookDao.all else books ?: bookDao.all
+                val sourceBooks = if (forceDatabase) {
+                    repository.getAllBooks()
+                } else {
+                    books ?: repository.getAllBooks()
+                }
                 val items = sortItems(
                 val items = sortItems(
                     sourceBooks
                     sourceBooks
                         .filterNot { it.isLocal || it.isAudio }
                         .filterNot { it.isLocal || it.isAudio }
@@ -393,7 +393,7 @@ class BookCacheManageViewModel(
     private suspend fun reloadBook(bookUrl: String) {
     private suspend fun reloadBook(bookUrl: String) {
         val expanded = uiState.value.expandedBookUrls.contains(bookUrl)
         val expanded = uiState.value.expandedBookUrls.contains(bookUrl)
         val result = withContext(Dispatchers.IO) {
         val result = withContext(Dispatchers.IO) {
-            val book = bookDao.getBook(bookUrl)
+            val book = repository.getBook(bookUrl)
             val item = book
             val item = book
                 ?.takeUnless { it.isLocal || it.isAudio }
                 ?.takeUnless { it.isLocal || it.isAudio }
                 ?.let { buildBookItem(it) }
                 ?.let { buildBookItem(it) }
@@ -434,7 +434,7 @@ class BookCacheManageViewModel(
         }
         }
     }
     }
 
 
-    private fun buildBookItem(book: Book): BookCacheBookItem? {
+    private suspend fun buildBookItem(book: Book): BookCacheBookItem? {
         val cacheFiles = BookHelp.getChapterFiles(book)
         val cacheFiles = BookHelp.getChapterFiles(book)
         val bookState = CacheBook.downloadStateFlow.value.books[book.bookUrl]
         val bookState = CacheBook.downloadStateFlow.value.books[book.bookUrl]
         val model = CacheBook.cacheBookMap[book.bookUrl]
         val model = CacheBook.cacheBookMap[book.bookUrl]
@@ -451,7 +451,7 @@ class BookCacheManageViewModel(
         val waitingCount = if (isBookPaused) 0 else rawWaitingCount
         val waitingCount = if (isBookPaused) 0 else rawWaitingCount
         val downloadingCount = if (isBookPaused) 0 else rawDownloadingCount
         val downloadingCount = if (isBookPaused) 0 else rawDownloadingCount
         val errorIndices = errorIndices(book.bookUrl)
         val errorIndices = errorIndices(book.bookUrl)
-        val totalCount = bookChapterDao.getChapterCount(book.bookUrl)
+        val totalCount = repository.getChapterCount(book.bookUrl)
         val cachedFileCount = cacheFiles.count { it.endsWith(".nb") }
         val cachedFileCount = cacheFiles.count { it.endsWith(".nb") }
         val cachedCount = min(BookHelp.countCachedChapters(book), totalCount)
         val cachedCount = min(BookHelp.countCachedChapters(book), totalCount)
         if (totalCount == 0 && cacheFiles.isEmpty() && waitingCount == 0 && downloadingCount == 0 && pausedCount == 0 && !book.isNotShelf) {
         if (totalCount == 0 && cacheFiles.isEmpty() && waitingCount == 0 && downloadingCount == 0 && pausedCount == 0 && !book.isNotShelf) {
@@ -477,9 +477,9 @@ class BookCacheManageViewModel(
         return item.cachedFileCount > 0 || item.hasDownloadTask || item.errorCount > 0
         return item.cachedFileCount > 0 || item.hasDownloadTask || item.errorCount > 0
     }
     }
 
 
-    private fun buildChapterItems(bookUrl: String): List<BookCacheChapterItem> {
-        val book = bookDao.getBook(bookUrl) ?: return emptyList()
-        val chapters = bookChapterDao.getChapterCacheInfoList(bookUrl)
+    private suspend fun buildChapterItems(bookUrl: String): List<BookCacheChapterItem> {
+        val book = repository.getBook(bookUrl) ?: return emptyList()
+        val chapters = repository.getChapterCacheInfo(bookUrl)
         val model = CacheBook.cacheBookMap[bookUrl]
         val model = CacheBook.cacheBookMap[bookUrl]
         val bookState = CacheBook.downloadStateFlow.value.books[bookUrl]
         val bookState = CacheBook.downloadStateFlow.value.books[bookUrl]
         val errorIndices = errorIndices(bookUrl)
         val errorIndices = errorIndices(bookUrl)
@@ -664,32 +664,35 @@ class BookCacheManageViewModel(
         }
         }
     }
     }
 
 
-    private fun downloadableChapterIndexBatches(
+    private suspend fun downloadableChapterIndexBatches(
         bookUrl: String,
         bookUrl: String,
         batchSize: Int = DOWNLOAD_BATCH_SIZE,
         batchSize: Int = DOWNLOAD_BATCH_SIZE,
-    ): Sequence<List<Int>> = sequence {
-        val book = bookDao.getBook(bookUrl) ?: return@sequence
-        val model = CacheBook.cacheBookMap[bookUrl]
-        var batch = ArrayList<Int>(batchSize)
-        for (chapter in bookChapterDao.getChapterCacheInfoList(bookUrl)) {
-            if (
-                chapter.isVolume ||
-                    isChapterFullyCached(book, chapter) ||
-                    model?.isPaused(chapter.index) == true ||
-                    model?.isWaiting(chapter.index) == true ||
-                    model?.isDownloading(chapter.index) == true
-            ) {
-                continue
-            }
-            batch.add(chapter.index)
-            if (batch.size == batchSize) {
+    ): Sequence<List<Int>> {
+        val book = repository.getBook(bookUrl) ?: return emptySequence()
+        val chapters = repository.getChapterCacheInfo(bookUrl)
+        return sequence {
+            val model = CacheBook.cacheBookMap[bookUrl]
+            var batch = ArrayList<Int>(batchSize)
+            for (chapter in chapters) {
+                if (
+                    chapter.isVolume ||
+                        isChapterFullyCached(book, chapter) ||
+                        model?.isPaused(chapter.index) == true ||
+                        model?.isWaiting(chapter.index) == true ||
+                        model?.isDownloading(chapter.index) == true
+                ) {
+                    continue
+                }
+                batch.add(chapter.index)
+                if (batch.size == batchSize) {
+                    yield(batch)
+                    batch = ArrayList(batchSize)
+                }
+            }
+            if (batch.isNotEmpty()) {
                 yield(batch)
                 yield(batch)
-                batch = ArrayList(batchSize)
             }
             }
         }
         }
-        if (batch.isNotEmpty()) {
-            yield(batch)
-        }
     }
     }
 
 
     private fun isChapterFullyCached(
     private fun isChapterFullyCached(
@@ -834,7 +837,7 @@ class BookCacheManageViewModel(
         chapterIndex: Int,
         chapterIndex: Int,
     ) {
     ) {
         execute {
         execute {
-            val book = bookDao.getBook(bookUrl) ?: return@execute false
+            val book = repository.getBook(bookUrl) ?: return@execute false
             val chapter = BookChapter(
             val chapter = BookChapter(
                 url = chapterUrl,
                 url = chapterUrl,
                 title = chapterTitle,
                 title = chapterTitle,

+ 15 - 16
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceComposeViewModel.kt

@@ -6,13 +6,13 @@ import androidx.lifecycle.viewModelScope
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppLog
 import io.legado.app.constant.AppPattern
 import io.legado.app.constant.AppPattern
-import io.legado.app.data.dao.BookDao
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.entities.SearchBook
 import io.legado.app.data.repository.SearchRepository
 import io.legado.app.data.repository.SearchRepository
+import io.legado.app.data.repository.BookRepository
 import io.legado.app.domain.gateway.ChangeSourceSettingsGateway
 import io.legado.app.domain.gateway.ChangeSourceSettingsGateway
 import io.legado.app.domain.model.settings.ChangeSourceSettings
 import io.legado.app.domain.model.settings.ChangeSourceSettings
 import io.legado.app.domain.usecase.ChangeSourceSearchEvent
 import io.legado.app.domain.usecase.ChangeSourceSearchEvent
@@ -48,13 +48,17 @@ class ChangeBookSourceComposeViewModel(
     private val changeSourceSearchUseCase: ChangeSourceSearchUseCase,
     private val changeSourceSearchUseCase: ChangeSourceSearchUseCase,
     private val getChapterContentUseCase: GetChapterContentUseCase,
     private val getChapterContentUseCase: GetChapterContentUseCase,
     private val searchRepository: SearchRepository,
     private val searchRepository: SearchRepository,
-    private val bookDao: BookDao,
+    private val bookRepository: BookRepository,
     private val changeSourceSettingsGateway: ChangeSourceSettingsGateway,
     private val changeSourceSettingsGateway: ChangeSourceSettingsGateway,
 ) : ViewModel() {
 ) : ViewModel() {
 
 
     // Public state for the sheet
     // Public state for the sheet
     val enabledGroups = searchRepository.enabledGroups
     val enabledGroups = searchRepository.enabledGroups
-    val enabledSources = searchRepository.enabledSources
+    val enabledSources = searchRepository.enabledSources.stateIn(
+        scope = viewModelScope,
+        started = SharingStarted.Eagerly,
+        initialValue = emptyList(),
+    )
 
 
     val settings = changeSourceSettingsGateway.settings.stateIn(
     val settings = changeSourceSettingsGateway.settings.stateIn(
         viewModelScope,
         viewModelScope,
@@ -104,7 +108,7 @@ class ChangeBookSourceComposeViewModel(
 
 
     fun findShelfConflict(book: Book, onResult: (Book?) -> Unit) {
     fun findShelfConflict(book: Book, onResult: (Book?) -> Unit) {
         viewModelScope.launch(IO) {
         viewModelScope.launch(IO) {
-            val conflict = bookDao.getShelfBookConflict(book.name, book.author)
+            val conflict = bookRepository.getShelfBookConflict(book.name, book.author)
             onMain { onResult(conflict) }
             onMain { onResult(conflict) }
         }
         }
     }
     }
@@ -151,12 +155,10 @@ class ChangeBookSourceComposeViewModel(
         }
         }
     }
     }
 
 
-    private fun getDbSearchBooks(): List<SearchBook> {
+    private suspend fun getDbSearchBooks(): List<SearchBook> {
         val scope = SearchScope(settings.value.searchScope)
         val scope = SearchScope(settings.value.searchScope)
         val author = if (checkAuthor) bookAuthor else ""
         val author = if (checkAuthor) bookAuthor else ""
-        val dbBooks = io.legado.app.data.appDb.searchBookDao.changeSourceByGroup(
-            bookName, author, ""
-        )
+        val dbBooks = searchRepository.findChangeSourceBooks(bookName, author)
         if (scope.isAll()) return dbBooks
         if (scope.isAll()) return dbBooks
 
 
         val allowedOrigins = scope.getBookSourceParts()
         val allowedOrigins = scope.getBookSourceParts()
@@ -173,8 +175,7 @@ class ChangeBookSourceComposeViewModel(
 
 
     fun startSearch(origin: String) {
     fun startSearch(origin: String) {
         viewModelScope.launch(IO) {
         viewModelScope.launch(IO) {
-            val source = io.legado.app.data.appDb.bookSourceDao
-                .getBookSourcePart(origin) ?: return@launch
+            val source = searchRepository.getBookSourcePart(origin) ?: return@launch
             startSearch(listOf(source))
             startSearch(listOf(source))
         }
         }
     }
     }
@@ -216,7 +217,7 @@ class ChangeBookSourceComposeViewModel(
         searchJob = viewModelScope.launch(IO) {
         searchJob = viewModelScope.launch(IO) {
             try {
             try {
                 if (removedResults.isNotEmpty()) {
                 if (removedResults.isNotEmpty()) {
-                    io.legado.app.data.appDb.searchBookDao.delete(*removedResults.toTypedArray())
+                    searchRepository.deleteSearchBooks(removedResults)
                 }
                 }
                 collectSearchEvents(
                 collectSearchEvents(
                     events = changeSourceSearchUseCase.search(
                     events = changeSourceSearchUseCase.search(
@@ -374,16 +375,14 @@ class ChangeBookSourceComposeViewModel(
             try {
             try {
                 val cachedToc = tocMap[book.primaryStr()]
                 val cachedToc = tocMap[book.primaryStr()]
                 if (cachedToc != null) {
                 if (cachedToc != null) {
-                    val source =
-                        io.legado.app.data.appDb.bookSourceDao.getBookSource(book.origin)
+                    val source = searchRepository.getBookSource(book.origin)
                     if (source != null) {
                     if (source != null) {
                         onMain { onSuccess(cachedToc, source) }
                         onMain { onSuccess(cachedToc, source) }
                         return@launch
                         return@launch
                     }
                     }
                 }
                 }
                 if (book.isWebFile) {
                 if (book.isWebFile) {
-                    val source =
-                        io.legado.app.data.appDb.bookSourceDao.getBookSource(book.origin)
+                    val source = searchRepository.getBookSource(book.origin)
                         ?: throw io.legado.app.exception.NoStackTraceException("书源不存在")
                         ?: throw io.legado.app.exception.NoStackTraceException("书源不存在")
                     onMain { onSuccess(emptyList(), source) }
                     onMain { onSuccess(emptyList(), source) }
                     return@launch
                     return@launch
@@ -568,7 +567,7 @@ class ChangeBookSourceComposeViewModel(
         if (selectedUrls.isEmpty()) {
         if (selectedUrls.isEmpty()) {
             searchScope.update("")
             searchScope.update("")
         } else {
         } else {
-            val selectedSources = io.legado.app.data.appDb.bookSourceDao.allEnabledPart.filter {
+            val selectedSources = enabledSources.value.filter {
                 selectedUrls.contains(it.bookSourceUrl)
                 selectedUrls.contains(it.bookSourceUrl)
             }
             }
             searchScope.updateSources(selectedSources)
             searchScope.updateSources(selectedSources)

+ 2 - 3
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleViewModel.kt

@@ -6,7 +6,6 @@ import io.legado.app.base.BaseRuleEvent
 import io.legado.app.base.BaseRuleViewModel
 import io.legado.app.base.BaseRuleViewModel
 import io.legado.app.constant.AppPattern
 import io.legado.app.constant.AppPattern
 import io.legado.app.constant.PreferKey
 import io.legado.app.constant.PreferKey
-import io.legado.app.data.appDb
 import io.legado.app.data.entities.BookContentProcess
 import io.legado.app.data.entities.BookContentProcess
 import io.legado.app.data.entities.ReplaceRule
 import io.legado.app.data.entities.ReplaceRule
 import io.legado.app.data.repository.ReadSettingsRepository
 import io.legado.app.data.repository.ReadSettingsRepository
@@ -54,12 +53,12 @@ class ReplaceRuleViewModel(
     uploadRepository: UploadRepository,
     uploadRepository: UploadRepository,
     private val bookContentProcessGateway: BookContentProcessGateway,
     private val bookContentProcessGateway: BookContentProcessGateway,
     private val readSettingsRepository: ReadSettingsRepository,
     private val readSettingsRepository: ReadSettingsRepository,
+    private val repository: ReplaceRuleRepository,
 ) : BaseRuleViewModel<ReplaceRuleItemUi, ReplaceRule, Long, ReplaceRuleUiState>(
 ) : BaseRuleViewModel<ReplaceRuleItemUi, ReplaceRule, Long, ReplaceRuleUiState>(
     application,
     application,
     ReplaceRuleUiState(interaction = InteractionState(isLoading = true)),
     ReplaceRuleUiState(interaction = InteractionState(isLoading = true)),
     uploadRepository
     uploadRepository
 ) {
 ) {
-    private val repository = ReplaceRuleRepository()
     private val _sortMode = MutableStateFlow(context.getPrefString(PreferKey.replaceSortMode, "desc") ?: "desc")
     private val _sortMode = MutableStateFlow(context.getPrefString(PreferKey.replaceSortMode, "desc") ?: "desc")
     val sortMode = _sortMode.asStateFlow()
     val sortMode = _sortMode.asStateFlow()
     private val _group = MutableStateFlow<String?>(null)
     private val _group = MutableStateFlow<String?>(null)
@@ -302,7 +301,7 @@ class ReplaceRuleViewModel(
     }
     }
 
 
     override suspend fun findOldRule(newRule: ReplaceRule): ReplaceRule? {
     override suspend fun findOldRule(newRule: ReplaceRule): ReplaceRule? {
-        return appDb.replaceRuleDao.findById(newRule.id)
+        return repository.findById(newRule.id)
     }
     }
 
 
     override fun saveImportedRules() {
     override fun saveImportedRules() {

+ 8 - 8
app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt

@@ -3,8 +3,8 @@ package io.legado.app.ui.replace.edit
 import android.app.Application
 import android.app.Application
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewModelScope
-import io.legado.app.data.dao.ReplaceRuleDao
 import io.legado.app.data.entities.ReplaceRule
 import io.legado.app.data.entities.ReplaceRule
+import io.legado.app.data.repository.ReplaceRuleRepository
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.ui.replace.ReplaceEditRoute
 import io.legado.app.ui.replace.ReplaceEditRoute
 import io.legado.app.utils.GSON
 import io.legado.app.utils.GSON
@@ -22,7 +22,7 @@ import kotlinx.coroutines.launch
 
 
 class ReplaceEditViewModel(
 class ReplaceEditViewModel(
     private val app: Application,
     private val app: Application,
-    private val replaceRuleDao: ReplaceRuleDao,
+    private val replaceRuleRepository: ReplaceRuleRepository,
     private val route: ReplaceEditRoute
     private val route: ReplaceEditRoute
 ) : ViewModel() {
 ) : ViewModel() {
 
 
@@ -64,7 +64,7 @@ class ReplaceEditViewModel(
             val id = route.id
             val id = route.id
 
 
             if (id > 0) {
             if (id > 0) {
-                val rule = replaceRuleDao.findById(id)
+                val rule = replaceRuleRepository.findById(id)
                 rule?.let { updateStateFromRule(it) }
                 rule?.let { updateStateFromRule(it) }
             } else {
             } else {
                 _uiState.update {
                 _uiState.update {
@@ -85,7 +85,7 @@ class ReplaceEditViewModel(
 
 
     private fun observeGroups() {
     private fun observeGroups() {
         viewModelScope.launch {
         viewModelScope.launch {
-            replaceRuleDao.flowGroups().collectLatest { groups ->
+            replaceRuleRepository.flowGroups().collectLatest { groups ->
                 _uiState.update { it.copy(allGroups = listOf("默认") + groups) }
                 _uiState.update { it.copy(allGroups = listOf("默认") + groups) }
             }
             }
         }
         }
@@ -207,7 +207,7 @@ class ReplaceEditViewModel(
         viewModelScope.launch(Dispatchers.IO) {
         viewModelScope.launch(Dispatchers.IO) {
             val state = _uiState.value
             val state = _uiState.value
 
 
-            val existingRule = if (state.id > 0) replaceRuleDao.findById(state.id) else null
+            val existingRule = if (state.id > 0) replaceRuleRepository.findById(state.id) else null
             val rule = (existingRule ?: ReplaceRule()).apply {
             val rule = (existingRule ?: ReplaceRule()).apply {
                 id = existingRule?.id ?: if (state.id <= 0) System.currentTimeMillis() else state.id
                 id = existingRule?.id ?: if (state.id <= 0) System.currentTimeMillis() else state.id
                 name = state.name
                 name = state.name
@@ -223,10 +223,10 @@ class ReplaceEditViewModel(
             }
             }
 
 
             if (existingRule == null && rule.order == Int.MIN_VALUE) {
             if (existingRule == null && rule.order == Int.MIN_VALUE) {
-                rule.order = replaceRuleDao.maxOrder + 1
+                rule.order = replaceRuleRepository.getNextOrder()
             }
             }
 
 
-            replaceRuleDao.insert(rule)
+            replaceRuleRepository.insert(rule)
 
 
             _effects.tryEmit(ReplaceEditEffect.NavigateBack)
             _effects.tryEmit(ReplaceEditEffect.NavigateBack)
         }
         }
@@ -235,7 +235,7 @@ class ReplaceEditViewModel(
 
 
     private fun deleteGroups(groups: List<String>) {
     private fun deleteGroups(groups: List<String>) {
         viewModelScope.launch {
         viewModelScope.launch {
-            replaceRuleDao.clearGroups(groups)
+            replaceRuleRepository.clearGroups(groups)
             toggleGroupDialog(false)
             toggleGroupDialog(false)
         }
         }
     }
     }

+ 64 - 1
build.gradle.kts

@@ -18,6 +18,9 @@ abstract class VerifyConfigArchitectureTask : DefaultTask() {
     @get:Input
     @get:Input
     abstract val legacyPreferenceCallBaseline: MapProperty<String, Int>
     abstract val legacyPreferenceCallBaseline: MapProperty<String, Int>
 
 
+    @get:Input
+    abstract val legacyDaoInjectionBaseline: MapProperty<String, Int>
+
     @TaskAction
     @TaskAction
     fun verify() {
     fun verify() {
         val sourceRootDir = sourceRoot.get().asFile
         val sourceRootDir = sourceRoot.get().asFile
@@ -25,12 +28,20 @@ abstract class VerifyConfigArchitectureTask : DefaultTask() {
             .filter { it.isFile && it.extension == "kt" }
             .filter { it.isFile && it.extension == "kt" }
             .toList()
             .toList()
         val preferenceBaseline = legacyPreferenceCallBaseline.get()
         val preferenceBaseline = legacyPreferenceCallBaseline.get()
+        val daoInjectionBaseline = legacyDaoInjectionBaseline.get()
         val violations = mutableListOf<String>()
         val violations = mutableListOf<String>()
         val forbiddenConfigImport = Regex(
         val forbiddenConfigImport = Regex(
             """^import io\.legado\.app\.(?:help\.config\.AppConfig|ui\.config\..*Config)$""",
             """^import io\.legado\.app\.(?:help\.config\.AppConfig|ui\.config\..*Config)$""",
             RegexOption.MULTILINE,
             RegexOption.MULTILINE,
         )
         )
         val preferenceCall = Regex("""\b(?:getPref|putPref)[A-Za-z0-9_]*\s*\(""")
         val preferenceCall = Regex("""\b(?:getPref|putPref)[A-Za-z0-9_]*\s*\(""")
+        val daoImport = Regex(
+            """^import io\.legado\.app\.data\.dao\.[A-Za-z0-9_*]+$""",
+            RegexOption.MULTILINE,
+        )
+        val appDbDaoAccess = Regex(
+            """(?:\bappDb|io\.legado\.app\.data\.appDb)\.[A-Za-z0-9_]*Dao\b"""
+        )
         val readBookConfigWrite = Regex(
         val readBookConfigWrite = Regex(
             """\bReadBookConfig\.[a-z_][A-Za-z0-9_]*(?:\.[a-z_][A-Za-z0-9_]*)?\s*="""
             """\bReadBookConfig\.[a-z_][A-Za-z0-9_]*(?:\.[a-z_][A-Za-z0-9_]*)?\s*="""
         )
         )
@@ -100,6 +111,26 @@ abstract class VerifyConfigArchitectureTask : DefaultTask() {
             if (preferenceCalls > allowedCalls) {
             if (preferenceCalls > allowedCalls) {
                 violations += "$displayPath: 新增了 ${preferenceCalls - allowedCalls} 个旧偏好调用"
                 violations += "$displayPath: 新增了 ${preferenceCalls - allowedCalls} 个旧偏好调用"
             }
             }
+
+            if (relativePath.startsWith("io/legado/app/ui/") &&
+                file.name.contains("ViewModel")
+            ) {
+                val daoDependencies = daoImport.findAll(text).count() +
+                    appDbDaoAccess.findAll(text).count()
+                val allowedDaoDependencies = daoInjectionBaseline[relativePath] ?: 0
+                if (daoDependencies > allowedDaoDependencies) {
+                    violations += "$displayPath: ViewModel 新增了 ${daoDependencies - allowedDaoDependencies} 个 DAO 直连"
+                } else if (daoDependencies < allowedDaoDependencies) {
+                    violations += "$displayPath: 已减少 DAO 直连,请将基线从 $allowedDaoDependencies 下调到 $daoDependencies"
+                }
+            }
+        }
+
+        val sourcePaths = kotlinFiles.mapTo(hashSetOf()) {
+            it.relativeTo(sourceRootDir).invariantSeparatorsPath
+        }
+        (daoInjectionBaseline.keys - sourcePaths).forEach { relativePath ->
+            violations += "app/src/main/java/$relativePath: 文件已移除,请删除 DAO 直连基线"
         }
         }
 
 
         check(violations.isEmpty()) {
         check(violations.isEmpty()) {
@@ -137,7 +168,7 @@ val verifyConfigArchitecture = tasks.register<VerifyConfigArchitectureTask>(
     "verifyConfigArchitecture"
     "verifyConfigArchitecture"
 ) {
 ) {
     group = "verification"
     group = "verification"
-    description = "禁止配置 Snapshot 桥、SettingsUpdate 分发、跨层全局 Config 直读和新增旧偏好调用"
+    description = "禁止配置架构回退、ViewModel 新增 DAO 直连和新增旧偏好调用"
     sourceRoot.set(layout.projectDirectory.dir("app/src/main/java"))
     sourceRoot.set(layout.projectDirectory.dir("app/src/main/java"))
     legacyPreferenceCallBaseline.set(
     legacyPreferenceCallBaseline.set(
         mapOf(
         mapOf(
@@ -166,6 +197,38 @@ val verifyConfigArchitecture = tasks.register<VerifyConfigArchitectureTask>(
             "io/legado/app/web/socket/BookSearchWebSocket.kt" to 2,
             "io/legado/app/web/socket/BookSearchWebSocket.kt" to 2,
         )
         )
     )
     )
+    legacyDaoInjectionBaseline.set(
+        mapOf(
+            "io/legado/app/ui/association/ImportBookSourceViewModel.kt" to 1,
+            "io/legado/app/ui/association/ImportDictRuleViewModel.kt" to 2,
+            "io/legado/app/ui/association/ImportHttpTtsViewModel.kt" to 2,
+            "io/legado/app/ui/association/ImportReplaceRuleViewModel.kt" to 2,
+            "io/legado/app/ui/association/ImportRssSourceViewModel.kt" to 1,
+            "io/legado/app/ui/association/ImportTxtTocRuleViewModel.kt" to 2,
+            "io/legado/app/ui/book/audio/AudioPlayViewModel.kt" to 11,
+            "io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt" to 3,
+            "io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt" to 20,
+            "io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt" to 5,
+            "io/legado/app/ui/book/import/local/ImportBookViewModel.kt" to 4,
+            "io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt" to 2,
+            "io/legado/app/ui/book/info/BookInfoViewModel.kt" to 34,
+            "io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt" to 2,
+            "io/legado/app/ui/book/manga/ReadMangaViewModel.kt" to 17,
+            "io/legado/app/ui/book/read/ReadBookViewModel.kt" to 52,
+            "io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt" to 3,
+            "io/legado/app/ui/book/source/manage/BookSourceViewModel.kt" to 29,
+            "io/legado/app/ui/book/toc/TocViewModel.kt" to 13,
+            "io/legado/app/ui/book/toc/rule/preview/TxtTocRulePreviewViewModel.kt" to 2,
+            "io/legado/app/ui/browser/WebViewModel.kt" to 1,
+            "io/legado/app/ui/login/SourceLoginViewModel.kt" to 6,
+            "io/legado/app/ui/rss/article/RssArticlesViewModel.kt" to 5,
+            "io/legado/app/ui/rss/article/RssSortViewModel.kt" to 8,
+            "io/legado/app/ui/rss/read/ReadRssViewModel.kt" to 9,
+            "io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt" to 6,
+            "io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt" to 1,
+            "io/legado/app/ui/tagGroupRule/TagGroupRuleViewModel.kt" to 4,
+        )
+    )
 }
 }
 
 
 subprojects {
 subprojects {