Răsfoiți Sursa

feat: 一些Compose的WebView问题

HapeLee 3 luni în urmă
părinte
comite
31c4bf4fef

+ 0 - 2
app/src/main/java/io/legado/app/di/appModule.kt

@@ -215,7 +215,6 @@ import io.legado.app.model.ReadAloudSessionStore
 import io.legado.app.model.ReaderSession
 import io.legado.app.ui.about.AboutViewModel
 import io.legado.app.ui.ai.chat.AiChatViewModel
-import io.legado.app.ui.association.ImportBookSourceViewModel
 import io.legado.app.ui.association.ImportDictRuleViewModel
 import io.legado.app.ui.association.ImportHttpTtsViewModel
 import io.legado.app.ui.association.ImportReplaceRuleViewModel
@@ -509,7 +508,6 @@ val appModule = module {
     }
 
     viewModelOf(::DictRuleViewModel)
-    viewModelOf(::ImportBookSourceViewModel)
     viewModelOf(::ImportDictRuleViewModel)
     viewModelOf(::ImportHttpTtsViewModel)
     viewModelOf(::ImportReplaceRuleViewModel)

+ 5 - 1
app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt

@@ -16,6 +16,7 @@ import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.permission.Permissions
 import io.legado.app.lib.permission.PermissionsCompat
 import io.legado.app.ui.file.HandleFileContract
+import io.legado.app.ui.main.MainActivity
 import io.legado.app.utils.FileUtils
 import io.legado.app.utils.buildMainHandler
 import io.legado.app.utils.canRead
@@ -82,7 +83,10 @@ class FileAssociationActivity :
         }
         viewModel.successLive.observe(this) {
             when (it.first) {
-                "bookSource" -> showDialogFragment(ImportBookSourceDialog(it.second, true))
+                "bookSource" -> {
+                    startActivity(MainActivity.createBookSourceManageIntent(this, it.second))
+                    finish()
+                }
                 "rssSource" -> showDialogFragment(ImportRssSourceDialog(it.second, true))
                 "replaceRule" -> showDialogFragment(ImportReplaceRuleDialog(it.second, true))
                 "httpTts" -> showDialogFragment(ImportHttpTtsDialog(it.second, true))

+ 0 - 292
app/src/main/java/io/legado/app/ui/association/ImportBookSourceDialog.kt

@@ -1,292 +0,0 @@
-package io.legado.app.ui.association
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.content.DialogInterface
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.LinearLayoutManager
-import io.legado.app.R
-import io.legado.app.base.BaseBottomSheetDialogFragment
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.data.appDb
-import io.legado.app.data.entities.BookSource
-import io.legado.app.databinding.DialogCustomGroupBinding
-import io.legado.app.databinding.DialogRecyclerViewBinding
-import io.legado.app.databinding.ItemSourceImportBinding
-import io.legado.app.help.config.AppConfig
-import io.legado.app.domain.gateway.OtherSettingsGateway
-import io.legado.app.domain.model.settings.OtherSettings
-import io.legado.app.lib.dialogs.alert
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.widget.dialog.CodeDialog
-import io.legado.app.ui.widget.dialog.WaitDialog
-import io.legado.app.utils.*
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import splitties.views.onClick
-import kotlinx.coroutines.launch
-import org.koin.android.ext.android.inject
-import org.koin.androidx.viewmodel.ext.android.viewModel
-
-
-/**
- * 导入书源弹出窗口
- */
-class ImportBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_recycler_view),
-    Toolbar.OnMenuItemClickListener,
-    CodeDialog.Callback {
-
-    constructor(source: String, finishOnDismiss: Boolean = false) : this() {
-        arguments = Bundle().apply {
-            putString("source", source)
-            putBoolean("finishOnDismiss", finishOnDismiss)
-        }
-    }
-
-    private val binding by viewBinding(DialogRecyclerViewBinding::bind)
-    private val viewModel by viewModel<ImportBookSourceViewModel>()
-    private val otherSettingsGateway by inject<OtherSettingsGateway>()
-    private val adapter by lazy { SourcesAdapter(requireContext()) }
-
-    override fun onStart() {
-        super.onStart()
-    }
-
-    override fun onDismiss(dialog: DialogInterface) {
-        super.onDismiss(dialog)
-        if (arguments?.getBoolean("finishOnDismiss") == true) {
-            activity?.finish()
-        }
-    }
-
-    @SuppressLint("NotifyDataSetChanged")
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        binding.toolBar.setTitle(R.string.import_book_source)
-        binding.rotateLoading.visible()
-        initMenu()
-        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
-        binding.recyclerView.adapter = adapter
-        binding.tvCancel.visible()
-        binding.tvCancel.setOnClickListener {
-            dismissAllowingStateLoss()
-        }
-        binding.tvOk.visible()
-        binding.tvOk.setOnClickListener {
-            val waitDialog = WaitDialog(requireContext())
-            waitDialog.show()
-            viewModel.importSelect {
-                waitDialog.dismiss()
-                dismissAllowingStateLoss()
-            }
-        }
-        binding.tvFooterLeft.visible()
-        binding.tvFooterLeft.setOnClickListener {
-            val selectAll = viewModel.isSelectAll
-            viewModel.selectStatus.forEachIndexed { index, b ->
-                if (b != !selectAll) {
-                    viewModel.selectStatus[index] = !selectAll
-                }
-            }
-            adapter.notifyDataSetChanged()
-            upSelectText()
-        }
-        viewModel.errorLiveData.observe(this) {
-            binding.rotateLoading.gone()
-            binding.emptyView.apply {
-                setMessage(it)
-                visible()
-            }
-        }
-        viewModel.successLiveData.observe(this) {
-            binding.rotateLoading.gone()
-            if (it > 0) {
-                adapter.setItems(viewModel.allSources)
-                upSelectText()
-            } else {
-                binding.emptyView.apply {
-                    setMessage(R.string.wrong_format)
-                    visible()
-                }
-            }
-        }
-        val source = arguments?.getString("source")
-        if (source.isNullOrEmpty()) {
-            dismiss()
-            return
-        }
-        viewModel.importSource(source)
-    }
-
-    private fun upSelectText() {
-        if (viewModel.isSelectAll) {
-            binding.tvFooterLeft.text = getString(
-                R.string.select_cancel_count,
-                viewModel.selectCount,
-                viewModel.allSources.size
-            )
-        } else {
-            binding.tvFooterLeft.text = getString(
-                R.string.select_all_count,
-                viewModel.selectCount,
-                viewModel.allSources.size
-            )
-        }
-    }
-
-    private fun initMenu() {
-        binding.toolBar.setOnMenuItemClickListener(this)
-        binding.toolBar.inflateMenu(R.menu.import_source)
-        binding.toolBar.menu.findItem(R.id.menu_keep_original_name)
-            ?.isChecked = AppConfig.importKeepName
-        binding.toolBar.menu.findItem(R.id.menu_keep_group)
-            ?.isChecked = AppConfig.importKeepGroup
-        binding.toolBar.menu.findItem(R.id.menu_keep_enable)
-            ?.isChecked = AppConfig.importKeepEnable
-    }
-
-    @SuppressLint("InflateParams", "NotifyDataSetChanged")
-    override fun onMenuItemClick(item: MenuItem): Boolean {
-        when (item.itemId) {
-            R.id.menu_new_group -> alertCustomGroup(item)
-            R.id.menu_select_new_source -> {
-                val selectAllNew = viewModel.isSelectAllNew
-                viewModel.newSourceStatus.forEachIndexed { index, b ->
-                    if (b) {
-                        viewModel.selectStatus[index] = !selectAllNew
-                    }
-                }
-                adapter.notifyDataSetChanged()
-                upSelectText()
-            }
-
-            R.id.menu_select_update_source -> {
-                val selectAllUpdate = viewModel.isSelectAllUpdate
-                viewModel.updateSourceStatus.forEachIndexed { index, b ->
-                    if (b) {
-                        viewModel.selectStatus[index] = !selectAllUpdate
-                    }
-                }
-                adapter.notifyDataSetChanged()
-                upSelectText()
-            }
-
-            R.id.menu_keep_original_name -> {
-                item.isChecked = !item.isChecked
-                updateImportSetting { it.copy(importKeepName = item.isChecked) }
-            }
-
-            R.id.menu_keep_group -> {
-                item.isChecked = !item.isChecked
-                updateImportSetting { it.copy(importKeepGroup = item.isChecked) }
-            }
-
-            R.id.menu_keep_enable -> {
-                item.isChecked = !item.isChecked
-                updateImportSetting { it.copy(importKeepEnable = item.isChecked) }
-            }
-        }
-        return false
-    }
-
-    private fun updateImportSetting(transform: (OtherSettings) -> OtherSettings) {
-        lifecycleScope.launch { otherSettingsGateway.update(transform) }
-    }
-
-    private fun alertCustomGroup(item: MenuItem) {
-        alert(R.string.diy_edit_source_group) {
-            val alertBinding = DialogCustomGroupBinding.inflate(layoutInflater).apply {
-                val groups = appDb.bookSourceDao.allGroups()
-                textInputLayout.setHint(R.string.group_name)
-                editView.setFilterValues(groups.toList())
-                editView.dropDownHeight = 180.dpToPx()
-            }
-            customView {
-                alertBinding.root
-            }
-            okButton {
-                viewModel.isAddGroup = alertBinding.swAddGroup.isChecked
-                viewModel.groupName = alertBinding.editView.text?.toString()
-                if (viewModel.groupName.isNullOrBlank()) {
-                    item.title = getString(R.string.diy_source_group)
-                } else {
-                    val group = getString(R.string.diy_edit_source_group_title, viewModel.groupName)
-                    if (viewModel.isAddGroup) {
-                        item.title = "+$group"
-                    } else {
-                        item.title = group
-                    }
-                }
-            }
-            cancelButton()
-        }
-    }
-
-    override fun onCodeSave(code: String, requestId: String?) {
-        requestId?.toInt()?.let {
-            GSON.fromJsonObject<BookSource>(code).getOrNull()?.let { source ->
-                viewModel.allSources[it] = source
-                adapter.setItem(it, source)
-            }
-        }
-    }
-
-    inner class SourcesAdapter(context: Context) :
-        RecyclerAdapter<BookSource, ItemSourceImportBinding>(context) {
-
-        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {
-            return ItemSourceImportBinding.inflate(inflater, parent, false)
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemSourceImportBinding,
-            item: BookSource,
-            payloads: MutableList<Any>
-        ) {
-            binding.apply {
-                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]
-                cbSourceName.text = item.bookSourceName
-                val localSource = viewModel.checkSources[holder.layoutPosition]
-                tvSourceState.text = when {
-                    localSource == null -> "新增"
-                    item.lastUpdateTime > localSource.lastUpdateTime -> "更新"
-                    else -> "已有"
-                }
-            }
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {
-            binding.apply {
-                cbSourceName.setOnCheckedChangeListener { buttonView, isChecked ->
-                    if (buttonView.isPressed) {
-                        viewModel.selectStatus[holder.layoutPosition] = isChecked
-                        upSelectText()
-                    }
-                }
-                root.onClick {
-                    cbSourceName.isChecked = !cbSourceName.isChecked
-                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked
-                    upSelectText()
-                }
-                tvOpen.setOnClickListener {
-                    val source = viewModel.allSources[holder.layoutPosition]
-                    showDialogFragment(
-                        CodeDialog(
-                            GSON.toJson(source),
-                            disableEdit = false,
-                            requestId = holder.layoutPosition.toString()
-                        )
-                    )
-                }
-            }
-        }
-
-    }
-
-}

+ 0 - 224
app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt

@@ -1,224 +0,0 @@
-package io.legado.app.ui.association
-
-import android.app.Application
-import android.net.Uri
-import androidx.lifecycle.MutableLiveData
-import com.jayway.jsonpath.JsonPath
-import io.legado.app.R
-import io.legado.app.base.BaseViewModel
-import io.legado.app.constant.AppConst
-import io.legado.app.constant.AppLog
-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.exception.NoStackTraceException
-import io.legado.app.help.book.ContentProcessor
-import io.legado.app.help.config.AppConfig
-import io.legado.app.help.http.decompressed
-import io.legado.app.help.http.newCallResponseBody
-import io.legado.app.help.http.okHttpClient
-import io.legado.app.help.source.SourceHelp
-import io.legado.app.utils.GSON
-import io.legado.app.utils.fromJsonArray
-import io.legado.app.utils.fromJsonObject
-import io.legado.app.utils.inputStream
-import io.legado.app.utils.isAbsUrl
-import io.legado.app.utils.isJsonArray
-import io.legado.app.utils.isJsonObject
-import io.legado.app.utils.isUri
-import io.legado.app.utils.splitNotBlank
-
-
-class ImportBookSourceViewModel(
-    app: Application,
-    private val repository: BookSourceRepository,
-) : BaseViewModel(app) {
-    var isAddGroup = false
-    var groupName: String? = null
-    val errorLiveData = MutableLiveData<String>()
-    val successLiveData = MutableLiveData<Int>()
-
-    val allSources = arrayListOf<BookSource>()
-    val checkSources = arrayListOf<BookSourcePart?>()
-    val selectStatus = arrayListOf<Boolean>()
-    val newSourceStatus = arrayListOf<Boolean>()
-    val updateSourceStatus = arrayListOf<Boolean>()
-
-    val isSelectAll: Boolean
-        get() {
-            selectStatus.forEach {
-                if (!it) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    val isSelectAllNew: Boolean
-        get() {
-            newSourceStatus.forEachIndexed { index, b ->
-                if (b && !selectStatus[index]) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    val isSelectAllUpdate: Boolean
-        get() {
-            updateSourceStatus.forEachIndexed { index, b ->
-                if (b && !selectStatus[index]) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    val selectCount: Int
-        get() {
-            var count = 0
-            selectStatus.forEach {
-                if (it) {
-                    count++
-                }
-            }
-            return count
-        }
-
-    fun importSelect(finally: () -> Unit) {
-        execute {
-            val group = groupName?.trim()
-            val keepName = AppConfig.importKeepName
-            val keepGroup = AppConfig.importKeepGroup
-            val keepEnable = AppConfig.importKeepEnable
-            val selectSource = arrayListOf<BookSource>()
-            selectStatus.forEachIndexed { index, b ->
-                if (b) {
-                    val source = allSources[index]
-                    checkSources[index]?.let {
-                        if (keepName) {
-                            source.bookSourceName = it.bookSourceName
-                        }
-                        if (keepGroup) {
-                            source.bookSourceGroup = it.bookSourceGroup
-                        }
-                        if (keepEnable) {
-                            source.enabled = it.enabled
-                            source.enabledExplore = it.enabledExplore
-                        }
-                        source.customOrder = it.customOrder
-                    }
-                    if (!group.isNullOrEmpty()) {
-                        if (isAddGroup) {
-                            val groups = linkedSetOf<String>()
-                            source.bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.let {
-                                groups.addAll(it)
-                            }
-                            groups.add(group)
-                            source.bookSourceGroup = groups.joinToString(",")
-                        } else {
-                            source.bookSourceGroup = group
-                        }
-                    }
-                    selectSource.add(source)
-                }
-            }
-            SourceHelp.insertBookSource(*selectSource.toTypedArray())
-            ContentProcessor.upReplaceRules()
-        }.onFinally {
-            finally.invoke()
-        }
-    }
-
-    fun importSource(text: String) {
-        execute {
-            val mText = text.trim()
-            when {
-                mText.isJsonObject() -> {
-                    kotlin.runCatching {
-                        val json = JsonPath.parse(mText)
-                        json.read<List<String>>("$.sourceUrls")
-                    }.onSuccess { listUrl ->
-                        listUrl.forEach {
-                            importSourceUrl(it)
-                        }
-                    }.onFailure {
-                        GSON.fromJsonObject<BookSource>(mText).getOrThrow().let {
-                            if (it.bookSourceUrl.isEmpty()) {
-                                throw NoStackTraceException("不是书源")
-                            }
-                            allSources.add(it)
-                        }
-                    }
-                }
-
-                mText.isJsonArray() -> GSON.fromJsonArray<BookSource>(mText).getOrThrow()
-                    .let { items ->
-                        val source = items.firstOrNull() ?: return@let
-                        if (source.bookSourceUrl.isEmpty()) {
-                            throw NoStackTraceException("不是书源")
-                        }
-                        allSources.addAll(items)
-                    }
-
-                mText.isAbsUrl() -> {
-                    importSourceUrl(mText)
-                }
-
-                mText.isUri() -> {
-                    val uri = Uri.parse(mText)
-                    uri.inputStream(context).getOrThrow().use { inputS ->
-                        GSON.fromJsonArray<BookSource>(inputS).getOrThrow().let {
-                            val source = it.firstOrNull() ?: return@let
-                            if (source.bookSourceUrl.isEmpty()) {
-                                throw NoStackTraceException("不是书源")
-                            }
-                            allSources.addAll(it)
-                        }
-                    }
-                }
-
-                else -> throw NoStackTraceException(context.getString(R.string.wrong_format))
-            }
-        }.onError {
-            errorLiveData.postValue("ImportError:${it.localizedMessage}")
-            AppLog.put("ImportError:${it.localizedMessage}", it)
-        }.onSuccess {
-            comparisonSource()
-        }
-    }
-
-    private suspend fun importSourceUrl(url: String) {
-        okHttpClient.newCallResponseBody {
-            if (url.endsWith("#requestWithoutUA")) {
-                url(url.substringBeforeLast("#requestWithoutUA"))
-                header(AppConst.UA_NAME, "null")
-            } else {
-                url(url)
-            }
-        }.decompressed().byteStream().use {
-            GSON.fromJsonArray<BookSource>(it).getOrThrow().let { list ->
-                val source = list.firstOrNull() ?: return@let
-                if (source.bookSourceUrl.isEmpty()) {
-                    throw NoStackTraceException("不是书源")
-                }
-                allSources.addAll(list)
-            }
-        }
-    }
-
-    private fun comparisonSource() {
-        execute {
-            allSources.forEach {
-                val source = repository.getBookSourcePart(it.bookSourceUrl)
-                checkSources.add(source)
-                selectStatus.add(source == null || source.lastUpdateTime < it.lastUpdateTime)
-                newSourceStatus.add(source == null)
-                updateSourceStatus.add(source != null && source.lastUpdateTime < it.lastUpdateTime)
-            }
-            successLiveData.postValue(allSources.size)
-        }
-    }
-
-}

+ 10 - 10
app/src/main/java/io/legado/app/ui/association/OnLineImportActivity.kt

@@ -6,6 +6,7 @@ import io.legado.app.R
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.databinding.ActivityTranslucenceBinding
 import io.legado.app.lib.dialogs.alert
+import io.legado.app.ui.main.MainActivity
 import io.legado.app.utils.showDialogFragment
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 
@@ -23,9 +24,7 @@ class OnLineImportActivity :
         super.onCreate(savedInstanceState)
         viewModel.successLive.observe(this) {
             when (it.first) {
-                "bookSource" -> showDialogFragment(
-                    ImportBookSourceDialog(it.second, true)
-                )
+                "bookSource" -> openComposeBookSourceImport(it.second)
                 "rssSource" -> showDialogFragment(
                     ImportRssSourceDialog(it.second, true)
                 )
@@ -56,9 +55,7 @@ class OnLineImportActivity :
                 return
             }
             when (it.path) {
-                "/bookSource" -> showDialogFragment(
-                    ImportBookSourceDialog(url, true)
-                )
+                "/bookSource" -> openComposeBookSourceImport(url)
 
                 "/rssSource" -> showDialogFragment(
                     ImportRssSourceDialog(url, true)
@@ -86,9 +83,7 @@ class OnLineImportActivity :
                     AddToBookshelfDialog(url, true)
                 )
                 "/importonline" -> when (it.host) {
-                    "booksource" -> showDialogFragment(
-                        ImportBookSourceDialog(url, true)
-                    )
+                    "booksource" -> openComposeBookSourceImport(url)
                     "rsssource" -> showDialogFragment(
                         ImportRssSourceDialog(url, true)
                     )
@@ -113,4 +108,9 @@ class OnLineImportActivity :
         }
     }
 
-}
+    private fun openComposeBookSourceImport(source: String) {
+        startActivity(MainActivity.createBookSourceManageIntent(this, source))
+        finish()
+    }
+
+}

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

@@ -116,6 +116,8 @@ sealed interface BookSourceEffect {
 
     data object CancelCheck : BookSourceEffect
 
+    data object ImportFinished : BookSourceEffect
+
     data class ShowSnackbar(
         val message: String,
         val actionLabel: String? = null,

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

@@ -84,6 +84,9 @@ import androidx.compose.foundation.lazy.grid.items as gridItems
 @Composable
 fun BookSourceRouteScreen(
     viewModel: BookSourceViewModel = koinViewModel(),
+    initialImportUrl: String? = null,
+    closeAfterImport: Boolean = false,
+    onImportClosed: () -> Unit = {},
     onBackClick: () -> Unit,
     onAddSource: () -> Unit,
     onEditSource: (String) -> Unit,
@@ -91,6 +94,9 @@ fun BookSourceRouteScreen(
     onSearchSource: (String, String) -> Unit,
     onDebugSource: (String) -> Unit,
 ) {
+    LaunchedEffect(initialImportUrl) {
+        initialImportUrl?.let { viewModel.onIntent(BookSourceIntent.Import(it)) }
+    }
     val state by viewModel.uiState.collectAsStateWithLifecycle()
     val context = LocalContext.current
     val snackbarHostState = remember { SnackbarHostState() }
@@ -104,6 +110,10 @@ fun BookSourceRouteScreen(
 
                 BookSourceEffect.CancelCheck -> BookSourceCheckService.stop(context)
 
+                BookSourceEffect.ImportFinished -> {
+                    if (closeAfterImport) onImportClosed()
+                }
+
                 is BookSourceEffect.ShowSnackbar -> {
                     val result = snackbarHostState.showSnackbar(
                         message = effect.message,
@@ -123,6 +133,9 @@ fun BookSourceRouteScreen(
         state = state,
         onIntent = viewModel::onIntent,
         snackbarHostState = snackbarHostState,
+        onImportDismissed = {
+            if (closeAfterImport) onImportClosed()
+        },
         onBackClick = onBackClick,
         onAddSource = onAddSource,
         onEditSource = onEditSource,
@@ -138,6 +151,7 @@ fun BookSourceScreen(
     state: BookSourceUiState,
     onIntent: (BookSourceIntent) -> Unit,
     snackbarHostState: SnackbarHostState,
+    onImportDismissed: () -> Unit = {},
     onBackClick: () -> Unit,
     onAddSource: () -> Unit,
     onEditSource: (String) -> Unit,
@@ -232,7 +246,10 @@ fun BookSourceScreen(
     BatchImportDialog(
         title = stringResource(R.string.import_book_source),
         importState = state.importState,
-        onDismissRequest = { onIntent(BookSourceIntent.CancelImport) },
+        onDismissRequest = {
+            onIntent(BookSourceIntent.CancelImport)
+            onImportDismissed()
+        },
         onConfirm = { onIntent(BookSourceIntent.SaveImportedSources) },
         onToggleItem = { onIntent(BookSourceIntent.ToggleImportItem(it)) },
         onToggleAll = { onIntent(BookSourceIntent.ToggleImportAll(it)) },

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

@@ -518,6 +518,7 @@ class BookSourceViewModel(
             SourceHelp.insertBookSource(*sources.toTypedArray())
             ContentProcessor.upReplaceRules()
             importState.value = BaseImportUiState.Idle
+            _effects.tryEmit(BookSourceEffect.ImportFinished)
             _effects.tryEmit(BookSourceEffect.ShowSnackbar("导入完成"))
         }
     }

+ 3 - 2
app/src/main/java/io/legado/app/ui/browser/WebViewModel.kt

@@ -48,11 +48,12 @@ class WebViewModel(
             sourceType = intent.getIntExtra("sourceType", SourceType.book)
             sourceVerificationEnable = intent.getBooleanExtra("sourceVerificationEnable", false)
             refetchAfterSuccess = intent.getBooleanExtra("refetchAfterSuccess", true)
+            html = intent.getStringExtra("html")
             val source = SourceHelp.getSource(sourceOrigin, sourceType)
             val analyzeUrl = AnalyzeUrl(url, source = source, coroutineContext = coroutineContext)
             baseUrl = analyzeUrl.url
             headerMap.putAll(analyzeUrl.headerMap)
-            if (analyzeUrl.isPost()) {
+            if (html.isNullOrEmpty() && analyzeUrl.isPost()) {
                 html = analyzeUrl.getStrResponseAwait(useWebView = false).body
             }
         }.onSuccess {
@@ -139,4 +140,4 @@ class WebViewModel(
         }
     }
 
-}
+}

+ 138 - 28
app/src/main/java/io/legado/app/ui/browser/WebViewRouteScreen.kt

@@ -2,8 +2,9 @@ package io.legado.app.ui.browser
 
 import android.annotation.SuppressLint
 import android.content.Intent
-import android.view.View
+import android.net.Uri
 import android.webkit.SslErrorHandler
+import android.webkit.URLUtil
 import android.webkit.WebChromeClient
 import android.webkit.WebResourceRequest
 import android.webkit.WebSettings
@@ -16,10 +17,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.ContentCopy
 import androidx.compose.material.icons.filled.OpenInBrowser
 import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material.icons.filled.Save
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.SnackbarResult
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
@@ -28,18 +33,24 @@ import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.viewinterop.AndroidView
 import io.legado.app.R
 import io.legado.app.constant.AppConst
 import io.legado.app.help.http.CookieManager
 import io.legado.app.help.http.CookieStore
 import io.legado.app.help.source.SourceVerificationHelp
+import io.legado.app.model.Download
+import io.legado.app.ui.association.OnLineImportActivity
+import io.legado.app.ui.rss.read.VisibleWebViewCompose
 import io.legado.app.ui.widget.components.AppScaffold
+import io.legado.app.ui.widget.components.modalBottomSheet.OptionCard
+import io.legado.app.ui.widget.components.modalBottomSheet.OptionSheet
 import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator
 import io.legado.app.ui.widget.components.topbar.GlassSmallTopAppBar
 import io.legado.app.ui.widget.components.topbar.TopBarActionButton
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
 import io.legado.app.utils.openUrl
+import io.legado.app.utils.sendToClip
+import java.net.URLDecoder
 import android.webkit.CookieManager as AndroidCookieManager
 
 @SuppressLint("SetJavaScriptEnabled", "WebViewClientOnReceivedSslError")
@@ -48,14 +59,17 @@ fun WebViewRouteScreen(
     intent: Intent,
     viewModel: WebViewModel,
     onFinish: () -> Unit,
+    onImportBookSource: (String) -> Unit,
 ) {
     val context = LocalContext.current
     var ready by remember { mutableStateOf(false) }
     var progress by remember { mutableIntStateOf(0) }
     var webView by remember { mutableStateOf<WebView?>(null) }
-    var customView by remember { mutableStateOf<View?>(null) }
-    var customViewCallback by remember { mutableStateOf<WebChromeClient.CustomViewCallback?>(null) }
     var cloudflareChallenge by remember { mutableStateOf(false) }
+    var pageTitle by remember { mutableStateOf<String?>(null) }
+    var imageUrlToSave by remember { mutableStateOf<String?>(null) }
+    var pendingDownload by remember { mutableStateOf<Pair<String, String>?>(null) }
+    val snackbarHostState = remember { SnackbarHostState() }
 
     LaunchedEffect(intent) {
         viewModel.initData(intent) { ready = true }
@@ -75,7 +89,6 @@ fun WebViewRouteScreen(
 
     BackHandler {
         when {
-            customView != null -> customViewCallback?.onCustomViewHidden()
             webView?.canGoBack() == true && (webView?.copyBackForwardList()?.size
                 ?: 0) > 1 -> webView?.goBack()
 
@@ -86,9 +99,10 @@ fun WebViewRouteScreen(
     AppScaffold(
         // WebView 内容不能作为 haze 模糊采样源,否则会被反复重采样导致闪烁(与 RSS 页一致)
         disableHazeSource = true,
+        snackbarHost = { SnackbarHost(snackbarHostState) },
         topBar = {
             GlassSmallTopAppBar(
-                title = viewModel.sourceName.ifBlank {
+                title = pageTitle ?: viewModel.sourceName.ifBlank {
                     intent.getStringExtra("title") ?: context.getString(R.string.loading)
                 },
                 navigationIcon = { TopBarNavigationButton(onClick = ::finishScreen) },
@@ -99,10 +113,26 @@ fun WebViewRouteScreen(
                         contentDescription = context.getString(R.string.refresh),
                     )
                     TopBarActionButton(
-                        onClick = { webView?.url?.let(context::openUrl) },
+                        onClick = {
+                            val currentUrl = webView?.url
+                            val externalUrl = currentUrl
+                                ?.takeIf { it.startsWith("http://") || it.startsWith("https://") }
+                                ?: viewModel.baseUrl
+                            context.openUrl(externalUrl)
+                        },
                         imageVector = Icons.Default.OpenInBrowser,
                         contentDescription = context.getString(R.string.open_in_browser),
                     )
+                    TopBarActionButton(
+                        onClick = {
+                            val currentUrl = webView?.url.orEmpty()
+                                .takeIf { it.isNotBlank() }
+                                ?: viewModel.baseUrl
+                            context.sendToClip(currentUrl)
+                        },
+                        imageVector = Icons.Default.ContentCopy,
+                        contentDescription = context.getString(R.string.copy_url),
+                    )
                     TopBarActionButton(
                         onClick = ::finishWithVerification,
                         imageVector = Icons.Default.Check,
@@ -116,12 +146,10 @@ fun WebViewRouteScreen(
             .fillMaxSize()
             .padding(paddingValues)) {
             if (ready) {
-                AndroidView(
+                VisibleWebViewCompose(
                     modifier = Modifier.fillMaxSize(),
-                    factory = { viewContext ->
-                        WebView(viewContext).apply {
-                            // 透明背景,避免页面加载/跳转时白屏闪烁(与 RSS 的 VisibleWebView 一致)
-                            setBackgroundColor(0)
+                    onCreated = { createdWebView ->
+                        createdWebView.apply {
                             settings.apply {
                                 mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
                                 domStorageEnabled = true
@@ -131,18 +159,64 @@ fun WebViewRouteScreen(
                                 builtInZoomControls = true
                                 displayZoomControls = false
                                 javaScriptEnabled = true
-                                viewModel.headerMap[AppConst.UA_NAME]?.let { userAgentString = it }
+                                viewModel.headerMap[AppConst.UA_NAME]?.let {
+                                    userAgentString = it
+                                }
+                            }
+                            isClickable = true
+                            isFocusable = true
+                            isFocusableInTouchMode = true
+                            val currentWebView = this
+                            AndroidCookieManager.getInstance().apply {
+                                setAcceptCookie(true)
+                                setAcceptThirdPartyCookies(currentWebView, true)
                             }
                             webViewClient = object : WebViewClient() {
                                 override fun shouldOverrideUrlLoading(
                                     view: WebView,
                                     request: WebResourceRequest
-                                ): Boolean =
-                                    request.url.scheme !in setOf("http", "https")
+                                ): Boolean = handleUrl(request.url)
+
+                                @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
+                                override fun shouldOverrideUrlLoading(
+                                    view: WebView,
+                                    url: String,
+                                ): Boolean = handleUrl(Uri.parse(url))
+
+                                private fun handleUrl(uri: Uri): Boolean {
+                                    return when (uri.scheme) {
+                                        "http", "https" -> false
+                                        "legado", "yuedu" -> {
+                                            val importUrl = uri.getQueryParameter("src")
+                                            val isBookSourceImport = uri.path == "/bookSource" ||
+                                                    (uri.path == "/importonline" &&
+                                                            uri.host.equals("booksource", true))
+                                            if (isBookSourceImport && !importUrl.isNullOrBlank()) {
+                                                onImportBookSource(importUrl)
+                                            } else {
+                                                context.startActivity(
+                                                    Intent(
+                                                        context,
+                                                        OnLineImportActivity::class.java
+                                                    )
+                                                        .setData(uri)
+                                                )
+                                            }
+                                            true
+                                        }
+
+                                        else -> {
+                                            context.openUrl(uri)
+                                            true
+                                        }
+                                    }
+                                }
 
                                 override fun onPageFinished(view: WebView, url: String) {
+                                    pageTitle = view.title
+                                        ?.takeIf { it.isNotBlank() && it != url && it != view.url }
                                     AndroidCookieManager.getInstance().getCookie(url)
-                                        ?.let { CookieStore.setCookie(viewModel.sourceOrigin, it) }
+                                        ?.let { CookieStore.setCookie(url, it) }
                                     if (viewModel.sourceVerificationEnable) view.evaluateJavascript(
                                         "!!window._cf_chl_opt"
                                     ) {
@@ -166,17 +240,26 @@ fun WebViewRouteScreen(
                                 override fun onProgressChanged(view: WebView?, newProgress: Int) {
                                     progress = newProgress
                                 }
-
-                                override fun onShowCustomView(
-                                    view: View?,
-                                    callback: CustomViewCallback?
+                            }
+                            setOnLongClickListener {
+                                val result = hitTestResult
+                                if (result.type == WebView.HitTestResult.IMAGE_TYPE ||
+                                    result.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
                                 ) {
-                                    customView = view; customViewCallback = callback
-                                }
-
-                                override fun onHideCustomView() {
-                                    customView = null; customViewCallback = null
-                                }
+                                    imageUrlToSave = result.extra
+                                    imageUrlToSave != null
+                                } else false
+                            }
+                            setDownloadListener { url, _, contentDisposition, mimeType, _ ->
+                                val encodedName = URLUtil.guessFileName(
+                                    url,
+                                    contentDisposition,
+                                    mimeType,
+                                )
+                                val fileName = runCatching {
+                                    URLDecoder.decode(encodedName, "UTF-8")
+                                }.getOrDefault(encodedName)
+                                pendingDownload = url to fileName
                             }
                             CookieManager.applyToWebView(viewModel.baseUrl)
                             val html = viewModel.html
@@ -199,5 +282,32 @@ fun WebViewRouteScreen(
             if (progress in 0..99) AppLinearProgressIndicator(Modifier.fillMaxWidth())
         }
     }
-    DisposableEffect(Unit) { onDispose { webView?.destroy() } }
+    OptionSheet(
+        show = imageUrlToSave != null,
+        onDismissRequest = { imageUrlToSave = null },
+        title = context.getString(R.string.select_operation),
+    ) {
+        OptionCard(
+            icon = Icons.Default.Save,
+            text = context.getString(R.string.action_save),
+            onClick = {
+                val imageUrl = imageUrlToSave
+                imageUrlToSave = null
+                viewModel.saveImage(imageUrl)
+            },
+        )
+    }
+    val download = pendingDownload
+    LaunchedEffect(download) {
+        download ?: return@LaunchedEffect
+        val result = snackbarHostState.showSnackbar(
+            message = download.second,
+            actionLabel = context.getString(R.string.action_download),
+            withDismissAction = true,
+        )
+        if (result == SnackbarResult.ActionPerformed) {
+            Download.start(context, download.first, download.second)
+        }
+        if (pendingDownload == download) pendingDownload = null
+    }
 }

+ 2 - 2
app/src/main/java/io/legado/app/ui/main/MainActivity.kt

@@ -113,8 +113,8 @@ open class MainActivity : BaseComposeActivity() {
             sourceVerificationEnable, refetchAfterSuccess, html,
         )
 
-        fun createBookSourceManageIntent(context: Context) =
-            MainIntent.createBookSourceManageIntent(context)
+        fun createBookSourceManageIntent(context: Context, importSource: String? = null) =
+            MainIntent.createBookSourceManageIntent(context, importSource)
 
         fun createBookSourceEditIntent(context: Context, sourceUrl: String? = null) =
             MainIntent.createBookSourceEditIntent(context, sourceUrl)

+ 7 - 1
app/src/main/java/io/legado/app/ui/main/MainIntent.kt

@@ -22,6 +22,7 @@ object MainIntent {
     const val EXTRA_CHAPTER_CHANGED = "chapterChanged"
     const val EXTRA_EXPLORE_NAME = "exploreName"
     const val EXTRA_SOURCE_URL = "sourceUrl"
+    const val EXTRA_BOOK_SOURCE_IMPORT = "bookSourceImport"
     const val EXTRA_SOURCE_LOGIN_TYPE = "source_login_type"
     const val EXTRA_SOURCE_LOGIN_KEY = "source_login_key"
     const val EXTRA_EXPLORE_URL = "exploreUrl"
@@ -100,9 +101,14 @@ object MainIntent {
         putExtra(EXTRA_WEB_VIEW_HTML, html)
     }
 
-    fun createBookSourceManageIntent(context: Context): Intent =
+    fun createBookSourceManageIntent(
+        context: Context,
+        importSource: String? = null,
+    ): Intent =
         createLauncherIntent(context).apply {
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
             putExtra(EXTRA_START_ROUTE, MainRouteConst.ROUTE_BOOK_SOURCE_MANAGE)
+            putExtra(EXTRA_BOOK_SOURCE_IMPORT, importSource)
         }
 
     fun createBookSourceEditIntent(context: Context, sourceUrl: String? = null): Intent =

+ 19 - 7
app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt

@@ -148,8 +148,8 @@ fun MainActivity.mainEntryProvider(
         val viewModel = koinViewModel<WebViewModel>(
             key = "WebView:${route.url}:${route.sourceOrigin}:${route.sourceVerificationEnable}",
         )
-        WebViewRouteScreen(
-            intent = Intent().apply {
+        val browserIntent = remember(route) {
+            Intent().apply {
                 putExtra("title", route.title)
                 putExtra("url", route.url)
                 putExtra("sourceOrigin", route.sourceOrigin)
@@ -158,9 +158,15 @@ fun MainActivity.mainEntryProvider(
                 putExtra("sourceVerificationEnable", route.sourceVerificationEnable)
                 putExtra("refetchAfterSuccess", route.refetchAfterSuccess)
                 putExtra("html", route.html)
-            },
+            }
+        }
+        WebViewRouteScreen(
+            intent = browserIntent,
             viewModel = viewModel,
             onFinish = onNavigateBack,
+            onImportBookSource = { importUrl ->
+                onNavigateToRoute(MainRouteBookSourceManage(importUrl))
+            },
         )
     }
     entry<MainRouteSourceLogin>(
@@ -176,8 +182,11 @@ fun MainActivity.mainEntryProvider(
             onBack = onNavigateBack,
         )
     }
-    entry<MainRouteBookSourceManage> {
+    entry<MainRouteBookSourceManage> { route ->
         BookSourceRouteScreen(
+            initialImportUrl = route.importUrl,
+            closeAfterImport = route.importUrl != null,
+            onImportClosed = onNavigateBack,
             onBackClick = onNavigateBack,
             onAddSource = { onNavigateToRoute(MainRouteBookSourceEdit()) },
             onEditSource = { onNavigateToRoute(MainRouteBookSourceEdit(it)) },
@@ -332,7 +341,7 @@ fun MainActivity.mainEntryProvider(
                 onNavigateToRoute(MainRouteSourceLogin(type, sourceUrl))
             },
             onNavigateToBookSourceManage = {
-                onNavigateToRoute(MainRouteBookSourceManage)
+                onNavigateToRoute(MainRouteBookSourceManage())
             },
             onNavigateToBookSourceEdit = {
                 onNavigateToRoute(MainRouteBookSourceEdit(it))
@@ -710,7 +719,7 @@ fun MainActivity.mainEntryProvider(
                 )
             },
             onOpenSourceManage = {
-                onNavigateToRoute(MainRouteBookSourceManage)
+                onNavigateToRoute(MainRouteBookSourceManage())
             },
             sharedTransitionScope = sharedTransitionScope,
             animatedVisibilityScope = LocalNavAnimatedContentScope.current,
@@ -794,7 +803,10 @@ fun MainActivity.mainEntryProvider(
 
     entry<MainRouteRuleSub> {
         RuleSubRouteScreen(
-            onBackClick = { onNavigateBack() }
+            onBackClick = { onNavigateBack() },
+            onImportBookSource = {
+                onNavigateToRoute(MainRouteBookSourceManage(it))
+            },
         )
     }
 

+ 3 - 1
app/src/main/java/io/legado/app/ui/main/MainNavKey.kt

@@ -30,7 +30,9 @@ data class MainRouteWebView(
 ) : MainRoute
 
 @Serializable
-data object MainRouteBookSourceManage : MainRoute
+data class MainRouteBookSourceManage(
+    val importUrl: String? = null,
+) : MainRoute
 
 @Serializable
 data class MainRouteBookSourceEdit(val sourceUrl: String? = null) : MainRoute

+ 4 - 2
app/src/main/java/io/legado/app/ui/main/MainNavigator.kt

@@ -36,7 +36,7 @@ object MainNavigator {
 
             is MainRouteWebView -> backStack.add(route)
 
-            MainRouteBookSourceManage,
+            is MainRouteBookSourceManage,
             is MainRouteBookSourceEdit,
             MainRouteRssSourceManage,
             is MainRouteRssSourceEdit,
@@ -355,7 +355,9 @@ object MainNavigator {
                     )
                 } ?: MainRouteHome
 
-            MainRouteConst.ROUTE_BOOK_SOURCE_MANAGE -> MainRouteBookSourceManage
+            MainRouteConst.ROUTE_BOOK_SOURCE_MANAGE -> MainRouteBookSourceManage(
+                intent?.getStringExtra(MainIntent.EXTRA_BOOK_SOURCE_IMPORT)
+            )
             MainRouteConst.ROUTE_BOOK_SOURCE_EDIT -> MainRouteBookSourceEdit(
                 intent?.getStringExtra(MainIntent.EXTRA_SOURCE_URL)
             )

+ 6 - 8
app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubScreen.kt

@@ -22,7 +22,6 @@ import androidx.compose.material3.Icon
 import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.animateFloatingActionButton
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
@@ -36,10 +35,10 @@ import androidx.compose.ui.res.stringArrayResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.core.net.toUri
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.data.entities.RuleSub
 import io.legado.app.data.entities.RuleSubType
-import io.legado.app.ui.association.ImportBookSourceDialog
 import io.legado.app.ui.association.ImportReplaceRuleDialog
 import io.legado.app.ui.association.ImportRssSourceDialog
 import io.legado.app.ui.theme.LegadoTheme
@@ -57,12 +56,12 @@ import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.utils.showDialogFragment
 import io.legado.app.utils.toastOnUi
 import org.koin.androidx.compose.koinViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
 @Composable
 fun RuleSubRouteScreen(
     onBackClick: () -> Unit,
+    onImportBookSource: (String) -> Unit,
     viewModel: RuleSubViewModel = koinViewModel()
 ) {
     val context = LocalContext.current
@@ -74,10 +73,7 @@ fun RuleSubRouteScreen(
                 is RuleSubEffect.OpenRule -> {
                     val ruleSub = effect.rule
                     when (ruleSub.type) {
-                        RuleSubType.BOOK_SOURCE ->
-                            (context as? AppCompatActivity)?.showDialogFragment(
-                                ImportBookSourceDialog(ruleSub.url)
-                            )
+                        RuleSubType.BOOK_SOURCE -> onImportBookSource(ruleSub.url)
                         RuleSubType.RSS_SOURCE ->
                             (context as? AppCompatActivity)?.showDialogFragment(
                                 ImportRssSourceDialog(ruleSub.url)
@@ -269,7 +265,9 @@ fun RuleSubEditDialog(
                 )
 
                 Column(
-                    modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(vertical = 4.dp),
                     verticalArrangement = Arrangement.spacedBy(4.dp)
                 ) {
                     typeArray.indices.chunked(2).forEach { indices ->

+ 11 - 9
app/src/main/java/io/legado/app/ui/widget/components/importComponents/ImportComponents.kt

@@ -24,7 +24,6 @@ import androidx.compose.material.icons.filled.Info
 import androidx.compose.material.icons.filled.SelectAll
 import androidx.compose.material3.AssistChip
 import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -44,6 +43,7 @@ import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
+import io.legado.app.ui.widget.components.button.series.MediumTonalButton
 import io.legado.app.ui.widget.components.button.series.SmallPlainButton
 import io.legado.app.ui.widget.components.card.GlassCard
 import io.legado.app.ui.widget.components.card.SelectionItemCard
@@ -183,7 +183,7 @@ fun <T> BatchImportDialog(
         title = sheetTitle,
         startAction = if (isEditing) {
             {
-                SmallPlainButton(
+                MediumTonalButton(
                     onClick = { editingIndex = null },
                     icon = Icons.AutoMirrored.Filled.ArrowBack,
                     contentDescription = stringResource(R.string.back)
@@ -191,9 +191,11 @@ fun <T> BatchImportDialog(
             }
         } else {
             {
-                Row {
+                Row(
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
                     topBarActions()
-                    SmallPlainButton(
+                    MediumTonalButton(
                         onClick = { onToggleAll(!allSelected) },
                         icon = Icons.Default.SelectAll,
                         contentDescription = stringResource(if (allSelected) R.string.deselect_all else R.string.select_all)
@@ -203,7 +205,7 @@ fun <T> BatchImportDialog(
         },
         endAction = if (!isEditing && selectedCount > 0) {
             {
-                SmallPlainButton(
+                MediumTonalButton(
                     onClick = {
                         val selectedData = currentState.items.filter { it.isSelected }.map { it.data }
                         onConfirm(selectedData)
@@ -364,10 +366,10 @@ fun ImportItemRow(
                 },
                 style = LegadoTheme.typography.labelMedium,
                 color = when (status) {
-                    ImportStatus.New -> MaterialTheme.colorScheme.primary
-                    ImportStatus.Update -> MaterialTheme.colorScheme.secondary
-                    ImportStatus.Error -> MaterialTheme.colorScheme.error
-                    else -> MaterialTheme.colorScheme.outline
+                    ImportStatus.New -> LegadoTheme.colorScheme.primary
+                    ImportStatus.Update -> LegadoTheme.colorScheme.secondary
+                    ImportStatus.Error -> LegadoTheme.colorScheme.error
+                    else -> LegadoTheme.colorScheme.outline
                 },
                 modifier = Modifier.padding(end = 4.dp)
             )

+ 0 - 1
build.gradle.kts

@@ -260,7 +260,6 @@ val verifyConfigArchitecture = tasks.register<VerifyConfigArchitectureTask>(
     legacyUiDaoAccessBaseline.set(
         mapOf(
             "io/legado/app/ui/association/AddToBookshelfDialog.kt" to 5,
-            "io/legado/app/ui/association/ImportBookSourceDialog.kt" to 1,
             "io/legado/app/ui/association/ImportReplaceRuleDialog.kt" to 1,
             "io/legado/app/ui/association/ImportRssSourceDialog.kt" to 1,
             "io/legado/app/ui/book/audio/AudioPlayActivity.kt" to 1,