HapeLee 3 месяцев назад
Родитель
Сommit
e286a3f297

+ 0 - 1
app/src/main/java/io/legado/app/ui/book/info/BookInfoRouteScreen.kt

@@ -104,7 +104,6 @@ fun BookInfoRouteScreen(
         val observer = LifecycleEventObserver { _, event ->
             if (event == Lifecycle.Event.ON_RESUME) {
                 viewModel.refreshShelfState()
-                viewModel.onSourceEdited()
             }
         }
         lifecycleOwner.lifecycle.addObserver(observer)

+ 6 - 1
app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceContract.kt

@@ -95,6 +95,7 @@ sealed interface BookSourceIntent {
     data object CancelCheck : BookSourceIntent
     data class Import(val text: String) : BookSourceIntent
     data class Export(val uri: Uri, val ids: Set<String>) : BookSourceIntent
+    data class Upload(val ids: Set<String>) : BookSourceIntent
     data class ToggleImportItem(val index: Int) : BookSourceIntent
     data class ToggleImportAll(val selected: Boolean) : BookSourceIntent
     data class UpdateImportItem(val index: Int, val source: BookSource) : BookSourceIntent
@@ -108,5 +109,9 @@ sealed interface BookSourceIntent {
 }
 
 sealed interface BookSourceEffect {
-    data class ShowSnackbar(val message: String) : BookSourceEffect
+    data class ShowSnackbar(
+        val message: String,
+        val actionLabel: String? = null,
+        val url: String? = null,
+    ) : BookSourceEffect
 }

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

@@ -1,5 +1,6 @@
 package io.legado.app.ui.book.source.manage
 
+import android.content.ClipData
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.background
@@ -22,6 +23,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.SnackbarResult
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -31,6 +33,8 @@ import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.LocalClipboard
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
@@ -51,6 +55,7 @@ import io.legado.app.ui.widget.components.button.series.SmallPlainButton
 import io.legado.app.ui.widget.components.card.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.dialog.TextListInputDialog
 import io.legado.app.ui.widget.components.divider.PillDivider
+import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
 import io.legado.app.ui.widget.components.icon.AppIcons
 import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
 import io.legado.app.ui.widget.components.importComponents.BatchImportDialog
@@ -121,6 +126,7 @@ fun BookSourceScreen(
     var checkSheet by remember { mutableStateOf<CheckSheet?>(null) }
     var checkOptionsDraft by remember { mutableStateOf(state.checkOptions) }
     var pendingExportIds by remember { mutableStateOf<Set<String>>(emptySet()) }
+    var showExportSheet by remember { mutableStateOf(false) }
     var showImportOptions by remember { mutableStateOf(false) }
     var showCustomGroup by remember { mutableStateOf(false) }
     val importSuccess = state.importState as? BaseImportUiState.Success
@@ -146,10 +152,22 @@ fun BookSourceScreen(
         }
     }
     val snackbarHostState = remember { SnackbarHostState() }
+    val clipboardManager = LocalClipboard.current
     LaunchedEffect(Unit) {
         effects.collectLatest { effect ->
             when (effect) {
-                is BookSourceEffect.ShowSnackbar -> snackbarHostState.showSnackbar(effect.message)
+                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))
+                        )
+                    }
+                }
             }
         }
     }
@@ -177,6 +195,20 @@ fun BookSourceScreen(
         rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
             uri?.let { onIntent(BookSourceIntent.Export(it, pendingExportIds)) }
         }
+    FilePickerSheet(
+        show = showExportSheet,
+        onDismissRequest = { showExportSheet = false },
+        title = stringResource(R.string.export),
+        onSelectSysDir = {
+            showExportSheet = false
+            exportDocument.launch("bookSource.json")
+        },
+        onUpload = {
+            showExportSheet = false
+            onIntent(BookSourceIntent.Upload(pendingExportIds))
+        },
+        allowExtensions = arrayOf("json"),
+    )
     SourceInputDialog(
         show = showOnlineImport,
         title = stringResource(R.string.import_on_line),
@@ -432,7 +464,7 @@ fun BookSourceScreen(
             },
             ActionItem(stringResource(R.string.export)) {
                 pendingExportIds = selectedIds
-                exportDocument.launch("bookSource.json")
+                showExportSheet = true
             },
         ),
         onDeleteSelected = { deleteIds = it as Set<String> },

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

@@ -10,6 +10,7 @@ import io.legado.app.constant.AppPattern
 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.BookSourceCheckGateway
 import io.legado.app.domain.gateway.CheckSourceSettings
 import io.legado.app.domain.gateway.CheckSourceSettingsGateway
@@ -50,6 +51,7 @@ import kotlinx.coroutines.launch
 class BookSourceViewModel(
     private val application: Application,
     private val repository: BookSourceRepository,
+    private val uploadRepository: UploadRepository,
     private val otherSettingsGateway: OtherSettingsGateway,
     private val checkGateway: BookSourceCheckGateway,
     private val checkSettingsGateway: CheckSourceSettingsGateway,
@@ -242,6 +244,7 @@ class BookSourceViewModel(
             BookSourceIntent.CancelCheck -> checkJob?.cancel()
             is BookSourceIntent.Import -> importSources(intent.text)
             is BookSourceIntent.Export -> exportSources(intent.uri, intent.ids)
+            is BookSourceIntent.Upload -> uploadSources(intent.ids)
             is BookSourceIntent.ToggleImportItem -> updateImportItems { items ->
                 items.mapIndexed { index, item -> if (index == intent.index) item.copy(isSelected = !item.isSelected) else item }
             }
@@ -506,6 +509,27 @@ class BookSourceViewModel(
             .onFailure { _effects.tryEmit(BookSourceEffect.ShowSnackbar("导出失败: ${it.localizedMessage}")) }
     }
 
+    private fun uploadSources(ids: Set<String>) = launch {
+        runCatching {
+            val selected = repository.getAll().filter { ids.isEmpty() || it.bookSourceUrl in ids }
+            uploadRepository.upload(
+                fileName = "bookSource.json",
+                file = GSON.toJson(selected),
+                contentType = "application/json",
+            )
+        }.onSuccess { url ->
+            _effects.tryEmit(
+                BookSourceEffect.ShowSnackbar(
+                    message = "上传成功: $url",
+                    actionLabel = "复制链接",
+                    url = url,
+                )
+            )
+        }.onFailure {
+            _effects.tryEmit(BookSourceEffect.ShowSnackbar("上传失败: ${it.localizedMessage}"))
+        }
+    }
+
 }
 
 private fun BookSourceCheckOptionsUi.toSettings() = CheckSourceSettings(