[优化] 架构优化
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
|
||||||
|
|
||||||
|
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
||||||
|
|
||||||
|
## 1. Think Before Coding
|
||||||
|
|
||||||
|
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
||||||
|
|
||||||
|
Before implementing:
|
||||||
|
- State your assumptions explicitly. If uncertain, ask.
|
||||||
|
- If multiple interpretations exist, present them - don't pick silently.
|
||||||
|
- If a simpler approach exists, say so. Push back when warranted.
|
||||||
|
- If something is unclear, stop. Name what's confusing. Ask.
|
||||||
|
|
||||||
|
## 2. Simplicity First
|
||||||
|
|
||||||
|
**Minimum code that solves the problem. Nothing speculative.**
|
||||||
|
|
||||||
|
- No features beyond what was asked.
|
||||||
|
- No abstractions for single-use code.
|
||||||
|
- No "flexibility" or "configurability" that wasn't requested.
|
||||||
|
- No error handling for impossible scenarios.
|
||||||
|
- If you write 200 lines and it could be 50, rewrite it.
|
||||||
|
|
||||||
|
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
||||||
|
|
||||||
|
## 3. Surgical Changes
|
||||||
|
|
||||||
|
**Touch only what you must. Clean up only your own mess.**
|
||||||
|
|
||||||
|
When editing existing code:
|
||||||
|
- Don't "improve" adjacent code, comments, or formatting.
|
||||||
|
- Don't refactor things that aren't broken.
|
||||||
|
- Match existing style, even if you'd do it differently.
|
||||||
|
- If you notice unrelated dead code, mention it - don't delete it.
|
||||||
|
|
||||||
|
When your changes create orphans:
|
||||||
|
- Remove imports/variables/functions that YOUR changes made unused.
|
||||||
|
- Don't remove pre-existing dead code unless asked.
|
||||||
|
|
||||||
|
The test: Every changed line should trace directly to the user's request.
|
||||||
|
|
||||||
|
## 4. Goal-Driven Execution
|
||||||
|
|
||||||
|
**Define success criteria. Loop until verified.**
|
||||||
|
|
||||||
|
Transform tasks into verifiable goals:
|
||||||
|
- "Add validation" -> "Write tests for invalid inputs, then make them pass"
|
||||||
|
- "Fix the bug" -> "Write a test that reproduces it, then make it pass"
|
||||||
|
- "Refactor X" -> "Ensure tests pass before and after"
|
||||||
|
|
||||||
|
For multi-step tasks, state a brief plan:
|
||||||
|
```
|
||||||
|
1. [Step] -> verify: [check]
|
||||||
|
2. [Step] -> verify: [check]
|
||||||
|
3. [Step] -> verify: [check]
|
||||||
|
```
|
||||||
|
|
||||||
|
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.data.AppDatabase
|
||||||
|
import io.legado.app.domain.gateway.AppStartupGateway
|
||||||
|
import io.legado.app.help.DefaultData
|
||||||
|
|
||||||
|
class AppStartupRepository(
|
||||||
|
private val appDatabase: AppDatabase
|
||||||
|
) : AppStartupGateway {
|
||||||
|
|
||||||
|
override suspend fun deleteNotShelfBooks() {
|
||||||
|
appDatabase.bookDao.deleteNotShelfBook()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun ensureDefaultHttpTts() {
|
||||||
|
if (appDatabase.httpTTSDao.count == 0) {
|
||||||
|
appDatabase.httpTTSDao.insert(*DefaultData.httpTTS.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.data.dao.BookDao
|
||||||
|
import io.legado.app.domain.gateway.BookCacheCleanupGateway
|
||||||
|
import io.legado.app.help.book.BookHelp
|
||||||
|
|
||||||
|
class BookCacheCleanupRepository(
|
||||||
|
private val bookDao: BookDao
|
||||||
|
) : BookCacheCleanupGateway {
|
||||||
|
|
||||||
|
override fun clearAll() {
|
||||||
|
BookHelp.clearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clear(bookUrl: String): Boolean {
|
||||||
|
val book = bookDao.getBook(bookUrl) ?: return false
|
||||||
|
BookHelp.clearCache(book)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
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.entities.Book
|
||||||
|
import io.legado.app.domain.model.BookGroupAssignment
|
||||||
|
import io.legado.app.domain.model.CacheableBook
|
||||||
|
import io.legado.app.domain.model.DeletableBook
|
||||||
|
import io.legado.app.domain.repository.BookDomainRepository
|
||||||
|
import io.legado.app.help.book.isAudio
|
||||||
|
import io.legado.app.help.book.isLocal
|
||||||
|
|
||||||
|
class BookDomainRepositoryImpl(
|
||||||
|
private val bookDao: BookDao,
|
||||||
|
private val bookChapterDao: BookChapterDao
|
||||||
|
) : BookDomainRepository {
|
||||||
|
|
||||||
|
private suspend fun getBooks(bookUrls: Set<String>): List<Book> {
|
||||||
|
if (bookUrls.isEmpty()) return emptyList()
|
||||||
|
return bookUrls.mapNotNull { bookDao.getBook(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCacheableBooks(bookUrls: Set<String>): List<CacheableBook> {
|
||||||
|
return getBooks(bookUrls).map { book ->
|
||||||
|
CacheableBook(
|
||||||
|
bookUrl = book.bookUrl,
|
||||||
|
isLocal = book.isLocal,
|
||||||
|
isAudio = book.isAudio,
|
||||||
|
durChapterIndex = book.durChapterIndex,
|
||||||
|
lastChapterIndex = book.lastChapterIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDeletableBooks(bookUrls: Set<String>): List<DeletableBook> {
|
||||||
|
return getBooks(bookUrls).map { book ->
|
||||||
|
DeletableBook(
|
||||||
|
bookUrl = book.bookUrl,
|
||||||
|
origin = book.origin,
|
||||||
|
isLocal = book.isLocal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getBookGroupAssignments(bookUrls: Set<String>): List<BookGroupAssignment> {
|
||||||
|
return getBooks(bookUrls).map { book ->
|
||||||
|
BookGroupAssignment(
|
||||||
|
bookUrl = book.bookUrl,
|
||||||
|
group = book.group
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateBookGroups(assignments: List<BookGroupAssignment>) {
|
||||||
|
if (assignments.isEmpty()) return
|
||||||
|
val groups = assignments.associateBy { it.bookUrl }
|
||||||
|
val books = getBooks(groups.keys).mapNotNull { book ->
|
||||||
|
groups[book.bookUrl]?.let { assignment ->
|
||||||
|
if (book.group == assignment.group) null else book.copy(group = assignment.group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (books.isNotEmpty()) {
|
||||||
|
bookDao.update(*books.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeGroupFromBooks(groupId: Long) {
|
||||||
|
bookDao.removeGroup(groupId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteBooks(bookUrls: Set<String>) {
|
||||||
|
val books = getBooks(bookUrls)
|
||||||
|
if (books.isNotEmpty()) {
|
||||||
|
bookDao.delete(*books.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteChaptersByBook(bookUrl: String) {
|
||||||
|
bookChapterDao.delByBook(bookUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.data.dao.BookDao
|
||||||
|
import io.legado.app.data.dao.BookSourceDao
|
||||||
|
import io.legado.app.domain.gateway.BookSourceCallbackGateway
|
||||||
|
import io.legado.app.model.SourceCallBack
|
||||||
|
|
||||||
|
class BookSourceCallbackRepository(
|
||||||
|
private val bookDao: BookDao,
|
||||||
|
private val bookSourceDao: BookSourceDao
|
||||||
|
) : BookSourceCallbackGateway {
|
||||||
|
override suspend fun onDeleteFromShelf(bookUrl: String) {
|
||||||
|
val book = bookDao.getBook(bookUrl) ?: return
|
||||||
|
val source = bookSourceDao.getBookSource(book.origin)
|
||||||
|
SourceCallBack.callBackBook(SourceCallBack.DEL_BOOK_SHELF, source, book)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.data.dao.BookDao
|
||||||
|
import io.legado.app.domain.gateway.BookCacheDownloadGateway
|
||||||
|
import io.legado.app.model.CacheBook
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
|
class CacheBookDownloadRepository(
|
||||||
|
private val bookDao: BookDao
|
||||||
|
) : BookCacheDownloadGateway {
|
||||||
|
|
||||||
|
override suspend fun start(bookUrl: String, chapterIndices: List<Int>) {
|
||||||
|
val book = bookDao.getBook(bookUrl) ?: return
|
||||||
|
CacheBook.start(appCtx, book, chapterIndices)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.data.AppDatabase
|
||||||
|
import io.legado.app.domain.gateway.DatabaseMaintenanceGateway
|
||||||
|
|
||||||
|
class DatabaseMaintenanceRepository(
|
||||||
|
private val appDatabase: AppDatabase
|
||||||
|
) : DatabaseMaintenanceGateway {
|
||||||
|
override fun shrink() {
|
||||||
|
appDatabase.openHelper.writableDatabase.execSQL("VACUUM")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,10 @@ class DictRuleRepository {
|
|||||||
return dao.all
|
return dao.all
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getEnabled(): List<DictRule> {
|
||||||
|
return dao.enabled
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun insert(vararg rule: DictRule) {
|
suspend fun insert(vararg rule: DictRule) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
dao.insert(*rule)
|
dao.insert(*rule)
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.data.dao.BookDao
|
||||||
|
import io.legado.app.domain.gateway.LocalBookGateway
|
||||||
|
import io.legado.app.model.localBook.LocalBook
|
||||||
|
|
||||||
|
class LocalBookRepository(
|
||||||
|
private val bookDao: BookDao
|
||||||
|
) : LocalBookGateway {
|
||||||
|
override suspend fun deleteBook(bookUrl: String, deleteOriginal: Boolean) {
|
||||||
|
val book = bookDao.getBook(bookUrl) ?: return
|
||||||
|
LocalBook.deleteBook(book, deleteOriginal)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import io.legado.app.constant.AppConst
|
|||||||
import io.legado.app.constant.BookType
|
import io.legado.app.constant.BookType
|
||||||
import io.legado.app.data.AppDatabase
|
import io.legado.app.data.AppDatabase
|
||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
|
import io.legado.app.data.entities.Server
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.AppWebDav
|
import io.legado.app.help.AppWebDav
|
||||||
import io.legado.app.help.book.getRemoteUrl
|
import io.legado.app.help.book.getRemoteUrl
|
||||||
@@ -90,6 +91,10 @@ class RemoteBookRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDefaultBookWebDav(): RemoteBookWebDav? {
|
||||||
|
return AppWebDav.defaultBookWebDav
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun loadBooks(
|
suspend fun loadBooks(
|
||||||
webDav: RemoteBookWebDav,
|
webDav: RemoteBookWebDav,
|
||||||
path: String?
|
path: String?
|
||||||
@@ -120,4 +125,20 @@ class RemoteBookRepository(
|
|||||||
return appDb.bookDao.flowLocal()
|
return appDb.bookDao.flowLocal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun flowServers(): Flow<List<Server>> {
|
||||||
|
return appDb.serverDao.observeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getServer(id: Long): Server? {
|
||||||
|
return appDb.serverDao.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveServer(server: Server) {
|
||||||
|
appDb.serverDao.insert(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun deleteServer(server: Server) {
|
||||||
|
appDb.serverDao.delete(server)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import io.legado.app.data.AppDatabase
|
|||||||
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
|
||||||
|
import io.legado.app.domain.usecase.BookShelfKey
|
||||||
import io.legado.app.help.book.isNotShelf
|
import io.legado.app.help.book.isNotShelf
|
||||||
import io.legado.app.model.webBook.SearchModel
|
import io.legado.app.model.webBook.SearchModel
|
||||||
import io.legado.app.ui.book.search.BookKey
|
|
||||||
import io.legado.app.ui.book.search.SearchScope
|
import io.legado.app.ui.book.search.SearchScope
|
||||||
import io.legado.app.ui.main.bookshelf.BookShelfItem
|
import io.legado.app.ui.main.bookshelf.BookShelfItem
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -23,7 +23,7 @@ import kotlinx.coroutines.withContext
|
|||||||
interface SearchRepository {
|
interface SearchRepository {
|
||||||
val enabledGroups: Flow<List<String>>
|
val enabledGroups: Flow<List<String>>
|
||||||
val enabledSources: Flow<List<BookSourcePart>>
|
val enabledSources: Flow<List<BookSourcePart>>
|
||||||
val bookshelfKeys: Flow<Set<BookKey>>
|
val bookshelfKeys: Flow<Set<BookShelfKey>>
|
||||||
|
|
||||||
fun searchBookshelf(query: String): Flow<List<BookShelfItem>>
|
fun searchBookshelf(query: String): Flow<List<BookShelfItem>>
|
||||||
fun searchHistory(query: String): Flow<List<SearchKeyword>>
|
fun searchHistory(query: String): Flow<List<SearchKeyword>>
|
||||||
@@ -68,9 +68,9 @@ class SearchRepositoryImpl(
|
|||||||
override val enabledGroups: Flow<List<String>> = appDb.bookSourceDao.flowEnabledGroups()
|
override val enabledGroups: Flow<List<String>> = appDb.bookSourceDao.flowEnabledGroups()
|
||||||
override val enabledSources: Flow<List<BookSourcePart>> = appDb.bookSourceDao.flowEnabled()
|
override val enabledSources: Flow<List<BookSourcePart>> = appDb.bookSourceDao.flowEnabled()
|
||||||
|
|
||||||
override val bookshelfKeys: Flow<Set<BookKey>> = appDb.bookDao.flowBookShelf().map { books ->
|
override val bookshelfKeys: Flow<Set<BookShelfKey>> = appDb.bookDao.flowBookShelf().map { books ->
|
||||||
books.filterNot { it.isNotShelf }
|
books.filterNot { it.isNotShelf }
|
||||||
.map { book -> BookKey(book.name, book.author, book.bookUrl) }
|
.map { book -> BookShelfKey(book.name, book.author, book.bookUrl) }
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.domain.gateway.WebDavBackupGateway
|
||||||
|
import io.legado.app.domain.model.WebDavBackup
|
||||||
|
import io.legado.app.help.AppWebDav
|
||||||
|
|
||||||
|
class WebDavBackupRepository : WebDavBackupGateway {
|
||||||
|
|
||||||
|
override val isJianGuoYun: Boolean
|
||||||
|
get() = AppWebDav.isJianGuoYun
|
||||||
|
|
||||||
|
override suspend fun syncConfig() {
|
||||||
|
AppWebDav.upConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun test(): Boolean {
|
||||||
|
return AppWebDav.testWebDav()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getBackupNames(): List<String> {
|
||||||
|
return AppWebDav.getBackupNames()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getLatestBackup(): WebDavBackup? {
|
||||||
|
return AppWebDav.lastBackUp().getOrNull()?.let {
|
||||||
|
WebDavBackup(
|
||||||
|
name = it.displayName,
|
||||||
|
lastModify = it.lastModify
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun restore(name: String) {
|
||||||
|
AppWebDav.restoreWebDav(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package io.legado.app.data.repository
|
||||||
|
|
||||||
|
import io.legado.app.data.entities.BookProgress
|
||||||
|
import io.legado.app.domain.gateway.ReadingProgressGateway
|
||||||
|
import io.legado.app.domain.model.ReadingProgress
|
||||||
|
import io.legado.app.help.AppWebDav
|
||||||
|
|
||||||
|
class WebDavReadingProgressRepository : ReadingProgressGateway {
|
||||||
|
|
||||||
|
override val isConfigured: Boolean
|
||||||
|
get() = AppWebDav.isOk
|
||||||
|
|
||||||
|
override suspend fun getProgress(name: String, author: String): ReadingProgress? {
|
||||||
|
return AppWebDav.getBookProgress(name, author)?.let {
|
||||||
|
ReadingProgress(
|
||||||
|
name = it.name,
|
||||||
|
author = it.author,
|
||||||
|
durChapterIndex = it.durChapterIndex,
|
||||||
|
durChapterPos = it.durChapterPos,
|
||||||
|
durChapterTime = it.durChapterTime,
|
||||||
|
durChapterTitle = it.durChapterTitle
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun uploadProgress(progress: ReadingProgress): Long? {
|
||||||
|
val uploadTime = System.currentTimeMillis()
|
||||||
|
val uploaded = AppWebDav.uploadBookProgress(
|
||||||
|
BookProgress(
|
||||||
|
name = progress.name,
|
||||||
|
author = progress.author,
|
||||||
|
durChapterIndex = progress.durChapterIndex,
|
||||||
|
durChapterPos = progress.durChapterPos,
|
||||||
|
durChapterTime = progress.durChapterTime,
|
||||||
|
durChapterTitle = progress.durChapterTitle
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return if (uploaded) uploadTime else null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,22 +6,49 @@ import coil.decode.GifDecoder
|
|||||||
import coil.decode.ImageDecoderDecoder
|
import coil.decode.ImageDecoderDecoder
|
||||||
import coil.decode.SvgDecoder
|
import coil.decode.SvgDecoder
|
||||||
import io.legado.app.data.AppDatabase
|
import io.legado.app.data.AppDatabase
|
||||||
|
import io.legado.app.data.repository.AppStartupRepository
|
||||||
|
import io.legado.app.data.repository.BookCacheCleanupRepository
|
||||||
|
import io.legado.app.data.repository.BookDomainRepositoryImpl
|
||||||
import io.legado.app.data.repository.BookGroupRepository
|
import io.legado.app.data.repository.BookGroupRepository
|
||||||
import io.legado.app.data.repository.BookRepository
|
import io.legado.app.data.repository.BookRepository
|
||||||
|
import io.legado.app.data.repository.BookSourceCallbackRepository
|
||||||
|
import io.legado.app.data.repository.CacheBookDownloadRepository
|
||||||
|
import io.legado.app.data.repository.DatabaseMaintenanceRepository
|
||||||
import io.legado.app.data.repository.DirectLinkUploadRepository
|
import io.legado.app.data.repository.DirectLinkUploadRepository
|
||||||
|
import io.legado.app.data.repository.DictRuleRepository
|
||||||
import io.legado.app.data.repository.ExploreRepository
|
import io.legado.app.data.repository.ExploreRepository
|
||||||
import io.legado.app.data.repository.ExploreRepositoryImpl
|
import io.legado.app.data.repository.ExploreRepositoryImpl
|
||||||
|
import io.legado.app.data.repository.LocalBookRepository
|
||||||
import io.legado.app.data.repository.ReadRecordRepository
|
import io.legado.app.data.repository.ReadRecordRepository
|
||||||
import io.legado.app.data.repository.RemoteBookRepository
|
import io.legado.app.data.repository.RemoteBookRepository
|
||||||
import io.legado.app.data.repository.SearchRepository
|
import io.legado.app.data.repository.SearchRepository
|
||||||
import io.legado.app.data.repository.SearchRepositoryImpl
|
import io.legado.app.data.repository.SearchRepositoryImpl
|
||||||
import io.legado.app.data.repository.SearchContentRepository
|
import io.legado.app.data.repository.SearchContentRepository
|
||||||
import io.legado.app.data.repository.UploadRepository
|
import io.legado.app.data.repository.UploadRepository
|
||||||
|
import io.legado.app.data.repository.WebDavBackupRepository
|
||||||
|
import io.legado.app.data.repository.WebDavReadingProgressRepository
|
||||||
|
import io.legado.app.domain.gateway.BookCacheCleanupGateway
|
||||||
|
import io.legado.app.domain.gateway.AppStartupGateway
|
||||||
|
import io.legado.app.domain.gateway.BookCacheDownloadGateway
|
||||||
|
import io.legado.app.domain.gateway.BookSourceCallbackGateway
|
||||||
|
import io.legado.app.domain.gateway.DatabaseMaintenanceGateway
|
||||||
|
import io.legado.app.domain.gateway.LocalBookGateway
|
||||||
|
import io.legado.app.domain.gateway.ReadingProgressGateway
|
||||||
|
import io.legado.app.domain.gateway.WebDavBackupGateway
|
||||||
|
import io.legado.app.domain.repository.BookDomainRepository
|
||||||
|
import io.legado.app.domain.usecase.AppStartupMaintenanceUseCase
|
||||||
import io.legado.app.domain.usecase.BatchCacheDownloadUseCase
|
import io.legado.app.domain.usecase.BatchCacheDownloadUseCase
|
||||||
|
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.domain.usecase.DeleteBooksUseCase
|
import io.legado.app.domain.usecase.DeleteBooksUseCase
|
||||||
import io.legado.app.domain.usecase.ExploreKindUiUseCase
|
import io.legado.app.domain.usecase.GetReadingProgressUseCase
|
||||||
|
import io.legado.app.domain.usecase.RemoveBookGroupAssignmentUseCase
|
||||||
|
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
|
||||||
|
import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase
|
||||||
|
import io.legado.app.domain.usecase.ShrinkDatabaseUseCase
|
||||||
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
|
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
|
||||||
|
import io.legado.app.domain.usecase.UploadReadingProgressUseCase
|
||||||
|
import io.legado.app.domain.usecase.WebDavBackupUseCase
|
||||||
import io.legado.app.help.coil.CoverFetcher
|
import io.legado.app.help.coil.CoverFetcher
|
||||||
import io.legado.app.help.coil.CoverInterceptor
|
import io.legado.app.help.coil.CoverInterceptor
|
||||||
import io.legado.app.help.http.okHttpClient
|
import io.legado.app.help.http.okHttpClient
|
||||||
@@ -35,6 +62,8 @@ import io.legado.app.ui.book.explore.ExploreShowViewModel
|
|||||||
import io.legado.app.ui.book.group.GroupViewModel
|
import io.legado.app.ui.book.group.GroupViewModel
|
||||||
import io.legado.app.ui.book.import.local.ImportBookViewModel
|
import io.legado.app.ui.book.import.local.ImportBookViewModel
|
||||||
import io.legado.app.ui.book.import.remote.RemoteBookViewModel
|
import io.legado.app.ui.book.import.remote.RemoteBookViewModel
|
||||||
|
import io.legado.app.ui.book.import.remote.ServerConfigViewModel
|
||||||
|
import io.legado.app.ui.book.import.remote.ServersViewModel
|
||||||
import io.legado.app.ui.book.info.BookInfoViewModel
|
import io.legado.app.ui.book.info.BookInfoViewModel
|
||||||
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
|
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
|
||||||
import io.legado.app.ui.book.search.SearchViewModel
|
import io.legado.app.ui.book.search.SearchViewModel
|
||||||
@@ -47,6 +76,7 @@ import io.legado.app.ui.config.coverConfig.CoverConfigViewModel
|
|||||||
import io.legado.app.ui.config.otherConfig.OtherConfigViewModel
|
import io.legado.app.ui.config.otherConfig.OtherConfigViewModel
|
||||||
import io.legado.app.ui.config.readConfig.ReadConfigViewModel
|
import io.legado.app.ui.config.readConfig.ReadConfigViewModel
|
||||||
import io.legado.app.ui.config.themeConfig.ThemeConfigViewModel
|
import io.legado.app.ui.config.themeConfig.ThemeConfigViewModel
|
||||||
|
import io.legado.app.ui.dict.DictViewModel
|
||||||
import io.legado.app.ui.dict.rule.DictRuleViewModel
|
import io.legado.app.ui.dict.rule.DictRuleViewModel
|
||||||
import io.legado.app.ui.main.MainViewModel
|
import io.legado.app.ui.main.MainViewModel
|
||||||
import io.legado.app.ui.main.bookshelf.BookshelfViewModel
|
import io.legado.app.ui.main.bookshelf.BookshelfViewModel
|
||||||
@@ -76,16 +106,34 @@ val appModule = module {
|
|||||||
singleOf(::ReadRecordRepository)
|
singleOf(::ReadRecordRepository)
|
||||||
singleOf(::BookRepository)
|
singleOf(::BookRepository)
|
||||||
singleOf(::BookGroupRepository)
|
singleOf(::BookGroupRepository)
|
||||||
|
singleOf(::DictRuleRepository)
|
||||||
singleOf(::SearchContentRepository)
|
singleOf(::SearchContentRepository)
|
||||||
singleOf(::RemoteBookRepository)
|
singleOf(::RemoteBookRepository)
|
||||||
singleOf(::ExploreKindUiUseCase)
|
singleOf(::ExploreKindUiUseCase)
|
||||||
|
singleOf(::AppStartupMaintenanceUseCase)
|
||||||
singleOf(::BatchCacheDownloadUseCase)
|
singleOf(::BatchCacheDownloadUseCase)
|
||||||
|
singleOf(::CacheBookChaptersUseCase)
|
||||||
singleOf(::ClearBookCacheUseCase)
|
singleOf(::ClearBookCacheUseCase)
|
||||||
singleOf(::DeleteBooksUseCase)
|
singleOf(::DeleteBooksUseCase)
|
||||||
|
singleOf(::GetReadingProgressUseCase)
|
||||||
|
singleOf(::RemoveBookGroupAssignmentUseCase)
|
||||||
singleOf(::UpdateBooksGroupUseCase)
|
singleOf(::UpdateBooksGroupUseCase)
|
||||||
|
singleOf(::UploadReadingProgressUseCase)
|
||||||
|
singleOf(::ResolveBookShelfStateUseCase)
|
||||||
|
singleOf(::ShrinkDatabaseUseCase)
|
||||||
|
singleOf(::WebDavBackupUseCase)
|
||||||
singleOf(::CacheConfig)
|
singleOf(::CacheConfig)
|
||||||
|
|
||||||
single<UploadRepository> { DirectLinkUploadRepository() }
|
single<UploadRepository> { DirectLinkUploadRepository() }
|
||||||
|
single<AppStartupGateway> { AppStartupRepository(get()) }
|
||||||
|
single<BookCacheDownloadGateway> { CacheBookDownloadRepository(get()) }
|
||||||
|
single<BookCacheCleanupGateway> { BookCacheCleanupRepository(get()) }
|
||||||
|
single<BookSourceCallbackGateway> { BookSourceCallbackRepository(get(), get()) }
|
||||||
|
single<LocalBookGateway> { LocalBookRepository(get()) }
|
||||||
|
single<DatabaseMaintenanceGateway> { DatabaseMaintenanceRepository(get()) }
|
||||||
|
single<WebDavBackupGateway> { WebDavBackupRepository() }
|
||||||
|
single<ReadingProgressGateway> { WebDavReadingProgressRepository() }
|
||||||
|
single<BookDomainRepository> { BookDomainRepositoryImpl(get(), get()) }
|
||||||
single<ExploreRepository> { ExploreRepositoryImpl(get()) }
|
single<ExploreRepository> { ExploreRepositoryImpl(get()) }
|
||||||
single<SearchRepository> { SearchRepositoryImpl(get()) }
|
single<SearchRepository> { SearchRepositoryImpl(get()) }
|
||||||
|
|
||||||
@@ -106,6 +154,7 @@ val appModule = module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewModelOf(::DictRuleViewModel)
|
viewModelOf(::DictRuleViewModel)
|
||||||
|
viewModelOf(::DictViewModel)
|
||||||
viewModelOf(::RssSourceViewModel)
|
viewModelOf(::RssSourceViewModel)
|
||||||
viewModelOf(::RssSortViewModel)
|
viewModelOf(::RssSortViewModel)
|
||||||
viewModelOf(::RssArticlesViewModel)
|
viewModelOf(::RssArticlesViewModel)
|
||||||
@@ -127,6 +176,8 @@ val appModule = module {
|
|||||||
viewModelOf(::TocViewModel)
|
viewModelOf(::TocViewModel)
|
||||||
viewModelOf(::ImportBookViewModel)
|
viewModelOf(::ImportBookViewModel)
|
||||||
viewModelOf(::RemoteBookViewModel)
|
viewModelOf(::RemoteBookViewModel)
|
||||||
|
viewModelOf(::ServerConfigViewModel)
|
||||||
|
viewModelOf(::ServersViewModel)
|
||||||
viewModelOf(::BookInfoViewModel)
|
viewModelOf(::BookInfoViewModel)
|
||||||
viewModelOf(::ChangeCoverViewModel)
|
viewModelOf(::ChangeCoverViewModel)
|
||||||
viewModelOf(::ChangeBookSourceComposeViewModel)
|
viewModelOf(::ChangeBookSourceComposeViewModel)
|
||||||
@@ -142,6 +193,7 @@ val appModule = module {
|
|||||||
bookChapterDao = get(),
|
bookChapterDao = get(),
|
||||||
cacheConfig = get(),
|
cacheConfig = get(),
|
||||||
batchCacheDownloadUseCase = get(),
|
batchCacheDownloadUseCase = get(),
|
||||||
|
cacheBookChaptersUseCase = get(),
|
||||||
clearBookCacheUseCase = get(),
|
clearBookCacheUseCase = get(),
|
||||||
deleteBooksUseCase = get(),
|
deleteBooksUseCase = get(),
|
||||||
updateBooksGroupUseCase = get()
|
updateBooksGroupUseCase = get()
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
interface AppStartupGateway {
|
||||||
|
suspend fun deleteNotShelfBooks()
|
||||||
|
suspend fun ensureDefaultHttpTts()
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
interface BookCacheCleanupGateway {
|
||||||
|
fun clearAll()
|
||||||
|
suspend fun clear(bookUrl: String): Boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
interface BookCacheDownloadGateway {
|
||||||
|
suspend fun start(bookUrl: String, chapterIndices: List<Int>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
interface BookSourceCallbackGateway {
|
||||||
|
suspend fun onDeleteFromShelf(bookUrl: String)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
interface DatabaseMaintenanceGateway {
|
||||||
|
fun shrink()
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
interface LocalBookGateway {
|
||||||
|
suspend fun deleteBook(bookUrl: String, deleteOriginal: Boolean)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
import io.legado.app.domain.model.ReadingProgress
|
||||||
|
|
||||||
|
interface ReadingProgressGateway {
|
||||||
|
val isConfigured: Boolean
|
||||||
|
|
||||||
|
suspend fun getProgress(name: String, author: String): ReadingProgress?
|
||||||
|
suspend fun uploadProgress(progress: ReadingProgress): Long?
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package io.legado.app.domain.gateway
|
||||||
|
|
||||||
|
import io.legado.app.domain.model.WebDavBackup
|
||||||
|
|
||||||
|
interface WebDavBackupGateway {
|
||||||
|
val isJianGuoYun: Boolean
|
||||||
|
|
||||||
|
suspend fun syncConfig()
|
||||||
|
suspend fun test(): Boolean
|
||||||
|
suspend fun getBackupNames(): List<String>
|
||||||
|
suspend fun getLatestBackup(): WebDavBackup?
|
||||||
|
suspend fun restore(name: String)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package io.legado.app.domain.model
|
||||||
|
|
||||||
|
data class BookGroupAssignment(
|
||||||
|
val bookUrl: String,
|
||||||
|
val group: Long
|
||||||
|
)
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package io.legado.app.model
|
package io.legado.app.domain.model
|
||||||
|
|
||||||
enum class BookShelfState {
|
enum class BookShelfState {
|
||||||
IN_SHELF,
|
IN_SHELF,
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package io.legado.app.domain.model
|
||||||
|
|
||||||
|
data class CacheableBook(
|
||||||
|
val bookUrl: String,
|
||||||
|
val isLocal: Boolean,
|
||||||
|
val isAudio: Boolean,
|
||||||
|
val durChapterIndex: Int,
|
||||||
|
val lastChapterIndex: Int
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package io.legado.app.domain.model
|
||||||
|
|
||||||
|
data class DeletableBook(
|
||||||
|
val bookUrl: String,
|
||||||
|
val origin: String,
|
||||||
|
val isLocal: Boolean
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package io.legado.app.domain.model
|
||||||
|
|
||||||
|
data class ReadingProgress(
|
||||||
|
val name: String,
|
||||||
|
val author: String,
|
||||||
|
val durChapterIndex: Int,
|
||||||
|
val durChapterPos: Int,
|
||||||
|
val durChapterTime: Long,
|
||||||
|
val durChapterTitle: String?
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package io.legado.app.domain.model
|
||||||
|
|
||||||
|
data class WebDavBackup(
|
||||||
|
val name: String,
|
||||||
|
val lastModify: Long
|
||||||
|
)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package io.legado.app.domain.repository
|
||||||
|
|
||||||
|
import io.legado.app.domain.model.BookGroupAssignment
|
||||||
|
import io.legado.app.domain.model.CacheableBook
|
||||||
|
import io.legado.app.domain.model.DeletableBook
|
||||||
|
|
||||||
|
interface BookDomainRepository {
|
||||||
|
suspend fun getCacheableBooks(bookUrls: Set<String>): List<CacheableBook>
|
||||||
|
suspend fun getDeletableBooks(bookUrls: Set<String>): List<DeletableBook>
|
||||||
|
suspend fun getBookGroupAssignments(bookUrls: Set<String>): List<BookGroupAssignment>
|
||||||
|
suspend fun updateBookGroups(assignments: List<BookGroupAssignment>)
|
||||||
|
suspend fun removeGroupFromBooks(groupId: Long)
|
||||||
|
suspend fun deleteBooks(bookUrls: Set<String>)
|
||||||
|
suspend fun deleteChaptersByBook(bookUrl: String)
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.gateway.AppStartupGateway
|
||||||
|
|
||||||
|
class AppStartupMaintenanceUseCase(
|
||||||
|
private val appStartupGateway: AppStartupGateway
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun deleteNotShelfBooks() {
|
||||||
|
appStartupGateway.deleteNotShelfBooks()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun ensureDefaultHttpTts() {
|
||||||
|
appStartupGateway.ensureDefaultHttpTts()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +1,43 @@
|
|||||||
package io.legado.app.domain.usecase
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
import android.content.Context
|
import io.legado.app.domain.gateway.BookCacheDownloadGateway
|
||||||
import io.legado.app.constant.BookType
|
import io.legado.app.domain.model.CacheableBook
|
||||||
import io.legado.app.data.dao.BookDao
|
import io.legado.app.domain.repository.BookDomainRepository
|
||||||
import io.legado.app.data.entities.Book
|
|
||||||
import io.legado.app.model.CacheBook
|
|
||||||
|
|
||||||
class BatchCacheDownloadUseCase(
|
class BatchCacheDownloadUseCase(
|
||||||
private val bookDao: BookDao
|
private val bookRepository: BookDomainRepository,
|
||||||
|
private val bookCacheDownloadGateway: BookCacheDownloadGateway
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(
|
suspend fun execute(
|
||||||
context: Context,
|
|
||||||
bookUrls: Set<String>,
|
bookUrls: Set<String>,
|
||||||
downloadAllChapters: Boolean,
|
downloadAllChapters: Boolean,
|
||||||
skipAudioBooks: Boolean = false
|
skipAudioBooks: Boolean = false
|
||||||
): Int {
|
): Int {
|
||||||
if (bookUrls.isEmpty()) return 0
|
if (bookUrls.isEmpty()) return 0
|
||||||
var count = 0
|
var count = 0
|
||||||
bookUrls.forEach { bookUrl ->
|
bookRepository.getCacheableBooks(bookUrls).forEach { book ->
|
||||||
val book = bookDao.getBook(bookUrl) ?: return@forEach
|
if (startIfNeeded(book, downloadAllChapters, skipAudioBooks)) {
|
||||||
if (startIfNeeded(context, book, downloadAllChapters, skipAudioBooks)) {
|
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
fun execute(
|
private suspend fun startIfNeeded(
|
||||||
context: Context,
|
book: CacheableBook,
|
||||||
books: List<Book>,
|
|
||||||
downloadAllChapters: Boolean,
|
|
||||||
skipAudioBooks: Boolean = false
|
|
||||||
): Int {
|
|
||||||
if (books.isEmpty()) return 0
|
|
||||||
var count = 0
|
|
||||||
books.forEach { book ->
|
|
||||||
if (startIfNeeded(context, book, downloadAllChapters, skipAudioBooks)) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startIfNeeded(
|
|
||||||
context: Context,
|
|
||||||
book: Book,
|
|
||||||
downloadAllChapters: Boolean,
|
downloadAllChapters: Boolean,
|
||||||
skipAudioBooks: Boolean
|
skipAudioBooks: Boolean
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (book.type and BookType.local > 0) return false
|
if (book.isLocal) return false
|
||||||
if (skipAudioBooks && book.type and BookType.audio > 0) return false
|
if (skipAudioBooks && book.isAudio) return false
|
||||||
val indices = if (downloadAllChapters) {
|
val indices = if (downloadAllChapters) {
|
||||||
(0..book.lastChapterIndex).toList()
|
(0..book.lastChapterIndex).toList()
|
||||||
} else {
|
} else {
|
||||||
(book.durChapterIndex..book.lastChapterIndex).toList()
|
(book.durChapterIndex..book.lastChapterIndex).toList()
|
||||||
}
|
}
|
||||||
if (indices.isEmpty()) return false
|
if (indices.isEmpty()) return false
|
||||||
CacheBook.start(context, book, indices)
|
bookCacheDownloadGateway.start(book.bookUrl, indices)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.gateway.BookCacheDownloadGateway
|
||||||
|
|
||||||
|
class CacheBookChaptersUseCase(
|
||||||
|
private val bookCacheDownloadGateway: BookCacheDownloadGateway
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(bookUrl: String, chapterIndices: Iterable<Int>): Int {
|
||||||
|
val indices = chapterIndices.distinct()
|
||||||
|
if (indices.isEmpty()) return 0
|
||||||
|
bookCacheDownloadGateway.start(bookUrl, indices)
|
||||||
|
return indices.size
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
package io.legado.app.domain.usecase
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
import io.legado.app.data.dao.BookDao
|
import io.legado.app.domain.gateway.BookCacheCleanupGateway
|
||||||
import io.legado.app.data.entities.Book
|
|
||||||
import io.legado.app.help.book.BookHelp
|
|
||||||
|
|
||||||
class ClearBookCacheUseCase(
|
class ClearBookCacheUseCase(
|
||||||
private val bookDao: BookDao
|
private val bookCacheCleanupGateway: BookCacheCleanupGateway
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(book: Book): String {
|
fun executeAll() {
|
||||||
BookHelp.clearCache(book)
|
bookCacheCleanupGateway.clearAll()
|
||||||
return book.bookUrl
|
}
|
||||||
|
|
||||||
|
suspend fun execute(bookUrl: String): String? {
|
||||||
|
return if (bookCacheCleanupGateway.clear(bookUrl)) bookUrl else null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun execute(bookUrls: Set<String>): List<String> {
|
suspend fun execute(bookUrls: Set<String>): List<String> {
|
||||||
if (bookUrls.isEmpty()) return emptyList()
|
if (bookUrls.isEmpty()) return emptyList()
|
||||||
return bookUrls.mapNotNull { bookUrl ->
|
return bookUrls.mapNotNull { execute(it) }
|
||||||
bookDao.getBook(bookUrl)?.let { execute(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,27 @@
|
|||||||
package io.legado.app.domain.usecase
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
import io.legado.app.data.dao.BookChapterDao
|
import io.legado.app.domain.gateway.BookSourceCallbackGateway
|
||||||
import io.legado.app.data.dao.BookDao
|
import io.legado.app.domain.gateway.LocalBookGateway
|
||||||
import io.legado.app.data.dao.BookSourceDao
|
import io.legado.app.domain.repository.BookDomainRepository
|
||||||
import io.legado.app.help.book.isLocal
|
|
||||||
import io.legado.app.model.SourceCallBack
|
|
||||||
import io.legado.app.model.localBook.LocalBook
|
|
||||||
|
|
||||||
class DeleteBooksUseCase(
|
class DeleteBooksUseCase(
|
||||||
private val bookDao: BookDao,
|
private val bookRepository: BookDomainRepository,
|
||||||
private val bookChapterDao: BookChapterDao,
|
private val localBookGateway: LocalBookGateway,
|
||||||
private val bookSourceDao: BookSourceDao
|
private val bookSourceCallbackGateway: BookSourceCallbackGateway
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun execute(bookUrls: Set<String>, deleteOriginal: Boolean): List<String> {
|
suspend fun execute(bookUrls: Set<String>, deleteOriginal: Boolean): List<String> {
|
||||||
if (bookUrls.isEmpty()) return emptyList()
|
if (bookUrls.isEmpty()) return emptyList()
|
||||||
val books = bookUrls.mapNotNull { bookDao.getBook(it) }
|
val books = bookRepository.getDeletableBooks(bookUrls)
|
||||||
books.forEach { book ->
|
books.forEach { book ->
|
||||||
if (book.isLocal) {
|
if (book.isLocal) {
|
||||||
LocalBook.deleteBook(book, deleteOriginal)
|
localBookGateway.deleteBook(book.bookUrl, deleteOriginal)
|
||||||
} else {
|
} else {
|
||||||
val source = bookSourceDao.getBookSource(book.origin)
|
bookSourceCallbackGateway.onDeleteFromShelf(book.bookUrl)
|
||||||
SourceCallBack.callBackBook(SourceCallBack.DEL_BOOK_SHELF, source, book)
|
|
||||||
}
|
}
|
||||||
bookChapterDao.delByBook(book.bookUrl)
|
bookRepository.deleteChaptersByBook(book.bookUrl)
|
||||||
}
|
|
||||||
if (books.isNotEmpty()) {
|
|
||||||
bookDao.delete(*books.toTypedArray())
|
|
||||||
}
|
}
|
||||||
|
bookRepository.deleteBooks(books.map { it.bookUrl }.toSet())
|
||||||
return books.map { it.bookUrl }
|
return books.map { it.bookUrl }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.gateway.ReadingProgressGateway
|
||||||
|
import io.legado.app.domain.model.ReadingProgress
|
||||||
|
|
||||||
|
class GetReadingProgressUseCase(
|
||||||
|
private val readingProgressGateway: ReadingProgressGateway
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isConfigured: Boolean
|
||||||
|
get() = readingProgressGateway.isConfigured
|
||||||
|
|
||||||
|
suspend fun execute(name: String, author: String): ReadingProgress? {
|
||||||
|
return readingProgressGateway.getProgress(name, author)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.repository.BookDomainRepository
|
||||||
|
|
||||||
|
class RemoveBookGroupAssignmentUseCase(
|
||||||
|
private val bookRepository: BookDomainRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(groupId: Long) {
|
||||||
|
bookRepository.removeGroupFromBooks(groupId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.model.BookShelfState
|
||||||
|
|
||||||
|
data class BookShelfKey(
|
||||||
|
val name: String,
|
||||||
|
val author: String,
|
||||||
|
val url: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
class ResolveBookShelfStateUseCase {
|
||||||
|
|
||||||
|
fun execute(
|
||||||
|
name: String,
|
||||||
|
author: String,
|
||||||
|
url: String?,
|
||||||
|
shelf: Set<BookShelfKey>
|
||||||
|
): BookShelfState {
|
||||||
|
val exactMatch = shelf.any {
|
||||||
|
it.name == name && it.author == author && it.url == url
|
||||||
|
}
|
||||||
|
if (exactMatch) return BookShelfState.IN_SHELF
|
||||||
|
|
||||||
|
val sameNameAuthor = shelf.any {
|
||||||
|
it.name == name && it.author == author && it.url != url
|
||||||
|
}
|
||||||
|
if (sameNameAuthor) return BookShelfState.SAME_NAME_AUTHOR
|
||||||
|
|
||||||
|
return BookShelfState.NOT_IN_SHELF
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.gateway.DatabaseMaintenanceGateway
|
||||||
|
|
||||||
|
class ShrinkDatabaseUseCase(
|
||||||
|
private val databaseMaintenanceGateway: DatabaseMaintenanceGateway
|
||||||
|
) {
|
||||||
|
fun execute() {
|
||||||
|
databaseMaintenanceGateway.shrink()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package io.legado.app.domain.usecase
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
import io.legado.app.data.dao.BookDao
|
import io.legado.app.domain.repository.BookDomainRepository
|
||||||
|
|
||||||
class UpdateBooksGroupUseCase(
|
class UpdateBooksGroupUseCase(
|
||||||
private val bookDao: BookDao
|
private val bookRepository: BookDomainRepository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun replaceGroup(bookUrls: Set<String>, groupId: Long) {
|
suspend fun replaceGroup(bookUrls: Set<String>, groupId: Long) {
|
||||||
@@ -12,18 +12,14 @@ class UpdateBooksGroupUseCase(
|
|||||||
|
|
||||||
suspend fun updateGroups(bookUrls: Set<String>, transform: (Long) -> Long) {
|
suspend fun updateGroups(bookUrls: Set<String>, transform: (Long) -> Long) {
|
||||||
if (bookUrls.isEmpty()) return
|
if (bookUrls.isEmpty()) return
|
||||||
val updateBooks = bookUrls.mapNotNull { bookUrl ->
|
val updateGroups = bookRepository.getBookGroupAssignments(bookUrls).mapNotNull { book ->
|
||||||
bookDao.getBook(bookUrl)?.let { book ->
|
val targetGroup = transform(book.group)
|
||||||
val targetGroup = transform(book.group)
|
if (targetGroup == book.group) {
|
||||||
if (targetGroup == book.group) {
|
null
|
||||||
null
|
} else {
|
||||||
} else {
|
book.copy(group = targetGroup)
|
||||||
book.copy(group = targetGroup)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (updateBooks.isNotEmpty()) {
|
bookRepository.updateBookGroups(updateGroups)
|
||||||
bookDao.update(*updateBooks.toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.gateway.ReadingProgressGateway
|
||||||
|
import io.legado.app.domain.model.ReadingProgress
|
||||||
|
|
||||||
|
class UploadReadingProgressUseCase(
|
||||||
|
private val readingProgressGateway: ReadingProgressGateway
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(progress: ReadingProgress): Long? {
|
||||||
|
return readingProgressGateway.uploadProgress(progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package io.legado.app.domain.usecase
|
||||||
|
|
||||||
|
import io.legado.app.domain.gateway.WebDavBackupGateway
|
||||||
|
import io.legado.app.domain.model.WebDavBackup
|
||||||
|
|
||||||
|
class WebDavBackupUseCase(
|
||||||
|
private val webDavBackupGateway: WebDavBackupGateway
|
||||||
|
) {
|
||||||
|
|
||||||
|
val isJianGuoYun: Boolean
|
||||||
|
get() = webDavBackupGateway.isJianGuoYun
|
||||||
|
|
||||||
|
suspend fun refreshConfig() {
|
||||||
|
webDavBackupGateway.syncConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun test(): Boolean {
|
||||||
|
webDavBackupGateway.syncConfig()
|
||||||
|
return webDavBackupGateway.test()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getBackupNames(): List<String> {
|
||||||
|
webDavBackupGateway.syncConfig()
|
||||||
|
return webDavBackupGateway.getBackupNames()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getLatestBackup(): WebDavBackup? {
|
||||||
|
webDavBackupGateway.syncConfig()
|
||||||
|
return webDavBackupGateway.getLatestBackup()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun restore(name: String) {
|
||||||
|
webDavBackupGateway.syncConfig()
|
||||||
|
webDavBackupGateway.restore(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -285,18 +285,23 @@ object AppWebDav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uploadBookProgress(bookProgress: BookProgress, onSuccess: (() -> Unit)? = null) {
|
suspend fun uploadBookProgress(
|
||||||
|
bookProgress: BookProgress,
|
||||||
|
onSuccess: (() -> Unit)? = null
|
||||||
|
): Boolean {
|
||||||
try {
|
try {
|
||||||
val authorization = authorization ?: return
|
val authorization = authorization ?: return false
|
||||||
if (!AppConfig.syncBookProgress) return
|
if (!AppConfig.syncBookProgress) return false
|
||||||
if (!NetworkUtils.isAvailable()) return
|
if (!NetworkUtils.isAvailable()) return false
|
||||||
val json = GSON.toJson(bookProgress)
|
val json = GSON.toJson(bookProgress)
|
||||||
val url = getProgressUrl(bookProgress.name, bookProgress.author)
|
val url = getProgressUrl(bookProgress.name, bookProgress.author)
|
||||||
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
WebDav(url, authorization).upload(json.toByteArray(), "application/json")
|
||||||
onSuccess?.invoke()
|
onSuccess?.invoke()
|
||||||
|
return true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
currentCoroutineContext().ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
AppLog.put("上传进度失败\n${e.localizedMessage}", e)
|
AppLog.put("上传进度失败\n${e.localizedMessage}", e)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +317,14 @@ object AppWebDav {
|
|||||||
* 获取书籍进度
|
* 获取书籍进度
|
||||||
*/
|
*/
|
||||||
suspend fun getBookProgress(book: Book): BookProgress? {
|
suspend fun getBookProgress(book: Book): BookProgress? {
|
||||||
val url = getProgressUrl(book.name, book.author)
|
return getBookProgress(book.name, book.author)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取书籍进度
|
||||||
|
*/
|
||||||
|
suspend fun getBookProgress(name: String, author: String): BookProgress? {
|
||||||
|
val url = getProgressUrl(name, author)
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val authorization = authorization ?: return null
|
val authorization = authorization ?: return null
|
||||||
WebDav(url, authorization).download().let { byteArray ->
|
WebDav(url, authorization).download().let { byteArray ->
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package io.legado.app.help.book
|
package io.legado.app.help.book
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.script.buildScriptBindings
|
import com.script.buildScriptBindings
|
||||||
@@ -168,7 +169,7 @@ fun Book.getLocalUri(): Uri {
|
|||||||
return uri
|
return uri
|
||||||
}
|
}
|
||||||
//不同的设备书籍保存路径可能不一样, uri无效时尝试寻找当前保存路径下的文件
|
//不同的设备书籍保存路径可能不一样, uri无效时尝试寻找当前保存路径下的文件
|
||||||
val defaultBookDir = AppConfig.defaultBookTreeUri
|
val defaultBookDir = OtherConfig.defaultBookTreeUri
|
||||||
val importBookDir = AppConfig.importBookPath
|
val importBookDir = AppConfig.importBookPath
|
||||||
|
|
||||||
// 查找书籍保存目录
|
// 查找书籍保存目录
|
||||||
@@ -240,7 +241,7 @@ fun Book.getLocalUri(): Uri {
|
|||||||
|
|
||||||
|
|
||||||
fun Book.getArchiveUri(): Uri? {
|
fun Book.getArchiveUri(): Uri? {
|
||||||
val defaultBookDir = AppConfig.defaultBookTreeUri
|
val defaultBookDir = OtherConfig.defaultBookTreeUri
|
||||||
return if (isArchive && !defaultBookDir.isNullOrBlank()) {
|
return if (isArchive && !defaultBookDir.isNullOrBlank()) {
|
||||||
FileDoc.fromUri(defaultBookDir.toUri(), true)
|
FileDoc.fromUri(defaultBookDir.toUri(), true)
|
||||||
.find(archiveName)?.uri
|
.find(archiveName)?.uri
|
||||||
|
|||||||
@@ -303,17 +303,6 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 书籍保存位置
|
|
||||||
var defaultBookTreeUri: String?
|
|
||||||
get() = appCtx.getPrefString(PreferKey.defaultBookTreeUri)
|
|
||||||
set(value) {
|
|
||||||
if (value.isNullOrEmpty()) {
|
|
||||||
appCtx.removePref(PreferKey.defaultBookTreeUri)
|
|
||||||
} else {
|
|
||||||
appCtx.putPrefString(PreferKey.defaultBookTreeUri, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val showDiscovery: Boolean
|
val showDiscovery: Boolean
|
||||||
get() = appCtx.getPrefBoolean(PreferKey.showDiscovery, true)
|
get() = appCtx.getPrefBoolean(PreferKey.showDiscovery, true)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.model.localBook
|
package io.legado.app.model.localBook
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
@@ -401,7 +402,7 @@ object LocalBook {
|
|||||||
fileName: String,
|
fileName: String,
|
||||||
source: BaseSource? = null,
|
source: BaseSource? = null,
|
||||||
): Uri {
|
): Uri {
|
||||||
AppConfig.defaultBookTreeUri
|
OtherConfig.defaultBookTreeUri
|
||||||
?: throw NoBooksDirException()
|
?: throw NoBooksDirException()
|
||||||
val inputStream = when {
|
val inputStream = when {
|
||||||
str.isAbsUrl() -> AnalyzeUrl(
|
str.isAbsUrl() -> AnalyzeUrl(
|
||||||
@@ -427,7 +428,7 @@ object LocalBook {
|
|||||||
fileName: String
|
fileName: String
|
||||||
): Uri {
|
): Uri {
|
||||||
inputStream.use {
|
inputStream.use {
|
||||||
val defaultBookTreeUri = AppConfig.defaultBookTreeUri
|
val defaultBookTreeUri = OtherConfig.defaultBookTreeUri
|
||||||
if (defaultBookTreeUri.isNullOrBlank()) throw NoBooksDirException()
|
if (defaultBookTreeUri.isNullOrBlank()) throw NoBooksDirException()
|
||||||
val treeUri = defaultBookTreeUri.toUri()
|
val treeUri = defaultBookTreeUri.toUri()
|
||||||
return if (treeUri.isContentScheme()) {
|
return if (treeUri.isContentScheme()) {
|
||||||
@@ -481,7 +482,7 @@ object LocalBook {
|
|||||||
val webDavUrl = localBook.getRemoteUrl()
|
val webDavUrl = localBook.getRemoteUrl()
|
||||||
if (webDavUrl.isNullOrBlank()) throw NoStackTraceException("Book file is not webDav File")
|
if (webDavUrl.isNullOrBlank()) throw NoStackTraceException("Book file is not webDav File")
|
||||||
try {
|
try {
|
||||||
AppConfig.defaultBookTreeUri
|
OtherConfig.defaultBookTreeUri
|
||||||
?: throw NoBooksDirException()
|
?: throw NoBooksDirException()
|
||||||
// 兼容旧版链接
|
// 兼容旧版链接
|
||||||
val webdav: WebDav = kotlin.runCatching {
|
val webdav: WebDav = kotlin.runCatching {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.model.remote
|
package io.legado.app.model.remote
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import io.legado.app.constant.AppPattern.archiveFileRegex
|
import io.legado.app.constant.AppPattern.archiveFileRegex
|
||||||
import io.legado.app.constant.AppPattern.bookFileRegex
|
import io.legado.app.constant.AppPattern.bookFileRegex
|
||||||
@@ -7,7 +8,6 @@ import io.legado.app.constant.BookType
|
|||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.book.update
|
import io.legado.app.help.book.update
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.lib.webdav.Authorization
|
import io.legado.app.lib.webdav.Authorization
|
||||||
import io.legado.app.lib.webdav.WebDav
|
import io.legado.app.lib.webdav.WebDav
|
||||||
import io.legado.app.lib.webdav.WebDavFile
|
import io.legado.app.lib.webdav.WebDavFile
|
||||||
@@ -57,7 +57,7 @@ class RemoteBookWebDav(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun downloadRemoteBook(remoteBook: RemoteBook): Uri {
|
override suspend fun downloadRemoteBook(remoteBook: RemoteBook): Uri {
|
||||||
AppConfig.defaultBookTreeUri
|
OtherConfig.defaultBookTreeUri
|
||||||
?: throw NoStackTraceException("没有设置书籍保存位置!")
|
?: throw NoStackTraceException("没有设置书籍保存位置!")
|
||||||
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
|
if (!NetworkUtils.isAvailable()) throw NoStackTraceException("网络不可用")
|
||||||
val webdav = WebDav(remoteBook.path, authorization)
|
val webdav = WebDav(remoteBook.path, authorization)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.ui.association
|
package io.legado.app.ui.association
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
@@ -11,7 +12,6 @@ import io.legado.app.base.VMBaseActivity
|
|||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.databinding.ActivityTranslucenceBinding
|
import io.legado.app.databinding.ActivityTranslucenceBinding
|
||||||
import io.legado.app.exception.InvalidBooksDirException
|
import io.legado.app.exception.InvalidBooksDirException
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
import io.legado.app.lib.permission.Permissions
|
import io.legado.app.lib.permission.Permissions
|
||||||
import io.legado.app.lib.permission.PermissionsCompat
|
import io.legado.app.lib.permission.PermissionsCompat
|
||||||
@@ -43,7 +43,7 @@ class FileAssociationActivity :
|
|||||||
private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
|
private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
|
||||||
intent.data?.let { uri ->
|
intent.data?.let { uri ->
|
||||||
it.uri?.let { treeUri ->
|
it.uri?.let { treeUri ->
|
||||||
AppConfig.defaultBookTreeUri = treeUri.toString()
|
OtherConfig.defaultBookTreeUri = treeUri.toString()
|
||||||
importBook(treeUri, uri)
|
importBook(treeUri, uri)
|
||||||
} ?: let {
|
} ?: let {
|
||||||
val storageHelp = String(assets.open("storageHelp.md").readBytes())
|
val storageHelp = String(assets.open("storageHelp.md").readBytes())
|
||||||
@@ -134,7 +134,7 @@ class FileAssociationActivity :
|
|||||||
|
|
||||||
private fun importBook(uri: Uri) {
|
private fun importBook(uri: Uri) {
|
||||||
if (uri.isContentScheme()) {
|
if (uri.isContentScheme()) {
|
||||||
val treeUriStr = AppConfig.defaultBookTreeUri
|
val treeUriStr = OtherConfig.defaultBookTreeUri
|
||||||
if (treeUriStr.isNullOrEmpty()) {
|
if (treeUriStr.isNullOrEmpty()) {
|
||||||
localBookTreeSelect.launch {
|
localBookTreeSelect.launch {
|
||||||
title = getString(R.string.select_book_folder)
|
title = getString(R.string.select_book_folder)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.ui.book.cache
|
package io.legado.app.ui.book.cache
|
||||||
|
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@@ -50,7 +51,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -89,7 +89,6 @@ import io.legado.app.utils.isContentScheme
|
|||||||
import io.legado.app.utils.startService
|
import io.legado.app.utils.startService
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import io.legado.app.utils.verificationField
|
import io.legado.app.utils.verificationField
|
||||||
import io.legado.app.help.config.LocalConfig
|
|
||||||
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@@ -158,7 +157,7 @@ private fun CacheScreen(
|
|||||||
var isSearchMode by remember { mutableStateOf(false) }
|
var isSearchMode by remember { mutableStateOf(false) }
|
||||||
var searchKey by remember { mutableStateOf("") }
|
var searchKey by remember { mutableStateOf("") }
|
||||||
var selectedBookUrls by remember { mutableStateOf<Set<String>>(emptySet()) }
|
var selectedBookUrls by remember { mutableStateOf<Set<String>>(emptySet()) }
|
||||||
var deleteOriginalBookFile by remember { mutableStateOf(LocalConfig.deleteBookOriginal) }
|
var deleteOriginalBookFile by remember { mutableStateOf(state.deleteBookOriginal) }
|
||||||
val exportBookPathKey = remember { "exportBookPath" }
|
val exportBookPathKey = remember { "exportBookPath" }
|
||||||
val exportTypes = remember { arrayListOf("txt", "epub") }
|
val exportTypes = remember { arrayListOf("txt", "epub") }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
@@ -409,7 +408,7 @@ private fun CacheScreen(
|
|||||||
) {
|
) {
|
||||||
if (selectedBookUrls.isNotEmpty()) {
|
if (selectedBookUrls.isNotEmpty()) {
|
||||||
pendingDeleteBookUrls = selectedBookUrls
|
pendingDeleteBookUrls = selectedBookUrls
|
||||||
deleteOriginalBookFile = LocalConfig.deleteBookOriginal
|
deleteOriginalBookFile = state.deleteBookOriginal
|
||||||
showDeleteBookConfirmDialog = true
|
showDeleteBookConfirmDialog = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,7 +637,7 @@ private fun CacheScreen(
|
|||||||
onClick = { toggleBookSelection(book) },
|
onClick = { toggleBookSelection(book) },
|
||||||
onLongClick = { toggleBookSelection(book) },
|
onLongClick = { toggleBookSelection(book) },
|
||||||
containerColor = if (isSelected) {
|
containerColor = if (isSelected) {
|
||||||
LegadoTheme.colorScheme.secondaryContainer
|
LegadoTheme.colorScheme.surfaceContainerHigh
|
||||||
} else {
|
} else {
|
||||||
LegadoTheme.colorScheme.surfaceContainerLow
|
LegadoTheme.colorScheme.surfaceContainerLow
|
||||||
}
|
}
|
||||||
@@ -727,7 +726,7 @@ private fun CacheScreen(
|
|||||||
text = "删除书籍",
|
text = "删除书籍",
|
||||||
onClick = {
|
onClick = {
|
||||||
pendingDeleteBookUrls = setOf(book.bookUrl)
|
pendingDeleteBookUrls = setOf(book.bookUrl)
|
||||||
deleteOriginalBookFile = LocalConfig.deleteBookOriginal
|
deleteOriginalBookFile = state.deleteBookOriginal
|
||||||
showDeleteBookConfirmDialog = true
|
showDeleteBookConfirmDialog = true
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@@ -806,7 +805,6 @@ private fun CacheScreen(
|
|||||||
confirmText = stringResource(android.R.string.ok),
|
confirmText = stringResource(android.R.string.ok),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
showDeleteBookConfirmDialog = false
|
showDeleteBookConfirmDialog = false
|
||||||
LocalConfig.deleteBookOriginal = deleteOriginalBookFile
|
|
||||||
viewModel.dispatch(
|
viewModel.dispatch(
|
||||||
CacheIntent.DeleteBooks(
|
CacheIntent.DeleteBooks(
|
||||||
bookUrls = pendingDeleteBookUrls,
|
bookUrls = pendingDeleteBookUrls,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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.domain.usecase.BatchCacheDownloadUseCase
|
import io.legado.app.domain.usecase.BatchCacheDownloadUseCase
|
||||||
|
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.domain.usecase.DeleteBooksUseCase
|
import io.legado.app.domain.usecase.DeleteBooksUseCase
|
||||||
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
|
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
|
||||||
@@ -17,6 +18,7 @@ import io.legado.app.help.book.BookHelp
|
|||||||
import io.legado.app.help.book.isAudio
|
import io.legado.app.help.book.isAudio
|
||||||
import io.legado.app.help.book.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
import io.legado.app.model.CacheBook
|
import io.legado.app.model.CacheBook
|
||||||
|
import io.legado.app.help.config.LocalConfig
|
||||||
import io.legado.app.service.ExportBookService
|
import io.legado.app.service.ExportBookService
|
||||||
import io.legado.app.ui.config.cacheConfig.CacheConfig
|
import io.legado.app.ui.config.cacheConfig.CacheConfig
|
||||||
import io.legado.app.utils.cnCompare
|
import io.legado.app.utils.cnCompare
|
||||||
@@ -51,6 +53,7 @@ data class CacheUiState(
|
|||||||
val books: List<Book> = emptyList(),
|
val books: List<Book> = emptyList(),
|
||||||
val isDownloadRunning: Boolean = false,
|
val isDownloadRunning: Boolean = false,
|
||||||
val cacheVersion: Long = 0,
|
val cacheVersion: Long = 0,
|
||||||
|
val deleteBookOriginal: Boolean = LocalConfig.deleteBookOriginal,
|
||||||
val exportConfig: CacheExportConfig = CacheExportConfig()
|
val exportConfig: CacheExportConfig = CacheExportConfig()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,6 +96,7 @@ class CacheViewModel(
|
|||||||
private val bookChapterDao: BookChapterDao,
|
private val bookChapterDao: BookChapterDao,
|
||||||
val cacheConfig: CacheConfig,
|
val cacheConfig: CacheConfig,
|
||||||
private val batchCacheDownloadUseCase: BatchCacheDownloadUseCase,
|
private val batchCacheDownloadUseCase: BatchCacheDownloadUseCase,
|
||||||
|
private val cacheBookChaptersUseCase: CacheBookChaptersUseCase,
|
||||||
private val clearBookCacheUseCase: ClearBookCacheUseCase,
|
private val clearBookCacheUseCase: ClearBookCacheUseCase,
|
||||||
private val deleteBooksUseCase: DeleteBooksUseCase,
|
private val deleteBooksUseCase: DeleteBooksUseCase,
|
||||||
private val updateBooksGroupUseCase: UpdateBooksGroupUseCase
|
private val updateBooksGroupUseCase: UpdateBooksGroupUseCase
|
||||||
@@ -335,8 +339,7 @@ class CacheViewModel(
|
|||||||
private fun startDownloadForVisibleBooks(books: List<Book>, downloadAllChapters: Boolean) {
|
private fun startDownloadForVisibleBooks(books: List<Book>, downloadAllChapters: Boolean) {
|
||||||
execute {
|
execute {
|
||||||
batchCacheDownloadUseCase.execute(
|
batchCacheDownloadUseCase.execute(
|
||||||
context = context,
|
bookUrls = books.map { it.bookUrl }.toSet(),
|
||||||
books = books,
|
|
||||||
downloadAllChapters = downloadAllChapters,
|
downloadAllChapters = downloadAllChapters,
|
||||||
skipAudioBooks = true
|
skipAudioBooks = true
|
||||||
)
|
)
|
||||||
@@ -349,10 +352,14 @@ class CacheViewModel(
|
|||||||
if (book.isLocal) return
|
if (book.isLocal) return
|
||||||
if (isBookDownloading(book.bookUrl)) {
|
if (isBookDownloading(book.bookUrl)) {
|
||||||
CacheBook.remove(context, book.bookUrl)
|
CacheBook.remove(context, book.bookUrl)
|
||||||
|
syncDownloadRunning()
|
||||||
} else {
|
} else {
|
||||||
CacheBook.start(context, book, (0..book.lastChapterIndex).toList())
|
execute {
|
||||||
|
cacheBookChaptersUseCase.execute(book.bookUrl, 0..book.lastChapterIndex)
|
||||||
|
}.onFinally {
|
||||||
|
syncDownloadRunning()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
syncDownloadRunning()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun moveBooksToGroup(bookUrls: Set<String>, groupId: Long) {
|
private fun moveBooksToGroup(bookUrls: Set<String>, groupId: Long) {
|
||||||
@@ -368,8 +375,10 @@ class CacheViewModel(
|
|||||||
private fun deleteBooks(bookUrls: Set<String>, deleteOriginal: Boolean) {
|
private fun deleteBooks(bookUrls: Set<String>, deleteOriginal: Boolean) {
|
||||||
if (bookUrls.isEmpty()) return
|
if (bookUrls.isEmpty()) return
|
||||||
execute {
|
execute {
|
||||||
|
LocalConfig.deleteBookOriginal = deleteOriginal
|
||||||
deleteBooksUseCase.execute(bookUrls, deleteOriginal)
|
deleteBooksUseCase.execute(bookUrls, deleteOriginal)
|
||||||
}.onSuccess { deletedBookUrls ->
|
}.onSuccess { deletedBookUrls ->
|
||||||
|
_uiState.update { it.copy(deleteBookOriginal = deleteOriginal) }
|
||||||
deletedBookUrls.forEach { cacheChapters.remove(it) }
|
deletedBookUrls.forEach { cacheChapters.remove(it) }
|
||||||
_effects.tryEmit(CacheEffect.ShowMessage("删除成功"))
|
_effects.tryEmit(CacheEffect.ShowMessage("删除成功"))
|
||||||
}.onError {
|
}.onError {
|
||||||
@@ -396,7 +405,6 @@ class CacheViewModel(
|
|||||||
if (bookUrls.isEmpty()) return
|
if (bookUrls.isEmpty()) return
|
||||||
execute {
|
execute {
|
||||||
batchCacheDownloadUseCase.execute(
|
batchCacheDownloadUseCase.execute(
|
||||||
context = context,
|
|
||||||
bookUrls = bookUrls,
|
bookUrls = bookUrls,
|
||||||
downloadAllChapters = downloadAllChapters,
|
downloadAllChapters = downloadAllChapters,
|
||||||
skipAudioBooks = true
|
skipAudioBooks = true
|
||||||
@@ -414,9 +422,13 @@ class CacheViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCacheForBook(book: Book) {
|
private fun clearCacheForBook(book: Book) {
|
||||||
val bookUrl = clearBookCacheUseCase.execute(book)
|
execute {
|
||||||
cacheChapters[bookUrl] = hashSetOf()
|
clearBookCacheUseCase.execute(book.bookUrl)
|
||||||
emitBookChanged(bookUrl)
|
}.onSuccess { bookUrl ->
|
||||||
|
bookUrl ?: return@onSuccess
|
||||||
|
cacheChapters[bookUrl] = hashSetOf()
|
||||||
|
emitBookChanged(bookUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitBookChanged(bookUrl: String) {
|
private fun emitBookChanged(bookUrl: String) {
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
import io.legado.app.data.entities.SearchBook
|
import io.legado.app.data.entities.SearchBook
|
||||||
import io.legado.app.domain.usecase.ExploreKindUiUseCase
|
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
|
||||||
import io.legado.app.model.BookShelfState
|
import io.legado.app.domain.model.BookShelfState
|
||||||
import io.legado.app.ui.theme.LegadoTheme
|
import io.legado.app.ui.theme.LegadoTheme
|
||||||
import io.legado.app.ui.theme.ThemeResolver
|
import io.legado.app.ui.theme.ThemeResolver
|
||||||
import io.legado.app.ui.theme.responsiveHazeEffect
|
import io.legado.app.ui.theme.responsiveHazeEffect
|
||||||
@@ -84,7 +84,6 @@ import io.legado.app.ui.widget.components.text.AppText
|
|||||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
@@ -444,12 +443,12 @@ fun ExploreShowScreen(
|
|||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = books,
|
items = books,
|
||||||
key = { it.bookUrl }
|
key = { it.book.bookUrl }
|
||||||
) { book ->
|
) { item ->
|
||||||
ExploreBookGridItem(
|
ExploreBookGridItem(
|
||||||
book = book,
|
book = item.book,
|
||||||
shelfState = viewModel.getBookShelfStateFlow(book),
|
shelfState = item.shelfState,
|
||||||
onClick = { onBookClick(book) },
|
onClick = { onBookClick(item.book) },
|
||||||
modifier = Modifier.animateItem()
|
modifier = Modifier.animateItem()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -476,12 +475,12 @@ fun ExploreShowScreen(
|
|||||||
) {
|
) {
|
||||||
items(
|
items(
|
||||||
items = books,
|
items = books,
|
||||||
key = { it.bookUrl }
|
key = { it.book.bookUrl }
|
||||||
) { book ->
|
) { item ->
|
||||||
ExploreBookItem(
|
ExploreBookItem(
|
||||||
book = book,
|
book = item.book,
|
||||||
shelfState = viewModel.getBookShelfStateFlow(book),
|
shelfState = item.shelfState,
|
||||||
onClick = { onBookClick(book) },
|
onClick = { onBookClick(item.book) },
|
||||||
modifier = Modifier.animateItem()
|
modifier = Modifier.animateItem()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -505,7 +504,7 @@ fun ExploreShowScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ExploreBookItem(
|
fun ExploreBookItem(
|
||||||
book: SearchBook,
|
book: SearchBook,
|
||||||
shelfState: Flow<BookShelfState>,
|
shelfState: BookShelfState,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@@ -521,7 +520,7 @@ fun ExploreBookItem(
|
|||||||
fun ExploreBookGridItem(
|
fun ExploreBookGridItem(
|
||||||
book: SearchBook,
|
book: SearchBook,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
shelfState: Flow<BookShelfState>,
|
shelfState: BookShelfState,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
SearchBookGridItem(
|
SearchBookGridItem(
|
||||||
|
|||||||
@@ -7,18 +7,16 @@ import io.legado.app.data.entities.BookSource
|
|||||||
import io.legado.app.data.entities.SearchBook
|
import io.legado.app.data.entities.SearchBook
|
||||||
import io.legado.app.data.entities.rule.ExploreKind
|
import io.legado.app.data.entities.rule.ExploreKind
|
||||||
import io.legado.app.data.repository.ExploreRepository
|
import io.legado.app.data.repository.ExploreRepository
|
||||||
|
import io.legado.app.domain.usecase.BookShelfKey
|
||||||
|
import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.model.BookShelfState
|
import io.legado.app.domain.model.BookShelfState
|
||||||
import io.legado.app.ui.book.search.BookKey
|
|
||||||
import io.legado.app.utils.exploreLayoutGrid
|
import io.legado.app.utils.exploreLayoutGrid
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
@@ -46,8 +44,14 @@ sealed class UiState<out T> {
|
|||||||
data object Empty : UiState<Nothing>()
|
data object Empty : UiState<Nothing>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ExploreBookItemUi(
|
||||||
|
val book: SearchBook,
|
||||||
|
val shelfState: BookShelfState = BookShelfState.NOT_IN_SHELF,
|
||||||
|
)
|
||||||
|
|
||||||
class ExploreShowViewModel(
|
class ExploreShowViewModel(
|
||||||
private val repository: ExploreRepository
|
private val repository: ExploreRepository,
|
||||||
|
private val resolveBookShelfStateUseCase: ResolveBookShelfStateUseCase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _rawBooks = MutableStateFlow<List<SearchBook>>(emptyList())
|
private val _rawBooks = MutableStateFlow<List<SearchBook>>(emptyList())
|
||||||
@@ -60,7 +64,7 @@ class ExploreShowViewModel(
|
|||||||
private var exploreUrl: String? = null
|
private var exploreUrl: String? = null
|
||||||
private var page = 1
|
private var page = 1
|
||||||
private val _isEndStateFlow = MutableStateFlow(false)
|
private val _isEndStateFlow = MutableStateFlow(false)
|
||||||
private val _bookshelf = MutableStateFlow<Set<BookKey>>(emptySet())
|
private val _bookshelf = MutableStateFlow<Set<BookShelfKey>>(emptySet())
|
||||||
private val _kinds = MutableStateFlow<List<ExploreKind>>(emptyList())
|
private val _kinds = MutableStateFlow<List<ExploreKind>>(emptyList())
|
||||||
val kinds = _kinds.asStateFlow()
|
val kinds = _kinds.asStateFlow()
|
||||||
private val _selectedKindTitle = MutableStateFlow<String?>(null)
|
private val _selectedKindTitle = MutableStateFlow<String?>(null)
|
||||||
@@ -85,6 +89,16 @@ class ExploreShowViewModel(
|
|||||||
) { books, filter, bookshelf ->
|
) { books, filter, bookshelf ->
|
||||||
books.filter { item ->
|
books.filter { item ->
|
||||||
isBookValid(item, filter, bookshelf)
|
isBookValid(item, filter, bookshelf)
|
||||||
|
}.map { item ->
|
||||||
|
ExploreBookItemUi(
|
||||||
|
book = item,
|
||||||
|
shelfState = resolveBookShelfStateUseCase.execute(
|
||||||
|
name = item.name,
|
||||||
|
author = item.author,
|
||||||
|
url = item.bookUrl,
|
||||||
|
shelf = bookshelf
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||||
|
|
||||||
@@ -104,7 +118,7 @@ class ExploreShowViewModel(
|
|||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
repository.getBookshelfItems().collect { list ->
|
repository.getBookshelfItems().collect { list ->
|
||||||
val keys = list.map { BookKey(it.name, it.author, it.bookUrl) }.toSet()
|
val keys = list.map { BookShelfKey(it.name, it.author, it.bookUrl) }.toSet()
|
||||||
_bookshelf.value = keys
|
_bookshelf.value = keys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,11 +213,25 @@ class ExploreShowViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentBookShelfState(item: SearchBook): BookShelfState {
|
fun getCurrentBookShelfState(item: SearchBook): BookShelfState {
|
||||||
return getBookShelfState(item, _bookshelf.value)
|
return resolveBookShelfStateUseCase.execute(
|
||||||
|
name = item.name,
|
||||||
|
author = item.author,
|
||||||
|
url = item.bookUrl,
|
||||||
|
shelf = _bookshelf.value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isBookValid(book: SearchBook, filter: BookFilterState, shelf: Set<BookKey>): Boolean {
|
private fun isBookValid(
|
||||||
val state = getBookShelfState(book, shelf)
|
book: SearchBook,
|
||||||
|
filter: BookFilterState,
|
||||||
|
shelf: Set<BookShelfKey>
|
||||||
|
): Boolean {
|
||||||
|
val state = resolveBookShelfStateUseCase.execute(
|
||||||
|
name = book.name,
|
||||||
|
author = book.author,
|
||||||
|
url = book.bookUrl,
|
||||||
|
shelf = shelf
|
||||||
|
)
|
||||||
return when (filter) {
|
return when (filter) {
|
||||||
BookFilterState.SHOW_ALL -> true
|
BookFilterState.SHOW_ALL -> true
|
||||||
BookFilterState.HIDE_IN_SHELF -> state != BookShelfState.IN_SHELF
|
BookFilterState.HIDE_IN_SHELF -> state != BookShelfState.IN_SHELF
|
||||||
@@ -211,20 +239,4 @@ class ExploreShowViewModel(
|
|||||||
BookFilterState.SHOW_NOT_IN_SHELF_ONLY -> state == BookShelfState.NOT_IN_SHELF
|
BookFilterState.SHOW_NOT_IN_SHELF_ONLY -> state == BookShelfState.NOT_IN_SHELF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBookShelfStateFlow(item: SearchBook): Flow<BookShelfState> {
|
|
||||||
return _bookshelf.map { shelf ->
|
|
||||||
getBookShelfState(item, shelf)
|
|
||||||
}.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getBookShelfState(item: SearchBook, shelf: Set<BookKey>): BookShelfState {
|
|
||||||
val exactMatch = shelf.any { it.name == item.name && it.author == item.author && it.url == item.bookUrl }
|
|
||||||
if (exactMatch) return BookShelfState.IN_SHELF
|
|
||||||
|
|
||||||
val sameNameAuthor = shelf.any { it.name == item.name && it.author == item.author && it.url != item.bookUrl }
|
|
||||||
if (sameNameAuthor) return BookShelfState.SAME_NAME_AUTHOR
|
|
||||||
|
|
||||||
return BookShelfState.NOT_IN_SHELF
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package io.legado.app.ui.book.group
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.BookGroup
|
import io.legado.app.data.entities.BookGroup
|
||||||
import io.legado.app.data.repository.BookGroupRepository
|
import io.legado.app.data.repository.BookGroupRepository
|
||||||
|
import io.legado.app.domain.usecase.RemoveBookGroupAssignmentUseCase
|
||||||
|
|
||||||
class GroupViewModel(
|
class GroupViewModel(
|
||||||
application: Application,
|
application: Application,
|
||||||
private val bookGroupRepository: BookGroupRepository
|
private val bookGroupRepository: BookGroupRepository,
|
||||||
|
private val removeBookGroupAssignmentUseCase: RemoveBookGroupAssignmentUseCase
|
||||||
) : BaseViewModel(application) {
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
fun upGroup(vararg bookGroup: BookGroup, finally: (() -> Unit)? = null) {
|
fun upGroup(vararg bookGroup: BookGroup, finally: (() -> Unit)? = null) {
|
||||||
@@ -36,7 +37,7 @@ class GroupViewModel(
|
|||||||
enableRefresh = enableRefresh,
|
enableRefresh = enableRefresh,
|
||||||
order = bookGroupRepository.getMaxOrder().plus(1)
|
order = bookGroupRepository.getMaxOrder().plus(1)
|
||||||
)
|
)
|
||||||
bookGroupRepository.getByID(groupId) ?: appDb.bookDao.removeGroup(groupId)
|
bookGroupRepository.getByID(groupId) ?: removeBookGroupAssignmentUseCase.execute(groupId)
|
||||||
bookGroupRepository.insert(bookGroup)
|
bookGroupRepository.insert(bookGroup)
|
||||||
}.onFinally {
|
}.onFinally {
|
||||||
finally()
|
finally()
|
||||||
@@ -46,7 +47,7 @@ class GroupViewModel(
|
|||||||
fun delGroup(bookGroup: BookGroup, finally: () -> Unit) {
|
fun delGroup(bookGroup: BookGroup, finally: () -> Unit) {
|
||||||
execute {
|
execute {
|
||||||
bookGroupRepository.delete(bookGroup)
|
bookGroupRepository.delete(bookGroup)
|
||||||
appDb.bookDao.removeGroup(bookGroup.groupId)
|
removeBookGroupAssignmentUseCase.execute(bookGroup.groupId)
|
||||||
}.onFinally {
|
}.onFinally {
|
||||||
finally()
|
finally()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.ui.book.import.local
|
package io.legado.app.ui.book.import.local
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
@@ -231,7 +232,7 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
|
|||||||
fun hasRootDoc(): Boolean = _state.value.rootDoc != null
|
fun hasRootDoc(): Boolean = _state.value.rootDoc != null
|
||||||
|
|
||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
val defaultPath = ImportBookConfig.defaultBookTreeUri?.takeIf { it.isUri() }
|
val defaultPath = OtherConfig.defaultBookTreeUri?.takeIf { it.isUri() }
|
||||||
val effectiveDefaultPath = defaultPath ?: firstPersistedTreeUri()?.toString()
|
val effectiveDefaultPath = defaultPath ?: firstPersistedTreeUri()?.toString()
|
||||||
if (effectiveDefaultPath.isNullOrBlank()) {
|
if (effectiveDefaultPath.isNullOrBlank()) {
|
||||||
_effects.tryEmit(
|
_effects.tryEmit(
|
||||||
@@ -239,8 +240,8 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (ImportBookConfig.defaultBookTreeUri != effectiveDefaultPath) {
|
if (OtherConfig.defaultBookTreeUri != effectiveDefaultPath) {
|
||||||
ImportBookConfig.defaultBookTreeUri = effectiveDefaultPath
|
OtherConfig.defaultBookTreeUri = effectiveDefaultPath
|
||||||
}
|
}
|
||||||
val importPath = ImportBookConfig.importBookPath
|
val importPath = ImportBookConfig.importBookPath
|
||||||
if (importPath.isNullOrBlank() || !importPath.isUri()) {
|
if (importPath.isNullOrBlank() || !importPath.isUri()) {
|
||||||
@@ -255,16 +256,16 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
|
|||||||
val pickedUri = persistFolderPermission(uri)
|
val pickedUri = persistFolderPermission(uri)
|
||||||
when (target) {
|
when (target) {
|
||||||
ImportFolderPickTarget.DEFAULT_BOOK -> {
|
ImportFolderPickTarget.DEFAULT_BOOK -> {
|
||||||
ImportBookConfig.defaultBookTreeUri = pickedUri.toString()
|
OtherConfig.defaultBookTreeUri = pickedUri.toString()
|
||||||
if (ImportBookConfig.importBookPath.isNullOrBlank()) {
|
if (ImportBookConfig.importBookPath.isNullOrBlank()) {
|
||||||
ImportBookConfig.importBookPath = ImportBookConfig.defaultBookTreeUri
|
ImportBookConfig.importBookPath = OtherConfig.defaultBookTreeUri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImportFolderPickTarget.IMPORT_FOLDER -> {
|
ImportFolderPickTarget.IMPORT_FOLDER -> {
|
||||||
ImportBookConfig.importBookPath = pickedUri.toString()
|
ImportBookConfig.importBookPath = pickedUri.toString()
|
||||||
if (ImportBookConfig.defaultBookTreeUri.isNullOrBlank()) {
|
if (OtherConfig.defaultBookTreeUri.isNullOrBlank()) {
|
||||||
ImportBookConfig.defaultBookTreeUri = pickedUri.toString()
|
OtherConfig.defaultBookTreeUri = pickedUri.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,14 +303,14 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
|
|||||||
ImportBookConfig.importBookPath
|
ImportBookConfig.importBookPath
|
||||||
?.takeIf { it.isNotBlank() && it.isUri() }
|
?.takeIf { it.isNotBlank() && it.isUri() }
|
||||||
?.let(::add)
|
?.let(::add)
|
||||||
ImportBookConfig.defaultBookTreeUri
|
OtherConfig.defaultBookTreeUri
|
||||||
?.takeIf { it.isNotBlank() && it.isUri() }
|
?.takeIf { it.isNotBlank() && it.isUri() }
|
||||||
?.let(::add)
|
?.let(::add)
|
||||||
}
|
}
|
||||||
if (candidates.isEmpty()) {
|
if (candidates.isEmpty()) {
|
||||||
firstPersistedTreeUri()?.toString()?.let { persistedPath ->
|
firstPersistedTreeUri()?.toString()?.let { persistedPath ->
|
||||||
if (trySetRootDoc(persistedPath)) {
|
if (trySetRootDoc(persistedPath)) {
|
||||||
ImportBookConfig.defaultBookTreeUri = persistedPath
|
OtherConfig.defaultBookTreeUri = persistedPath
|
||||||
ImportBookConfig.importBookPath = persistedPath
|
ImportBookConfig.importBookPath = persistedPath
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -341,7 +342,7 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
|
|||||||
|
|
||||||
firstPersistedTreeUri()?.toString()?.let { persistedPath ->
|
firstPersistedTreeUri()?.toString()?.let { persistedPath ->
|
||||||
if (trySetRootDoc(persistedPath)) {
|
if (trySetRootDoc(persistedPath)) {
|
||||||
ImportBookConfig.defaultBookTreeUri = persistedPath
|
OtherConfig.defaultBookTreeUri = persistedPath
|
||||||
ImportBookConfig.importBookPath = persistedPath
|
ImportBookConfig.importBookPath = persistedPath
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,12 +62,10 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.constant.AppConst
|
import io.legado.app.constant.AppConst
|
||||||
import io.legado.app.data.entities.Server
|
import io.legado.app.data.entities.Server
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.lib.dialogs.alert
|
import io.legado.app.lib.dialogs.alert
|
||||||
import io.legado.app.lib.dialogs.selector
|
import io.legado.app.lib.dialogs.selector
|
||||||
import io.legado.app.model.remote.RemoteBook
|
import io.legado.app.model.remote.RemoteBook
|
||||||
@@ -91,7 +89,6 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
|||||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||||
import io.legado.app.ui.widget.components.text.AppText
|
import io.legado.app.ui.widget.components.text.AppText
|
||||||
import io.legado.app.utils.ConvertUtils
|
import io.legado.app.utils.ConvertUtils
|
||||||
import io.legado.app.utils.isUri
|
|
||||||
import io.legado.app.utils.startActivityForBook
|
import io.legado.app.utils.startActivityForBook
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@@ -218,10 +215,7 @@ fun RemoteBookScreen(
|
|||||||
when (effect) {
|
when (effect) {
|
||||||
is RemoteBookEffect.OpenBook -> context.startActivityForBook(effect.book)
|
is RemoteBookEffect.OpenBook -> context.startActivityForBook(effect.book)
|
||||||
is RemoteBookEffect.RequestBookFolderPicker -> {
|
is RemoteBookEffect.RequestBookFolderPicker -> {
|
||||||
selectDocTree.launch(
|
selectDocTree.launch(effect.initialUri)
|
||||||
effect.initialUri
|
|
||||||
?: AppConfig.defaultBookTreeUri?.takeIf { it.isUri() }?.toUri()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is RemoteBookEffect.ShowArchiveEntries -> {
|
is RemoteBookEffect.ShowArchiveEntries -> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.ui.book.import.remote
|
package io.legado.app.ui.book.import.remote
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
@@ -14,8 +15,6 @@ import io.legado.app.data.entities.Book
|
|||||||
import io.legado.app.data.entities.Server
|
import io.legado.app.data.entities.Server
|
||||||
import io.legado.app.data.repository.RemoteBookRepository
|
import io.legado.app.data.repository.RemoteBookRepository
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.AppWebDav
|
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.model.analyzeRule.CustomUrl
|
import io.legado.app.model.analyzeRule.CustomUrl
|
||||||
import io.legado.app.model.localBook.LocalBook
|
import io.legado.app.model.localBook.LocalBook
|
||||||
import io.legado.app.model.remote.RemoteBook
|
import io.legado.app.model.remote.RemoteBook
|
||||||
@@ -29,6 +28,7 @@ import io.legado.app.utils.ArchiveUtils
|
|||||||
import io.legado.app.utils.ConvertUtils
|
import io.legado.app.utils.ConvertUtils
|
||||||
import io.legado.app.utils.FileDoc
|
import io.legado.app.utils.FileDoc
|
||||||
import io.legado.app.utils.find
|
import io.legado.app.utils.find
|
||||||
|
import io.legado.app.utils.isUri
|
||||||
import io.legado.app.utils.takePersistablePermissionSafely
|
import io.legado.app.utils.takePersistablePermissionSafely
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -162,13 +162,13 @@ class RemoteBookViewModel(
|
|||||||
private fun onBookFolderPicked(uri: android.net.Uri?) {
|
private fun onBookFolderPicked(uri: android.net.Uri?) {
|
||||||
uri ?: return
|
uri ?: return
|
||||||
uri.takePersistablePermissionSafely(context)
|
uri.takePersistablePermissionSafely(context)
|
||||||
AppConfig.defaultBookTreeUri = uri.toString()
|
OtherConfig.defaultBookTreeUri = uri.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val uiState: StateFlow<RemoteBookUiState> = combine(
|
val uiState: StateFlow<RemoteBookUiState> = combine(
|
||||||
_state,
|
_state,
|
||||||
repository.flowLocalBooks(),
|
repository.flowLocalBooks(),
|
||||||
appDb.serverDao.observeAll()
|
repository.flowServers()
|
||||||
) { state, localBooks, servers ->
|
) { state, localBooks, servers ->
|
||||||
val localFileNames = localBooks.map { it.originName }.toSet()
|
val localFileNames = localBooks.map { it.originName }.toSet()
|
||||||
|
|
||||||
@@ -224,7 +224,7 @@ class RemoteBookViewModel(
|
|||||||
}
|
}
|
||||||
onSuccess()
|
onSuccess()
|
||||||
} else {
|
} else {
|
||||||
val defaultWebDav = AppWebDav.defaultBookWebDav
|
val defaultWebDav = repository.getDefaultBookWebDav()
|
||||||
?: throw NoStackTraceException("webDav没有配置")
|
?: throw NoStackTraceException("webDav没有配置")
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -282,7 +282,7 @@ class RemoteBookViewModel(
|
|||||||
_state.update { it.copy(selectedIds = emptySet()) }
|
_state.update { it.copy(selectedIds = emptySet()) }
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
_effects.emit(RemoteBookEffect.RequestBookFolderPicker())
|
_effects.emit(RemoteBookEffect.RequestBookFolderPicker(defaultBookTreeUri()))
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
AppLog.put("导入出错\n${e.localizedMessage}", e)
|
AppLog.put("导入出错\n${e.localizedMessage}", e)
|
||||||
@@ -419,14 +419,14 @@ class RemoteBookViewModel(
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val bookTreeUri = AppConfig.defaultBookTreeUri
|
val bookTreeUri = defaultBookTreeUri()
|
||||||
if (bookTreeUri == null) {
|
if (bookTreeUri == null) {
|
||||||
_effects.emit(RemoteBookEffect.RequestBookFolderPicker())
|
_effects.emit(RemoteBookEffect.RequestBookFolderPicker(defaultBookTreeUri()))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadArchiveFileDoc = withContext(Dispatchers.IO) {
|
val downloadArchiveFileDoc = withContext(Dispatchers.IO) {
|
||||||
FileDoc.fromUri(bookTreeUri.toUri(), true)
|
FileDoc.fromUri(bookTreeUri, true)
|
||||||
.find(downloadFileName)
|
.find(downloadFileName)
|
||||||
}
|
}
|
||||||
if (downloadArchiveFileDoc == null) {
|
if (downloadArchiveFileDoc == null) {
|
||||||
@@ -495,9 +495,15 @@ class RemoteBookViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun defaultBookTreeUri(): android.net.Uri? {
|
||||||
|
return OtherConfig.defaultBookTreeUri
|
||||||
|
?.takeIf { it.isUri() }
|
||||||
|
?.toUri()
|
||||||
|
}
|
||||||
|
|
||||||
fun saveServer(server: Server) {
|
fun saveServer(server: Server) {
|
||||||
execute {
|
execute {
|
||||||
appDb.serverDao.insert(server)
|
repository.saveServer(server)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
if (ImportBookConfig.remoteServerId == server.id) {
|
if (ImportBookConfig.remoteServerId == server.id) {
|
||||||
initData { loadRemoteBookList() }
|
initData { loadRemoteBookList() }
|
||||||
@@ -507,7 +513,7 @@ class RemoteBookViewModel(
|
|||||||
|
|
||||||
fun deleteServer(server: Server) {
|
fun deleteServer(server: Server) {
|
||||||
execute {
|
execute {
|
||||||
appDb.serverDao.delete(server)
|
repository.deleteServer(server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseDialogFragment
|
import io.legado.app.base.BaseDialogFragment
|
||||||
import io.legado.app.data.entities.Server
|
import io.legado.app.data.entities.Server
|
||||||
@@ -18,6 +17,7 @@ import io.legado.app.utils.GSON
|
|||||||
import io.legado.app.utils.setLayout
|
import io.legado.app.utils.setLayout
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
class ServerConfigDialog() : BaseDialogFragment(R.layout.dialog_webdav_server, true),
|
class ServerConfigDialog() : BaseDialogFragment(R.layout.dialog_webdav_server, true),
|
||||||
Toolbar.OnMenuItemClickListener {
|
Toolbar.OnMenuItemClickListener {
|
||||||
@@ -29,7 +29,7 @@ class ServerConfigDialog() : BaseDialogFragment(R.layout.dialog_webdav_server, t
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val binding by viewBinding(DialogWebdavServerBinding::bind)
|
private val binding by viewBinding(DialogWebdavServerBinding::bind)
|
||||||
private val viewModel by viewModels<ServerConfigViewModel>()
|
private val viewModel by viewModel<ServerConfigViewModel>()
|
||||||
|
|
||||||
private val webDavServerUi = listOf(
|
private val webDavServerUi = listOf(
|
||||||
RowUi("url"),
|
RowUi("url"),
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package io.legado.app.ui.book.import.remote
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.Server
|
import io.legado.app.data.entities.Server
|
||||||
|
import io.legado.app.data.repository.RemoteBookRepository
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
|
|
||||||
class ServerConfigViewModel(application: Application): BaseViewModel(application) {
|
class ServerConfigViewModel(
|
||||||
|
application: Application,
|
||||||
|
private val remoteBookRepository: RemoteBookRepository
|
||||||
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
var mServer: Server? = null
|
var mServer: Server? = null
|
||||||
|
|
||||||
@@ -15,7 +18,7 @@ class ServerConfigViewModel(application: Application): BaseViewModel(application
|
|||||||
if (mServer != null) return
|
if (mServer != null) return
|
||||||
execute {
|
execute {
|
||||||
mServer = if (id != null) {
|
mServer = if (id != null) {
|
||||||
appDb.serverDao.get(id)
|
remoteBookRepository.getServer(id)
|
||||||
} else {
|
} else {
|
||||||
Server()
|
Server()
|
||||||
}
|
}
|
||||||
@@ -27,10 +30,10 @@ class ServerConfigViewModel(application: Application): BaseViewModel(application
|
|||||||
fun save(server: Server, onSuccess: () -> Unit) {
|
fun save(server: Server, onSuccess: () -> Unit) {
|
||||||
execute {
|
execute {
|
||||||
mServer?.let {
|
mServer?.let {
|
||||||
appDb.serverDao.delete(it)
|
remoteBookRepository.deleteServer(it)
|
||||||
}
|
}
|
||||||
mServer = server
|
mServer = server
|
||||||
appDb.serverDao.insert(server)
|
remoteBookRepository.saveServer(server)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
onSuccess.invoke()
|
onSuccess.invoke()
|
||||||
}.onError {
|
}.onError {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
@@ -16,7 +15,6 @@ import io.legado.app.base.adapter.ItemViewHolder
|
|||||||
import io.legado.app.base.adapter.RecyclerAdapter
|
import io.legado.app.base.adapter.RecyclerAdapter
|
||||||
import io.legado.app.constant.AppConst.DEFAULT_WEBDAV_ID
|
import io.legado.app.constant.AppConst.DEFAULT_WEBDAV_ID
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.Server
|
import io.legado.app.data.entities.Server
|
||||||
import io.legado.app.databinding.DialogRecyclerViewBinding
|
import io.legado.app.databinding.DialogRecyclerViewBinding
|
||||||
import io.legado.app.databinding.ItemServerSelectBinding
|
import io.legado.app.databinding.ItemServerSelectBinding
|
||||||
@@ -34,6 +32,7 @@ import kotlinx.coroutines.Dispatchers.IO
|
|||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务器配置
|
* 服务器配置
|
||||||
@@ -42,7 +41,7 @@ class ServersDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
|
|||||||
Toolbar.OnMenuItemClickListener {
|
Toolbar.OnMenuItemClickListener {
|
||||||
|
|
||||||
val binding by viewBinding(DialogRecyclerViewBinding::bind)
|
val binding by viewBinding(DialogRecyclerViewBinding::bind)
|
||||||
val viewModel by viewModels<ServersViewModel>()
|
val viewModel by viewModel<ServersViewModel>()
|
||||||
|
|
||||||
private val callback get() = (activity as? Callback)
|
private val callback get() = (activity as? Callback)
|
||||||
private val adapter by lazy { ServersAdapter(requireContext()) }
|
private val adapter by lazy { ServersAdapter(requireContext()) }
|
||||||
@@ -86,7 +85,7 @@ class ServersDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
|
|||||||
|
|
||||||
private fun initData() {
|
private fun initData() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
appDb.serverDao.observeAll().catch {
|
viewModel.flowServers().catch {
|
||||||
AppLog.put("服务器配置界面获取数据失败\n${it.localizedMessage}", it)
|
AppLog.put("服务器配置界面获取数据失败\n${it.localizedMessage}", it)
|
||||||
}.flowOn(IO).collect {
|
}.flowOn(IO).collect {
|
||||||
adapter.setItems(it)
|
adapter.setItems(it)
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ package io.legado.app.ui.book.import.remote
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.Server
|
import io.legado.app.data.entities.Server
|
||||||
|
import io.legado.app.data.repository.RemoteBookRepository
|
||||||
|
|
||||||
class ServersViewModel(application: Application): BaseViewModel(application) {
|
class ServersViewModel(
|
||||||
|
application: Application,
|
||||||
|
private val remoteBookRepository: RemoteBookRepository
|
||||||
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
|
fun flowServers() = remoteBookRepository.flowServers()
|
||||||
|
|
||||||
fun delete(server: Server) {
|
fun delete(server: Server) {
|
||||||
execute {
|
execute {
|
||||||
appDb.serverDao.delete(server)
|
remoteBookRepository.deleteServer(server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.ui.book.info
|
package io.legado.app.ui.book.info
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@@ -41,7 +42,7 @@ class BookInfoActivity : BaseComposeActivity(), VariableDialog.Callback {
|
|||||||
}
|
}
|
||||||
private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
|
private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
|
||||||
it.uri?.let { treeUri ->
|
it.uri?.let { treeUri ->
|
||||||
AppConfig.defaultBookTreeUri = treeUri.toString()
|
OtherConfig.defaultBookTreeUri = treeUri.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val infoEditResult = registerForActivityResult(
|
private val infoEditResult = registerForActivityResult(
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ data class BookInfoUiState(
|
|||||||
val bookSource: BookSource? = null,
|
val bookSource: BookSource? = null,
|
||||||
val isTocLoading: Boolean = true,
|
val isTocLoading: Boolean = true,
|
||||||
val isBusy: Boolean = false,
|
val isBusy: Boolean = false,
|
||||||
|
val deleteAlertEnabled: Boolean = true,
|
||||||
|
val deleteOriginal: Boolean = false,
|
||||||
val showAppLogSheet: Boolean = false,
|
val showAppLogSheet: Boolean = false,
|
||||||
val sheet: BookInfoSheet = BookInfoSheet.None,
|
val sheet: BookInfoSheet = BookInfoSheet.None,
|
||||||
val dialog: BookInfoDialog? = null,
|
val dialog: BookInfoDialog? = null,
|
||||||
|
|||||||
@@ -573,7 +573,7 @@ private fun BookInfoOverflowMenu(
|
|||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = stringResource(R.string.delete_alert),
|
text = stringResource(R.string.delete_alert),
|
||||||
onClick = { onMenuAction(BookInfoMenuAction.ToggleDeleteAlert) },
|
onClick = { onMenuAction(BookInfoMenuAction.ToggleDeleteAlert) },
|
||||||
isSelected = io.legado.app.help.config.LocalConfig.bookInfoDeleteAlert
|
isSelected = state.deleteAlertEnabled
|
||||||
)
|
)
|
||||||
RoundDropdownMenuItem(
|
RoundDropdownMenuItem(
|
||||||
text = stringResource(R.string.clear_cache),
|
text = stringResource(R.string.clear_cache),
|
||||||
@@ -894,7 +894,7 @@ private fun BookInfoDialogs(
|
|||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val dialog = state.dialog
|
val dialog = state.dialog
|
||||||
var deleteOriginal by remember(dialog) { mutableStateOf(io.legado.app.help.config.LocalConfig.deleteBookOriginal) }
|
var deleteOriginal by remember(dialog, state.deleteOriginal) { mutableStateOf(state.deleteOriginal) }
|
||||||
var remarkText by remember(dialog) { mutableStateOf((dialog as? BookInfoDialog.EditRemark)?.remark.orEmpty()) }
|
var remarkText by remember(dialog) { mutableStateOf((dialog as? BookInfoDialog.EditRemark)?.remark.orEmpty()) }
|
||||||
|
|
||||||
if (dialog is BookInfoDialog.AddToShelfOnBack) {
|
if (dialog is BookInfoDialog.AddToShelfOnBack) {
|
||||||
@@ -918,7 +918,6 @@ private fun BookInfoDialogs(
|
|||||||
text = stringResource(R.string.sure_del),
|
text = stringResource(R.string.sure_del),
|
||||||
confirmText = stringResource(android.R.string.ok),
|
confirmText = stringResource(android.R.string.ok),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
io.legado.app.help.config.LocalConfig.deleteBookOriginal = deleteOriginal
|
|
||||||
onIntent(BookInfoIntent.ConfirmDelete(deleteOriginal))
|
onIntent(BookInfoIntent.ConfirmDelete(deleteOriginal))
|
||||||
},
|
},
|
||||||
dismissText = stringResource(android.R.string.cancel),
|
dismissText = stringResource(android.R.string.cancel),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import io.legado.app.data.entities.BookSource
|
|||||||
import io.legado.app.data.entities.readRecord.ReadRecordTimelineDay
|
import io.legado.app.data.entities.readRecord.ReadRecordTimelineDay
|
||||||
import io.legado.app.data.repository.ReadRecordRepository
|
import io.legado.app.data.repository.ReadRecordRepository
|
||||||
import io.legado.app.data.repository.RemoteBookRepository
|
import io.legado.app.data.repository.RemoteBookRepository
|
||||||
|
import io.legado.app.domain.usecase.ClearBookCacheUseCase
|
||||||
import io.legado.app.exception.NoBooksDirException
|
import io.legado.app.exception.NoBooksDirException
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
@@ -63,7 +64,8 @@ import kotlinx.coroutines.launch
|
|||||||
class BookInfoViewModel(
|
class BookInfoViewModel(
|
||||||
application: Application,
|
application: Application,
|
||||||
private val remoteBookRepository: RemoteBookRepository,
|
private val remoteBookRepository: RemoteBookRepository,
|
||||||
private val readRecordRepository: ReadRecordRepository
|
private val readRecordRepository: ReadRecordRepository,
|
||||||
|
private val clearBookCacheUseCase: ClearBookCacheUseCase
|
||||||
) : BaseViewModel(application) {
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(BookInfoUiState())
|
private val _uiState = MutableStateFlow(BookInfoUiState())
|
||||||
@@ -400,7 +402,7 @@ class BookInfoViewModel(
|
|||||||
fun clearCache() {
|
fun clearCache() {
|
||||||
currentBook?.let { book ->
|
currentBook?.let { book ->
|
||||||
execute {
|
execute {
|
||||||
BookHelp.clearCache(book)
|
clearBookCacheUseCase.execute(book.bookUrl)
|
||||||
if (ReadBook.book?.bookUrl == book.bookUrl) {
|
if (ReadBook.book?.bookUrl == book.bookUrl) {
|
||||||
ReadBook.clearTextChapter()
|
ReadBook.clearTextChapter()
|
||||||
}
|
}
|
||||||
@@ -851,6 +853,8 @@ class BookInfoViewModel(
|
|||||||
}
|
}
|
||||||
private fun deleteBook(deleteOriginal: Boolean) {
|
private fun deleteBook(deleteOriginal: Boolean) {
|
||||||
currentBook?.let { book ->
|
currentBook?.let { book ->
|
||||||
|
LocalConfig.deleteBookOriginal = deleteOriginal
|
||||||
|
_uiState.update { it.copy(deleteOriginal = deleteOriginal) }
|
||||||
SourceCallBack.callBackBook(SourceCallBack.DEL_BOOK_SHELF, bookSource, book)
|
SourceCallBack.callBackBook(SourceCallBack.DEL_BOOK_SHELF, bookSource, book)
|
||||||
delBook(deleteOriginal) {
|
delBook(deleteOriginal) {
|
||||||
emitEffect(BookInfoEffect.Finish(resultCode = RESULT_OK))
|
emitEffect(BookInfoEffect.Finish(resultCode = RESULT_OK))
|
||||||
@@ -1168,6 +1172,8 @@ class BookInfoViewModel(
|
|||||||
inBookshelf = inBookshelf,
|
inBookshelf = inBookshelf,
|
||||||
bookSource = bookSource,
|
bookSource = bookSource,
|
||||||
isTocLoading = isTocLoading,
|
isTocLoading = isTocLoading,
|
||||||
|
deleteAlertEnabled = LocalConfig.bookInfoDeleteAlert,
|
||||||
|
deleteOriginal = LocalConfig.deleteBookOriginal,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import io.legado.app.data.entities.Book
|
|||||||
import io.legado.app.data.entities.Book.ReadConfig
|
import io.legado.app.data.entities.Book.ReadConfig
|
||||||
import io.legado.app.data.entities.BookChapter
|
import io.legado.app.data.entities.BookChapter
|
||||||
import io.legado.app.data.entities.BookProgress
|
import io.legado.app.data.entities.BookProgress
|
||||||
|
import io.legado.app.domain.model.ReadingProgress
|
||||||
|
import io.legado.app.domain.usecase.GetReadingProgressUseCase
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.AppWebDav
|
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
import io.legado.app.help.book.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
import io.legado.app.help.book.isLocalModified
|
import io.legado.app.help.book.isLocalModified
|
||||||
@@ -47,7 +48,10 @@ import kotlinx.coroutines.withContext
|
|||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
class ReadMangaViewModel(application: Application) : BaseViewModel(application) {
|
class ReadMangaViewModel(
|
||||||
|
application: Application,
|
||||||
|
private val getReadingProgressUseCase: GetReadingProgressUseCase
|
||||||
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
private var changeSourceCoroutine: Coroutine<*>? = null
|
private var changeSourceCoroutine: Coroutine<*>? = null
|
||||||
|
|
||||||
@@ -243,7 +247,7 @@ class ReadMangaViewModel(application: Application) : BaseViewModel(application)
|
|||||||
) {
|
) {
|
||||||
if (!AppConfig.syncBookProgress) return
|
if (!AppConfig.syncBookProgress) return
|
||||||
execute {
|
execute {
|
||||||
AppWebDav.getBookProgress(book)
|
getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress()
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("拉取阅读进度失败《${book.name}》\n${it.localizedMessage}", it)
|
AppLog.put("拉取阅读进度失败《${book.name}》\n${it.localizedMessage}", it)
|
||||||
}.onSuccess { progress ->
|
}.onSuccess { progress ->
|
||||||
@@ -260,6 +264,15 @@ class ReadMangaViewModel(application: Application) : BaseViewModel(application)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ReadingProgress.toBookProgress() = BookProgress(
|
||||||
|
name = name,
|
||||||
|
author = author,
|
||||||
|
durChapterIndex = durChapterIndex,
|
||||||
|
durChapterPos = durChapterPos,
|
||||||
|
durChapterTime = durChapterTime,
|
||||||
|
durChapterTitle = durChapterTitle
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 换源
|
* 换源
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import io.legado.app.data.entities.BookChapter
|
|||||||
import io.legado.app.data.entities.BookProgress
|
import io.legado.app.data.entities.BookProgress
|
||||||
import io.legado.app.data.entities.BookSource
|
import io.legado.app.data.entities.BookSource
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.AppWebDav
|
|
||||||
import io.legado.app.help.IntentData
|
import io.legado.app.help.IntentData
|
||||||
import io.legado.app.help.TTS
|
import io.legado.app.help.TTS
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
@@ -50,7 +49,6 @@ import io.legado.app.help.book.isLocal
|
|||||||
import io.legado.app.help.book.isLocalTxt
|
import io.legado.app.help.book.isLocalTxt
|
||||||
import io.legado.app.help.book.isMobi
|
import io.legado.app.help.book.isMobi
|
||||||
import io.legado.app.help.book.removeType
|
import io.legado.app.help.book.removeType
|
||||||
import io.legado.app.help.book.update
|
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.help.config.ReadTipConfig
|
import io.legado.app.help.config.ReadTipConfig
|
||||||
@@ -525,10 +523,10 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
menu.findItem(R.id.menu_get_progress)?.isVisible = withContext(IO) {
|
menu.findItem(R.id.menu_get_progress)?.isVisible = withContext(IO) {
|
||||||
AppWebDav.isOk
|
viewModel.isReadingProgressSyncConfigured()
|
||||||
}
|
}
|
||||||
menu.findItem(R.id.menu_cover_progress)?.isVisible = withContext(IO) {
|
menu.findItem(R.id.menu_cover_progress)?.isVisible = withContext(IO) {
|
||||||
AppWebDav.isOk
|
viewModel.isReadingProgressSyncConfigured()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1841,9 +1839,8 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
backupJob = lifecycleScope.launch(IO) {
|
backupJob = lifecycleScope.launch(IO) {
|
||||||
delay(300000)
|
delay(300000)
|
||||||
ReadBook.book?.let {
|
ReadBook.book?.let {
|
||||||
AppWebDav.uploadBookProgress(it)
|
viewModel.uploadBookProgress(it)
|
||||||
ensureActive()
|
ensureActive()
|
||||||
it.update()
|
|
||||||
Backup.autoBack(this@ReadBookActivity)
|
Backup.autoBack(this@ReadBookActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ 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.BookChapter
|
import io.legado.app.data.entities.BookChapter
|
||||||
import io.legado.app.data.entities.BookProgress
|
import io.legado.app.data.entities.BookProgress
|
||||||
|
import io.legado.app.domain.model.ReadingProgress
|
||||||
|
import io.legado.app.domain.usecase.GetReadingProgressUseCase
|
||||||
|
import io.legado.app.domain.usecase.UploadReadingProgressUseCase
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.AppWebDav
|
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
import io.legado.app.help.book.ContentProcessor
|
import io.legado.app.help.book.ContentProcessor
|
||||||
import io.legado.app.help.book.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
@@ -52,7 +54,11 @@ import kotlin.coroutines.coroutineContext
|
|||||||
/**
|
/**
|
||||||
* 阅读界面数据处理
|
* 阅读界面数据处理
|
||||||
*/
|
*/
|
||||||
class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
class ReadBookViewModel(
|
||||||
|
application: Application,
|
||||||
|
private val getReadingProgressUseCase: GetReadingProgressUseCase,
|
||||||
|
private val uploadReadingProgressUseCase: UploadReadingProgressUseCase
|
||||||
|
) : BaseViewModel(application) {
|
||||||
val permissionDenialLiveData = MutableLiveData<Int>()
|
val permissionDenialLiveData = MutableLiveData<Int>()
|
||||||
var isInitFinish = false
|
var isInitFinish = false
|
||||||
var searchContentQuery = ""
|
var searchContentQuery = ""
|
||||||
@@ -253,7 +259,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
) {
|
) {
|
||||||
if (!AppConfig.syncBookProgress) return
|
if (!AppConfig.syncBookProgress) return
|
||||||
execute {
|
execute {
|
||||||
AppWebDav.getBookProgress(book)
|
getReadingProgressUseCase.execute(book.name, book.author)?.toBookProgress()
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("拉取阅读进度失败《${book.name}》\n${it.localizedMessage}", it)
|
AppLog.put("拉取阅读进度失败《${book.name}》\n${it.localizedMessage}", it)
|
||||||
}.onSuccess { progress ->
|
}.onSuccess { progress ->
|
||||||
@@ -270,6 +276,35 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isReadingProgressSyncConfigured(): Boolean {
|
||||||
|
return getReadingProgressUseCase.isConfigured
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun uploadBookProgress(book: Book) {
|
||||||
|
uploadReadingProgressUseCase.execute(book.toReadingProgress())?.let { uploadTime ->
|
||||||
|
book.syncTime = uploadTime
|
||||||
|
appDb.bookDao.update(book)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Book.toReadingProgress() = ReadingProgress(
|
||||||
|
name = name,
|
||||||
|
author = author,
|
||||||
|
durChapterIndex = durChapterIndex,
|
||||||
|
durChapterPos = durChapterPos,
|
||||||
|
durChapterTime = durChapterTime,
|
||||||
|
durChapterTitle = durChapterTitle
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun ReadingProgress.toBookProgress() = BookProgress(
|
||||||
|
name = name,
|
||||||
|
author = author,
|
||||||
|
durChapterIndex = durChapterIndex,
|
||||||
|
durChapterPos = durChapterPos,
|
||||||
|
durChapterTime = durChapterTime,
|
||||||
|
durChapterTitle = durChapterTitle
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 换源
|
* 换源
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package io.legado.app.ui.book.search
|
|
||||||
|
|
||||||
data class BookKey(
|
|
||||||
val name: String,
|
|
||||||
val author: String,
|
|
||||||
val url: String?
|
|
||||||
)
|
|
||||||
@@ -12,7 +12,7 @@ import io.legado.app.base.adapter.ItemViewHolder
|
|||||||
import io.legado.app.data.entities.SearchBook
|
import io.legado.app.data.entities.SearchBook
|
||||||
import io.legado.app.databinding.ItemSearchBinding
|
import io.legado.app.databinding.ItemSearchBinding
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.model.BookShelfState
|
import io.legado.app.domain.model.BookShelfState
|
||||||
import io.legado.app.ui.widget.text.AccentBgTextView
|
import io.legado.app.ui.widget.text.AccentBgTextView
|
||||||
import io.legado.app.utils.dpToPx
|
import io.legado.app.utils.dpToPx
|
||||||
import io.legado.app.utils.gone
|
import io.legado.app.utils.gone
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ package io.legado.app.ui.book.search
|
|||||||
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
|
||||||
|
import io.legado.app.domain.model.BookShelfState
|
||||||
import io.legado.app.ui.main.bookshelf.BookShelfItem
|
import io.legado.app.ui.main.bookshelf.BookShelfItem
|
||||||
|
|
||||||
|
data class SearchResultItemUi(
|
||||||
|
val book: SearchBook,
|
||||||
|
val shelfState: BookShelfState = BookShelfState.NOT_IN_SHELF,
|
||||||
|
)
|
||||||
|
|
||||||
data class SearchUiState(
|
data class SearchUiState(
|
||||||
val query: String = "",
|
val query: String = "",
|
||||||
val committedQuery: String = "",
|
val committedQuery: String = "",
|
||||||
val results: List<SearchBook> = emptyList(),
|
val results: List<SearchResultItemUi> = emptyList(),
|
||||||
val history: List<SearchKeyword> = emptyList(),
|
val history: List<SearchKeyword> = emptyList(),
|
||||||
val bookshelfHints: List<BookShelfItem> = emptyList(),
|
val bookshelfHints: List<BookShelfItem> = emptyList(),
|
||||||
val enabledGroups: List<String> = emptyList(),
|
val enabledGroups: List<String> = emptyList(),
|
||||||
|
|||||||
@@ -337,16 +337,13 @@ fun SearchScreen(
|
|||||||
) {
|
) {
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = state.results,
|
items = state.results,
|
||||||
key = { index, book -> "${book.origin}:${book.bookUrl}:$index" }
|
key = { index, item -> "${item.book.origin}:${item.book.bookUrl}:$index" }
|
||||||
) { _, book ->
|
) { _, item ->
|
||||||
val shelfStateFlow = remember(book.bookUrl) {
|
|
||||||
viewModel.getBookShelfStateFlow(book)
|
|
||||||
}
|
|
||||||
SearchBookListItem(
|
SearchBookListItem(
|
||||||
book = book,
|
book = item.book,
|
||||||
shelfState = shelfStateFlow,
|
shelfState = item.shelfState,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onIntent(SearchIntent.OpenSearchBook(book))
|
viewModel.onIntent(SearchIntent.OpenSearchBook(item.book))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ 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.SearchSessionEvent
|
import io.legado.app.data.repository.SearchSessionEvent
|
||||||
|
import io.legado.app.domain.usecase.BookShelfKey
|
||||||
|
import io.legado.app.domain.usecase.ResolveBookShelfStateUseCase
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.model.BookShelfState
|
|
||||||
import io.legado.app.ui.main.bookshelf.BookShelfItem
|
import io.legado.app.ui.main.bookshelf.BookShelfItem
|
||||||
import io.legado.app.utils.getPrefBoolean
|
import io.legado.app.utils.getPrefBoolean
|
||||||
import io.legado.app.utils.putPrefBoolean
|
import io.legado.app.utils.putPrefBoolean
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
@@ -29,6 +29,7 @@ import splitties.init.appCtx
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class SearchViewModel(
|
class SearchViewModel(
|
||||||
private val repository: SearchRepository,
|
private val repository: SearchRepository,
|
||||||
|
private val resolveBookShelfStateUseCase: ResolveBookShelfStateUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(
|
private val _uiState = MutableStateFlow(
|
||||||
@@ -46,7 +47,7 @@ class SearchViewModel(
|
|||||||
val effects = _effects.asSharedFlow()
|
val effects = _effects.asSharedFlow()
|
||||||
|
|
||||||
private val queryFlow = MutableStateFlow("")
|
private val queryFlow = MutableStateFlow("")
|
||||||
private val bookshelfKeys = MutableStateFlow<Set<BookKey>>(emptySet())
|
private val bookshelfKeys = MutableStateFlow<Set<BookShelfKey>>(emptySet())
|
||||||
private val searchScope = SearchScope(AppConfig.searchScope)
|
private val searchScope = SearchScope(AppConfig.searchScope)
|
||||||
private val searchSession = repository.createSearchSession { searchScope }
|
private val searchSession = repository.createSearchSession { searchScope }
|
||||||
|
|
||||||
@@ -142,12 +143,6 @@ class SearchViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBookShelfStateFlow(item: SearchBook): Flow<BookShelfState> {
|
|
||||||
return bookshelfKeys
|
|
||||||
.map { keys -> resolveBookShelfState(item, keys) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
searchSession.close()
|
searchSession.close()
|
||||||
@@ -194,6 +189,9 @@ class SearchViewModel(
|
|||||||
.catch { emit(emptySet()) }
|
.catch { emit(emptySet()) }
|
||||||
.collect { keys ->
|
.collect { keys ->
|
||||||
bookshelfKeys.value = keys
|
bookshelfKeys.value = keys
|
||||||
|
_uiState.update { state ->
|
||||||
|
state.copy(results = state.results.withShelfState(keys))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,9 +231,10 @@ class SearchViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is SearchSessionEvent.Progress -> {
|
is SearchSessionEvent.Progress -> {
|
||||||
|
val shelf = bookshelfKeys.value
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
results = event.books,
|
results = event.books.toSearchResultItems(shelf),
|
||||||
processedSources = event.processedSources,
|
processedSources = event.processedSources,
|
||||||
totalSources = event.totalSources,
|
totalSources = event.totalSources,
|
||||||
)
|
)
|
||||||
@@ -403,18 +402,35 @@ class SearchViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveBookShelfState(item: SearchBook, shelf: Set<BookKey>): BookShelfState {
|
private fun List<SearchBook>.toSearchResultItems(
|
||||||
val exactMatch = shelf.any {
|
shelf: Set<BookShelfKey>
|
||||||
it.name == item.name && it.author == item.author && it.url == item.bookUrl
|
): List<SearchResultItemUi> {
|
||||||
|
return map { book ->
|
||||||
|
SearchResultItemUi(
|
||||||
|
book = book,
|
||||||
|
shelfState = resolveBookShelfStateUseCase.execute(
|
||||||
|
name = book.name,
|
||||||
|
author = book.author,
|
||||||
|
url = book.bookUrl,
|
||||||
|
shelf = shelf
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (exactMatch) return BookShelfState.IN_SHELF
|
}
|
||||||
|
|
||||||
val sameNameAuthor = shelf.any {
|
private fun List<SearchResultItemUi>.withShelfState(
|
||||||
it.name == item.name && it.author == item.author && it.url != item.bookUrl
|
shelf: Set<BookShelfKey>
|
||||||
|
): List<SearchResultItemUi> {
|
||||||
|
return map { item ->
|
||||||
|
item.copy(
|
||||||
|
shelfState = resolveBookShelfStateUseCase.execute(
|
||||||
|
name = item.book.name,
|
||||||
|
author = item.book.author,
|
||||||
|
url = item.book.bookUrl,
|
||||||
|
shelf = shelf
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (sameNameAuthor) return BookShelfState.SAME_NAME_AUTHOR
|
|
||||||
|
|
||||||
return BookShelfState.NOT_IN_SHELF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitEffect(effect: SearchEffect) {
|
private fun emitEffect(effect: SearchEffect) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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.Bookmark
|
import io.legado.app.data.entities.Bookmark
|
||||||
import io.legado.app.data.entities.ReplaceRule
|
import io.legado.app.data.entities.ReplaceRule
|
||||||
|
import io.legado.app.domain.usecase.CacheBookChaptersUseCase
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
import io.legado.app.help.book.ContentProcessor
|
import io.legado.app.help.book.ContentProcessor
|
||||||
import io.legado.app.help.book.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
@@ -113,7 +114,8 @@ data class FabAction(val icon: ImageVector, val label: String, val action: () ->
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class TocViewModel(
|
class TocViewModel(
|
||||||
application: Application,
|
application: Application,
|
||||||
savedStateHandle: SavedStateHandle
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val cacheBookChaptersUseCase: CacheBookChaptersUseCase
|
||||||
) : BaseRuleViewModel<TocItemUi, TocDomainItem, Int, TocActionState>(
|
) : BaseRuleViewModel<TocItemUi, TocDomainItem, Int, TocActionState>(
|
||||||
application,
|
application,
|
||||||
initialState = TocActionState()
|
initialState = TocActionState()
|
||||||
@@ -524,15 +526,21 @@ class TocViewModel(
|
|||||||
val book = bookState.value ?: return
|
val book = bookState.value ?: return
|
||||||
val indices = uiState.value.selectedIds.toList()
|
val indices = uiState.value.selectedIds.toList()
|
||||||
if (indices.isEmpty()) return
|
if (indices.isEmpty()) return
|
||||||
CacheBook.start(getApplication(), book, indices)
|
execute {
|
||||||
getApplication<Application>().toastOnUi("开始下载 ${indices.size} 个章节")
|
cacheBookChaptersUseCase.execute(book.bookUrl, indices)
|
||||||
clearSelection()
|
}.onSuccess { count ->
|
||||||
|
getApplication<Application>().toastOnUi("开始下载 $count 个章节")
|
||||||
|
clearSelection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadChapter(index: Int) {
|
fun downloadChapter(index: Int) {
|
||||||
val book = bookState.value ?: return
|
val book = bookState.value ?: return
|
||||||
CacheBook.start(getApplication(), book, listOf(index))
|
execute {
|
||||||
getApplication<Application>().toastOnUi("开始下载章节")
|
cacheBookChaptersUseCase.execute(book.bookUrl, listOf(index))
|
||||||
|
}.onSuccess {
|
||||||
|
getApplication<Application>().toastOnUi("开始下载章节")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun downloadAll() {
|
fun downloadAll() {
|
||||||
@@ -546,8 +554,11 @@ class TocViewModel(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheBook.start(getApplication(), book, targetIndices)
|
execute {
|
||||||
getApplication<Application>().toastOnUi("开始下载剩余 ${targetIndices.size} 个章节")
|
cacheBookChaptersUseCase.execute(book.bookUrl, targetIndices)
|
||||||
|
}.onSuccess { count ->
|
||||||
|
getApplication<Application>().toastOnUi("开始下载剩余 $count 个章节")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<BookChapter>.groupAndReverseVolumes(): List<BookChapter> {
|
private fun List<BookChapter>.groupAndReverseVolumes(): List<BookChapter> {
|
||||||
|
|||||||
@@ -2,18 +2,20 @@ package io.legado.app.ui.config.backupConfig
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.legado.app.help.AppWebDav
|
import io.legado.app.domain.usecase.WebDavBackupUseCase
|
||||||
import io.legado.app.help.storage.Backup
|
import io.legado.app.help.storage.Backup
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class BackupConfigViewModel : ViewModel() {
|
class BackupConfigViewModel(
|
||||||
|
private val webDavBackupUseCase: WebDavBackupUseCase
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
private suspend fun syncWebDavConfig() {
|
private suspend fun syncWebDavConfig() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
AppWebDav.upConfig()
|
webDavBackupUseCase.refreshConfig()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,11 +37,9 @@ class BackupConfigViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun testWebDav(): Boolean {
|
suspend fun testWebDav(): Boolean {
|
||||||
syncWebDavConfig()
|
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
AppWebDav.testWebDav()
|
webDavBackupUseCase.test()
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -62,17 +62,15 @@ class BackupConfigViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getBackupNames(): List<String> {
|
suspend fun getBackupNames(): List<String> {
|
||||||
syncWebDavConfig()
|
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
AppWebDav.getBackupNames()
|
webDavBackupUseCase.getBackupNames()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreWebDav(name: String, onSuccess: () -> Unit, onError: (String) -> Unit) {
|
fun restoreWebDav(name: String, onSuccess: () -> Unit, onError: (String) -> Unit) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
AppWebDav.upConfig()
|
webDavBackupUseCase.restore(name)
|
||||||
AppWebDav.restoreWebDav(name)
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ import io.legado.app.ui.config.prefDelegate
|
|||||||
|
|
||||||
object ImportBookConfig {
|
object ImportBookConfig {
|
||||||
|
|
||||||
var defaultBookTreeUri by prefDelegate(
|
|
||||||
PreferKey.defaultBookTreeUri,
|
|
||||||
null as String?
|
|
||||||
)
|
|
||||||
|
|
||||||
var importBookPath by prefDelegate(
|
var importBookPath by prefDelegate(
|
||||||
PreferKey.importBookPath,
|
PreferKey.importBookPath,
|
||||||
null as String?
|
null as String?
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ object OtherConfig {
|
|||||||
|
|
||||||
var defaultBookTreeUri by prefDelegate(
|
var defaultBookTreeUri by prefDelegate(
|
||||||
PreferKey.defaultBookTreeUri,
|
PreferKey.defaultBookTreeUri,
|
||||||
""
|
null as String?
|
||||||
)
|
)
|
||||||
|
|
||||||
var antiAlias by prefDelegate(
|
var antiAlias by prefDelegate(
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.legado.app.constant.PreferKey
|
import io.legado.app.constant.PreferKey
|
||||||
import io.legado.app.data.appDb
|
import io.legado.app.domain.usecase.ClearBookCacheUseCase
|
||||||
|
import io.legado.app.domain.usecase.ShrinkDatabaseUseCase
|
||||||
import io.legado.app.help.DirectLinkUpload
|
import io.legado.app.help.DirectLinkUpload
|
||||||
import io.legado.app.help.book.BookHelp
|
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.LocalConfig
|
import io.legado.app.help.config.LocalConfig
|
||||||
import io.legado.app.model.CheckSource
|
import io.legado.app.model.CheckSource
|
||||||
@@ -25,7 +25,10 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
class OtherConfigViewModel : ViewModel() {
|
class OtherConfigViewModel(
|
||||||
|
private val clearBookCacheUseCase: ClearBookCacheUseCase,
|
||||||
|
private val shrinkDatabaseUseCase: ShrinkDatabaseUseCase
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
private val packageManager = appCtx.packageManager
|
private val packageManager = appCtx.packageManager
|
||||||
private val componentName = ComponentName(
|
private val componentName = ComponentName(
|
||||||
@@ -52,7 +55,7 @@ class OtherConfigViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun clearCache(context: Context) {
|
fun clearCache(context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
BookHelp.clearCache()
|
clearBookCacheUseCase.executeAll()
|
||||||
FileUtils.delete(context.cacheDir.absolutePath)
|
FileUtils.delete(context.cacheDir.absolutePath)
|
||||||
context.externalCacheDir?.deleteRecursively()
|
context.externalCacheDir?.deleteRecursively()
|
||||||
context.cacheDir.deleteRecursively()
|
context.cacheDir.deleteRecursively()
|
||||||
@@ -61,7 +64,7 @@ class OtherConfigViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun shrinkDatabase() {
|
fun shrinkDatabase() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
appDb.openHelper.writableDatabase.execSQL("VACUUM")
|
shrinkDatabaseUseCase.execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +93,6 @@ class OtherConfigViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun updateLocalBookDir(path: String) {
|
fun updateLocalBookDir(path: String) {
|
||||||
OtherConfig.defaultBookTreeUri = path
|
OtherConfig.defaultBookTreeUri = path
|
||||||
AppConfig.defaultBookTreeUri = OtherConfig.defaultBookTreeUri
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkSourceTimeout by mutableStateOf((CheckSource.timeout / 1000))
|
var checkSourceTimeout by mutableStateOf((CheckSource.timeout / 1000))
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package io.legado.app.ui.dict
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseBottomSheetDialogFragment
|
import io.legado.app.base.BaseBottomSheetDialogFragment
|
||||||
@@ -15,6 +14,7 @@ import io.legado.app.utils.setHtml
|
|||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
import io.legado.app.utils.visible
|
import io.legado.app.utils.visible
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 词典
|
* 词典
|
||||||
@@ -27,7 +27,7 @@ class DictDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_dict) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel by viewModels<DictViewModel>()
|
private val viewModel by viewModel<DictViewModel>()
|
||||||
private val binding by viewBinding(DialogDictBinding::bind)
|
private val binding by viewBinding(DialogDictBinding::bind)
|
||||||
private var word: String? = null
|
private var word: String? = null
|
||||||
private var glideImageGetter: GlideImageGetter? = null
|
private var glideImageGetter: GlideImageGetter? = null
|
||||||
|
|||||||
@@ -2,17 +2,20 @@ package io.legado.app.ui.dict
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.DictRule
|
import io.legado.app.data.entities.DictRule
|
||||||
|
import io.legado.app.data.repository.DictRuleRepository
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
|
|
||||||
class DictViewModel(application: Application) : BaseViewModel(application) {
|
class DictViewModel(
|
||||||
|
application: Application,
|
||||||
|
private val dictRuleRepository: DictRuleRepository
|
||||||
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
private var dictJob: Coroutine<String>? = null
|
private var dictJob: Coroutine<String>? = null
|
||||||
|
|
||||||
fun initData(onSuccess: (List<DictRule>) -> Unit) {
|
fun initData(onSuccess: (List<DictRule>) -> Unit) {
|
||||||
execute {
|
execute {
|
||||||
appDb.dictRuleDao.enabled
|
dictRuleRepository.getEnabled()
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
onSuccess.invoke(it)
|
onSuccess.invoke(it)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import io.legado.app.R
|
|||||||
import io.legado.app.base.BaseComposeActivity
|
import io.legado.app.base.BaseComposeActivity
|
||||||
import io.legado.app.constant.AppConst.appInfo
|
import io.legado.app.constant.AppConst.appInfo
|
||||||
import io.legado.app.constant.PreferKey
|
import io.legado.app.constant.PreferKey
|
||||||
import io.legado.app.help.AppWebDav
|
|
||||||
import io.legado.app.help.book.BookHelp
|
import io.legado.app.help.book.BookHelp
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.LocalConfig
|
import io.legado.app.help.config.LocalConfig
|
||||||
@@ -716,13 +715,13 @@ open class MainActivity : BaseComposeActivity() {
|
|||||||
}
|
}
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val lastBackupFile =
|
val lastBackupFile =
|
||||||
withContext(IO) { AppWebDav.lastBackUp().getOrNull() } ?: return@launch
|
withContext(IO) { viewModel.getLatestWebDavBackup() } ?: return@launch
|
||||||
if (lastBackupFile.lastModify - LocalConfig.lastBackup > DateUtils.MINUTE_IN_MILLIS) {
|
if (lastBackupFile.lastModify - LocalConfig.lastBackup > DateUtils.MINUTE_IN_MILLIS) {
|
||||||
LocalConfig.lastBackup = lastBackupFile.lastModify
|
LocalConfig.lastBackup = lastBackupFile.lastModify
|
||||||
alert(R.string.restore, R.string.webdav_after_local_restore_confirm) {
|
alert(R.string.restore, R.string.webdav_after_local_restore_confirm) {
|
||||||
cancelButton()
|
cancelButton()
|
||||||
okButton {
|
okButton {
|
||||||
viewModel.restoreWebDav(lastBackupFile.displayName)
|
viewModel.restoreWebDav(lastBackupFile.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.constant.EventBus
|
import io.legado.app.constant.EventBus
|
||||||
import io.legado.app.data.appDb
|
import io.legado.app.domain.usecase.AppStartupMaintenanceUseCase
|
||||||
import io.legado.app.help.AppWebDav
|
import io.legado.app.domain.usecase.WebDavBackupUseCase
|
||||||
import io.legado.app.help.DefaultData
|
|
||||||
import io.legado.app.ui.main.my.PrefClickEvent
|
import io.legado.app.ui.main.my.PrefClickEvent
|
||||||
import io.legado.app.utils.eventBus.FlowEventBus
|
import io.legado.app.utils.eventBus.FlowEventBus
|
||||||
import io.legado.app.utils.sendToClip
|
import io.legado.app.utils.sendToClip
|
||||||
|
|
||||||
class MainViewModel(application: Application) : BaseViewModel(application) {
|
class MainViewModel(
|
||||||
|
application: Application,
|
||||||
|
private val appStartupMaintenanceUseCase: AppStartupMaintenanceUseCase,
|
||||||
|
private val webDavBackupUseCase: WebDavBackupUseCase
|
||||||
|
) : BaseViewModel(application) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
deleteNotShelfBook()
|
deleteNotShelfBook()
|
||||||
@@ -24,23 +27,21 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
|
|
||||||
fun postLoad() {
|
fun postLoad() {
|
||||||
execute {
|
execute {
|
||||||
if (appDb.httpTTSDao.count == 0) {
|
appStartupMaintenanceUseCase.ensureDefaultHttpTts()
|
||||||
DefaultData.httpTTS.let {
|
|
||||||
appDb.httpTTSDao.insert(*it.toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restoreWebDav(name: String) {
|
fun restoreWebDav(name: String) {
|
||||||
execute {
|
execute {
|
||||||
AppWebDav.restoreWebDav(name)
|
webDavBackupUseCase.restore(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getLatestWebDavBackup() = webDavBackupUseCase.getLatestBackup()
|
||||||
|
|
||||||
private fun deleteNotShelfBook() {
|
private fun deleteNotShelfBook() {
|
||||||
execute {
|
execute {
|
||||||
appDb.bookDao.deleteNotShelfBook()
|
appStartupMaintenanceUseCase.deleteNotShelfBooks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseRuleEvent
|
import io.legado.app.base.BaseRuleEvent
|
||||||
import io.legado.app.data.appDb
|
|
||||||
import io.legado.app.data.entities.BookGroup
|
import io.legado.app.data.entities.BookGroup
|
||||||
import io.legado.app.ui.about.AppLogSheet
|
import io.legado.app.ui.about.AppLogSheet
|
||||||
import io.legado.app.ui.book.info.GroupSelectSheet
|
import io.legado.app.ui.book.info.GroupSelectSheet
|
||||||
@@ -101,7 +100,6 @@ import io.legado.app.utils.readText
|
|||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@@ -226,14 +224,8 @@ fun BookshelfScreen(
|
|||||||
}
|
}
|
||||||
val isUsingStandaloneSearchGroup = uiState.isSearch &&
|
val isUsingStandaloneSearchGroup = uiState.isSearch &&
|
||||||
uiState.groups.none { it.groupId == currentGroupId }
|
uiState.groups.none { it.groupId == currentGroupId }
|
||||||
val currentGroupBookCountFlow = remember(currentGroupId) {
|
val currentGroupBookCount = uiState.currentGroupBookCount
|
||||||
appDb.bookDao.flowBookShelfByGroup(currentGroupId).map { it.size }
|
val allGroupsBookCount = uiState.allBooksCount
|
||||||
}
|
|
||||||
val currentGroupBookCount by currentGroupBookCountFlow.collectAsState(initial = 0)
|
|
||||||
val allGroupsBookCountFlow = remember {
|
|
||||||
appDb.bookDao.flowBookShelfByGroup(BookGroup.IdAll).map { it.size }
|
|
||||||
}
|
|
||||||
val allGroupsBookCount by allGroupsBookCountFlow.collectAsState(initial = 0)
|
|
||||||
|
|
||||||
val bookGroupStyle = BookshelfConfig.bookGroupStyle
|
val bookGroupStyle = BookshelfConfig.bookGroupStyle
|
||||||
// 控制是否处于“文件夹列表”根视图,还是“文件夹内部”书籍视图
|
// 控制是否处于“文件夹列表”根视图,还是“文件夹内部”书籍视图
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ data class BookshelfUiState(
|
|||||||
val allGroups: List<BookGroup> = emptyList(),
|
val allGroups: List<BookGroup> = emptyList(),
|
||||||
val groupPreviews: Map<Long, List<BookShelfItem>> = emptyMap(),
|
val groupPreviews: Map<Long, List<BookShelfItem>> = emptyMap(),
|
||||||
val groupBookCounts: Map<Long, Int> = emptyMap(),
|
val groupBookCounts: Map<Long, Int> = emptyMap(),
|
||||||
|
val currentGroupBookCount: Int = 0,
|
||||||
|
val allBooksCount: Int = 0,
|
||||||
val selectedGroupIndex: Int = 0,
|
val selectedGroupIndex: Int = 0,
|
||||||
val selectedGroupId: Long = BookGroup.IdAll,
|
val selectedGroupId: Long = BookGroup.IdAll,
|
||||||
val loadingText: String? = null,
|
val loadingText: String? = null,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.legado.app.ui.main.bookshelf
|
package io.legado.app.ui.main.bookshelf
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
@@ -69,7 +70,6 @@ import kotlinx.coroutines.flow.collect
|
|||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
@@ -134,7 +134,8 @@ class BookshelfViewModel(
|
|||||||
|
|
||||||
private data class GroupPreviewState(
|
private data class GroupPreviewState(
|
||||||
val previews: Map<Long, List<BookShelfItem>>,
|
val previews: Map<Long, List<BookShelfItem>>,
|
||||||
val counts: Map<Long, Int>
|
val counts: Map<Long, Int>,
|
||||||
|
val allBookCount: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@@ -206,9 +207,9 @@ class BookshelfViewModel(
|
|||||||
}
|
}
|
||||||
previews[group.groupId] = result
|
previews[group.groupId] = result
|
||||||
}
|
}
|
||||||
GroupPreviewState(previews, counts)
|
GroupPreviewState(previews, counts, allBooks.size)
|
||||||
} else {
|
} else {
|
||||||
GroupPreviewState(emptyMap(), emptyMap())
|
GroupPreviewState(emptyMap(), emptyMap(), allBooks.size)
|
||||||
}
|
}
|
||||||
}.distinctUntilChanged().flowOn(Dispatchers.Default)
|
}.distinctUntilChanged().flowOn(Dispatchers.Default)
|
||||||
|
|
||||||
@@ -257,6 +258,8 @@ class BookshelfViewModel(
|
|||||||
allGroups = allGroups,
|
allGroups = allGroups,
|
||||||
groupPreviews = previews.previews,
|
groupPreviews = previews.previews,
|
||||||
groupBookCounts = previews.counts,
|
groupBookCounts = previews.counts,
|
||||||
|
currentGroupBookCount = books.size,
|
||||||
|
allBooksCount = previews.allBookCount,
|
||||||
selectedGroupIndex = groups.indexOfFirst { it.groupId == internal.groupId }
|
selectedGroupIndex = groups.indexOfFirst { it.groupId == internal.groupId }
|
||||||
.coerceAtLeast(0),
|
.coerceAtLeast(0),
|
||||||
selectedGroupId = internal.groupId,
|
selectedGroupId = internal.groupId,
|
||||||
@@ -393,7 +396,6 @@ class BookshelfViewModel(
|
|||||||
if (bookUrls.isEmpty()) return
|
if (bookUrls.isEmpty()) return
|
||||||
execute {
|
execute {
|
||||||
batchCacheDownloadUseCase.execute(
|
batchCacheDownloadUseCase.execute(
|
||||||
context = context,
|
|
||||||
bookUrls = bookUrls,
|
bookUrls = bookUrls,
|
||||||
downloadAllChapters = downloadAllChapters,
|
downloadAllChapters = downloadAllChapters,
|
||||||
skipAudioBooks = true
|
skipAudioBooks = true
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.data.entities.BookSourcePart
|
import io.legado.app.data.entities.BookSourcePart
|
||||||
import io.legado.app.domain.usecase.ExploreKindUiUseCase
|
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
|
||||||
import io.legado.app.ui.book.explore.ExploreShowActivity
|
import io.legado.app.ui.book.explore.ExploreShowActivity
|
||||||
import io.legado.app.ui.book.search.SearchActivity
|
import io.legado.app.ui.book.search.SearchActivity
|
||||||
import io.legado.app.ui.book.search.SearchScope
|
import io.legado.app.ui.book.search.SearchScope
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ import com.script.rhino.runScriptWithContext
|
|||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.constant.AppConst
|
import io.legado.app.constant.AppConst
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.help.http.CookieManager
|
import io.legado.app.help.http.CookieManager
|
||||||
import io.legado.app.lib.dialogs.SelectItem
|
import io.legado.app.lib.dialogs.SelectItem
|
||||||
import io.legado.app.lib.dialogs.selector
|
import io.legado.app.lib.dialogs.selector
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package io.legado.app.ui.welcome
|
package io.legado.app.ui.welcome
|
||||||
|
|
||||||
|
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.BaseFragment
|
import io.legado.app.base.BaseFragment
|
||||||
import io.legado.app.databinding.FragmentBookFolderBinding
|
import io.legado.app.databinding.FragmentBookFolderBinding
|
||||||
import io.legado.app.help.config.AppConfig
|
|
||||||
import io.legado.app.ui.file.HandleFileContract
|
import io.legado.app.ui.file.HandleFileContract
|
||||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ class BookFolderFragment : BaseFragment(R.layout.fragment_book_folder) {
|
|||||||
|
|
||||||
private val selectBookFolder = registerForActivityResult(HandleFileContract()) { result ->
|
private val selectBookFolder = registerForActivityResult(HandleFileContract()) { result ->
|
||||||
result.uri?.let { treeUri ->
|
result.uri?.let { treeUri ->
|
||||||
AppConfig.defaultBookTreeUri = treeUri.toString()
|
OtherConfig.defaultBookTreeUri = treeUri.toString()
|
||||||
updatePathText()
|
updatePathText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,6 @@ class BookFolderFragment : BaseFragment(R.layout.fragment_book_folder) {
|
|||||||
|
|
||||||
private fun updatePathText() {
|
private fun updatePathText() {
|
||||||
binding.tvFolderPath.text =
|
binding.tvFolderPath.text =
|
||||||
AppConfig.defaultBookTreeUri ?: getString(R.string.welcome_book_folder_not_selected)
|
OtherConfig.defaultBookTreeUri ?: getString(R.string.welcome_book_folder_not_selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ import io.legado.app.base.BaseFragment
|
|||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
import io.legado.app.constant.PreferKey
|
import io.legado.app.constant.PreferKey
|
||||||
import io.legado.app.databinding.FragmentWebdavAuthBinding
|
import io.legado.app.databinding.FragmentWebdavAuthBinding
|
||||||
|
import io.legado.app.domain.usecase.WebDavBackupUseCase
|
||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.AppWebDav
|
|
||||||
import io.legado.app.help.AppWebDav.testWebDav
|
|
||||||
import io.legado.app.help.config.LocalConfig
|
import io.legado.app.help.config.LocalConfig
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import io.legado.app.help.storage.Restore
|
import io.legado.app.help.storage.Restore
|
||||||
@@ -35,11 +34,13 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
class WebDavFragment : BaseFragment(R.layout.fragment_webdav_auth) {
|
class WebDavFragment : BaseFragment(R.layout.fragment_webdav_auth) {
|
||||||
|
|
||||||
|
private val webDavBackupUseCase by inject<WebDavBackupUseCase>()
|
||||||
private var _binding: FragmentWebdavAuthBinding? = null
|
private var _binding: FragmentWebdavAuthBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
private var restoreJob: Job? = null
|
private var restoreJob: Job? = null
|
||||||
@@ -105,7 +106,7 @@ class WebDavFragment : BaseFragment(R.layout.fragment_webdav_auth) {
|
|||||||
)
|
)
|
||||||
saveWebDavConfig(config)
|
saveWebDavConfig(config)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
testWebDav()
|
webDavBackupUseCase.test()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ class WebDavFragment : BaseFragment(R.layout.fragment_webdav_auth) {
|
|||||||
binding.progressRestore.visible()
|
binding.progressRestore.visible()
|
||||||
binding.btnRestore.text = ""
|
binding.btnRestore.text = ""
|
||||||
saveWebDavConfig(config)
|
saveWebDavConfig(config)
|
||||||
AppWebDav.upConfig()
|
webDavBackupUseCase.refreshConfig()
|
||||||
restore()
|
restore()
|
||||||
} finally {
|
} finally {
|
||||||
binding.progressRestore.gone()
|
binding.progressRestore.gone()
|
||||||
@@ -169,8 +170,8 @@ class WebDavFragment : BaseFragment(R.layout.fragment_webdav_auth) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun showRestoreDialog(context: Context) {
|
private suspend fun showRestoreDialog(context: Context) {
|
||||||
val names = withContext(IO) { AppWebDav.getBackupNames() }
|
val names = withContext(IO) { webDavBackupUseCase.getBackupNames() }
|
||||||
if (AppWebDav.isJianGuoYun && names.size > 700) {
|
if (webDavBackupUseCase.isJianGuoYun && names.size > 700) {
|
||||||
context.toastOnUi("由于坚果云限制列出文件数量,部分备份可能未显示,请及时清理旧备份")
|
context.toastOnUi("由于坚果云限制列出文件数量,部分备份可能未显示,请及时清理旧备份")
|
||||||
}
|
}
|
||||||
if (names.isNotEmpty()) {
|
if (names.isNotEmpty()) {
|
||||||
@@ -194,7 +195,7 @@ class WebDavFragment : BaseFragment(R.layout.fragment_webdav_auth) {
|
|||||||
waitDialog.setText("恢复中…")
|
waitDialog.setText("恢复中…")
|
||||||
waitDialog.show()
|
waitDialog.show()
|
||||||
val task = Coroutine.async {
|
val task = Coroutine.async {
|
||||||
AppWebDav.restoreWebDav(name)
|
webDavBackupUseCase.restore(name)
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("WebDav恢复出错\n${it.localizedMessage}", it)
|
AppLog.put("WebDav恢复出错\n${it.localizedMessage}", it)
|
||||||
appCtx.toastOnUi("WebDav恢复出错\n${it.localizedMessage}")
|
appCtx.toastOnUi("WebDav恢复出错\n${it.localizedMessage}")
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import io.legado.app.data.entities.SearchBook
|
import io.legado.app.data.entities.SearchBook
|
||||||
import io.legado.app.model.BookShelfState
|
import io.legado.app.domain.model.BookShelfState
|
||||||
import io.legado.app.ui.theme.LegadoTheme
|
import io.legado.app.ui.theme.LegadoTheme
|
||||||
import io.legado.app.ui.widget.components.cover.Cover
|
import io.legado.app.ui.widget.components.cover.Cover
|
||||||
import io.legado.app.ui.widget.components.text.AppText
|
import io.legado.app.ui.widget.components.text.AppText
|
||||||
@@ -46,8 +46,22 @@ fun SearchBookListItem(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val currentShelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF)
|
val currentShelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF)
|
||||||
|
SearchBookListItem(
|
||||||
|
book = book,
|
||||||
|
shelfState = currentShelfState,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val badgeContent: (@Composable RowScope.() -> Unit)? = when (currentShelfState) {
|
@Composable
|
||||||
|
fun SearchBookListItem(
|
||||||
|
book: SearchBook,
|
||||||
|
shelfState: BookShelfState,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val badgeContent: (@Composable RowScope.() -> Unit)? = when (shelfState) {
|
||||||
BookShelfState.IN_SHELF -> {
|
BookShelfState.IN_SHELF -> {
|
||||||
{
|
{
|
||||||
androidx.compose.material3.Icon(
|
androidx.compose.material3.Icon(
|
||||||
@@ -158,8 +172,22 @@ fun SearchBookGridItem(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val currentShelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF)
|
val currentShelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF)
|
||||||
|
SearchBookGridItem(
|
||||||
|
book = book,
|
||||||
|
shelfState = currentShelfState,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val badgeText: String? = when (currentShelfState) {
|
@Composable
|
||||||
|
fun SearchBookGridItem(
|
||||||
|
book: SearchBook,
|
||||||
|
shelfState: BookShelfState,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val badgeText: String? = when (shelfState) {
|
||||||
BookShelfState.IN_SHELF -> "已在书架"
|
BookShelfState.IN_SHELF -> "已在书架"
|
||||||
BookShelfState.SAME_NAME_AUTHOR -> "同名书籍"
|
BookShelfState.SAME_NAME_AUTHOR -> "同名书籍"
|
||||||
BookShelfState.NOT_IN_SHELF -> null
|
BookShelfState.NOT_IN_SHELF -> null
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ import androidx.compose.ui.unit.LayoutDirection
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.legado.app.App
|
import io.legado.app.App
|
||||||
import io.legado.app.data.entities.rule.ExploreKind
|
import io.legado.app.data.entities.rule.ExploreKind
|
||||||
import io.legado.app.domain.usecase.ExploreKindUiUseCase
|
import io.legado.app.ui.widget.components.explore.ExploreKindUiUseCase
|
||||||
import io.legado.app.help.source.getExploreInfoMap
|
import io.legado.app.help.source.getExploreInfoMap
|
||||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
||||||
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package io.legado.app.domain.usecase
|
package io.legado.app.ui.widget.components.explore
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.script.rhino.runScriptWithContext
|
import com.script.rhino.runScriptWithContext
|
||||||
Reference in New Issue
Block a user