a328929 пре 3 месеци
родитељ
комит
ad4a420f6e

+ 20 - 23
app/src/main/java/io/legado/app/data/repository/BookSourceCheckRepository.kt

@@ -5,6 +5,7 @@ import io.legado.app.constant.BookSourceType
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookSource
 import io.legado.app.domain.gateway.BookSourceCheckGateway
+import io.legado.app.domain.gateway.BookSourceCheckFailure
 import io.legado.app.domain.gateway.BookSourceCheckResult
 import io.legado.app.domain.gateway.BookSourceCheckState
 import io.legado.app.domain.gateway.BookSourceCheckStatus
@@ -42,7 +43,7 @@ class BookSourceCheckRepository(
             isRunning = true,
             total = sourceIds.size,
             results = sourceIds.associateWith {
-                BookSourceCheckResult(BookSourceCheckStatus.Pending, "等待校验")
+                BookSourceCheckResult(BookSourceCheckStatus.Pending)
             },
         )
         val dispatcher = Executors.newFixedThreadPool(
@@ -60,7 +61,8 @@ class BookSourceCheckRepository(
                                     id = id,
                                     name = "",
                                     status = BookSourceCheckStatus.Failed,
-                                    message = "校验失败:${error.displayMessage()}",
+                                    failure = BookSourceCheckFailure.CheckFailed,
+                                    detail = error.displayMessage(),
                                 )
                             }
                     }
@@ -74,11 +76,14 @@ class BookSourceCheckRepository(
             } else {
                 BookSourceCheckStatus.Cancelled
             }
-            val unfinishedMessage = if (completedNormally) "校验失败:任务未完成" else "校验已取消"
             _state.update { state ->
                 val results = state.results.mapValues { (_, result) ->
                     if (result.status.isTerminal) result
-                    else BookSourceCheckResult(unfinishedStatus, unfinishedMessage)
+                    else BookSourceCheckResult(
+                        status = unfinishedStatus,
+                        failure = BookSourceCheckFailure.Incomplete
+                            .takeIf { completedNormally },
+                    )
                 }
                 state.copy(
                     isRunning = false,
@@ -97,7 +102,7 @@ class BookSourceCheckRepository(
                 id = sourceId,
                 name = "",
                 status = BookSourceCheckStatus.Failed,
-                message = "校验失败:书源不存在",
+                failure = BookSourceCheckFailure.SourceMissing,
             )
             return
         }
@@ -106,7 +111,6 @@ class BookSourceCheckRepository(
             sourceId,
             source.bookSourceName,
             BookSourceCheckStatus.Running,
-            "正在校验",
         )
         val result = runCatching {
             withTimeout(settingsGateway.currentSettings.timeoutMillis) {
@@ -133,21 +137,22 @@ class BookSourceCheckRepository(
                 sourceId,
                 source.bookSourceName,
                 BookSourceCheckStatus.Failed,
-                "校验失败:${result.exceptionOrNull().displayMessage()}",
+                BookSourceCheckFailure.CheckFailed,
+                result.exceptionOrNull().displayMessage(),
             )
 
             saveResult.isFailure -> updateResult(
                 sourceId,
                 source.bookSourceName,
                 BookSourceCheckStatus.Failed,
-                "校验失败:无法保存结果,${saveResult.exceptionOrNull().displayMessage()}",
+                BookSourceCheckFailure.SaveFailed,
+                saveResult.exceptionOrNull().displayMessage(),
             )
 
             else -> updateResult(
                 sourceId,
                 source.bookSourceName,
                 BookSourceCheckStatus.Succeeded,
-                "校验成功",
             )
         }
     }
@@ -156,7 +161,8 @@ class BookSourceCheckRepository(
         id: String,
         name: String,
         status: BookSourceCheckStatus,
-        message: String,
+        failure: BookSourceCheckFailure? = null,
+        detail: String? = null,
     ) {
         _state.update { state ->
             val previous = state.results[id]
@@ -168,7 +174,9 @@ class BookSourceCheckRepository(
             state.copy(
                 completed = completed,
                 currentSourceName = name.ifBlank { state.currentSourceName },
-                results = state.results + (id to BookSourceCheckResult(status, message)),
+                results = state.results + (
+                    id to BookSourceCheckResult(status, failure, detail)
+                ),
             )
         }
     }
@@ -236,17 +244,6 @@ class BookSourceCheckRepository(
     }
 }
 
-private val BookSourceCheckStatus.isTerminal: Boolean
-    get() = when (this) {
-        BookSourceCheckStatus.Pending,
-        BookSourceCheckStatus.Running -> false
-
-        BookSourceCheckStatus.Succeeded,
-        BookSourceCheckStatus.Failed,
-        BookSourceCheckStatus.Cancelled -> true
-    }
-
-private fun Throwable?.displayMessage(): String =
+private fun Throwable?.displayMessage(): String? =
     this?.localizedMessage?.takeIf { it.isNotBlank() }
         ?: this?.javaClass?.simpleName?.takeIf { it.isNotBlank() }
-        ?: "未知错误"

+ 25 - 8
app/src/main/java/io/legado/app/domain/gateway/BookSourceCheckGateway.kt

@@ -2,17 +2,25 @@ package io.legado.app.domain.gateway
 
 import kotlinx.coroutines.flow.StateFlow
 
-enum class BookSourceCheckStatus {
-    Pending,
-    Running,
-    Succeeded,
-    Failed,
-    Cancelled,
+enum class BookSourceCheckStatus(val isTerminal: Boolean) {
+    Pending(false),
+    Running(false),
+    Succeeded(true),
+    Failed(true),
+    Cancelled(true),
+}
+
+enum class BookSourceCheckFailure {
+    CheckFailed,
+    SourceMissing,
+    SaveFailed,
+    Incomplete,
 }
 
 data class BookSourceCheckResult(
     val status: BookSourceCheckStatus,
-    val message: String,
+    val failure: BookSourceCheckFailure? = null,
+    val detail: String? = null,
 )
 
 data class BookSourceCheckState(
@@ -21,7 +29,16 @@ data class BookSourceCheckState(
     val completed: Int = 0,
     val currentSourceName: String = "",
     val results: Map<String, BookSourceCheckResult> = emptyMap(),
-)
+) {
+    val succeededCount: Int
+        get() = results.values.count { it.status == BookSourceCheckStatus.Succeeded }
+
+    val failedCount: Int
+        get() = results.values.count { it.status == BookSourceCheckStatus.Failed }
+
+    val cancelledCount: Int
+        get() = results.values.count { it.status == BookSourceCheckStatus.Cancelled }
+}
 
 interface BookSourceCheckGateway {
     val state: StateFlow<BookSourceCheckState>

+ 13 - 13
app/src/main/java/io/legado/app/service/BookSourceCheckService.kt

@@ -12,7 +12,6 @@ import io.legado.app.constant.AppConst
 import io.legado.app.constant.IntentAction
 import io.legado.app.constant.NotificationId
 import io.legado.app.domain.gateway.BookSourceCheckGateway
-import io.legado.app.domain.gateway.BookSourceCheckStatus
 import io.legado.app.domain.usecase.StartBookSourceCheckUseCase
 import io.legado.app.help.IntentData
 import io.legado.app.ui.main.MainActivity
@@ -113,6 +112,7 @@ class BookSourceCheckService : BaseService() {
 
     @SuppressLint("WakelockTimeout")
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        super.onStartCommand(intent, flags, startId)
         when (intent?.action) {
             IntentAction.start -> {
                 val request = IntentData.get<CheckRequest>(
@@ -165,19 +165,19 @@ class BookSourceCheckService : BaseService() {
 
     private fun showFinishedNotification() {
         val state = checkGateway.state.value
-        val succeeded = state.results.values.count {
-            it.status == BookSourceCheckStatus.Succeeded
-        }
-        val failed = state.results.values.count {
-            it.status == BookSourceCheckStatus.Failed
-        }
-        val cancelled = state.results.values.count {
-            it.status == BookSourceCheckStatus.Cancelled
-        }
-        val content = if (cancelled > 0) {
-            getString(R.string.book_source_check_cancelled, succeeded, failed, cancelled)
+        val content = if (state.cancelledCount > 0) {
+            getString(
+                R.string.book_source_check_cancelled,
+                state.succeededCount,
+                state.failedCount,
+                state.cancelledCount,
+            )
         } else {
-            getString(R.string.book_source_check_completed, succeeded, failed)
+            getString(
+                R.string.book_source_check_completed,
+                state.succeededCount,
+                state.failedCount,
+            )
         }
         notificationBuilder
             .clearActions()

+ 37 - 37
app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceScreen.kt

@@ -76,7 +76,6 @@ import io.legado.app.ui.widget.components.rules.RuleListScaffold
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
 import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.ui.widget.components.topbar.TopBarActionButton
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
 import org.koin.androidx.compose.koinViewModel
 import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -93,16 +92,43 @@ fun BookSourceRouteScreen(
     onDebugSource: (String) -> Unit,
 ) {
     val state by viewModel.uiState.collectAsStateWithLifecycle()
+    val context = LocalContext.current
+    val snackbarHostState = remember { SnackbarHostState() }
+    val clipboardManager = LocalClipboard.current
+    LaunchedEffect(viewModel) {
+        viewModel.effects.collectLatest { effect ->
+            when (effect) {
+                is BookSourceEffect.StartCheck -> {
+                    BookSourceCheckService.start(context, effect.ids, effect.keyword)
+                }
+
+                BookSourceEffect.CancelCheck -> BookSourceCheckService.stop(context)
+
+                is BookSourceEffect.ShowSnackbar -> {
+                    val result = snackbarHostState.showSnackbar(
+                        message = effect.message,
+                        actionLabel = effect.actionLabel,
+                        withDismissAction = true,
+                    )
+                    if (result == SnackbarResult.ActionPerformed && effect.url != null) {
+                        clipboardManager.setClipEntry(
+                            ClipEntry(ClipData.newPlainText("url", effect.url))
+                        )
+                    }
+                }
+            }
+        }
+    }
     BookSourceScreen(
-        state,
-        viewModel::onIntent,
-        onBackClick,
-        onAddSource,
-        onEditSource,
-        onLoginSource,
-        onSearchSource,
-        onDebugSource,
-        viewModel.effects,
+        state = state,
+        onIntent = viewModel::onIntent,
+        snackbarHostState = snackbarHostState,
+        onBackClick = onBackClick,
+        onAddSource = onAddSource,
+        onEditSource = onEditSource,
+        onLoginSource = onLoginSource,
+        onSearchSource = onSearchSource,
+        onDebugSource = onDebugSource,
     )
 }
 
@@ -111,13 +137,13 @@ fun BookSourceRouteScreen(
 fun BookSourceScreen(
     state: BookSourceUiState,
     onIntent: (BookSourceIntent) -> Unit,
+    snackbarHostState: SnackbarHostState,
     onBackClick: () -> Unit,
     onAddSource: () -> Unit,
     onEditSource: (String) -> Unit,
     onLoginSource: (String) -> Unit,
     onSearchSource: (String, String) -> Unit,
     onDebugSource: (String) -> Unit,
-    effects: Flow<BookSourceEffect>,
 ) {
     val context = LocalContext.current
     val rules = state.items
@@ -158,33 +184,7 @@ fun BookSourceScreen(
             }
         }
     }
-    val snackbarHostState = remember { SnackbarHostState() }
-    val clipboardManager = LocalClipboard.current
     val cancelLabel = stringResource(R.string.cancel)
-    LaunchedEffect(Unit) {
-        effects.collectLatest { effect ->
-            when (effect) {
-                is BookSourceEffect.StartCheck -> {
-                    BookSourceCheckService.start(context, effect.ids, effect.keyword)
-                }
-
-                BookSourceEffect.CancelCheck -> BookSourceCheckService.stop(context)
-
-                is BookSourceEffect.ShowSnackbar -> {
-                    val result = snackbarHostState.showSnackbar(
-                        message = effect.message,
-                        actionLabel = effect.actionLabel,
-                        withDismissAction = true,
-                    )
-                    if (result == SnackbarResult.ActionPerformed && effect.url != null) {
-                        clipboardManager.setClipEntry(
-                            ClipEntry(ClipData.newPlainText("url", effect.url))
-                        )
-                    }
-                }
-            }
-        }
-    }
     LaunchedEffect(state.checkProgress) {
         val progress = state.checkProgress ?: return@LaunchedEffect
         val result = snackbarHostState.showSnackbar(

+ 55 - 16
app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt

@@ -11,7 +11,9 @@ import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.repository.BookSourceRepository
 import io.legado.app.data.repository.UploadRepository
+import io.legado.app.domain.gateway.BookSourceCheckFailure
 import io.legado.app.domain.gateway.BookSourceCheckGateway
+import io.legado.app.domain.gateway.BookSourceCheckResult
 import io.legado.app.domain.gateway.BookSourceCheckStatus
 import io.legado.app.domain.gateway.CheckSourceSettings
 import io.legado.app.domain.gateway.CheckSourceSettingsGateway
@@ -84,27 +86,18 @@ class BookSourceViewModel(
             var wasRunning = false
             checkGateway.state.collect { state ->
                 if (wasRunning && !state.isRunning) {
-                    val succeeded = state.results.values.count {
-                        it.status == BookSourceCheckStatus.Succeeded
-                    }
-                    val failed = state.results.values.count {
-                        it.status == BookSourceCheckStatus.Failed
-                    }
-                    val cancelled = state.results.values.count {
-                        it.status == BookSourceCheckStatus.Cancelled
-                    }
-                    val message = if (cancelled > 0) {
+                    val message = if (state.cancelledCount > 0) {
                         application.getString(
                             io.legado.app.R.string.book_source_check_cancelled,
-                            succeeded,
-                            failed,
-                            cancelled,
+                            state.succeededCount,
+                            state.failedCount,
+                            state.cancelledCount,
                         )
                     } else {
                         application.getString(
                             io.legado.app.R.string.book_source_check_completed,
-                            succeeded,
-                            failed,
+                            state.succeededCount,
+                            state.failedCount,
                         )
                     }
                     _effects.tryEmit(BookSourceEffect.ShowSnackbar(message))
@@ -180,7 +173,11 @@ class BookSourceViewModel(
         checkSettingsGateway.settings,
     ) { state, importing, check, settings ->
         state.copy(
-            items = state.items.map { it.copy(checkMessage = check.results[it.id]?.message) }
+            items = state.items.map { item ->
+                item.copy(
+                    checkMessage = check.results[item.id]?.displayMessage(application)
+                )
+            }
                 .toImmutableList(),
             importState = importing,
             checkProgress = if (check.isRunning) application.getString(
@@ -567,6 +564,48 @@ private fun BookSourceCheckOptionsUi.toSettings() = CheckSourceSettings(
     checkContent = checkContent,
 )
 
+private fun BookSourceCheckResult.displayMessage(application: Application): String {
+    val errorDetail = detail ?: application.getString(io.legado.app.R.string.unknown_error)
+    return when (status) {
+        BookSourceCheckStatus.Pending -> application.getString(
+            io.legado.app.R.string.book_source_check_waiting
+        )
+
+        BookSourceCheckStatus.Running -> application.getString(
+            io.legado.app.R.string.book_source_check_running
+        )
+
+        BookSourceCheckStatus.Succeeded -> application.getString(
+            io.legado.app.R.string.book_source_check_succeeded
+        )
+
+        BookSourceCheckStatus.Cancelled -> application.getString(
+            io.legado.app.R.string.book_source_check_item_cancelled
+        )
+
+        BookSourceCheckStatus.Failed -> when (failure) {
+            BookSourceCheckFailure.SourceMissing -> application.getString(
+                io.legado.app.R.string.book_source_check_source_missing
+            )
+
+            BookSourceCheckFailure.SaveFailed -> application.getString(
+                io.legado.app.R.string.book_source_check_save_failed,
+                errorDetail,
+            )
+
+            BookSourceCheckFailure.Incomplete -> application.getString(
+                io.legado.app.R.string.book_source_check_incomplete
+            )
+
+            BookSourceCheckFailure.CheckFailed,
+            null -> application.getString(
+                io.legado.app.R.string.book_source_check_failed,
+                errorDetail,
+            )
+        }
+    }
+}
+
 private fun List<BookSourcePart>.filterFor(filter: String?, query: String): List<BookSourcePart> =
     filter { source ->
         val filterMatch = when (filter) {

+ 8 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -363,6 +363,14 @@
     <string name="check_book_source">校验书源</string>
     <string name="book_source_check_completed">校验完成:成功 %1$d,失败 %2$d</string>
     <string name="book_source_check_cancelled">校验已停止:成功 %1$d,失败 %2$d,取消 %3$d</string>
+    <string name="book_source_check_waiting">等待校验</string>
+    <string name="book_source_check_running">正在校验</string>
+    <string name="book_source_check_succeeded">校验成功</string>
+    <string name="book_source_check_failed">校验失败:%1$s</string>
+    <string name="book_source_check_source_missing">校验失败:书源不存在</string>
+    <string name="book_source_check_save_failed">校验失败:无法保存结果,%1$s</string>
+    <string name="book_source_check_incomplete">校验失败:任务未完成</string>
+    <string name="book_source_check_item_cancelled">校验已取消</string>
     <string name="check_select_source">校验所选</string>
     <string name="progress_show">%1$s      进度 %2$d/%3$d</string>
     <string name="tts_fix">请安装并选择中文 TTS!</string>

+ 8 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -305,6 +305,14 @@
     <string name="check_book_source">校驗書源</string>
     <string name="book_source_check_completed">校驗完成:成功 %1$d,失敗 %2$d</string>
     <string name="book_source_check_cancelled">校驗已停止:成功 %1$d,失敗 %2$d,取消 %3$d</string>
+    <string name="book_source_check_waiting">等待校驗</string>
+    <string name="book_source_check_running">正在校驗</string>
+    <string name="book_source_check_succeeded">校驗成功</string>
+    <string name="book_source_check_failed">校驗失敗:%1$s</string>
+    <string name="book_source_check_source_missing">校驗失敗:書源不存在</string>
+    <string name="book_source_check_save_failed">校驗失敗:無法保存結果,%1$s</string>
+    <string name="book_source_check_incomplete">校驗失敗:任務未完成</string>
+    <string name="book_source_check_item_cancelled">校驗已取消</string>
     <string name="check_select_source">校驗所選</string>
     <string name="progress_show">%1$s      進度 %2$d/%3$d</string>
     <string name="tts_fix">請安裝並選擇中文 TTS!</string>

+ 8 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -307,6 +307,14 @@
     <string name="check_book_source">校驗書源</string>
     <string name="book_source_check_completed">校驗完成:成功 %1$d,失敗 %2$d</string>
     <string name="book_source_check_cancelled">校驗已停止:成功 %1$d,失敗 %2$d,取消 %3$d</string>
+    <string name="book_source_check_waiting">等待校驗</string>
+    <string name="book_source_check_running">正在校驗</string>
+    <string name="book_source_check_succeeded">校驗成功</string>
+    <string name="book_source_check_failed">校驗失敗:%1$s</string>
+    <string name="book_source_check_source_missing">校驗失敗:書源不存在</string>
+    <string name="book_source_check_save_failed">校驗失敗:無法儲存結果,%1$s</string>
+    <string name="book_source_check_incomplete">校驗失敗:工作未完成</string>
+    <string name="book_source_check_item_cancelled">校驗已取消</string>
     <string name="check_select_source">校驗所選</string>
     <string name="progress_show">%1$s      進度 %2$d/%3$d</string>
     <string name="tts_fix">請安裝並選擇中文TTS!</string>

+ 8 - 0
app/src/main/res/values/strings.xml

@@ -393,6 +393,14 @@
     <string name="check_book_source">Check book sources</string>
     <string name="book_source_check_completed">Check completed: %1$d succeeded, %2$d failed</string>
     <string name="book_source_check_cancelled">Check stopped: %1$d succeeded, %2$d failed, %3$d cancelled</string>
+    <string name="book_source_check_waiting">Waiting to check</string>
+    <string name="book_source_check_running">Checking</string>
+    <string name="book_source_check_succeeded">Check succeeded</string>
+    <string name="book_source_check_failed">Check failed: %1$s</string>
+    <string name="book_source_check_source_missing">Check failed: source no longer exists</string>
+    <string name="book_source_check_save_failed">Check failed: unable to save result, %1$s</string>
+    <string name="book_source_check_incomplete">Check failed: task did not finish</string>
+    <string name="book_source_check_item_cancelled">Check cancelled</string>
     <string name="check_select_source">Check the selected source</string>
     <string name="progress_show">%1$s      Progress %2$d/%3$d</string>
     <string name="tts_fix">Please install and select Chinese TTS!</string>