Просмотр исходного кода

refactor: 清理 View 体系死代码,遗留弹层迁移 Compose

- 删除无引用 Adapter/Dialog/自定义 View/死基类与配套 layout/menu 资源
- 有声书换源、书籍编辑换封面改用 ChangeSourceSheet/ChangeCoverSheet
- TextDialog 调用点改走 showTextSheet(MarkdownSheet) 宿主
- HandleFileActivity 链改用 FilePickerSheet + OpenDocumentTree,FileAssociationActivity 转 BaseComposeActivity
- BottomWebViewDialog 选目录改系统选择器
- 删除帮助文档入口(ShowMd 链)与崩溃日志启动兜底(CrashHandler 直启 CrashReportActivity)
- 同步下调 UI DAO 直连基线
HapeLee 2 месяцев назад
Родитель
Сommit
0ce6805ae8
100 измененных файлов с 207 добавлено и 8896 удалено
  1. 0 7
      app/src/main/AndroidManifest.xml
  2. 0 1
      app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
  3. 0 75
      app/src/main/java/io/legado/app/base/BaseFragment.kt
  4. 0 62
      app/src/main/java/io/legado/app/base/BaseOverlayDialogFragment.kt
  5. 0 9
      app/src/main/java/io/legado/app/base/VMBaseFragment.kt
  6. 0 2
      app/src/main/java/io/legado/app/di/appModule.kt
  7. 0 3
      app/src/main/java/io/legado/app/help/CrashHandler.kt
  8. 0 6
      app/src/main/java/io/legado/app/help/config/LocalConfig.kt
  9. 0 75
      app/src/main/java/io/legado/app/lib/prefs/fragment/PreferenceFragment.kt
  10. 0 92
      app/src/main/java/io/legado/app/ui/about/AppLogDialog.kt
  11. 0 164
      app/src/main/java/io/legado/app/ui/about/CrashLogsDialog.kt
  12. 84 36
      app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt
  13. 0 60
      app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkAdapter.kt
  14. 0 120
      app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkDecoration.kt
  15. 0 83
      app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkDialog.kt
  16. 0 146
      app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt
  17. 0 110
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt
  18. 0 55
      app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt
  19. 0 182
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceAdapter.kt
  20. 0 577
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt
  21. 0 588
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt
  22. 0 198
      app/src/main/java/io/legado/app/ui/book/group/GroupEditDialog.kt
  23. 0 174
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  24. 0 167
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  25. 0 141
      app/src/main/java/io/legado/app/ui/book/import/local/ImportBookAdapter.kt
  26. 0 131
      app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt
  27. 0 173
      app/src/main/java/io/legado/app/ui/book/import/remote/ServersDialog.kt
  28. 33 8
      app/src/main/java/io/legado/app/ui/book/info/BookInfoRouteScreen.kt
  29. 1 7
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  30. 13 10
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt
  31. 0 68
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentAdapter.kt
  32. 0 296
      app/src/main/java/io/legado/app/ui/file/FilePickerDialog.kt
  33. 0 76
      app/src/main/java/io/legado/app/ui/file/FilePickerViewModel.kt
  34. 0 307
      app/src/main/java/io/legado/app/ui/file/HandleFileActivity.kt
  35. 0 90
      app/src/main/java/io/legado/app/ui/file/HandleFileContract.kt
  36. 0 63
      app/src/main/java/io/legado/app/ui/file/HandleFileViewModel.kt
  37. 35 51
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  38. 0 1
      app/src/main/java/io/legado/app/ui/main/MainContract.kt
  39. 17 4
      app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt
  40. 0 18
      app/src/main/java/io/legado/app/ui/main/MainScreen.kt
  41. 0 3
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  42. 1 15
      app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt
  43. 0 1
      app/src/main/java/io/legado/app/ui/main/my/MyViewModel.kt
  44. 0 5
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt
  45. 0 97
      app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubAdapter.kt
  46. 0 193
      app/src/main/java/io/legado/app/ui/widget/BatteryView.kt
  47. 0 122
      app/src/main/java/io/legado/app/ui/widget/BatteryViewOrgin.kt
  48. 0 66
      app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt
  49. 0 65
      app/src/main/java/io/legado/app/ui/widget/NestedScrollableHost.kt
  50. 0 22
      app/src/main/java/io/legado/app/ui/widget/NoChildScrollNestedScrollView.kt
  51. 0 194
      app/src/main/java/io/legado/app/ui/widget/ReaderInfoBarView.kt
  52. 0 102
      app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt
  53. 0 185
      app/src/main/java/io/legado/app/ui/widget/SimpleSliderView.kt
  54. 0 152
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt
  55. 0 21
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt
  56. 0 156
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionView.kt
  57. 0 8
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/OnAnimatorListener.kt
  58. 0 80
      app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/Utils.kt
  59. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindItemState.kt
  60. 21 14
      app/src/main/java/io/legado/app/ui/widget/dialog/BottomWebViewDialog.kt
  61. 0 118
      app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt
  62. 0 81
      app/src/main/java/io/legado/app/ui/widget/dialog/TextListDialog.kt
  63. 0 51
      app/src/main/java/io/legado/app/ui/widget/dialog/UrlOptionDialog.kt
  64. 0 181
      app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardAssistsConfig.kt
  65. 0 182
      app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardToolPop.kt
  66. 0 112
      app/src/main/java/io/legado/app/ui/widget/number/NumberPickerDialog.kt
  67. 0 170
      app/src/main/java/io/legado/app/ui/widget/recycler/DividerNoLast.kt
  68. 0 34
      app/src/main/java/io/legado/app/ui/widget/recycler/HeaderAdapterDataObserver.kt
  69. 0 36
      app/src/main/java/io/legado/app/ui/widget/recycler/NoChildScrollLinearLayoutManager.kt
  70. 0 48
      app/src/main/java/io/legado/app/ui/widget/recycler/RecyclerViewAtPager2.kt
  71. 0 46
      app/src/main/java/io/legado/app/ui/widget/recycler/UpLinearLayoutManager.kt
  72. 0 31
      app/src/main/java/io/legado/app/ui/widget/text/EditEntity.kt
  73. 0 28
      app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt
  74. 0 17
      app/src/main/java/io/legado/app/ui/widget/text/PrimaryTextView.kt
  75. 0 18
      app/src/main/java/io/legado/app/ui/widget/text/SecondaryTextView.kt
  76. 0 9
      app/src/main/java/io/legado/app/utils/ActivityExtensions.kt
  77. 0 6
      app/src/main/java/io/legado/app/utils/FragmentExtensions.kt
  78. 0 150
      app/src/main/res/layout/dialog_book_change_source.xml
  79. 0 178
      app/src/main/res/layout/dialog_book_group_edit.xml
  80. 0 48
      app/src/main/res/layout/dialog_book_group_picker.xml
  81. 0 130
      app/src/main/res/layout/dialog_bookmark.xml
  82. 0 30
      app/src/main/res/layout/dialog_change_cover.xml
  83. 0 24
      app/src/main/res/layout/dialog_edit_text.xml
  84. 0 47
      app/src/main/res/layout/dialog_file_chooser.xml
  85. 0 44
      app/src/main/res/layout/dialog_multiple_edit_text.xml
  86. 0 48
      app/src/main/res/layout/dialog_number_picker.xml
  87. 0 48
      app/src/main/res/layout/dialog_text_view.xml
  88. 0 184
      app/src/main/res/layout/dialog_url_option_edit.xml
  89. 0 84
      app/src/main/res/layout/dialog_webdav_server.xml
  90. 0 22
      app/src/main/res/layout/item_app_log.xml
  91. 0 72
      app/src/main/res/layout/item_book_group_manage.xml
  92. 0 60
      app/src/main/res/layout/item_bookmark.xml
  93. 0 220
      app/src/main/res/layout/item_bookshelf_list.xml
  94. 0 31
      app/src/main/res/layout/item_cover.xml
  95. 0 123
      app/src/main/res/layout/item_download.xml
  96. 0 30
      app/src/main/res/layout/item_file_picker.xml
  97. 0 26
      app/src/main/res/layout/item_fillet_text.xml
  98. 0 74
      app/src/main/res/layout/item_find_book.xml
  99. 0 27
      app/src/main/res/layout/item_group_select.xml
  100. 0 89
      app/src/main/res/layout/item_import_book.xml

+ 0 - 7
app/src/main/AndroidManifest.xml

@@ -290,13 +290,6 @@
             android:enableOnBackInvokedCallback="true"
             android:configChanges="locale|layoutDirection|uiMode|orientation|screenSize|screenLayout"
             android:hardwareAccelerated="true" />
-        <!-- 选择文件 -->
-        <activity
-            android:name=".ui.file.HandleFileActivity"
-            android:enableOnBackInvokedCallback="true"
-            android:configChanges="locale|layoutDirection|uiMode|orientation|screenSize|screenLayout"
-            android:hardwareAccelerated="true"
-            android:theme="@style/AppTheme.Transparent" />
         <!-- 文字处理 -->
         <activity
             android:name=".receiver.SharedReceiverActivity"

+ 0 - 1
app/src/main/java/io/legado/app/base/BaseDialogFragment.kt

@@ -81,7 +81,6 @@ abstract class BaseDialogFragment(
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         if (adaptationSoftKeyboard) {
-            view.findViewById<View>(R.id.vw_bg)?.setOnClickListener(null)
             view.setOnClickListener { dismiss() }
         } else if (themeGateway.currentSettings.appTheme != "4") {
             //view.setBackgroundColor(ThemeStore.backgroundColor())

+ 0 - 75
app/src/main/java/io/legado/app/base/BaseFragment.kt

@@ -1,75 +0,0 @@
-package io.legado.app.base
-
-import android.annotation.SuppressLint
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuInflater
-import android.view.MenuItem
-import android.view.View
-import androidx.annotation.LayoutRes
-import androidx.appcompat.view.SupportMenuInflater
-import androidx.appcompat.widget.Toolbar
-import androidx.fragment.app.Fragment
-import io.legado.app.utils.applyTint
-
-@Suppress("MemberVisibilityCanBePrivate")
-abstract class BaseFragment(@LayoutRes layoutID: Int) : Fragment(layoutID) {
-
-    var supportToolbar: Toolbar? = null
-        private set
-
-    val menuInflater: MenuInflater
-        @SuppressLint("RestrictedApi")
-        get() = SupportMenuInflater(requireContext())
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        onMultiWindowModeChanged()
-        observeLiveBus()
-        onFragmentCreated(view, savedInstanceState)
-    }
-
-    abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
-
-    override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean) {
-        super.onMultiWindowModeChanged(isInMultiWindowMode)
-        onMultiWindowModeChanged()
-    }
-
-//    override fun onConfigurationChanged(newConfig: Configuration) {
-//        super.onConfigurationChanged(newConfig)
-//        onMultiWindowModeChanged()
-//    }
-
-    private fun onMultiWindowModeChanged() {
-//        (activity as? BaseActivity<*>)?.let {
-//            view?.findViewById<TitleBar>(R.id.title_bar)
-//                ?.onMultiWindowModeChanged(it.isInMultiWindow, it.fullScreen)
-//        }
-    }
-
-    fun setSupportToolbar(toolbar: Toolbar) {
-        supportToolbar = toolbar
-        supportToolbar?.let {
-            it.menu.apply {
-                onCompatCreateOptionsMenu(this)
-                applyTint(requireContext())
-            }
-
-            it.setOnMenuItemClickListener { item ->
-                onCompatOptionsItemSelected(item)
-                true
-            }
-        }
-    }
-
-    open fun observeLiveBus() {
-    }
-
-    open fun onCompatCreateOptionsMenu(menu: Menu) {
-    }
-
-    open fun onCompatOptionsItemSelected(item: MenuItem) {
-    }
-
-}

+ 0 - 62
app/src/main/java/io/legado/app/base/BaseOverlayDialogFragment.kt

@@ -1,62 +0,0 @@
-package io.legado.app.base
-
-import android.app.Dialog
-import android.content.DialogInterface
-import android.os.Bundle
-import android.view.View
-import androidx.annotation.LayoutRes
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.FragmentManager
-import androidx.lifecycle.lifecycleScope
-import io.legado.app.constant.AppLog
-import io.legado.app.help.coroutine.Coroutine
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlin.coroutines.CoroutineContext
-
-abstract class BaseOverlayDialogFragment(
-    @LayoutRes layoutID: Int
-) : DialogFragment(layoutID) {
-
-    private var onDismissListener: DialogInterface.OnDismissListener? = null
-
-    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
-        return Dialog(requireContext(), theme)
-    }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        onFragmentCreated(view, savedInstanceState)
-        observeLiveBus()
-    }
-
-    abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
-
-    open fun observeLiveBus() {
-    }
-
-    fun setOnDismissListener(onDismissListener: DialogInterface.OnDismissListener?) {
-        this.onDismissListener = onDismissListener
-    }
-
-    override fun onDismiss(dialog: DialogInterface) {
-        super.onDismiss(dialog)
-        onDismissListener?.onDismiss(dialog)
-    }
-
-    override fun show(manager: FragmentManager, tag: String?) {
-        if (isAdded) return
-        kotlin.runCatching {
-            manager.beginTransaction().remove(this).commitAllowingStateLoss()
-            super.show(manager, tag)
-        }.onFailure {
-            AppLog.put("显示对话框失败 tag:$tag", it)
-        }
-    }
-
-    fun <T> execute(
-        scope: CoroutineScope = lifecycleScope,
-        context: CoroutineContext = Dispatchers.IO,
-        block: suspend CoroutineScope.() -> T
-    ) = Coroutine.async(scope, context) { block() }
-}

+ 0 - 9
app/src/main/java/io/legado/app/base/VMBaseFragment.kt

@@ -1,9 +0,0 @@
-package io.legado.app.base
-
-import androidx.lifecycle.ViewModel
-
-abstract class VMBaseFragment<VM : ViewModel>(layoutID: Int) : BaseFragment(layoutID) {
-
-    protected abstract val viewModel: VM
-
-}

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

@@ -232,7 +232,6 @@ import io.legado.app.ui.book.bookmark.AllBookmarkViewModel
 import io.legado.app.ui.book.cache.manage.BookCacheManageViewModel
 import io.legado.app.ui.book.changecover.ChangeCoverViewModel
 import io.legado.app.ui.book.changesource.ChangeBookSourceComposeViewModel
-import io.legado.app.ui.book.changesource.ChangeBookSourceViewModel
 import io.legado.app.ui.book.changesource.ChangeChapterSourceViewModel
 import io.legado.app.ui.book.explore.ExploreShowViewModel
 import io.legado.app.ui.book.group.GroupViewModel
@@ -756,7 +755,6 @@ val appModule = module {
     }
     viewModelOf(::ChangeCoverViewModel)
     viewModelOf(::ChangeBookSourceComposeViewModel)
-    viewModelOf(::ChangeBookSourceViewModel)
     viewModelOf(::ChangeChapterSourceViewModel)
     viewModelOf(::ExploreViewModel)
     viewModelOf(::RssViewModel)

+ 0 - 3
app/src/main/java/io/legado/app/help/CrashHandler.kt

@@ -14,7 +14,6 @@ import io.legado.app.constant.AppLog
 import io.legado.app.domain.gateway.BackupSettingsGateway
 import io.legado.app.domain.gateway.OtherSettingsGateway
 import io.legado.app.exception.NoStackTraceException
-import io.legado.app.help.config.LocalConfig
 import io.legado.app.model.ReadAloud
 import io.legado.app.utils.FileDoc
 import io.legado.app.utils.FileUtils
@@ -84,7 +83,6 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
      */
     private fun handleException(ex: Throwable?): Boolean {
         if (ex == null) return false
-        LocalConfig.appCrash = true
         //保存日志文件
         val crashFileName = saveCrashInfo2File(ex)
         if ((ex is OutOfMemoryError || ex.cause is OutOfMemoryError) &&
@@ -93,7 +91,6 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
             doHeapDump()
         }
         return if (startCrashReport(crashFileName)) {
-            LocalConfig.appCrash = false
             true
         } else {
             context.longToastOnUiLegacy(ex.stackTraceStr)

+ 0 - 6
app/src/main/java/io/legado/app/help/config/LocalConfig.kt

@@ -153,10 +153,4 @@ by appCtx.getSharedPreferences("local", Context.MODE_PRIVATE) {
             putBoolean("deleteBookOriginal", value)
         }
 
-    var appCrash: Boolean
-        get() = getBoolean("appCrash")
-        set(value) {
-            putBoolean("appCrash", value)
-        }
-
 }

+ 0 - 75
app/src/main/java/io/legado/app/lib/prefs/fragment/PreferenceFragment.kt

@@ -1,75 +0,0 @@
-package io.legado.app.lib.prefs.fragment
-
-import android.annotation.SuppressLint
-import android.os.Bundle
-import android.view.View
-import androidx.fragment.app.DialogFragment
-import androidx.preference.EditTextPreference
-import androidx.preference.ListPreference
-import androidx.preference.MultiSelectListPreference
-import androidx.preference.Preference
-import androidx.preference.PreferenceFragmentCompat
-import io.legado.app.lib.prefs.EditTextPreferenceDialog
-import io.legado.app.lib.prefs.ListPreferenceDialog
-import io.legado.app.lib.prefs.MultiSelectListPreferenceDialog
-import io.legado.app.utils.applyNavigationBarPadding
-
-abstract class PreferenceFragment : PreferenceFragmentCompat() {
-
-    private val dialogFragmentTag = "androidx.preference.PreferenceFragment.DIALOG"
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        listView.clipToPadding = false
-        listView.applyNavigationBarPadding()
-    }
-
-    @SuppressLint("RestrictedApi")
-    override fun onDisplayPreferenceDialog(preference: Preference) {
-
-        var handled = false
-        if (callbackFragment is OnPreferenceDisplayDialogCallback) {
-            handled =
-                (callbackFragment as OnPreferenceDisplayDialogCallback)
-                    .onPreferenceDisplayDialog(this, preference)
-        }
-        if (!handled && activity is OnPreferenceDisplayDialogCallback) {
-            handled = (activity as OnPreferenceDisplayDialogCallback)
-                .onPreferenceDisplayDialog(this, preference)
-        }
-
-        if (handled) {
-            return
-        }
-
-        // check if dialog is already showing
-        if (parentFragmentManager.findFragmentByTag(dialogFragmentTag) != null) {
-            return
-        }
-
-        val dialogFragment: DialogFragment = when (preference) {
-            is EditTextPreference -> {
-                EditTextPreferenceDialog.newInstance(preference.getKey())
-            }
-            is ListPreference -> {
-                ListPreferenceDialog.newInstance(preference.getKey())
-            }
-            is MultiSelectListPreference -> {
-                MultiSelectListPreferenceDialog.newInstance(preference.getKey())
-            }
-            else -> {
-                throw IllegalArgumentException(
-                    "Cannot display dialog for an unknown Preference type: "
-                            + preference.javaClass.simpleName
-                            + ". Make sure to implement onPreferenceDisplayDialog() to handle "
-                            + "displaying a custom dialog for this Preference."
-                )
-            }
-        }
-        @Suppress("DEPRECATION")
-        dialogFragment.setTargetFragment(this, 0)
-
-        dialogFragment.show(parentFragmentManager, dialogFragmentTag)
-    }
-
-}

+ 0 - 92
app/src/main/java/io/legado/app/ui/about/AppLogDialog.kt

@@ -1,92 +0,0 @@
-package io.legado.app.ui.about
-
-import android.content.Context
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import androidx.recyclerview.widget.LinearLayoutManager
-import io.legado.app.R
-import io.legado.app.base.BaseDialogFragment
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.constant.AppLog
-import io.legado.app.databinding.DialogRecyclerViewBinding
-import io.legado.app.databinding.ItemAppLogBinding
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.widget.dialog.TextDialog
-import io.legado.app.utils.LogUtils
-import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import splitties.views.onClick
-import java.util.*
-
-class AppLogDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
-    Toolbar.OnMenuItemClickListener {
-
-    private val binding by viewBinding(DialogRecyclerViewBinding::bind)
-    private val adapter by lazy {
-        LogAdapter(requireContext())
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        binding.run {
-            //toolBar.setBackgroundColor(primaryColor)
-            toolBar.setTitle(R.string.log)
-            toolBar.inflateMenu(R.menu.app_log)
-            toolBar.setOnMenuItemClickListener(this@AppLogDialog)
-            recyclerView.layoutManager = LinearLayoutManager(requireContext())
-            recyclerView.adapter = adapter
-        }
-        adapter.setItems(AppLog.logs)
-        updateEmptyView()
-    }
-
-    private fun updateEmptyView() {
-        binding.emptyView.visibility =
-            if (adapter.itemCount == 0) View.VISIBLE else View.GONE
-    }
-
-
-    override fun onMenuItemClick(item: MenuItem?): Boolean {
-        when (item?.itemId) {
-            R.id.menu_clear -> {
-                AppLog.clear()
-                adapter.clearItems()
-                updateEmptyView()
-            }
-        }
-        return true
-    }
-
-    inner class LogAdapter(context: Context) :
-        RecyclerAdapter<Triple<Long, String, Throwable?>, ItemAppLogBinding>(context) {
-
-        override fun getViewBinding(parent: ViewGroup): ItemAppLogBinding {
-            return ItemAppLogBinding.inflate(inflater, parent, false)
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemAppLogBinding,
-            item: Triple<Long, String, Throwable?>,
-            payloads: MutableList<Any>
-        ) {
-            binding.textTime.text = LogUtils.logTimeFormat.format(Date(item.first))
-            binding.textMessage.text = item.second
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemAppLogBinding) {
-            binding.root.onClick {
-                getItem(holder.layoutPosition)?.let { item ->
-                    item.third?.let {
-                        showDialogFragment(TextDialog("Log", it.stackTraceToString()))
-                    }
-                }
-            }
-        }
-
-    }
-
-}

+ 0 - 164
app/src/main/java/io/legado/app/ui/about/CrashLogsDialog.kt

@@ -1,164 +0,0 @@
-package io.legado.app.ui.about
-
-import android.app.Application
-import android.net.Uri
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.MutableLiveData
-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.BaseViewModel
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.databinding.DialogRecyclerViewBinding
-import io.legado.app.databinding.Item1lineTextBinding
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.widget.dialog.TextDialog
-import io.legado.app.utils.FileDoc
-import io.legado.app.utils.FileUtils
-import io.legado.app.utils.delete
-import io.legado.app.utils.find
-import io.legado.app.utils.getFile
-import io.legado.app.utils.list
-import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import kotlinx.coroutines.isActive
-import java.io.FileFilter
-
-class CrashLogsDialog : BaseBottomSheetDialogFragment(R.layout.dialog_recycler_view),
-    Toolbar.OnMenuItemClickListener {
-
-    private val binding by viewBinding(DialogRecyclerViewBinding::bind)
-    private val viewModel by viewModels<CrashViewModel>()
-    private val adapter by lazy { LogAdapter() }
-
-    override fun onStart() {
-        super.onStart()
-
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        binding.toolBar.setTitle(R.string.crash_log)
-        binding.toolBar.inflateMenu(R.menu.crash_log)
-        binding.toolBar.setOnMenuItemClickListener(this)
-        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
-        binding.recyclerView.adapter = adapter
-        viewModel.logLiveData.observe(viewLifecycleOwner) {
-            adapter.setItems(it)
-        }
-        viewModel.initData()
-    }
-
-    override fun onMenuItemClick(item: MenuItem): Boolean {
-        when (item.itemId) {
-            R.id.menu_clear -> viewModel.clearCrashLog()
-        }
-        return true
-    }
-
-    private fun showLogFile(fileDoc: FileDoc) {
-        viewModel.readFile(fileDoc) {
-            if (lifecycleScope.isActive) {
-                showDialogFragment(TextDialog(fileDoc.name, it))
-            }
-        }
-
-    }
-
-    inner class LogAdapter : RecyclerAdapter<FileDoc, Item1lineTextBinding>(requireContext()) {
-
-        override fun getViewBinding(parent: ViewGroup): Item1lineTextBinding {
-            return Item1lineTextBinding.inflate(inflater, parent, false)
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextBinding) {
-            binding.root.setOnClickListener {
-                getItemByLayoutPosition(holder.layoutPosition)?.let { item ->
-                    showLogFile(item)
-                }
-            }
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: Item1lineTextBinding,
-            item: FileDoc,
-            payloads: MutableList<Any>
-        ) {
-            binding.textView.text = item.name
-        }
-
-    }
-
-    class CrashViewModel(application: Application) : BaseViewModel(application) {
-
-        val logLiveData = MutableLiveData<List<FileDoc>>()
-
-        fun initData() {
-            execute {
-                val list = arrayListOf<FileDoc>()
-                context.externalCacheDir
-                    ?.getFile("crash")
-                    ?.listFiles(FileFilter { it.isFile })
-                    ?.forEach {
-                        list.add(FileDoc.fromFile(it))
-                    }
-                val backupPath = org.koin.core.context.GlobalContext.get().get<io.legado.app.domain.gateway.BackupSettingsGateway>().currentSettings.backupPath
-                if (!backupPath.isNullOrEmpty()) {
-                    val uri = Uri.parse(backupPath)
-                    FileDoc.fromUri(uri, true)
-                        .find("crash")
-                        ?.list {
-                            !it.isDir
-                        }?.let {
-                            list.addAll(it)
-                        }
-                }
-                return@execute list.sortedByDescending { it.name }.distinctBy { it.name }
-            }.onSuccess {
-                logLiveData.postValue(it)
-            }
-        }
-
-        fun readFile(fileDoc: FileDoc, success: (String) -> Unit) {
-            execute {
-                String(fileDoc.readBytes())
-            }.onSuccess {
-                success.invoke(it)
-            }.onError {
-                context.toastOnUi(it.localizedMessage)
-            }
-        }
-
-        fun clearCrashLog() {
-            execute {
-                context.externalCacheDir
-                    ?.getFile("crash")
-                    ?.let {
-                        FileUtils.delete(it, false)
-                    }
-                val backupPath = org.koin.core.context.GlobalContext.get().get<io.legado.app.domain.gateway.BackupSettingsGateway>().currentSettings.backupPath
-                if (!backupPath.isNullOrEmpty()) {
-                    val uri = Uri.parse(backupPath)
-                    FileDoc.fromUri(uri, true)
-                        .find("crash")
-                        ?.delete()
-                }
-            }.onError {
-                context.toastOnUi(it.localizedMessage)
-            }.onFinally {
-                initData()
-            }
-        }
-
-    }
-
-}

+ 84 - 36
app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt

@@ -1,37 +1,48 @@
 package io.legado.app.ui.association
 
+import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.activity.viewModels
+import androidx.compose.ui.res.stringResource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
 import androidx.core.os.postDelayed
 import androidx.documentfile.provider.DocumentFile
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import io.legado.app.R
-import io.legado.app.base.VMBaseActivity
+import io.legado.app.base.BaseComposeActivity
 import io.legado.app.constant.AppLog
-import io.legado.app.databinding.ActivityTranslucenceBinding
 import io.legado.app.domain.gateway.OtherSettingsGateway
 import io.legado.app.exception.InvalidBooksDirException
 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.ui.widget.components.filePicker.FilePickerSheet
+import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
 import io.legado.app.utils.FileUtils
+import io.legado.app.utils.RealPathUtil
 import io.legado.app.utils.buildMainHandler
 import io.legado.app.utils.canRead
 import io.legado.app.utils.checkWrite
+import io.legado.app.utils.externalFiles
 import io.legado.app.utils.getFile
-import io.legado.app.utils.gone
 import io.legado.app.utils.isContentScheme
 import io.legado.app.utils.readUri
 import io.legado.app.utils.showDialogFragment
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivityForBook
+import io.legado.app.utils.takePersistablePermissionSafely
 import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
 import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import org.koin.android.ext.android.inject
@@ -39,31 +50,43 @@ import splitties.init.appCtx
 import java.io.File
 import java.io.FileOutputStream
 
-class FileAssociationActivity :
-    VMBaseActivity<ActivityTranslucenceBinding, FileAssociationViewModel>() {
+class FileAssociationActivity : BaseComposeActivity(transparent = true) {
 
     private val otherSettingsGateway by inject<OtherSettingsGateway>()
 
-    private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {
-        intent.data?.let { uri ->
-            it.uri?.let { treeUri ->
-                lifecycleScope.launch {
-                    otherSettingsGateway.update {
-                        it.copy(defaultBookTreeUri = treeUri.toString())
-                    }
+    private val viewModel by viewModels<FileAssociationViewModel>()
+
+    private val loadingFlow = MutableStateFlow(true)
+    private val showSelectDirFlow = MutableStateFlow(false)
+
+    /** 待导入的书籍文件,等待用户选择保存目录 */
+    private var pendingImportUri: Uri? = null
+
+    private val openTreeLauncher =
+        registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { treeUri ->
+            val bookUri = pendingImportUri
+            if (bookUri == null) {
+                finish()
+                return@registerForActivityResult
+            }
+            if (treeUri == null ||
+                RealPathUtil.getTreePath(treeUri)?.startsWith(appCtx.externalFiles.parent!!) == true
+            ) {
+                // 取消选择或选到了应用私有目录:提示说明后直接按原方式导入
+                toastStorageHelp()
+                importBook(null, bookUri)
+                return@registerForActivityResult
+            }
+            if (treeUri.isContentScheme()) {
+                treeUri.takePersistablePermissionSafely(this)
+            }
+            lifecycleScope.launch {
+                otherSettingsGateway.update {
+                    it.copy(defaultBookTreeUri = treeUri.toString())
                 }
-                importBook(treeUri, uri)
-            } ?: let {
-                val storageHelp = String(assets.open("storageHelp.md").readBytes())
-                toastOnUi(storageHelp)
-                importBook(null, uri)
             }
+            importBook(treeUri, bookUri)
         }
-    }
-
-    override val binding by viewBinding(ActivityTranslucenceBinding::inflate)
-
-    override val viewModel by viewModels<FileAssociationViewModel>()
 
     private val handler by lazy {
         buildMainHandler()
@@ -71,7 +94,6 @@ class FileAssociationActivity :
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        binding.rotateLoading.visible()
         viewModel.importBookLiveData.observe(this) { uri ->
             importBook(uri)
         }
@@ -96,19 +118,19 @@ class FileAssociationActivity :
             }
         }
         viewModel.errorLive.observe(this) {
-            binding.rotateLoading.gone()
+            loadingFlow.value = false
             toastOnUi(it)
             handler.postDelayed(2000) {
                 finish()
             }
         }
         viewModel.openBookLiveData.observe(this) {
-            binding.rotateLoading.gone()
+            loadingFlow.value = false
             startActivityForBook(it)
             finish()
         }
         viewModel.notSupportedLiveData.observe(this) { data ->
-            binding.rotateLoading.gone()
+            loadingFlow.value = false
             alert(
                 title = appCtx.getString(R.string.draw),
                 message = appCtx.getString(R.string.file_not_supported, data.second)
@@ -143,14 +165,40 @@ class FileAssociationActivity :
         } ?: finish()
     }
 
+    @Composable
+    override fun Content() {
+        val loading by loadingFlow.collectAsStateWithLifecycle()
+        val showSelectDir by showSelectDirFlow.collectAsStateWithLifecycle()
+        if (loading) {
+            Box(
+                modifier = Modifier.fillMaxSize(),
+                contentAlignment = Alignment.Center,
+            ) {
+                AppCircularProgressIndicator()
+            }
+        }
+        FilePickerSheet(
+            show = showSelectDir,
+            onDismissRequest = { showSelectDirFlow.value = false },
+            title = stringResource(R.string.select_book_folder),
+            onSelectSysDir = {
+                showSelectDirFlow.value = false
+                openTreeLauncher.launch(null)
+            },
+        )
+    }
+
+    private fun toastStorageHelp() {
+        val storageHelp = String(assets.open("storageHelp.md").readBytes())
+        toastOnUi(storageHelp)
+    }
+
     private fun importBook(uri: Uri) {
         if (uri.isContentScheme()) {
             val treeUriStr = otherSettingsGateway.currentSettings.defaultBookTreeUri
             if (treeUriStr.isNullOrEmpty()) {
-                localBookTreeSelect.launch {
-                    title = getString(R.string.select_book_folder)
-                    mode = HandleFileContract.DIR_SYS
-                }
+                pendingImportUri = uri
+                showSelectDirFlow.value = true
             } else {
                 importBook(Uri.parse(treeUriStr), uri)
             }
@@ -212,9 +260,9 @@ class FileAssociationActivity :
                 }
             }.onFailure {
                 when (it) {
-                    is InvalidBooksDirException -> localBookTreeSelect.launch {
-                        title = getString(R.string.select_book_folder)
-                        mode = HandleFileContract.DIR_SYS
+                    is InvalidBooksDirException -> {
+                        pendingImportUri = uri
+                        showSelectDirFlow.value = true
                     }
 
                     else -> {

+ 0 - 60
app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkAdapter.kt

@@ -1,60 +0,0 @@
-package io.legado.app.ui.book.bookmark
-
-import android.content.Context
-import android.view.ViewGroup
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.data.entities.Bookmark
-import io.legado.app.databinding.ItemBookmarkBinding
-import io.legado.app.utils.gone
-import splitties.views.onClick
-
-class BookmarkAdapter(context: Context, val callback: Callback) :
-    RecyclerAdapter<Bookmark, ItemBookmarkBinding>(context) {
-
-    override fun getViewBinding(parent: ViewGroup): ItemBookmarkBinding {
-        return ItemBookmarkBinding.inflate(inflater, parent, false)
-    }
-
-    override fun convert(
-        holder: ItemViewHolder,
-        binding: ItemBookmarkBinding,
-        item: Bookmark,
-        payloads: MutableList<Any>
-    ) {
-        binding.tvChapterName.text = item.chapterName
-        binding.tvBookText.gone(item.bookText.isEmpty())
-        binding.tvBookText.text = item.bookText
-        binding.tvContent.gone(item.content.isEmpty())
-        binding.tvContent.text = item.content
-    }
-
-    override fun registerListener(holder: ItemViewHolder, binding: ItemBookmarkBinding) {
-        binding.root.onClick {
-            getItemByLayoutPosition(holder.layoutPosition)?.let {
-                callback.onItemClick(it, holder.layoutPosition)
-            }
-        }
-    }
-
-    fun getHeaderText(position: Int): String {
-        return with(getItem(position)) {
-            "${this?.bookName ?: ""}(${this?.bookAuthor ?: ""})"
-        }
-    }
-
-    fun isItemHeader(position: Int): Boolean {
-        if (position == 0) return true
-        val lastItem = getItem(position - 1)
-        val curItem = getItem(position)
-        return !(lastItem?.bookName == curItem?.bookName
-                && lastItem?.bookAuthor == curItem?.bookAuthor)
-    }
-
-    interface Callback {
-
-        fun onItemClick(bookmark: Bookmark, position: Int)
-
-    }
-
-}

+ 0 - 120
app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkDecoration.kt

@@ -1,120 +0,0 @@
-package io.legado.app.ui.book.bookmark
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Rect
-import android.graphics.Typeface
-import android.text.TextPaint
-import android.view.View
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.color.MaterialColors
-//import io.legado.app.lib.theme.accentColor
-//import io.legado.app.lib.theme.backgroundColor
-import io.legado.app.utils.dpToPx
-import io.legado.app.utils.spToPx
-import kotlin.math.min
-
-class BookmarkDecoration(
-    private val context: Context,
-    val adapter: BookmarkAdapter) : RecyclerView.ItemDecoration() {
-
-    private val headerLeft = 16f.dpToPx()
-    private val headerHeight = 32f.dpToPx()
-
-    private val headerPaint = Paint().apply {
-        color = MaterialColors.getColor(context, com.google.android.material.R.attr.colorSurfaceContainer, Color.GRAY)
-    }
-
-    private val textPaint = TextPaint().apply {
-        textSize = 14f.spToPx()
-        color =
-            MaterialColors.getColor(context, androidx.appcompat.R.attr.colorPrimary, Color.BLACK)
-        isAntiAlias = true
-        typeface = Typeface.DEFAULT_BOLD
-    }
-
-    private val textRect = Rect()
-
-    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
-        val count = parent.childCount
-        for (i in 0 until count) {
-            val view = parent.getChildAt(i)
-            val position = parent.getChildLayoutPosition(view)
-            val isHeader = adapter.isItemHeader(position)
-            if (isHeader) {
-                c.drawRect(
-                    0f,
-                    view.top - headerHeight,
-                    parent.width.toFloat(),
-                    view.top.toFloat(),
-                    headerPaint
-                )
-                val headerText = adapter.getHeaderText(position)
-                textPaint.getTextBounds(headerText, 0, headerText.length, textRect)
-                c.drawText(
-                    headerText,
-                    headerLeft,
-                    (view.top - headerHeight) + headerHeight / 2 + textRect.height() / 2,
-                    textPaint
-                )
-            }
-        }
-    }
-
-    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
-        val position = (parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
-        val view = parent.findViewHolderForAdapterPosition(position)?.itemView ?: return
-        val isHeader = adapter.isItemHeader(position + 1)
-        val headerText = adapter.getHeaderText(position)
-        if (isHeader) {
-            val bottom = min(headerHeight.toInt(), view.bottom)
-            c.drawRect(
-                0f,
-                view.top - headerHeight,
-                parent.width.toFloat(),
-                bottom.toFloat(),
-                headerPaint
-            )
-            textPaint.getTextBounds(headerText, 0, headerText.length, textRect)
-            c.drawText(
-                headerText,
-                headerLeft,
-                headerHeight / 2 + textRect.height() / 2 - (headerHeight - bottom),
-                textPaint
-            )
-        } else {
-            c.drawRect(
-                0f,
-                0f,
-                parent.width.toFloat(),
-                headerHeight,
-                headerPaint
-            )
-            textPaint.getTextBounds(headerText, 0, headerText.length, textRect)
-            c.drawText(
-                headerText,
-                headerLeft,
-                headerHeight / 2 + textRect.height() / 2,
-                textPaint
-            )
-        }
-        c.save()
-    }
-
-    override fun getItemOffsets(
-        outRect: Rect,
-        view: View,
-        parent: RecyclerView,
-        state: RecyclerView.State
-    ) {
-        val position = parent.getChildLayoutPosition(view)
-        val isHeader = adapter.isItemHeader(position)
-        if (isHeader) {
-            outRect.top = headerHeight.toInt()
-        }
-    }
-
-}

+ 0 - 83
app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkDialog.kt

@@ -1,83 +0,0 @@
-package io.legado.app.ui.book.bookmark
-
-import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup
-import androidx.lifecycle.lifecycleScope
-import io.legado.app.R
-import io.legado.app.base.BaseBottomSheetDialogFragment
-import io.legado.app.data.appDb
-import io.legado.app.data.entities.Bookmark
-import io.legado.app.databinding.DialogBookmarkBinding
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.utils.setLayout
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-class BookmarkDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_bookmark) {
-
-    constructor(bookmark: Bookmark, editPos: Int = -1) : this() {
-        arguments = Bundle().apply {
-            putInt("editPos", editPos)
-            putParcelable("bookmark", bookmark)
-        }
-    }
-
-    private val binding by viewBinding(DialogBookmarkBinding::bind)
-
-    override fun onStart() {
-        super.onStart()
-        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        val arguments = arguments ?: let {
-            dismiss()
-            return
-        }
-
-        @Suppress("DEPRECATION")
-        val bookmark = arguments.getParcelable<Bookmark>("bookmark")
-        bookmark ?: let {
-            dismiss()
-            return
-        }
-        val editPos = arguments.getInt("editPos", -1)
-        binding.btnDelete.visible(editPos >= 0)
-
-        binding.run {
-            tvChapterName.text = bookmark.chapterName
-            editBookText.setText(bookmark.bookText)
-            editContent.setText(bookmark.content)
-
-            btnCancel.setOnClickListener {
-                dismiss()
-            }
-
-            btnOk.setOnClickListener {
-                bookmark.bookText = editBookText.text?.toString() ?: ""
-                bookmark.content = editContent.text?.toString() ?: ""
-                lifecycleScope.launch {
-                    withContext(IO) {
-                        appDb.bookmarkDao.insert(bookmark)
-                    }
-                    dismiss()
-                }
-            }
-
-            btnDelete.setOnClickListener {
-                lifecycleScope.launch {
-                    withContext(IO) {
-                        appDb.bookmarkDao.delete(bookmark)
-                    }
-                    dismiss()
-                }
-            }
-        }
-    }
-
-}

+ 0 - 146
app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt

@@ -1,146 +0,0 @@
-package io.legado.app.ui.book.cache
-
-import android.content.Context
-import android.view.ViewGroup
-import android.widget.ProgressBar
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.recyclerview.widget.DiffUtil
-import com.google.android.material.button.MaterialButton
-import io.legado.app.R
-import io.legado.app.base.adapter.DiffRecyclerAdapter
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.data.entities.Book
-import io.legado.app.databinding.ItemDownloadBinding
-import io.legado.app.help.book.isLocal
-import io.legado.app.utils.gone
-import io.legado.app.utils.visible
-
-class CacheAdapter(context: Context, private val callBack: CallBack) :
-    DiffRecyclerAdapter<Book, ItemDownloadBinding>(context) {
-
-    override val diffItemCallback: DiffUtil.ItemCallback<Book>
-        get() = object : DiffUtil.ItemCallback<Book>() {
-            override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
-                return oldItem.bookUrl == newItem.bookUrl
-            }
-
-            override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {
-                return oldItem.name == newItem.name
-                        && oldItem.author == newItem.author
-            }
-
-        }
-
-    override fun getViewBinding(parent: ViewGroup): ItemDownloadBinding {
-        return ItemDownloadBinding.inflate(inflater, parent, false)
-    }
-
-    override fun convert(
-        holder: ItemViewHolder,
-        binding: ItemDownloadBinding,
-        item: Book,
-        payloads: MutableList<Any>
-    ) {
-        binding.run {
-            progressDownload.gone()
-            progressExport.gone()
-            tvMsg.gone()
-            tvDownload.text = ""
-            ivDownload.icon = null
-
-            if (payloads.isEmpty()) {
-                tvName.text = item.name
-                tvAuthor.text = context.getString(R.string.author_show, item.getRealAuthor())
-
-                if (item.isLocal) {
-                    tvDownload.setText(R.string.local_book)
-                } else {
-                    val cs = callBack.cacheChapters[item.bookUrl]
-                    tvDownload.text = if (cs == null) {
-                        context.getString(R.string.loading)
-                    } else {
-                        context.getString(R.string.download_count, cs.size, item.totalChapterNum)
-                    }
-                }
-            } else {
-                if (item.isLocal) {
-                    tvDownload.setText(R.string.local_book)
-                } else {
-                    val cacheSize = callBack.cacheChapters[item.bookUrl]?.size ?: 0
-                    tvDownload.text = context.getString(R.string.download_count, cacheSize, item.totalChapterNum)
-                    if (callBack.isBookDownloading(item.bookUrl)) {
-                        val progress = if (item.totalChapterNum > 0) cacheSize * 100 / item.totalChapterNum else 0
-                        progressDownload.progress = progress
-                        progressDownload.visible()
-                    }
-                }
-            }
-            upDownloadIv(ivDownload, item)
-            upExportInfo(tvMsg, progressExport, item)
-        }
-    }
-
-
-    override fun registerListener(holder: ItemViewHolder, binding: ItemDownloadBinding) {
-        binding.run {
-            ivDownload.setOnClickListener {
-                getItem(holder.layoutPosition)?.let { book ->
-                    callBack.toggleBookDownload(book)
-                }
-            }
-            tvExport.setOnClickListener {
-                callBack.export(holder.layoutPosition)
-            }
-            ivDelete.setOnClickListener {
-                getItem(holder.layoutPosition)?.let { book ->
-                    callBack.deleteDownload(book)
-                }
-            }
-
-        }
-    }
-
-    private fun upDownloadIv(button: MaterialButton, book: Book) {
-        if (book.isLocal) {
-            button.gone()
-        } else {
-            button.visible()
-            val iconRes = if (callBack.isBookDownloading(book.bookUrl)) {
-                R.drawable.ic_stop_black_24dp
-            } else {
-                R.drawable.ic_play
-            }
-            button.icon = ContextCompat.getDrawable(context, iconRes)
-        }
-    }
-
-    private fun upExportInfo(msgView: TextView, progressView: ProgressBar, book: Book) {
-        msgView.gone()
-        progressView.gone()
-
-        val msg = callBack.exportMsg(book.bookUrl)
-        if (msg != null) {
-            msgView.text = msg
-            msgView.visible()
-            return
-        }
-        val progress = callBack.exportProgress(book.bookUrl)
-        if (progress != null) {
-            progressView.max = book.totalChapterNum
-            progressView.progress = progress
-            progressView.visible()
-        }
-    }
-
-    interface CallBack {
-        val cacheChapters: Map<String, Set<String>>
-        fun isBookDownloading(bookUrl: String): Boolean
-        fun toggleBookDownload(book: Book)
-        fun export(position: Int)
-        fun exportProgress(bookUrl: String): Int?
-        fun exportMsg(bookUrl: String): String?
-        fun deleteDownload(book: Book)
-    }
-
-}

+ 0 - 110
app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt

@@ -1,110 +0,0 @@
-package io.legado.app.ui.book.changecover
-
-//import io.legado.app.lib.theme.primaryColor
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import androidx.appcompat.widget.Toolbar
-import androidx.core.view.isVisible
-import org.koin.androidx.viewmodel.ext.android.viewModel
-import androidx.lifecycle.Lifecycle.State.STARTED
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.GridLayoutManager
-import io.legado.app.R
-import io.legado.app.base.BaseBottomSheetDialogFragment
-import io.legado.app.databinding.DialogChangeCoverBinding
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-
-/**
- * 换封面
- */
-class ChangeCoverDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_change_cover),
-    Toolbar.OnMenuItemClickListener,
-    CoverAdapter.CallBack {
-
-    constructor(name: String, author: String) : this() {
-        arguments = Bundle().apply {
-            putString("name", name)
-            putString("author", author)
-        }
-    }
-
-    private val binding by viewBinding(DialogChangeCoverBinding::bind)
-    private val callBack: CallBack? get() = activity as? CallBack
-    private val viewModel: ChangeCoverViewModel by viewModel()
-    private val adapter by lazy { CoverAdapter(requireContext(), this) }
-
-    private val startStopMenuItem: MenuItem?
-        get() = binding.toolBar.menu.findItem(R.id.menu_start_stop)
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        binding.toolBar.setTitle(R.string.change_cover_source)
-        viewModel.initData(arguments)
-        initMenu()
-        initView()
-        initData()
-    }
-
-    private fun initMenu() {
-        binding.toolBar.inflateMenu(R.menu.change_cover)
-        //binding.toolBar.menu.applyTint(requireContext())
-        binding.toolBar.setOnMenuItemClickListener(this)
-    }
-
-    private fun initView() {
-        binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 3)
-        binding.recyclerView.adapter = adapter
-    }
-
-    private fun initData() {
-        lifecycleScope.launch {
-            lifecycle.currentStateFlow.first { it.isAtLeast(STARTED) }
-            viewModel.dataFlow.conflate().collect {
-                adapter.setItems(it)
-                delay(1000)
-            }
-        }
-    }
-
-    override fun observeLiveBus() {
-        super.observeLiveBus()
-        viewModel.searchStateData.observe(viewLifecycleOwner) {
-            binding.refreshProgressBar.isVisible = it
-            if (it) {
-                startStopMenuItem?.let { item ->
-                    item.setIcon(R.drawable.ic_stop_black_24dp)
-                    item.setTitle(R.string.stop)
-                }
-            } else {
-                startStopMenuItem?.let { item ->
-                    item.setIcon(R.drawable.ic_refresh)
-                    item.setTitle(R.string.refresh)
-                }
-            }
-            //binding.toolBar.menu.applyTint(requireContext())
-        }
-
-    }
-
-    override fun onMenuItemClick(item: MenuItem?): Boolean {
-        when (item?.itemId) {
-            R.id.menu_start_stop -> viewModel.startOrStopSearch()
-        }
-        return false
-    }
-
-    override fun changeTo(coverUrl: String) {
-        callBack?.coverChangeTo(coverUrl)
-        dismissAllowingStateLoss()
-    }
-
-    interface CallBack {
-        fun coverChangeTo(coverUrl: String)
-    }
-}

+ 0 - 55
app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt

@@ -1,55 +0,0 @@
-package io.legado.app.ui.book.changecover
-
-import android.content.Context
-import android.view.ViewGroup
-import androidx.recyclerview.widget.DiffUtil
-import io.legado.app.base.adapter.DiffRecyclerAdapter
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.data.entities.SearchBook
-import io.legado.app.databinding.ItemCoverBinding
-
-
-class CoverAdapter(context: Context, val callBack: CallBack) :
-    DiffRecyclerAdapter<SearchBook, ItemCoverBinding>(context) {
-
-    override val diffItemCallback: DiffUtil.ItemCallback<SearchBook>
-        get() = object : DiffUtil.ItemCallback<SearchBook>() {
-            override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
-                return oldItem.bookUrl == newItem.bookUrl
-            }
-
-            override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
-                return oldItem.originName == newItem.originName
-                        && oldItem.coverUrl == newItem.coverUrl
-            }
-
-        }
-
-    override fun getViewBinding(parent: ViewGroup): ItemCoverBinding {
-        return ItemCoverBinding.inflate(inflater, parent, false)
-    }
-
-    override fun convert(
-        holder: ItemViewHolder,
-        binding: ItemCoverBinding,
-        item: SearchBook,
-        payloads: MutableList<Any>
-    ) = binding.run {
-        ivCover.load(item.coverUrl, item.name, item.author, false, item.origin)
-        tvSource.text = item.originName
-    }
-
-    override fun registerListener(holder: ItemViewHolder, binding: ItemCoverBinding) {
-        holder.itemView.apply {
-            setOnClickListener {
-                getItem(holder.layoutPosition)?.let {
-                    callBack.changeTo(it.coverUrl ?: "")
-                }
-            }
-        }
-    }
-
-    interface CallBack {
-        fun changeTo(coverUrl: String)
-    }
-}

+ 0 - 182
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceAdapter.kt

@@ -1,182 +0,0 @@
-package io.legado.app.ui.book.changesource
-
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.PopupMenu
-import androidx.recyclerview.widget.DiffUtil
-import io.legado.app.R
-import io.legado.app.base.adapter.DiffRecyclerAdapter
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.data.entities.SearchBook
-import io.legado.app.databinding.ItemChangeSourceBinding
-import io.legado.app.lib.dialogs.alert
-import io.legado.app.utils.gone
-import io.legado.app.utils.invisible
-import io.legado.app.utils.themeColor
-import io.legado.app.utils.visible
-import splitties.views.onLongClick
-
-
-class ChangeBookSourceAdapter(
-    context: Context,
-    val viewModel: ChangeBookSourceViewModel,
-    val callBack: CallBack
-) : DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) {
-
-    private val changeSourceSettingsGateway get() = org.koin.core.context.GlobalContext.get().get<io.legado.app.domain.gateway.ChangeSourceSettingsGateway>()
-
-    override val diffItemCallback = object : DiffUtil.ItemCallback<SearchBook>() {
-        override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
-            return oldItem.bookUrl == newItem.bookUrl
-        }
-
-        override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {
-            return oldItem.originName == newItem.originName
-                    && oldItem.getDisplayLastChapterTitle() == newItem.getDisplayLastChapterTitle()
-                    && oldItem.chapterWordCountText == newItem.chapterWordCountText
-                    && oldItem.respondTime == newItem.respondTime
-        }
-
-    }
-
-    override fun getViewBinding(parent: ViewGroup): ItemChangeSourceBinding {
-        return ItemChangeSourceBinding.inflate(inflater, parent, false)
-    }
-
-    override fun convert(
-        holder: ItemViewHolder,
-        binding: ItemChangeSourceBinding,
-        item: SearchBook,
-        payloads: MutableList<Any>
-    ) {
-        binding.apply {
-            if (payloads.isEmpty()) {
-                tvOrigin.text = item.originName
-                tvAuthor.text = item.author
-                tvLast.text = item.getDisplayLastChapterTitle()
-                tvCurrentChapterWordCount.text = item.chapterWordCountText
-                tvRespondTime.text = context.getString(R.string.respondTime, item.respondTime)
-                if (callBack.oldBookUrl == item.bookUrl) {
-                    ivChecked.visible()
-                    root.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainerHighest))
-                } else {
-                    ivChecked.invisible()
-                    root.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainerLow))
-                }
-            } else {
-                for (i in payloads.indices) {
-                    val bundle = payloads[i] as Bundle
-                    bundle.keySet().forEach {
-                        when (it) {
-                            "name" -> tvOrigin.text = item.originName
-                            "latest" -> tvLast.text = item.getDisplayLastChapterTitle()
-                            "upCurSource" -> if (callBack.oldBookUrl == item.bookUrl) {
-                                ivChecked.visible()
-                                root.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainer))
-                            } else {
-                                ivChecked.invisible()
-                                root.setBackgroundColor(context.themeColor(com.google.android.material.R.attr.colorSurfaceContainerLow))
-                            }
-                        }
-                    }
-                }
-            }
-            val score = callBack.getBookScore(item)
-            if (score > 0) {
-                // 已置顶
-                binding.ivGood.setImageResource(R.drawable.ic_praise_filled)
-            } else {
-                // 未置顶
-                binding.ivGood.setImageResource(R.drawable.ic_praise)
-            }
-
-            if (changeSourceSettingsGateway.currentSettings.loadWordCount && !item.chapterWordCountText.isNullOrBlank()) {
-                tvCurrentChapterWordCount.visible()
-            } else {
-                tvCurrentChapterWordCount.gone()
-            }
-
-            if (changeSourceSettingsGateway.currentSettings.loadWordCount && item.respondTime >= 0) {
-                tvRespondTime.visible()
-            } else {
-                tvRespondTime.gone()
-            }
-        }
-    }
-
-    override fun registerListener(holder: ItemViewHolder, binding: ItemChangeSourceBinding) {
-        binding.ivGood.setOnClickListener {
-            val item = getItem(holder.layoutPosition) ?: return@setOnClickListener
-            val score = callBack.getBookScore(item)
-            if (score > 0) {
-                // 已置顶 -> 取消置顶
-                binding.ivGood.setImageResource(R.drawable.ic_praise)
-                callBack.setBookScore(item, 0)
-            } else {
-                // 未置顶 -> 设置置顶
-                binding.ivGood.setImageResource(R.drawable.ic_praise_filled)
-                callBack.setBookScore(item, 1)
-            }
-        }
-        holder.itemView.setOnClickListener {
-            getItem(holder.layoutPosition)?.let {
-                if (it.bookUrl != callBack.oldBookUrl) {
-                    callBack.changeTo(it)
-                }
-            }
-        }
-        holder.itemView.onLongClick {
-            showMenu(holder.itemView, getItem(holder.layoutPosition))
-        }
-    }
-
-    private fun showMenu(view: View, searchBook: SearchBook?) {
-        searchBook ?: return
-        val popupMenu = PopupMenu(context, view)
-        popupMenu.inflate(R.menu.change_source_item)
-        popupMenu.setOnMenuItemClickListener {
-            when (it.itemId) {
-                R.id.menu_top_source -> {
-                    callBack.topSource(searchBook)
-                }
-
-                R.id.menu_bottom_source -> {
-                    callBack.bottomSource(searchBook)
-                }
-
-                R.id.menu_edit_source -> {
-                    callBack.editSource(searchBook)
-                }
-
-                R.id.menu_disable_source -> {
-                    callBack.disableSource(searchBook)
-                }
-
-                R.id.menu_delete_source -> context.alert(R.string.draw) {
-                    setMessage(context.getString(R.string.sure_del) + "\n" + searchBook.originName)
-                    noButton()
-                    yesButton {
-                        callBack.deleteSource(searchBook)
-                        updateItems(0, itemCount, listOf<Int>())
-                    }
-                }
-            }
-            true
-        }
-        popupMenu.show()
-    }
-
-    interface CallBack {
-        val oldBookUrl: String?
-        fun changeTo(searchBook: SearchBook)
-        fun topSource(searchBook: SearchBook)
-        fun bottomSource(searchBook: SearchBook)
-        fun editSource(searchBook: SearchBook)
-        fun disableSource(searchBook: SearchBook)
-        fun deleteSource(searchBook: SearchBook)
-        fun setBookScore(searchBook: SearchBook, score: Int)
-        fun getBookScore(searchBook: SearchBook): Int
-    }
-}

+ 0 - 577
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt

@@ -1,577 +0,0 @@
-package io.legado.app.ui.book.changesource
-
-//import io.legado.app.lib.theme.getPrimaryTextColor
-//import io.legado.app.lib.theme.primaryColor
-import android.annotation.SuppressLint
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import androidx.appcompat.widget.SearchView
-import androidx.appcompat.widget.Toolbar
-import androidx.core.os.bundleOf
-import androidx.core.view.isInvisible
-import androidx.lifecycle.Lifecycle.State.STARTED
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import io.legado.app.R
-import io.legado.app.base.BaseBottomSheetDialogFragment
-import io.legado.app.constant.AppLog
-import io.legado.app.constant.EventBus
-import io.legado.app.data.appDb
-import io.legado.app.data.entities.Book
-import io.legado.app.data.entities.BookChapter
-import io.legado.app.data.entities.BookSource
-import io.legado.app.data.entities.SearchBook
-import io.legado.app.databinding.DialogBookChangeSourceBinding
-import io.legado.app.domain.gateway.ChangeSourceSettingsGateway
-import io.legado.app.domain.gateway.OtherSettingsGateway
-import io.legado.app.domain.gateway.ReadSettingsGateway
-import io.legado.app.domain.model.settings.ChangeSourceSettings
-import io.legado.app.lib.dialogs.alert
-import io.legado.app.model.ReadBook
-import io.legado.app.ui.book.search.SearchScope
-import io.legado.app.ui.main.MainActivity
-import io.legado.app.ui.widget.dialog.WaitDialog
-import io.legado.app.ui.widget.recycler.VerticalDivider
-import io.legado.app.utils.applyTint
-import io.legado.app.utils.dpToPx
-import io.legado.app.utils.getCompatDrawable
-import io.legado.app.utils.observeEvent
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.transaction
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.drop
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-import org.koin.android.ext.android.inject
-import org.koin.androidx.viewmodel.ext.android.viewModel
-
-/**
- * 换源界面
- */
-class ChangeBookSourceDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_book_change_source),
-    Toolbar.OnMenuItemClickListener,
-    ChangeBookSourceAdapter.CallBack {
-
-    constructor(name: String, author: String) : this() {
-        arguments = Bundle().apply {
-            putString("name", name)
-            putString("author", author)
-        }
-    }
-
-    private val binding by viewBinding(DialogBookChangeSourceBinding::bind)
-    private val groups = linkedSetOf<String>()
-    private val callBack: CallBack? get() = activity as? CallBack
-    private val viewModel: ChangeBookSourceViewModel by viewModel()
-    private val changeSourceSettingsGateway by inject<ChangeSourceSettingsGateway>()
-    private val otherSettingsGateway by inject<OtherSettingsGateway>()
-    private val readSettingsGateway by inject<ReadSettingsGateway>()
-    private val waitDialog by lazy { WaitDialog(requireContext()) }
-    private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) }
-    private val editSourceResult =
-        registerForActivityResult(androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult()) {
-            val origin = it.data?.getStringExtra("origin") ?: return@registerForActivityResult
-            viewModel.startSearch(origin)
-        }
-    private var currentSelectedSearchBook: SearchBook? = null
-    private val searchFinishCallback: (isEmpty: Boolean) -> Unit = {
-        if (it) {
-            val searchScope = SearchScope(changeSourceSettingsGateway.currentSettings.searchScope)
-            val group = searchScope.display
-            if (!searchScope.isAll()) {
-                lifecycleScope.launch {
-                    context?.alert("搜索结果为空") {
-                        setMessage("${group}分组搜索结果为空,是否切换到全部分组")
-                        cancelButton()
-                        okButton {
-                            updateSetting({ it.copy(searchScope = "") }) {
-                                upGroupMenuName()
-                                viewModel.startSearch()
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        viewModel.initData(arguments, callBack?.oldBook, MainActivity.hasActiveReadBookRoute)
-        showTitle()
-        initMenu()
-        initRecyclerView()
-        initNavigationView()
-        initSearchView()
-        initBottomBar()
-        initLiveData()
-        viewModel.searchFinishCallback = searchFinishCallback
-    }
-
-    override fun onDestroy() {
-        super.onDestroy()
-        viewModel.searchFinishCallback = null
-        viewModel.stopSearch()
-    }
-
-    private fun showTitle() {
-        binding.toolBar.title = viewModel.name
-        binding.toolBar.subtitle = viewModel.author
-        binding.toolBar.navigationIcon =
-            getCompatDrawable(androidx.appcompat.R.drawable.abc_ic_ab_back_material)
-        //binding.toolBar.elevation = requireContext().elevation
-    }
-
-    private fun initMenu() {
-        binding.toolBar.inflateMenu(R.menu.change_source)
-        binding.toolBar.menu.applyTint(requireContext())
-        binding.toolBar.setOnMenuItemClickListener(this)
-        binding.toolBar.menu.findItem(R.id.menu_check_author)
-            ?.isChecked = changeSourceSettingsGateway.currentSettings.checkAuthor
-        binding.toolBar.menu.findItem(R.id.menu_load_info)
-            ?.isChecked = changeSourceSettingsGateway.currentSettings.loadInfo
-        binding.toolBar.menu.findItem(R.id.menu_load_toc)
-            ?.isChecked = changeSourceSettingsGateway.currentSettings.loadToc
-        binding.toolBar.menu.findItem(R.id.menu_load_word_count)
-            ?.isChecked = changeSourceSettingsGateway.currentSettings.loadWordCount
-    }
-
-    private fun initRecyclerView() {
-        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
-        binding.recyclerView.adapter = adapter
-        adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
-            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
-                if (positionStart == 0) {
-                    binding.recyclerView.scrollToPosition(0)
-                }
-            }
-
-            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
-                if (toPosition == 0) {
-                    binding.recyclerView.scrollToPosition(0)
-                }
-            }
-        })
-    }
-
-    private fun initSearchView() {
-        val searchView = binding.toolBar.menu.findItem(R.id.menu_screen).actionView as SearchView
-        searchView.setOnCloseListener {
-            showTitle()
-            false
-        }
-        searchView.setOnSearchClickListener {
-            binding.toolBar.title = ""
-            binding.toolBar.subtitle = ""
-            binding.toolBar.navigationIcon = null
-        }
-        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
-            override fun onQueryTextSubmit(query: String?): Boolean {
-                return false
-            }
-
-            override fun onQueryTextChange(newText: String?): Boolean {
-                viewModel.screen(newText)
-                return false
-            }
-
-        })
-    }
-
-    private fun initNavigationView() {
-        binding.toolBar.navigationIcon =
-            getCompatDrawable(androidx.appcompat.R.drawable.abc_ic_ab_back_material)
-        binding.toolBar.setNavigationContentDescription(
-            androidx.appcompat.R.string.abc_action_bar_up_description
-        )
-        binding.toolBar.setNavigationOnClickListener {
-            dismissAllowingStateLoss()
-        }
-        kotlin.runCatching {
-            val mNavButtonViewField = Toolbar::class.java.getDeclaredField("mNavButtonView")
-            mNavButtonViewField.isAccessible = true
-            //val navigationView = mNavButtonViewField.get(binding.toolBar) as ImageButton
-//            val isLight = ColorUtils.isColorLight(primaryColor)
-//            val textColor = requireContext().getPrimaryTextColor(isLight)
-            //navigationView.setColorFilter(textColor)
-        }
-    }
-
-    private fun initBottomBar() {
-        binding.tvDur.text = callBack?.oldBook?.originName
-        binding.tvDur.setOnClickListener {
-            scrollToDurSource()
-        }
-        binding.ivTop.setOnClickListener {
-            binding.recyclerView.scrollToPosition(0)
-        }
-        binding.ivBottom.setOnClickListener {
-            binding.recyclerView.scrollToPosition(adapter.itemCount - 1)
-        }
-    }
-
-    private var switchHandler: Handler? = null
-    private var switchRunnable: Runnable? = null
-
-
-    @SuppressLint("SetTextI18n")
-    private fun initLiveData() {
-
-        viewModel.searchStateData.observe(viewLifecycleOwner) { isSearching ->
-            if (isSearching) {
-                binding.refreshProgressBar.isInvisible = false
-                startStopMenuItem?.let { item ->
-                    item.setIcon(R.drawable.ic_stop_black_24dp)
-                    item.setTitle(R.string.stop)
-                }
-            } else {
-                binding.refreshProgressBar.isIndeterminate = false
-                binding.refreshProgressBar.progress = 0
-                binding.refreshProgressBar.isInvisible = true
-                startStopMenuItem?.let { item ->
-                    item.setIcon(R.drawable.ic_refresh)
-                    item.setTitle(R.string.refresh)
-                }
-            }
-        }
-
-        lifecycleScope.launch {
-            lifecycle.currentStateFlow.first { it.isAtLeast(STARTED) }
-            viewModel.searchDataFlow.conflate().collect {
-                adapter.setItems(it)
-                delay(1000)
-
-            }
-        }
-
-        lifecycleScope.launch {
-            repeatOnLifecycle(STARTED) {
-                var canSwitchToDeterminate = false
-
-                switchHandler?.removeCallbacks(switchRunnable ?: Runnable { })
-                switchHandler = Handler(Looper.getMainLooper())
-                switchRunnable = Runnable { canSwitchToDeterminate = true }
-                switchHandler?.postDelayed(switchRunnable!!, 5000)
-
-                viewModel.changeSourceProgress
-                    .drop(1)
-                    .collect { (count, name) ->
-                        val total = viewModel.totalSourceCount
-                        val progressPercent = if (total > 0) (count * 100f / total) else 0f
-
-                        binding.refreshProgressBar.isIndeterminate = true
-                        binding.refreshProgressBar.show()
-
-                        binding.llInfo.visible()
-
-                        if (count == 0) {
-                            canSwitchToDeterminate = false
-                            switchHandler?.removeCallbacks(switchRunnable!!)
-                            switchHandler?.postDelayed(switchRunnable!!, 5000)
-                        }
-
-                        val shouldBeIndeterminate = when {
-                            !canSwitchToDeterminate -> true
-                            progressPercent in 10f..90f -> false
-                            else -> true
-                        }
-
-                        if (shouldBeIndeterminate) {
-                            if (!binding.refreshProgressBar.isIndeterminate) {
-                                binding.refreshProgressBar.isIndeterminate = true
-                                binding.refreshProgressBar.show()
-                            }
-                        } else {
-                            if (binding.refreshProgressBar.isIndeterminate) {
-                                binding.refreshProgressBar.isIndeterminate = false
-                            }
-                            binding.refreshProgressBar.setProgressCompat(progressPercent.toInt(), true)
-                        }
-
-                        binding.tvProgress.text = "$count / $total"
-                        binding.tvResult.text =
-                            getString(R.string.change_source_progress, adapter.itemCount)
-                    }
-            }
-        }
-
-        lifecycleScope.launch {
-            appDb.bookSourceDao.flowEnabledGroups().conflate().collect {
-                groups.clear()
-                groups.addAll(it)
-                upGroupMenu()
-            }
-        }
-    }
-
-    private val startStopMenuItem: MenuItem?
-        get() = binding.toolBar.menu.findItem(R.id.menu_start_stop)
-
-    override fun onMenuItemClick(item: MenuItem?): Boolean {
-        when (item?.itemId) {
-            R.id.menu_check_author -> {
-                val enabled = !item.isChecked
-                updateSetting({ it.copy(checkAuthor = enabled) })
-                item.isChecked = enabled
-                lifecycleScope.launch { viewModel.refresh() }
-            }
-
-            R.id.menu_load_info -> {
-                val enabled = !item.isChecked
-                updateSetting({ it.copy(loadInfo = enabled) })
-                item.isChecked = enabled
-            }
-
-            R.id.menu_load_toc -> {
-                val enabled = !item.isChecked
-                updateSetting({ it.copy(loadToc = enabled) })
-                item.isChecked = enabled
-            }
-
-            R.id.menu_load_word_count -> {
-                val enabled = !item.isChecked
-                updateSetting({ it.copy(loadWordCount = enabled) })
-                item.isChecked = enabled
-                viewModel.onLoadWordCountChecked(item.isChecked)
-            }
-
-            R.id.menu_start_stop -> viewModel.startOrStopSearch()
-            R.id.menu_source_manage -> startActivity(
-                MainActivity.createBookSourceManageIntent(requireContext())
-            )
-            R.id.menu_close -> dismissAllowingStateLoss()
-            R.id.menu_refresh_list -> viewModel.startRefreshList()
-            else -> if (item?.groupId == R.id.source_group && !item.isChecked) {
-                item.isChecked = true
-                val scope = if (item.title.toString() == getString(R.string.all_source)) {
-                    ""
-                } else {
-                    item.title.toString()
-                }
-                updateSetting({ it.copy(searchScope = scope) }) {
-                    upGroupMenuName()
-                    lifecycleScope.launch(IO) {
-                        viewModel.stopSearch()
-                        if (viewModel.refresh()) {
-                            viewModel.startSearch()
-                        }
-                    }
-                }
-            }
-        }
-        return false
-    }
-
-    private fun scrollToDurSource() {
-        adapter.getItems().forEachIndexed { index, searchBook ->
-            if (searchBook.bookUrl == oldBookUrl) {
-                (binding.recyclerView.layoutManager as LinearLayoutManager)
-                    .scrollToPositionWithOffset(index, 60.dpToPx())
-                return
-            }
-        }
-    }
-
-    override fun changeTo(searchBook: SearchBook) {
-        currentSelectedSearchBook = searchBook
-        showChangeSourceOptionDialog(searchBook)
-    }
-
-    private fun showChangeSourceOptionDialog(searchBook: SearchBook) {
-        val oldBookType = callBack?.oldBook?.type ?: 0
-
-        if (!searchBook.sameBookTypeLocal(oldBookType)) {
-            alert(
-                titleResource = R.string.book_type_different,
-                messageResource = R.string.soure_change_source
-            ) {
-                okButton {
-                    showChangeSourceActionDialog(searchBook)
-                }
-                cancelButton()
-            }
-        } else {
-            showChangeSourceActionDialog(searchBook)
-        }
-    }
-
-    private fun showChangeSourceActionDialog(searchBook: SearchBook) {
-        context?.alert(getString(R.string.change_source_option_title)) {
-            positiveButton(getString(R.string.replace_current_book)) {
-                performChangeSource(searchBook, true)
-            }
-            negativeButton(getString(R.string.add_as_new_book)) {
-                performChangeSource(searchBook, false)
-            }
-        }
-    }
-
-    private fun performChangeSource(searchBook: SearchBook, isReplace: Boolean) {
-        val baseBook = viewModel.bookMap[searchBook.primaryStr()] ?: searchBook.toBook()
-        val book = baseBook
-
-        waitDialog.setText(if (isReplace) R.string.load_toc else R.string.load_info)
-        waitDialog.show()
-
-        val coroutine = viewModel.getToc(book, { toc, source ->
-            waitDialog.dismiss()
-            if (isReplace) {
-
-                callBack?.changeTo(source, book, toc)
-                dismissAllowingStateLoss()
-            } else {
-                ReadBook.book?.migrateTo(
-                    book,
-                    toc,
-                    otherSettingsGateway.currentSettings.replaceEnableDefault,
-                    readSettingsGateway.currentSettings.chineseConverterType,
-                )
-                callBack?.addToBookshelf(book, toc)
-                context?.toastOnUi(getString(R.string.book_added_to_shelf))
-            }
-        }, {
-            waitDialog.dismiss()
-            AppLog.put("${if (isReplace) "换源" else "添加书籍"}获取目录出错\n$it", it, true)
-            context?.toastOnUi("${if (isReplace) "换源" else "添加书籍"}失败")
-        })
-
-        waitDialog.setOnCancelListener {
-            coroutine.cancel()
-        }
-    }
-
-    override val oldBookUrl: String?
-        get() = callBack?.oldBook?.bookUrl
-
-    override fun topSource(searchBook: SearchBook) {
-        viewModel.topSource(searchBook)
-    }
-
-    override fun bottomSource(searchBook: SearchBook) {
-        viewModel.bottomSource(searchBook)
-    }
-
-    override fun editSource(searchBook: SearchBook) {
-        editSourceResult.launch(
-            MainActivity.createBookSourceEditIntent(requireContext(), searchBook.origin)
-        )
-    }
-
-    override fun disableSource(searchBook: SearchBook) {
-        viewModel.disableSource(searchBook)
-    }
-
-    override fun deleteSource(searchBook: SearchBook) {
-        viewModel.del(searchBook)
-        if (oldBookUrl == searchBook.bookUrl) {
-            viewModel.autoChangeSource(callBack?.oldBook?.type) { book, toc, source ->
-                callBack?.changeTo(source, book, toc)
-            }
-        }
-    }
-
-    override fun setBookScore(searchBook: SearchBook, score: Int) {
-        viewModel.setBookScore(searchBook, score)
-    }
-
-    override fun getBookScore(searchBook: SearchBook): Int {
-        return viewModel.getBookScore(searchBook)
-    }
-
-    private fun changeSource(searchBook: SearchBook, onSuccess: (() -> Unit)? = null) {
-        waitDialog.setText(R.string.load_toc)
-        waitDialog.show()
-        val book = viewModel.bookMap[searchBook.primaryStr()] ?: searchBook.toBook()
-        val coroutine = viewModel.getToc(book, { toc, source ->
-            waitDialog.dismiss()
-            callBack?.changeTo(source, book, toc)
-            onSuccess?.invoke()
-        }, {
-            waitDialog.dismiss()
-            AppLog.put("换源获取目录出错\n$it", it, true)
-        })
-        waitDialog.setOnCancelListener {
-            coroutine.cancel()
-        }
-    }
-
-    /**
-     * 更新分组菜单
-     */
-    private fun upGroupMenu() {
-        binding.toolBar.menu.findItem(R.id.menu_group)?.run {
-            subMenu?.transaction { menu ->
-                val searchScope = SearchScope(changeSourceSettingsGateway.currentSettings.searchScope)
-                val selectedGroup = searchScope.displayNames.firstOrNull() ?: ""
-                menu.removeGroup(R.id.source_group)
-                val allItem = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.all_source)
-                var hasSelectedGroup = false
-                groups.forEach { group ->
-                    menu.add(R.id.source_group, Menu.NONE, Menu.NONE, group)?.let {
-                        if (group == selectedGroup) {
-                            it.isChecked = true
-                            hasSelectedGroup = true
-                        }
-                    }
-                }
-                menu.setGroupCheckable(R.id.source_group, true, true)
-                if (hasSelectedGroup) {
-                    title = getString(R.string.group) + "(" + selectedGroup + ")"
-                } else {
-                    allItem.isChecked = true
-                    title = getString(R.string.group)
-                }
-            }
-        }
-    }
-
-    /**
-     * 更新分组菜单名
-     */
-    private fun upGroupMenuName() {
-        val menuGroup = binding.toolBar.menu.findItem(R.id.menu_group)
-        val searchScope = SearchScope(changeSourceSettingsGateway.currentSettings.searchScope)
-        if (searchScope.isAll()) {
-            menuGroup?.title = getString(R.string.group)
-        } else {
-            menuGroup?.title = getString(R.string.group) + "(${searchScope.display})"
-        }
-    }
-
-    private fun updateSetting(
-        transform: (ChangeSourceSettings) -> ChangeSourceSettings,
-        afterUpdate: (() -> Unit)? = null,
-    ) {
-        lifecycleScope.launch {
-            changeSourceSettingsGateway.update(transform)
-            afterUpdate?.invoke()
-        }
-    }
-
-    override fun observeLiveBus() {
-        observeEvent<String>(EventBus.SOURCE_CHANGED) {
-            adapter.notifyItemRangeChanged(
-                0,
-                adapter.itemCount,
-                bundleOf(Pair("upCurSource", oldBookUrl))
-            )
-        }
-    }
-
-    interface CallBack {
-        val oldBook: Book?
-        fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)
-        fun addToBookshelf(book: Book, toc: List<BookChapter>)
-    }
-
-}

+ 0 - 588
app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt

@@ -1,588 +0,0 @@
-package io.legado.app.ui.book.changesource
-
-import android.app.Application
-import android.os.Bundle
-import androidx.annotation.CallSuper
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import io.legado.app.base.BaseViewModel
-import io.legado.app.constant.AppLog
-import io.legado.app.constant.AppPattern
-import io.legado.app.data.entities.Book
-import io.legado.app.data.entities.BookChapter
-import io.legado.app.data.entities.BookSource
-import io.legado.app.data.entities.BookSourcePart
-import io.legado.app.data.entities.SearchBook
-import io.legado.app.data.repository.BookSourceRepository
-import io.legado.app.data.repository.SearchRepository
-import io.legado.app.exception.NoStackTraceException
-import io.legado.app.help.book.BookHelp
-import io.legado.app.help.book.ContentProcessor
-import io.legado.app.help.book.primaryStr
-import io.legado.app.help.book.releaseHtmlData
-import io.legado.app.help.config.SourceConfig
-import io.legado.app.help.coroutine.Coroutine
-import io.legado.app.model.webBook.WebBook
-import io.legado.app.domain.gateway.ChangeSourceSettingsGateway
-import io.legado.app.domain.gateway.DownloadCacheSettingsGateway
-import io.legado.app.utils.internString
-import io.legado.app.utils.mapParallel
-import io.legado.app.utils.mapParallelSafe
-import io.legado.app.utils.onEachIndexed
-import io.legado.app.utils.toastOnUi
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.ExecutorCoroutineDispatcher
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.asCoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.currentCoroutineContext
-import kotlinx.coroutines.ensureActive
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withTimeout
-import java.util.Collections
-import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.Executors
-
-@Suppress("MemberVisibilityCanBePrivate")
-open class ChangeBookSourceViewModel(
-    application: Application,
-    private val searchRepository: SearchRepository,
-    private val bookSourceRepository: BookSourceRepository,
-    private val downloadCacheSettingsGateway: DownloadCacheSettingsGateway,
-    private val changeSourceSettingsGateway: ChangeSourceSettingsGateway,
-) : BaseViewModel(application) {
-    private val threadCount = downloadCacheSettingsGateway.currentSettings.threadCount
-    private var searchPool: ExecutorCoroutineDispatcher? = null
-    val searchStateData = MutableLiveData<Boolean>()
-    private val _isSearching = MutableStateFlow(false)
-    val isSearching = _isSearching.asStateFlow()
-    var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null
-    var name: String = ""
-    var author: String = ""
-    private var fromReadBookActivity = false
-    private var oldBook: Book? = null
-    private var screenKey: String = ""
-    private var bookSourceParts = arrayListOf<BookSourcePart>()
-    val totalSourceCount: Int
-        get() = bookSourceParts.size
-    private var searchBookList = arrayListOf<SearchBook>()
-    private val searchBooks = Collections.synchronizedList(arrayListOf<SearchBook>())
-    private val tocMap = ConcurrentHashMap<String, List<BookChapter>>()
-    private val _changeSourceProgress = MutableStateFlow(0 to "")
-    val changeSourceProgress = _changeSourceProgress.asStateFlow()
-    private var tocMapChapterCount = 0
-    private val contentProcessor by lazy {
-        ContentProcessor.get(oldBook!!)
-    }
-    private var searchCallback: SourceCallback? = null
-    private val chapterNumRegex = "^\\[(\\d+)]".toRegex()
-    private val comparatorBase by lazy {
-        compareByDescending<SearchBook> { getBookScore(it) }
-            .thenByDescending { SourceConfig.getSourceScore(it.origin) }
-    }
-    private val defaultComparator by lazy {
-        comparatorBase.thenBy { it.originOrder }
-    }
-    private val wordCountComparator by lazy {
-        comparatorBase.thenByDescending { it.chapterWordCount > 1000 }
-            .thenByDescending { getChapterNum(it.chapterWordCountText) }
-            .thenByDescending { it.chapterWordCount }
-            .thenBy { it.originOrder }
-    }
-    private var task: Job? = null
-    private var isPaused = false
-    private var wasSearching = false
-    val bookMap = ConcurrentHashMap<String, Book>()
-    val searchDataFlow = callbackFlow {
-
-        searchCallback = object : SourceCallback {
-
-            override suspend fun searchSuccess(searchBook: SearchBook) {
-                searchBook.releaseHtmlData()
-                searchRepository.saveSearchBook(searchBook)
-                when {
-                    screenKey.isEmpty() -> searchBooks.add(searchBook)
-                    searchBook.name.contains(screenKey) -> searchBooks.add(searchBook)
-                    else -> return
-                }
-                trySend(arrayOf(searchBooks))
-            }
-
-            override fun upAdapter() {
-                trySend(arrayOf(searchBooks))
-            }
-
-        }
-
-        if (searchBooks.isEmpty()) {
-            getDbSearchBooks().let {
-                searchBooks.addAll(it)
-            }
-        }
-        trySend(arrayOf(searchBooks))
-
-        if (searchBooks.isEmpty() && !_isSearching.value) {
-            startSearch()
-        }
-
-        awaitClose {
-            searchCallback = null
-        }
-    }.map {
-        kotlin.runCatching {
-            val comparator = if (changeSourceSettingsGateway.currentSettings.loadWordCount) {
-                wordCountComparator
-            } else {
-                defaultComparator
-            }
-            searchBooks.sortedWith(comparator)
-        }.onFailure {
-            AppLog.put("换源排序出错\n${it.localizedMessage}", it)
-        }.getOrDefault(searchBooks)
-    }.flowOn(IO)
-
-    override fun onCleared() {
-        super.onCleared()
-        searchPool?.close()
-    }
-
-    @CallSuper
-    open fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {
-        arguments?.let { bundle ->
-            bundle.getString("name")?.let {
-                name = it
-            }
-            bundle.getString("author")?.let {
-                author = it.replace(AppPattern.authorRegex, "")
-            }
-            this.fromReadBookActivity = fromReadBookActivity
-            oldBook = book
-        }
-    }
-
-    @CallSuper
-    open fun initData(
-        name: String,
-        author: String,
-        book: Book?,
-        fromReadBookActivity: Boolean
-    ) {
-        this.name = name
-        this.author = author.replace(AppPattern.authorRegex, "")
-        this.fromReadBookActivity = fromReadBookActivity
-        oldBook = book
-    }
-
-    private fun initSearchPool() {
-        searchPool = Executors
-            .newFixedThreadPool(threadCount).asCoroutineDispatcher()
-    }
-
-    suspend fun refresh(): Boolean {
-        getDbSearchBooks().let {
-            searchBooks.clear()
-            searchBooks.addAll(it)
-            searchCallback?.upAdapter()
-        }
-        return searchBooks.isEmpty()
-    }
-
-    /**
-     * 搜索书籍
-     */
-    fun startSearch() {
-        execute {
-            stopSearch()
-            if (searchBooks.isNotEmpty()) {
-                searchRepository.deleteSearchBooks(searchBooks.toList())
-                searchBooks.clear()
-            }
-            searchCallback?.upAdapter()
-            bookSourceParts.clear()
-            tocMap.clear()
-            bookMap.clear()
-            tocMapChapterCount = 0
-            _changeSourceProgress.value = 0 to ""
-            bookSourceParts.addAll(io.legado.app.ui.book.search.SearchScope(changeSourceSettingsGateway.currentSettings.searchScope).getBookSourceParts())
-            initSearchPool()
-            search()
-        }
-    }
-
-    fun startSearch(origin: String) {
-        execute {
-            stopSearch()
-            bookSourceParts.clear()
-            tocMap.clear()
-            bookMap.clear()
-            tocMapChapterCount = 0
-            bookSourceParts.add(bookSourceRepository.getBookSourcePart(origin)!!)
-            searchBooks.removeIf { it.origin == origin }
-            initSearchPool()
-            search()
-        }
-    }
-
-    private fun search() {
-        task = viewModelScope.launch(searchPool!!) {
-            flow {
-                for (bs in bookSourceParts) {
-                    bs.getBookSource()?.let {
-                        emit(it)
-                    }
-                }
-            }.onStart {
-                searchStateData.postValue(true)
-                _isSearching.value = true
-            }.mapParallel(threadCount) {
-                while (isPaused) {
-                    kotlinx.coroutines.delay(100)
-                }
-                try {
-                    withTimeout(60000L) {
-                        search(it)
-                    }
-                } catch (_: Throwable) {
-                    currentCoroutineContext().ensureActive()
-                }
-                it
-            }.onEachIndexed { index, value ->
-                _changeSourceProgress.update { _ ->
-                    index + 1 to value.bookSourceName
-                }
-            }.onCompletion {
-                ensureActive()
-                searchStateData.postValue(false)
-                _isSearching.value = false
-                searchFinishCallback?.invoke(searchBooks.isEmpty())
-            }.catch {
-                AppLog.put("换源搜索出错\n${it.localizedMessage}", it)
-            }.collect()
-        }
-    }
-
-    private suspend fun search(source: BookSource) {
-        val checkAuthor = changeSourceSettingsGateway.currentSettings.checkAuthor
-        val loadInfo = changeSourceSettingsGateway.currentSettings.loadInfo
-        val loadToc = changeSourceSettingsGateway.currentSettings.loadToc
-        val loadWordCount = changeSourceSettingsGateway.currentSettings.loadWordCount
-        val resultBooks = WebBook.searchBookAwait(
-            source, name,
-            filter = { fName, fAuthor, _ ->
-                fName == name && (!checkAuthor || fAuthor.contains(author))
-            })
-        resultBooks.forEach { searchBook ->
-            when {
-                loadInfo || loadToc || loadWordCount -> {
-                    loadBookInfo(source, searchBook.toBook())
-                }
-
-                else -> {
-                    searchCallback?.searchSuccess(searchBook)
-                }
-            }
-        }
-    }
-
-    private suspend fun loadBookInfo(source: BookSource, book: Book) {
-        if (book.tocUrl.isEmpty()) {
-            WebBook.getBookInfoAwait(source, book)
-        }
-        if (changeSourceSettingsGateway.currentSettings.loadToc || changeSourceSettingsGateway.currentSettings.loadWordCount) {
-            loadBookToc(source, book)
-        } else {
-            //从详情页里获取最新章节
-            val searchBook = book.toSearchBook()
-            searchCallback?.searchSuccess(searchBook)
-        }
-    }
-
-    private suspend fun loadBookToc(source: BookSource, book: Book) {
-        val chapters = WebBook.getChapterListAwait(source, book).getOrThrow()
-        for (chapter in chapters) {
-            chapter.internString()
-        }
-        if (tocMapChapterCount < 30000) {
-            tocMapChapterCount += chapters.size
-            tocMap[book.primaryStr()] = chapters
-        }
-        bookMap[book.primaryStr()] = book
-        book.releaseHtmlData()
-        if (changeSourceSettingsGateway.currentSettings.loadWordCount) {
-            loadBookWordCount(source, book, chapters)
-        } else {
-            val searchBook = book.toSearchBook()
-            searchCallback?.searchSuccess(searchBook)
-        }
-    }
-
-    private suspend fun loadBookWordCount(
-        source: BookSource,
-        book: Book,
-        chapters: List<BookChapter>
-    ) = coroutineScope {
-        val chapterIndex = if (fromReadBookActivity) {
-            BookHelp.getDurChapter(oldBook!!, chapters)
-        } else {
-            chapters.lastIndex
-        }
-        val bookChapter = chapters[chapterIndex]
-        var title = bookChapter.title.trim()
-        if (title.length > 20) {
-            title = title.substring(0, 20) + "…"
-        }
-        val startTime = System.currentTimeMillis()
-        val pair = try {
-            val nextChapterUrl = chapters.getOrNull(chapterIndex + 1)?.url
-            var content = WebBook.getContentAwait(source, book, bookChapter, nextChapterUrl, false)
-            content = contentProcessor.getContent(oldBook!!, bookChapter, content, false).toString()
-            val len = content.length
-            len to "[${chapterIndex + 1}] ${title}\n字数:${len}"
-        } catch (t: Throwable) {
-            if (t is CancellationException) throw t
-            -1 to "[${chapterIndex + 1}] ${title}\n获取字数失败:${t.localizedMessage}"
-        }
-        val endTime = System.currentTimeMillis()
-        val searchBook = book.toSearchBook().apply {
-            chapterWordCountText = pair.second
-            chapterWordCount = pair.first
-            respondTime = (endTime - startTime).toInt()
-        }
-        searchCallback?.searchSuccess(searchBook)
-    }
-
-    fun onLoadWordCountChecked(isChecked: Boolean) {
-        if (isChecked) {
-            startRefreshList(true)
-        }
-    }
-
-    /**
-     * 刷新列表
-     */
-    fun startRefreshList(onlyRefreshNoWordCountBook: Boolean = false) {
-        execute {
-            stopSearch()
-            searchBookList.clear()
-            if (onlyRefreshNoWordCountBook) {
-                searchBooks.filterTo(searchBookList) {
-                    it.chapterWordCountText == null
-                }
-                searchBooks.removeIf { it.chapterWordCountText == null }
-            } else {
-                searchBookList.addAll(searchBooks)
-                searchBooks.clear()
-            }
-            searchCallback?.upAdapter()
-            initSearchPool()
-            refreshList()
-        }
-    }
-
-    private fun refreshList() {
-        task = viewModelScope.launch(searchPool!!) {
-            flow {
-                for (searchBook in searchBookList) {
-                    emit(searchBook)
-                }
-            }.onStart {
-                searchStateData.postValue(true)
-                _isSearching.value = true
-            }.mapParallelSafe(threadCount) {
-                val source = bookSourceRepository.getBookSource(it.origin)!!
-                withTimeout(60000L) {
-                    loadBookInfo(source, it.toBook())
-                }
-            }.onCompletion {
-                searchStateData.postValue(false)
-                _isSearching.value = false
-            }.catch {
-                AppLog.put("换源刷新列表出错\n${it.localizedMessage}", it)
-            }.collect()
-        }
-    }
-
-    private suspend fun getDbSearchBooks(): List<SearchBook> {
-        val searchScope = io.legado.app.ui.book.search.SearchScope(
-            changeSourceSettingsGateway.currentSettings.searchScope
-        )
-        val group = when {
-            searchScope.isAll() || searchScope.isSource() -> ""
-            else -> searchScope.displayNames.firstOrNull() ?: ""
-        }
-        val bookAuthor = if (changeSourceSettingsGateway.currentSettings.checkAuthor) author else ""
-        return searchRepository.findChangeSourceBooks(name, bookAuthor, screenKey, group)
-    }
-
-    /**
-     * 筛选
-     */
-    fun screen(key: String?) {
-        screenKey = key?.trim() ?: ""
-        execute {
-            getDbSearchBooks().let {
-                searchBooks.clear()
-                searchBooks.addAll(it)
-                searchCallback?.upAdapter()
-            }
-        }
-    }
-
-    fun startOrStopSearch() {
-        if (task == null || !task!!.isActive) {
-            startSearch()
-        } else {
-            stopSearch()
-        }
-    }
-
-    fun stopSearch() {
-        task?.cancel()
-        searchPool?.close()
-        searchStateData.postValue(false)
-        _isSearching.value = false
-        wasSearching = false
-    }
-
-    fun pause() {
-        isPaused = true
-        wasSearching = _isSearching.value
-    }
-
-    fun resume() {
-        isPaused = false
-        wasSearching = false
-    }
-
-    fun getToc(
-        book: Book,
-        onSuccess: (toc: List<BookChapter>, source: BookSource) -> Unit,
-        onError: (e: Throwable) -> Unit
-    ): Coroutine<Pair<List<BookChapter>, BookSource>> {
-        return execute {
-            val toc = tocMap[book.primaryStr()]
-            if (toc != null) {
-                val source = bookSourceRepository.getBookSource(book.origin)
-                return@execute Pair(toc, source!!)
-            }
-            val result = getToc(book).getOrThrow()
-            tocMap[book.primaryStr()] = result.first
-            return@execute result
-        }.onSuccess {
-            onSuccess.invoke(it.first, it.second)
-        }.onError {
-            onError.invoke(it)
-        }
-    }
-
-    suspend fun getToc(book: Book): Result<Pair<List<BookChapter>, BookSource>> {
-        return kotlin.runCatching {
-            val source = bookSourceRepository.getBookSource(book.origin)
-                ?: throw NoStackTraceException("书源不存在")
-            if (book.tocUrl.isEmpty()) {
-                WebBook.getBookInfoAwait(source, book)
-            }
-            val toc = WebBook.getChapterListAwait(source, book).getOrThrow()
-            Pair(toc, source)
-        }
-    }
-
-    fun disableSource(searchBook: SearchBook) {
-        execute {
-            bookSourceRepository.disableSource(searchBook.origin)
-            searchBooks.remove(searchBook)
-            searchCallback?.upAdapter()
-        }
-    }
-
-    fun topSource(searchBook: SearchBook) {
-        execute {
-            bookSourceRepository.moveSourceToTop(searchBook.origin)?.let { customOrder ->
-                searchBook.originOrder = customOrder
-                updateSource(searchBook)
-            }
-            searchCallback?.upAdapter()
-        }
-    }
-
-    fun bottomSource(searchBook: SearchBook) {
-        execute {
-            bookSourceRepository.moveSourceToBottom(searchBook.origin)?.let { customOrder ->
-                searchBook.originOrder = customOrder
-                updateSource(searchBook)
-            }
-            searchCallback?.upAdapter()
-        }
-    }
-
-    private suspend fun updateSource(searchBook: SearchBook) {
-        searchRepository.updateSearchBook(searchBook)
-    }
-
-    fun del(searchBook: SearchBook) {
-        execute {
-            bookSourceRepository.deleteSource(searchBook.origin)
-            searchRepository.deleteSearchBooks(listOf(searchBook))
-        }
-        searchBooks.remove(searchBook)
-        searchCallback?.upAdapter()
-    }
-
-    fun autoChangeSource(
-        bookType: Int?,
-        onSuccess: (book: Book, toc: List<BookChapter>, source: BookSource) -> Unit
-    ) {
-        execute {
-            searchBooks.forEach {
-                if (it.type == bookType) {
-                    val book = it.toBook()
-                    val result = getToc(book).getOrNull()
-                    if (result != null) {
-                        return@execute Triple(book, result.first, result.second)
-                    }
-                }
-            }
-            throw NoStackTraceException("没有有效源")
-        }.onSuccess {
-            onSuccess.invoke(it.first, it.second, it.third)
-        }.onError {
-            context.toastOnUi("自动换源失败\n${it.localizedMessage}")
-        }
-    }
-
-    fun setBookScore(searchBook: SearchBook, score: Int) {
-        SourceConfig.setBookScore(searchBook.origin, searchBook.name, searchBook.author, score)
-        searchCallback?.upAdapter()
-    }
-
-    fun getBookScore(searchBook: SearchBook): Int {
-        return SourceConfig.getBookScore(searchBook.origin, searchBook.name, searchBook.author)
-    }
-
-    private fun getChapterNum(wordCountText: String?): Int {
-        wordCountText ?: return -1
-        return chapterNumRegex.find(wordCountText)?.groupValues?.get(1)?.toIntOrNull() ?: -1
-    }
-
-    interface SourceCallback {
-
-        suspend fun searchSuccess(searchBook: SearchBook)
-
-        fun upAdapter()
-
-    }
-
-}

+ 0 - 198
app/src/main/java/io/legado/app/ui/book/group/GroupEditDialog.kt

@@ -1,198 +0,0 @@
-package io.legado.app.ui.book.group
-
-import android.os.Bundle
-import android.view.View
-import android.widget.ArrayAdapter
-import androidx.fragment.app.viewModels
-import io.legado.app.R
-import io.legado.app.base.BaseBottomSheetDialogFragment
-import io.legado.app.data.entities.BookGroup
-import io.legado.app.databinding.DialogBookGroupEditBinding
-import io.legado.app.lib.dialogs.alert
-import io.legado.app.utils.FileUtils
-import io.legado.app.utils.MD5Utils
-import io.legado.app.utils.SelectImageContract
-import io.legado.app.utils.externalFiles
-import io.legado.app.utils.gone
-import io.legado.app.utils.inputStream
-import io.legado.app.utils.launch
-import io.legado.app.utils.readUri
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import splitties.init.appCtx
-import splitties.views.onClick
-import java.io.FileOutputStream
-
-class GroupEditDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_book_group_edit) {
-
-    constructor(bookGroup: BookGroup? = null) : this() {
-        arguments = Bundle().apply {
-            putParcelable("group", bookGroup?.copy())
-        }
-    }
-
-    private var selectedSortIndex = -1
-    private lateinit var sortOptions: List<String>
-
-    private val binding by viewBinding(DialogBookGroupEditBinding::bind)
-    private val viewModel by viewModels<GroupViewModel>()
-    private var bookGroup: BookGroup? = null
-    private val selectImage = registerForActivityResult(SelectImageContract()) {
-        it.uri ?: return@registerForActivityResult
-        readUri(it.uri) { fileDoc, inputStream ->
-            try {
-                var file = requireContext().externalFiles
-                val suffix = fileDoc.name.substringAfterLast(".")
-                val fileName = it.uri.inputStream(requireContext()).getOrThrow().use { tmp ->
-                    MD5Utils.md5Encode(tmp) + ".$suffix"
-                }
-                file = FileUtils.createFileIfNotExist(file, "covers", fileName)
-                FileOutputStream(file).use { outputStream ->
-                    inputStream.copyTo(outputStream)
-                }
-                binding.ivCover.load(file.absolutePath)
-            } catch (e: Exception) {
-                appCtx.toastOnUi(e.localizedMessage)
-            }
-        }
-    }
-
-    override fun onStart() {
-        super.onStart()
-
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        sortOptions = resources.getStringArray(R.array.book_sort).toList()
-
-        @Suppress("DEPRECATION")
-        bookGroup = arguments?.getParcelable("group")
-
-        val sortAdapter = ArrayAdapter(
-            requireContext(),
-            android.R.layout.simple_list_item_1,
-            sortOptions
-        )
-        binding.actvSortMenu.setAdapter(sortAdapter)
-
-        bookGroup?.let { group ->
-            binding.btnDelete.isEnabled = (group.groupId > 0 || group.groupId == Long.MIN_VALUE)
-            binding.tieGroupName.setText(group.groupName)
-            if (group.cover != null) {
-                binding.ivCover.load(group.cover)
-            }
-            binding.cbEnableRefresh.isChecked = group.enableRefresh
-            binding.cbPrivate.isChecked = group.isPrivate
-            if (group.groupId <= 0) {
-                binding.cbPrivate.gone()
-                binding.tvPrivateDesc.gone()
-            }
-
-            selectedSortIndex = group.bookSort
-            val displayIndex = selectedSortIndex + 1
-            if (displayIndex in sortOptions.indices) {
-                binding.actvSortMenu.setText(sortOptions[displayIndex], false)
-            } else {
-                binding.actvSortMenu.setText(sortOptions[0], false)
-                selectedSortIndex = -1
-            }
-
-        } ?: run {
-            binding.btnDelete.gone()
-            binding.ivCover.load()
-            selectedSortIndex = -1
-            binding.actvSortMenu.setText(sortOptions[0], false)
-        }
-
-        binding.actvSortMenu.setOnItemClickListener { _, _, position, _ ->
-            selectedSortIndex = position - 1
-        }
-
-        binding.cbPrivate.setOnCheckedChangeListener { buttonView, isChecked ->
-            if (!isChecked && bookGroup?.isPrivate == true) {
-                alert(R.string.disable_private_group, R.string.sure_disable_private_group) {
-                    yesButton()
-                    noButton {
-                        buttonView.isChecked = true
-                    }
-                }
-            }
-        }
-
-        binding.run {
-            ivCover.onClick {
-                selectImage.launch()
-            }
-            btnReset.onClick {
-                bookGroup?.let {
-                    viewModel.clearCover(it) {
-                        toastOnUi("封面已重置")
-                        dismiss()
-                    }
-                }
-            }
-
-            btnCancel.onClick {
-                dismiss()
-            }
-
-            btnOk.onClick {
-                val groupName = tieGroupName.text?.toString()
-                if (groupName.isNullOrEmpty()) {
-                    toastOnUi("分组名称不能为空")
-                } else {
-                    val bookSort = selectedSortIndex
-                    val coverPath = binding.ivCover.bitmapPath
-                    val enableRefresh = binding.cbEnableRefresh.isChecked
-                    val isPrivate = binding.cbPrivate.isChecked
-                    bookGroup?.let {
-                        it.groupName = groupName
-                        it.cover = coverPath
-                        it.bookSort = bookSort
-                        it.enableRefresh = enableRefresh
-                        it.isPrivate = isPrivate
-                        viewModel.upGroup(it) {
-                            dismiss()
-                        }
-                    } ?: let {
-                        viewModel.addGroup(
-                            groupName,
-                            bookSort,
-                            enableRefresh,
-                            isPrivate,
-                            coverPath
-                        ) {
-                            dismiss()
-                        }
-                    }
-                }
-            }
-
-            btnDelete.onClick {
-                deleteGroup {
-                    bookGroup?.let {
-                        viewModel.delGroup(it) {
-                            dismiss()
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-
-    private fun deleteGroup(ok: () -> Unit) {
-        val message = if (bookGroup?.isPrivate == true) {
-            R.string.sure_del_private_group
-        } else {
-            R.string.sure_del
-        }
-        alert(R.string.delete, message) {
-            yesButton {
-                ok.invoke()
-            }
-            noButton()
-        }
-    }
-
-}

+ 0 - 174
app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt

@@ -1,174 +0,0 @@
-package io.legado.app.ui.book.group
-
-import android.content.Context
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.ItemTouchHelper
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-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.constant.AppLog
-import io.legado.app.data.appDb
-import io.legado.app.data.entities.BookGroup
-import io.legado.app.databinding.DialogRecyclerViewBinding
-import io.legado.app.databinding.ItemBookGroupManageBinding
-//import io.legado.app.lib.theme.accentColor
-//import io.legado.app.lib.theme.backgroundColor
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.widget.recycler.ItemTouchCallback
-import io.legado.app.utils.gone
-import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
-
-/**
- * 书籍分组管理
- */
-class GroupManageDialog : BaseBottomSheetDialogFragment(R.layout.dialog_recycler_view),
-    Toolbar.OnMenuItemClickListener {
-
-    private val viewModel: GroupViewModel by viewModels()
-    private val binding by viewBinding(DialogRecyclerViewBinding::bind)
-    private val adapter by lazy { GroupAdapter(requireContext()) }
-
-    override fun onStart() {
-        super.onStart()
-
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        binding.toolBar.setTitle(R.string.group_manage)
-        initView()
-        initData()
-        initMenu()
-    }
-
-    private fun initView() {
-        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
-        binding.recyclerView.adapter = adapter
-        val itemTouchCallback = ItemTouchCallback(adapter)
-        itemTouchCallback.isCanDrag = true
-        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)
-        //binding.tvOk.setTextColor(requireContext().accentColor)
-        binding.tvOk.visible()
-        binding.tvOk.setOnClickListener {
-            dismissAllowingStateLoss()
-        }
-    }
-
-    private fun initData() {
-        lifecycleScope.launch {
-            appDb.bookGroupDao.flowAll().catch {
-                AppLog.put("书籍分组管理界面获取分组数据失败\n${it.localizedMessage}", it)
-            }.flowOn(IO).conflate().collect {
-                adapter.setItems(it)
-            }
-        }
-    }
-
-    private fun initMenu() {
-        binding.toolBar.setOnMenuItemClickListener(this)
-        binding.toolBar.inflateMenu(R.menu.book_group_manage)
-        //binding.toolBar.menu.applyTint(requireContext())
-    }
-
-    override fun onMenuItemClick(item: MenuItem?): Boolean {
-        when (item?.itemId) {
-            R.id.menu_add -> {
-                if (appDb.bookGroupDao.canAddGroup) {
-                    showDialogFragment(GroupEditDialog())
-                } else {
-                    toastOnUi("分组已达上限(64个)")
-                }
-            }
-        }
-        return true
-    }
-
-    private inner class GroupAdapter(context: Context) :
-        RecyclerAdapter<BookGroup, ItemBookGroupManageBinding>(context),
-        ItemTouchCallback.Callback {
-
-        private var isMoved = false
-
-        override fun getViewBinding(parent: ViewGroup): ItemBookGroupManageBinding {
-            return ItemBookGroupManageBinding.inflate(inflater, parent, false)
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemBookGroupManageBinding,
-            item: BookGroup,
-            payloads: MutableList<Any>
-        ) {
-            binding.run {
-                val nameInfo = item.getManageName(context)
-                tvGroup.text = nameInfo.groupName
-
-                if (nameInfo.suffix != null) {
-                    tvSuffix.visible()
-                    tvSuffix.text = nameInfo.suffix
-                } else {
-                    tvSuffix.gone()
-                }
-
-                swShow.isChecked = item.show
-            }
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemBookGroupManageBinding) {
-            binding.run {
-                tvEdit.setOnClickListener {
-                    getItem(holder.layoutPosition)?.let { bookGroup ->
-                        showDialogFragment(
-                            GroupEditDialog(bookGroup)
-                        )
-                    }
-                }
-                swShow.setOnCheckedChangeListener { buttonView, isChecked ->
-                    if (buttonView.isPressed) {
-                        getItem(holder.layoutPosition)?.let {
-                            viewModel.upGroup(it.copy(show = isChecked))
-                        }
-                    }
-                }
-            }
-        }
-
-        override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
-            swapItem(srcPosition, targetPosition)
-            isMoved = true
-            return true
-        }
-
-        override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
-            if (isMoved) {
-                for ((index, item) in getItems().withIndex()) {
-                    item.order = index + 1
-                }
-                viewModel.upGroup(*getItems().toTypedArray())
-            }
-            isMoved = false
-            val position = viewHolder.layoutPosition
-            if (position != RecyclerView.NO_POSITION) {
-                notifyItemChanged(position)
-            }
-        }
-    }
-
-}

+ 0 - 167
app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt

@@ -1,167 +0,0 @@
-package io.legado.app.ui.book.group
-
-import android.content.Context
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import androidx.fragment.app.viewModels
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.ItemTouchHelper
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-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.BookGroup
-import io.legado.app.databinding.DialogBookGroupPickerBinding
-import io.legado.app.databinding.ItemGroupSelectBinding
-//import io.legado.app.lib.theme.accentColor
-//import io.legado.app.lib.theme.backgroundColor
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.widget.recycler.ItemTouchCallback
-import io.legado.app.ui.widget.recycler.VerticalDivider
-import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.launch
-
-
-class GroupSelectDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_book_group_picker),
-    Toolbar.OnMenuItemClickListener {
-
-    constructor(groupId: Long, requestCode: Int = -1) : this() {
-        arguments = Bundle().apply {
-            putLong("groupId", groupId)
-            putInt("requestCode", requestCode)
-        }
-    }
-
-    private val binding by viewBinding(DialogBookGroupPickerBinding::bind)
-    private var requestCode: Int = -1
-    private val viewModel: GroupViewModel by viewModels()
-    private val adapter by lazy { GroupAdapter(requireContext()) }
-    private val callBack get() = (activity as? CallBack)
-    private var groupId: Long = 0
-
-    override fun onStart() {
-        super.onStart()
-
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        arguments?.let {
-            groupId = it.getLong("groupId")
-            requestCode = it.getInt("requestCode", -1)
-        }
-        initView()
-        initData()
-    }
-
-    private fun initView() {
-        binding.toolBar.title = getString(R.string.group_select)
-        binding.toolBar.inflateMenu(R.menu.book_group_manage)
-        //binding.toolBar.menu.applyTint(requireContext())
-        binding.toolBar.setOnMenuItemClickListener(this)
-        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
-        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
-        binding.recyclerView.adapter = adapter
-        val itemTouchCallback = ItemTouchCallback(adapter)
-        itemTouchCallback.isCanDrag = true
-        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)
-        binding.tvCancel.setOnClickListener {
-            dismissAllowingStateLoss()
-        }
-        //binding.tvOk.setTextColor(requireContext().accentColor)
-        binding.tvOk.setOnClickListener {
-            callBack?.upGroup(requestCode, groupId)
-            dismissAllowingStateLoss()
-        }
-    }
-
-    private fun initData() {
-        lifecycleScope.launch {
-            appDb.bookGroupDao.flowSelect().conflate().collect {
-                adapter.setItems(it)
-            }
-        }
-    }
-
-    override fun onMenuItemClick(item: MenuItem?): Boolean {
-        when (item?.itemId) {
-            R.id.menu_add -> showDialogFragment(
-                GroupEditDialog()
-            )
-        }
-        return true
-    }
-
-    private inner class GroupAdapter(context: Context) :
-        RecyclerAdapter<BookGroup, ItemGroupSelectBinding>(context),
-        ItemTouchCallback.Callback {
-
-        private var isMoved: Boolean = false
-
-        override fun getViewBinding(parent: ViewGroup): ItemGroupSelectBinding {
-            return ItemGroupSelectBinding.inflate(inflater, parent, false)
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemGroupSelectBinding,
-            item: BookGroup,
-            payloads: MutableList<Any>
-        ) {
-            binding.run {
-                //root.setBackgroundColor(context.backgroundColor)
-                cbGroup.text = item.groupName
-                cbGroup.isChecked = (groupId and item.groupId) > 0
-            }
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemGroupSelectBinding) {
-            binding.run {
-                cbGroup.setOnCheckedChangeListener { buttonView, isChecked ->
-                    if (buttonView.isPressed) {
-                        getItem(holder.layoutPosition)?.let {
-                            groupId = if (isChecked) {
-                                groupId + it.groupId
-                            } else {
-                                groupId - it.groupId
-                            }
-                        }
-                    }
-                }
-                tvEdit.setOnClickListener {
-                    showDialogFragment(
-                        GroupEditDialog(getItem(holder.layoutPosition))
-                    )
-                }
-            }
-        }
-
-        override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
-            swapItem(srcPosition, targetPosition)
-            isMoved = true
-            return true
-        }
-
-        override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
-            if (isMoved) {
-                for ((index, item) in getItems().withIndex()) {
-                    item.order = index + 1
-                }
-                viewModel.upGroup(*getItems().toTypedArray())
-            }
-            isMoved = false
-        }
-    }
-
-    interface CallBack {
-        fun upGroup(requestCode: Int, groupId: Long)
-    }
-}

+ 0 - 141
app/src/main/java/io/legado/app/ui/book/import/local/ImportBookAdapter.kt

@@ -1,141 +0,0 @@
-package io.legado.app.ui.book.import.local
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.view.ViewGroup
-import io.legado.app.R
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.constant.AppConst
-import io.legado.app.databinding.ItemImportBookBinding
-import io.legado.app.utils.ConvertUtils
-import io.legado.app.utils.FileDoc
-import io.legado.app.utils.gone
-import io.legado.app.utils.invisible
-import io.legado.app.utils.visible
-
-
-class ImportBookAdapter(context: Context, val callBack: CallBack) :
-    RecyclerAdapter<ImportBook, ItemImportBookBinding>(context) {
-    val selected = hashSetOf<ImportBook>()
-    var checkableCount = 0
-
-    override fun getViewBinding(parent: ViewGroup): ItemImportBookBinding {
-        return ItemImportBookBinding.inflate(inflater, parent, false)
-    }
-
-    override fun onCurrentListChanged() {
-        upCheckableCount()
-    }
-
-    override fun convert(
-        holder: ItemViewHolder,
-        binding: ItemImportBookBinding,
-        item: ImportBook,
-        payloads: MutableList<Any>
-    ) {
-        binding.run {
-            if (payloads.isEmpty()) {
-                if (item.isDir) {
-                    ivIcon.setImageResource(R.drawable.ic_folder)
-                    ivIcon.visible()
-                    cbSelect.invisible()
-                    llBrief.gone()
-                    cbSelect.isChecked = false
-                } else {
-                    if (item.isOnBookShelf) {
-                        ivIcon.setImageResource(R.drawable.ic_book_has)
-                        ivIcon.visible()
-                        cbSelect.invisible()
-                    } else {
-                        ivIcon.invisible()
-                        cbSelect.visible()
-                    }
-                    llBrief.visible()
-                    tvTag.text = item.name.substringAfterLast(".")
-                    tvSize.text = ConvertUtils.formatFileSize(item.size)
-                    tvDate.text = AppConst.dateFormat.format(item.lastModified)
-                    cbSelect.isChecked = selected.contains(item)
-                }
-                tvName.text = item.name
-            } else {
-                cbSelect.isChecked = selected.contains(item)
-            }
-        }
-    }
-
-    override fun registerListener(holder: ItemViewHolder, binding: ItemImportBookBinding) {
-        holder.itemView.setOnClickListener {
-            getItem(holder.layoutPosition)?.let {
-                if (it.isDir) {
-                    callBack.nextDoc(it.file)
-                } else if (!it.isOnBookShelf) {
-                    if (!selected.contains(it)) {
-                        selected.add(it)
-                    } else {
-                        selected.remove(it)
-                    }
-                    notifyItemChanged(holder.layoutPosition, true)
-                    callBack.upCountView()
-                } else {
-                    /* 点击开始阅读 */
-                    callBack.startRead(it.file)
-                }
-            }
-        }
-    }
-
-    private fun upCheckableCount() {
-        checkableCount = 0
-        getItems().forEach {
-            if (!it.isDir && !it.isOnBookShelf) {
-                checkableCount++
-            }
-        }
-        callBack.upCountView()
-    }
-
-    @SuppressLint("NotifyDataSetChanged")
-    fun selectAll(selectAll: Boolean) {
-        if (selectAll) {
-            getItems().forEach {
-                if (!it.isDir && !it.isOnBookShelf) {
-                    selected.add(it)
-                }
-            }
-        } else {
-            selected.clear()
-        }
-        notifyDataSetChanged()
-        callBack.upCountView()
-    }
-
-    fun revertSelection() {
-        getItems().forEach {
-            if (!it.isDir && !it.isOnBookShelf) {
-                if (selected.contains(it)) {
-                    selected.remove(it)
-                } else {
-                    selected.add(it)
-                }
-            }
-        }
-        notifyItemRangeChanged(0, itemCount, true)
-        callBack.upCountView()
-    }
-
-    fun removeSelection() {
-        for (i in getItems().lastIndex downTo 0) {
-            if (getItem(i) in selected) {
-                removeItem(i)
-            }
-        }
-    }
-
-    interface CallBack {
-        fun nextDoc(fileDoc: FileDoc)
-        fun upCountView()
-        fun startRead(fileDoc: FileDoc)
-    }
-
-}

+ 0 - 131
app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt

@@ -1,131 +0,0 @@
-package io.legado.app.ui.book.import.remote
-
-import android.os.Bundle
-import android.text.InputType
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import io.legado.app.R
-import io.legado.app.base.BaseDialogFragment
-import io.legado.app.data.entities.Server
-import io.legado.app.data.entities.rule.RowUi
-import io.legado.app.databinding.DialogWebdavServerBinding
-import io.legado.app.databinding.ItemSourceEditBinding
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.utils.GSON
-import io.legado.app.utils.setLayout
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import org.json.JSONObject
-import org.koin.androidx.viewmodel.ext.android.viewModel
-
-class ServerConfigDialog() : BaseDialogFragment(R.layout.dialog_webdav_server, true),
-    Toolbar.OnMenuItemClickListener {
-
-    constructor(id: Long) : this() {
-        arguments = Bundle().apply {
-            putLong("id", id)
-        }
-    }
-
-    private val binding by viewBinding(DialogWebdavServerBinding::bind)
-    private val viewModel by viewModel<ServerConfigViewModel>()
-
-    private val webDavServerUi = listOf(
-        RowUi("url"),
-        RowUi("username"),
-        RowUi("password", RowUi.Type.password),
-        RowUi("ipRange")
-    )
-
-    override fun onStart() {
-        super.onStart()
-        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //inding.toolBar.setBackgroundColor(primaryColor)
-        binding.toolBar.inflateMenu(R.menu.server_config)
-        //binding.toolBar.menu.applyTint(requireContext())
-        binding.toolBar.setOnMenuItemClickListener(this)
-        viewModel.init(arguments?.getLong("id")) {
-            upConfigView(viewModel.mServer)
-        }
-    }
-
-    override fun onMenuItemClick(item: MenuItem): Boolean {
-        when (item.itemId) {
-            R.id.menu_save -> getServer().let {
-                viewModel.save(it) {
-                    dismissAllowingStateLoss()
-                }
-            }
-        }
-        return true
-    }
-
-    private fun upConfigView(server: Server?) {
-        binding.etName.setText(server?.name)
-        binding.spType.setSelection(
-            when (server?.type) {
-                else -> 0
-            }
-        )
-        when (server?.type) {
-            else -> upWebDavServerUi(server?.getConfigJsonObject())
-        }
-    }
-
-    private fun upWebDavServerUi(config: JSONObject?) {
-        webDavServerUi.forEachIndexed { index, rowUi ->
-            when (rowUi.type) {
-                RowUi.Type.text -> ItemSourceEditBinding.inflate(
-                    layoutInflater,
-                    binding.root,
-                    false
-                ).let {
-                    binding.flexbox.addView(it.root)
-                    it.root.id = index + 1000
-                    it.textInputLayout.hint = rowUi.name
-                    it.editText.setText(config?.getString(rowUi.name))
-                }
-                RowUi.Type.password -> ItemSourceEditBinding.inflate(
-                    layoutInflater,
-                    binding.root,
-                    false
-                ).let {
-                    binding.flexbox.addView(it.root)
-                    it.root.id = index + 1000
-                    it.textInputLayout.hint = rowUi.name
-                    it.editText.inputType =
-                        InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT
-                    it.editText.setText(config?.getString(rowUi.name))
-                }
-            }
-        }
-    }
-
-    private fun getServer(): Server {
-        val server = viewModel.mServer?.copy() ?: Server()
-        server.name = binding.etName.text.toString()
-        server.type = when (binding.spType.selectedItemPosition) {
-            else -> Server.TYPE.WEBDAV
-        }
-        server.config = when (server.type) {
-            else -> GSON.toJson(getWebDavConfig())
-        }
-        return server
-    }
-
-    private fun getWebDavConfig(): HashMap<String, String> {
-        val data = hashMapOf<String, String>()
-        webDavServerUi.forEachIndexed { index, rowUi ->
-            val rowView = binding.root.findViewById<View>(index + 1000)
-            ItemSourceEditBinding.bind(rowView).editText.text?.let {
-                data[rowUi.name] = it.toString()
-            }
-        }
-        return data
-    }
-
-}

+ 0 - 173
app/src/main/java/io/legado/app/ui/book/import/remote/ServersDialog.kt

@@ -1,173 +0,0 @@
-package io.legado.app.ui.book.import.remote
-
-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.BaseDialogFragment
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.constant.AppConst.DEFAULT_WEBDAV_ID
-import io.legado.app.constant.AppLog
-import io.legado.app.data.entities.Server
-import io.legado.app.domain.gateway.ImportBookSettingsGateway
-import io.legado.app.databinding.DialogRecyclerViewBinding
-import io.legado.app.databinding.ItemServerSelectBinding
-import io.legado.app.lib.dialogs.alert
-//import io.legado.app.lib.theme.backgroundColor
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.widget.recycler.VerticalDivider
-import io.legado.app.utils.applyTint
-import io.legado.app.utils.setLayout
-import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
-import org.koin.androidx.viewmodel.ext.android.viewModel
-import org.koin.android.ext.android.inject
-
-/**
- * 服务器配置
- */
-class ServersDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
-    Toolbar.OnMenuItemClickListener {
-
-    val binding by viewBinding(DialogRecyclerViewBinding::bind)
-    val viewModel by viewModel<ServersViewModel>()
-    private val importBookSettingsGateway by inject<ImportBookSettingsGateway>()
-
-    private val callback get() = (activity as? Callback)
-    private val adapter by lazy { ServersAdapter(requireContext()) }
-
-    override fun onStart() {
-        super.onStart()
-        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
-    }
-
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        binding.toolBar.setTitle(R.string.server_config)
-        initView()
-        initData()
-    }
-
-    private fun initView() {
-        binding.toolBar.inflateMenu(R.menu.servers)
-        binding.toolBar.menu.applyTint(requireContext())
-        binding.toolBar.setOnMenuItemClickListener(this)
-        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
-        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
-        binding.recyclerView.adapter = adapter
-        binding.tvFooterLeft.text = getString(R.string.text_default)
-        binding.tvFooterLeft.visible()
-        binding.tvFooterLeft.setOnClickListener {
-            saveSelectedServer(DEFAULT_WEBDAV_ID)
-        }
-        binding.tvCancel.visible()
-        binding.tvCancel.setOnClickListener {
-            dismissAllowingStateLoss()
-        }
-        binding.tvOk.visible()
-        binding.tvOk.setOnClickListener {
-            saveSelectedServer(adapter.selectServerId)
-        }
-    }
-
-    private fun initData() {
-        lifecycleScope.launch {
-            viewModel.flowServers().catch {
-                AppLog.put("服务器配置界面获取数据失败\n${it.localizedMessage}", it)
-            }.flowOn(IO).collect {
-                adapter.setItems(it)
-            }
-        }
-    }
-
-    private fun saveSelectedServer(serverId: Long) {
-        lifecycleScope.launch {
-            importBookSettingsGateway.update { it.copy(remoteServerId = serverId) }
-            dismissAllowingStateLoss()
-        }
-    }
-
-    override fun onMenuItemClick(item: MenuItem): Boolean {
-        when (item.itemId) {
-            R.id.menu_add -> showDialogFragment(ServerConfigDialog())
-        }
-        return true
-    }
-
-    override fun onDismiss(dialog: DialogInterface) {
-        super.onDismiss(dialog)
-        callback?.onDialogDismiss("serversDialog")
-    }
-
-    inner class ServersAdapter(context: Context) :
-        RecyclerAdapter<Server, ItemServerSelectBinding>(context) {
-
-        var selectServerId: Long = importBookSettingsGateway.currentSettings.remoteServerId
-
-        override fun getViewBinding(parent: ViewGroup): ItemServerSelectBinding {
-            return ItemServerSelectBinding.inflate(inflater, parent, false)
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemServerSelectBinding) {
-            binding.rbServer.setOnCheckedChangeListener { buttonView, isChecked ->
-                if (buttonView.isPressed && isChecked) {
-                    selectServerId = getItemByLayoutPosition(holder.layoutPosition)!!.id
-                    adapter.updateItems(0, itemCount - 1, "upSelect")
-                }
-            }
-            binding.ivEdit.setOnClickListener {
-                getItemByLayoutPosition(holder.layoutPosition)?.let { server ->
-                    showDialogFragment(ServerConfigDialog(server.id))
-                }
-            }
-            binding.ivDelete.setOnClickListener {
-                alert {
-                    setTitle(R.string.draw)
-                    setMessage(R.string.sure_del)
-                    yesButton {
-                        getItemByLayoutPosition(holder.layoutPosition)?.let { server ->
-                            viewModel.delete(server)
-                        }
-                    }
-                    noButton()
-                }
-            }
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemServerSelectBinding,
-            item: Server,
-            payloads: MutableList<Any>
-        ) {
-            if (payloads.isEmpty()) {
-                //binding.root.setBackgroundColor(context.backgroundColor)
-                binding.rbServer.text = item.name
-                binding.rbServer.isChecked = item.id == selectServerId
-            } else {
-                binding.rbServer.isChecked = item.id == selectServerId
-            }
-        }
-
-    }
-
-    interface Callback {
-
-        fun onDialogDismiss(tag: String)
-
-    }
-
-}

+ 33 - 8
app/src/main/java/io/legado/app/ui/book/info/BookInfoRouteScreen.kt

@@ -2,7 +2,9 @@ package io.legado.app.ui.book.info
 
 import android.app.Activity
 import android.content.Intent
+import android.net.Uri
 import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.ExperimentalSharedTransitionApi
@@ -11,8 +13,12 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -28,16 +34,21 @@ import io.legado.app.help.book.isLocal
 import io.legado.app.model.SourceCallBack
 import io.legado.app.ui.book.info.edit.BookInfoEditActivity
 import io.legado.app.ui.book.toc.TocActivityResult
-import io.legado.app.ui.file.HandleFileContract
 import io.legado.app.ui.login.SourceLoginJsExtensions
+import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
+import io.legado.app.utils.RealPathUtil
 import io.legado.app.utils.StartActivityContract
+import io.legado.app.utils.externalFiles
+import io.legado.app.utils.isContentScheme
 import io.legado.app.utils.openFileUri
 import io.legado.app.utils.sendToClip
+import io.legado.app.utils.takePersistablePermissionSafely
 import io.legado.app.utils.toastOnUi
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
+import splitties.init.appCtx
 
 @OptIn(ExperimentalSharedTransitionApi::class)
 @Composable
@@ -72,15 +83,22 @@ fun BookInfoRouteScreen(
     val lifecycleOwner = LocalLifecycleOwner.current
     val uiState by viewModel.uiState.collectAsStateWithLifecycle()
     val showMangaUi by rememberUpdatedState(uiState.showMangaUi)
+    var showSelectBooksDirSheet by remember { mutableStateOf(false) }
 
     val tocActivityResult = rememberLauncherForActivityResult(TocActivityResult()) {
         viewModel.onTocResult(it)
     }
-    val localBookTreeSelect = rememberLauncherForActivityResult(HandleFileContract()) {
-        it.uri?.let { treeUri ->
-            viewModel.onIntent(BookInfoIntent.SetDefaultBookTreeUri(treeUri.toString()))
+    val localBookTreeSelect =
+        rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
+            if (uri == null) return@rememberLauncherForActivityResult
+            if (RealPathUtil.getTreePath(uri)?.startsWith(appCtx.externalFiles.parent!!) == true) {
+                return@rememberLauncherForActivityResult
+            }
+            if (uri.isContentScheme()) {
+                uri.takePersistablePermissionSafely(activity)
+            }
+            viewModel.onIntent(BookInfoIntent.SetDefaultBookTreeUri(uri.toString()))
         }
-    }
     val infoEditResult = rememberLauncherForActivityResult(
         StartActivityContract(BookInfoEditActivity::class.java)
     ) {
@@ -159,9 +177,7 @@ fun BookInfoRouteScreen(
                     onOpenSourceLogin(effect.sourceUrl)
                 }
 
-                BookInfoEffect.OpenSelectBooksDir -> localBookTreeSelect.launch {
-                    title = activity.getString(R.string.select_book_folder)
-                }
+                BookInfoEffect.OpenSelectBooksDir -> showSelectBooksDirSheet = true
 
                 is BookInfoEffect.OpenFile -> activity.openFileUri(effect.uri, effect.mimeType)
                 is BookInfoEffect.RunSourceCallback -> {
@@ -204,6 +220,15 @@ fun BookInfoRouteScreen(
         }
     }
 
+    FilePickerSheet(
+        show = showSelectBooksDirSheet,
+        onDismissRequest = { showSelectBooksDirSheet = false },
+        title = stringResource(R.string.select_book_folder),
+        onSelectSysDir = {
+            showSelectBooksDirSheet = false
+            localBookTreeSelect.launch(null)
+        },
+    )
     BookInfoScreen(
         state = uiState,
         groups = viewModel.allGroups

+ 1 - 7
app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt

@@ -4,10 +4,9 @@ import android.os.Bundle
 import org.koin.androidx.viewmodel.ext.android.viewModel
 import androidx.compose.runtime.Composable
 import io.legado.app.base.BaseComposeActivity
-import io.legado.app.ui.book.changecover.ChangeCoverDialog
 import io.legado.app.ui.main.MainActivity
 
-class BookInfoEditActivity : BaseComposeActivity(), ChangeCoverDialog.CallBack {
+class BookInfoEditActivity : BaseComposeActivity() {
 
     private val viewModel by viewModel<BookInfoEditViewModel>()
 
@@ -44,9 +43,4 @@ class BookInfoEditActivity : BaseComposeActivity(), ChangeCoverDialog.CallBack {
         }
     }
 
-    override fun coverChangeTo(coverUrl: String) {
-        // 更新封面 URL
-        viewModel.onCoverUrlChange(coverUrl)
-    }
-
 }

+ 13 - 10
app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt

@@ -55,7 +55,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
-import io.legado.app.ui.book.changecover.ChangeCoverDialog
+import io.legado.app.ui.book.info.ChangeCoverSheet
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.fadingEdge
 import io.legado.app.ui.widget.components.AppScaffold
@@ -76,7 +76,6 @@ import io.legado.app.ui.widget.components.topbar.TopBarActionButton
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
 import io.legado.app.utils.SelectImageContract
 import io.legado.app.utils.launch
-import io.legado.app.utils.showDialogFragment
 import sh.calvin.reorderable.ReorderableItem
 import sh.calvin.reorderable.rememberReorderableLazyListState
 
@@ -147,6 +146,17 @@ fun BookInfoEditContent(
     onOpenEventList: (String) -> Unit,
 ) {
     val context = LocalContext.current
+    var showChangeCoverSheet by remember { mutableStateOf(false) }
+    ChangeCoverSheet(
+        show = showChangeCoverSheet,
+        name = uiState.name,
+        author = uiState.author,
+        onDismissRequest = { showChangeCoverSheet = false },
+        onSelect = { coverUrl ->
+            viewModel.onCoverUrlChange(coverUrl)
+            showChangeCoverSheet = false
+        },
+    )
 
     val selectCover = rememberLauncherForActivityResult(SelectImageContract()) {
         it.uri?.let { uri ->
@@ -176,14 +186,7 @@ fun BookInfoEditContent(
             ) {
                 Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
                     MediumOutlinedButton(
-                        onClick = {
-                            (context as? BookInfoEditActivity)?.showDialogFragment(
-                                ChangeCoverDialog(
-                                    uiState.name,
-                                    uiState.author
-                                )
-                            )
-                        },
+                        onClick = { showChangeCoverSheet = true },
                         icon = Icons.Default.ImageSearch,
                         contentDescription = stringResource(R.string.refresh_cover)
                     )

+ 0 - 68
app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentAdapter.kt

@@ -1,68 +0,0 @@
-package io.legado.app.ui.book.searchContent
-
-import android.content.Context
-import android.view.ViewGroup
-import androidx.core.view.isVisible
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.databinding.ItemSearchListBinding
-import io.legado.app.utils.themeColor
-
-
-class SearchContentAdapter(context: Context, val callback: Callback) :
-    RecyclerAdapter<SearchResult, ItemSearchListBinding>(context) {
-
-    private val textColorInt = context.themeColor(com.google.android.material.R.attr.colorOnSurface)
-    private val accentColorInt = context.themeColor(androidx.appcompat.R.attr.colorPrimary)
-    private val bgColorInt = context.themeColor(com.google.android.material.R.attr.colorSecondaryContainer)
-    private val isEInkMode get() = org.koin.core.context.GlobalContext.get().get<io.legado.app.domain.gateway.ThemeSettingsGateway>().currentSettings.appTheme == "4"
-
-    override fun getViewBinding(parent: ViewGroup): ItemSearchListBinding {
-        return ItemSearchListBinding.inflate(inflater, parent, false)
-    }
-
-    override fun convert(
-        holder: ItemViewHolder,
-        binding: ItemSearchListBinding,
-        item: SearchResult,
-        payloads: MutableList<Any>
-    ) {
-        binding.run {
-            val isDur = callback.durChapterIndex() == item.chapterIndex
-            if (payloads.isEmpty()) {
-                tvTitle.text = item.getTitleSpannable(textColorInt, isEInkMode)
-                tvContent.text = item.getContentSpannable(
-                    textColorInt,
-                    accentColorInt,
-                    bgColorInt,
-                    isEInkMode,
-                )
-
-                if (item.progressPercent > 0) {
-                    cdCount.isVisible = true
-                    tvCount.text = String.format("%.1f%%", item.progressPercent)
-                } else {
-                    tvCount.isVisible = false
-                }
-
-                cdRoot.setCardBackgroundColor(
-                    if (isDur) context.themeColor(com.google.android.material.R.attr.colorSurfaceContainer)
-                    else context.themeColor(com.google.android.material.R.attr.colorSurface)
-                )
-            }
-        }
-    }
-
-    override fun registerListener(holder: ItemViewHolder, binding: ItemSearchListBinding) {
-        binding.cdRoot.setOnClickListener {
-            getItem(holder.layoutPosition)?.let {
-                if (it.query.isNotBlank()) callback.openSearchResult(it, holder.layoutPosition)
-            }
-        }
-    }
-
-    interface Callback {
-        fun openSearchResult(searchResult: SearchResult, index: Int)
-        fun durChapterIndex(): Int
-    }
-}

+ 0 - 296
app/src/main/java/io/legado/app/ui/file/FilePickerDialog.kt

@@ -1,296 +0,0 @@
-package io.legado.app.ui.file
-
-import android.annotation.SuppressLint
-import android.content.DialogInterface
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.util.TypedValue
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import androidx.core.content.ContextCompat
-import androidx.core.content.res.ResourcesCompat
-import androidx.core.graphics.drawable.DrawableCompat
-import androidx.fragment.app.FragmentManager
-import androidx.fragment.app.viewModels
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import io.legado.app.R
-import io.legado.app.base.BaseDialogFragment
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.databinding.DialogEditTextBinding
-import io.legado.app.databinding.DialogFileChooserBinding
-import io.legado.app.databinding.ItemFilePickerBinding
-import io.legado.app.databinding.ItemPathPickerBinding
-import io.legado.app.lib.dialogs.alert
-//import io.legado.app.lib.theme.getPrimaryDisabledTextColor
-//import io.legado.app.lib.theme.getPrimaryTextColor
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.file.HandleFileContract.Companion.FILE
-import io.legado.app.ui.widget.recycler.VerticalDivider
-import io.legado.app.utils.FileUtils
-import io.legado.app.utils.applyTint
-import io.legado.app.utils.getCompatColor
-import io.legado.app.utils.setLayout
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import java.io.File
-
-
-class FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),
-    Toolbar.OnMenuItemClickListener {
-
-    companion object {
-        const val tag = "FileChooserDialog"
-
-        fun show(
-            manager: FragmentManager,
-            mode: Int = FILE,
-            title: String? = null,
-            initPath: String? = null,
-            isShowHideDir: Boolean = false,
-            allowExtensions: Array<String>? = null,
-        ) {
-            FilePickerDialog().apply {
-                val bundle = Bundle()
-                bundle.putInt("mode", mode)
-                bundle.putString("title", title)
-                bundle.putBoolean("isShowHideDir", isShowHideDir)
-                bundle.putString("initPath", initPath)
-                bundle.putStringArray("allowExtensions", allowExtensions)
-                arguments = bundle
-            }.show(manager, tag)
-        }
-    }
-
-    private val binding by viewBinding(DialogFileChooserBinding::bind)
-    private val viewModel by viewModels<FilePickerViewModel>()
-    private val dirParent = ".."
-    private val pathAdapter by lazy { PathAdapter() }
-    private val fileAdapter by lazy { FileAdapter() }
-
-    override fun onStart() {
-        super.onStart()
-        setLayout(0.9f, 0.8f)
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        //view.setBackgroundResource(R.color.background_card)
-        initMenu()
-        initContentView()
-        viewModel.filesLiveData.observe(viewLifecycleOwner) {
-            fileAdapter.selectFile = null
-            fileAdapter.setItems(it)
-        }
-        viewModel.initData(arguments)
-        binding.toolBar.title = arguments?.getString("title") ?: let {
-            if (viewModel.isSelectDir) {
-                getString(R.string.folder_chooser)
-            } else {
-                getString(R.string.file_chooser)
-            }
-        }
-    }
-
-    private fun initMenu() {
-        binding.toolBar.inflateMenu(R.menu.file_chooser)
-        binding.toolBar.menu.applyTint(requireContext())
-        binding.toolBar.setOnMenuItemClickListener(this)
-    }
-
-    private fun initContentView() {
-        binding.rvPath.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
-        binding.rvPath.adapter = pathAdapter
-
-        binding.rvFile.addItemDecoration(VerticalDivider(requireContext()))
-        binding.rvFile.layoutManager = LinearLayoutManager(activity)
-        binding.rvFile.adapter = fileAdapter
-
-        binding.tvOk.setOnClickListener {
-            if (viewModel.isSelectDir) {
-                viewModel.lastDir?.let {
-                    setResultData(it.path)
-                    dismissAllowingStateLoss()
-                }
-            } else {
-                val file = fileAdapter.selectFile
-                if (file == null) {
-                    toastOnUi("请选择文件")
-                } else {
-                    setResultData(file.path)
-                    dismissAllowingStateLoss()
-                }
-            }
-        }
-    }
-
-    override fun onMenuItemClick(item: MenuItem?): Boolean {
-        when (item?.itemId) {
-            R.id.menu_create -> alert(R.string.create_folder) {
-                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
-                    editView.hint = "文件夹名"
-                }
-                customView { alertBinding.root }
-                okButton {
-                    val text = alertBinding.editView.text?.toString()
-                    if (text.isNullOrBlank()) {
-                        toastOnUi("文件夹名不能为空")
-                    } else {
-                        viewModel.createFolder(text.trim())
-                    }
-                }
-                cancelButton()
-            }
-        }
-        return true
-    }
-
-    private fun setResultData(path: String) {
-        val data = Intent().setData(Uri.fromFile(File(path)))
-        (parentFragment as? CallBack)?.onResult(data)
-        (activity as? CallBack)?.onResult(data)
-    }
-
-    override fun onDismiss(dialog: DialogInterface) {
-        super.onDismiss(dialog)
-        activity?.finish()
-    }
-
-    @SuppressLint("SetTextI18n")
-    inner class PathAdapter :
-        RecyclerAdapter<File, ItemPathPickerBinding>(requireContext()) {
-
-        private val arrowIcon = ContextCompat.getDrawable(requireContext(),R.drawable.ic_arrow_right)
-
-        init {
-            addHeaderView {
-                ItemPathPickerBinding.inflate(inflater, it, false).apply {
-                    textView.text = "root"
-                    imageView.setImageDrawable(arrowIcon)
-                    root.setOnClickListener {
-                        viewModel.subDocs.clear()
-                        setItems(viewModel.subDocs)
-                        viewModel.upFiles(viewModel.rootDoc)
-                    }
-                }
-            }
-        }
-
-        override fun getViewBinding(parent: ViewGroup): ItemPathPickerBinding {
-            return ItemPathPickerBinding.inflate(inflater, parent, false).apply {
-                imageView.setImageDrawable(arrowIcon)
-            }
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemPathPickerBinding) {
-            binding.root.setOnClickListener {
-                viewModel.subDocs = viewModel.subDocs.subList(0, holder.layoutPosition)
-                setItems(viewModel.subDocs)
-                viewModel.upFiles(viewModel.subDocs.lastOrNull())
-            }
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemPathPickerBinding,
-            item: File,
-            payloads: MutableList<Any>
-        ) {
-            binding.textView.text = item.name
-        }
-
-    }
-
-    inner class FileAdapter : RecyclerAdapter<File, ItemFilePickerBinding>(requireContext()) {
-        private val upIcon = ContextCompat.getDrawable(requireContext(),R.drawable.ic_return)
-        private val folderIcon = ContextCompat.getDrawable(requireContext(),R.drawable.ic_folder)
-        private val fileIcon = ContextCompat.getDrawable(requireContext(),R.drawable.ic_file)
-        private val selectDrawable =
-            ResourcesCompat.getDrawable(resources, R.drawable.shape_radius_1dp, null)!!.apply {
-                // 从主题获取颜色
-                val typedValue = TypedValue()
-                requireContext().theme.resolveAttribute(android.R.attr.colorPrimary, typedValue, true)
-                val primaryColor = typedValue.data
-                DrawableCompat.setTint(this, primaryColor)
-            }
-        var selectFile: File? = null
-
-        override fun getViewBinding(parent: ViewGroup): ItemFilePickerBinding {
-            return ItemFilePickerBinding.inflate(inflater, parent, false)
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemFilePickerBinding) {
-            binding.root.setOnClickListener {
-                val item = getItemByLayoutPosition(holder.layoutPosition)
-                item?.let {
-                    if (item == viewModel.lastDir) {
-                        viewModel.subDocs.removeLastOrNull()
-                        pathAdapter.setItems(viewModel.subDocs)
-                        viewModel.upFiles(viewModel.subDocs.lastOrNull() ?: viewModel.rootDoc)
-                    } else if (item.isDirectory) {
-                        viewModel.subDocs.add(item)
-                        pathAdapter.setItems(viewModel.subDocs)
-                        viewModel.upFiles(item)
-                    } else if (viewModel.isSelectFile) {
-                        viewModel.allowExtensions.let {
-                            if (it.isNullOrEmpty() || it.contains(FileUtils.getExtension(item.path))) {
-                                selectFile = item
-                                notifyItemRangeChanged(getHeaderCount(), itemCount, "selectFile")
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemFilePickerBinding,
-            item: File,
-            payloads: MutableList<Any>
-        ) {
-            if (payloads.isEmpty()) {
-                if (item == viewModel.lastDir) {
-                    binding.imageView.setImageDrawable(upIcon)
-                    binding.textView.text = dirParent
-                } else if (item.isDirectory) {
-                    binding.imageView.setImageDrawable(folderIcon)
-                    binding.textView.text = item.name
-                } else {
-                    binding.imageView.setImageDrawable(fileIcon)
-                    binding.textView.text = item.name
-                }
-                if (item.isDirectory) {
-                    //binding.textView.setTextColor(primaryTextColor)
-                } else {
-                    if (viewModel.isSelectDir) {
-                        //binding.textView.setTextColor(disabledTextColor)
-                    } else {
-                        viewModel.allowExtensions?.let {
-                            if (it.isEmpty() || it.contains(FileUtils.getExtension(item.path))) {
-                                //binding.textView.setTextColor(primaryTextColor)
-                            } else {
-                                //binding.textView.setTextColor(disabledTextColor)
-                            }
-                        }// ?: binding.textView.setTextColor(primaryTextColor)
-                    }
-                }
-            }
-            binding.root.isSelected = item == selectFile
-            if (item == selectFile) {
-                binding.root.background = selectDrawable
-            } else {
-                binding.root.setBackgroundColor(getCompatColor(R.color.transparent))
-            }
-        }
-
-    }
-
-    interface CallBack {
-        fun onResult(data: Intent)
-    }
-}

+ 0 - 76
app/src/main/java/io/legado/app/ui/file/FilePickerViewModel.kt

@@ -1,76 +0,0 @@
-package io.legado.app.ui.file
-
-import android.app.Application
-import android.os.Bundle
-import android.os.Environment
-import androidx.lifecycle.MutableLiveData
-import io.legado.app.base.BaseViewModel
-import io.legado.app.exception.NoStackTraceException
-import io.legado.app.utils.toastOnUi
-import java.io.File
-
-class FilePickerViewModel(application: Application) : BaseViewModel(application) {
-
-    var rootDoc: File? = Environment.getExternalStorageDirectory()
-    var subDocs = mutableListOf<File>()
-    val filesLiveData = MutableLiveData<List<File>>()
-    var mode: Int = HandleFileContract.FILE
-    var isShowHideDir: Boolean = false
-    var allowExtensions: Array<String>? = null
-    val isSelectDir: Boolean get() = mode == HandleFileContract.DIR
-    val isSelectFile: Boolean get() = mode == HandleFileContract.FILE
-    val lastDir: File? get() = subDocs.lastOrNull() ?: rootDoc
-
-    fun initData(arguments: Bundle?) {
-        arguments?.let {
-            mode = it.getInt("mode", HandleFileContract.FILE)
-            isShowHideDir = it.getBoolean("isShowHideDir")
-            it.getString("initPath")?.let { path ->
-                rootDoc = File(path)
-            }
-            allowExtensions = it.getStringArray("allowExtensions")
-        }
-        upFiles(rootDoc)
-    }
-
-    fun upFiles(parentFile: File?) {
-        execute {
-            parentFile ?: return@execute emptyList()
-            if (parentFile == rootDoc) {
-                parentFile.listFiles()?.sortedWith(
-                    compareBy({ it.isFile }, { it.name })
-                )
-            } else {
-                val list = arrayListOf(parentFile)
-                parentFile.listFiles()?.sortedWith(
-                    compareBy({ it.isFile }, { it.name })
-                )?.let {
-                    list.addAll(it)
-                }
-                list
-            }
-        }.onStart {
-            filesLiveData.postValue(emptyList())
-        }.onSuccess {
-            filesLiveData.postValue(it ?: emptyList())
-        }.onError {
-            context.toastOnUi(it.localizedMessage)
-        }
-    }
-
-    fun createFolder(name: String) {
-        execute {
-            val dir = lastDir ?: throw NoStackTraceException("父文件夹不存在")
-            val folder = File(dir, name)
-            if (!folder.canonicalPath.contains(dir.canonicalPath)) {
-                throw NoStackTraceException("非法文件名")
-            }
-            folder.mkdir()
-        }.onSuccess {
-            upFiles(lastDir)
-        }.onError {
-            context.toastOnUi(it.localizedMessage)
-        }
-    }
-
-}

+ 0 - 307
app/src/main/java/io/legado/app/ui/file/HandleFileActivity.kt

@@ -1,307 +0,0 @@
-package io.legado.app.ui.file
-
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.os.Environment
-import android.webkit.MimeTypeMap
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.activity.viewModels
-import androidx.lifecycle.lifecycleScope
-import io.legado.app.R
-import io.legado.app.base.VMBaseActivity
-import io.legado.app.constant.AppLog
-import io.legado.app.databinding.ActivityTranslucenceBinding
-import io.legado.app.databinding.DialogEditTextBinding
-import io.legado.app.help.IntentData
-import io.legado.app.lib.dialogs.SelectItem
-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.utils.checkWrite
-import io.legado.app.utils.externalFiles
-import io.legado.app.utils.getJsonArray
-import io.legado.app.utils.isContentScheme
-import io.legado.app.utils.launch
-import io.legado.app.utils.takePersistablePermissionSafely
-import io.legado.app.utils.toastOnUi
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import splitties.init.appCtx
-import java.io.File
-
-class HandleFileActivity :
-    VMBaseActivity<ActivityTranslucenceBinding, HandleFileViewModel>(),
-    FilePickerDialog.CallBack {
-
-    override val binding by viewBinding(ActivityTranslucenceBinding::inflate)
-    override val viewModel by viewModels<HandleFileViewModel>()
-    private var mode = 0
-
-    private val selectDocTree =
-        registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
-            uri?.let {
-                if (uri.isContentScheme()) {
-                    uri.takePersistablePermissionSafely(this)
-                }
-                onResult(Intent().setData(uri))
-            } ?: finish()
-        }
-
-    private val selectDoc = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
-        it?.let {
-            if (it.isContentScheme()) {
-                it.takePersistablePermissionSafely(this)
-            }
-            onResult(Intent().setData(it))
-        } ?: finish()
-    }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        mode = intent.getIntExtra("mode", 0)
-        viewModel.errorLiveData.observe(this) {
-            toastOnUi(it)
-            finish()
-        }
-        val allowExtensions = intent.getStringArrayExtra("allowExtensions")
-        val selectList: ArrayList<SelectItem<Int>> = when (mode) {
-            HandleFileContract.DIR_SYS -> getDirActions(true)
-            HandleFileContract.DIR -> getDirActions()
-            HandleFileContract.FILE -> getFileActions()
-            HandleFileContract.EXPORT -> arrayListOf(
-                SelectItem(getString(R.string.upload_url), 111)
-            ).apply {
-                addAll(getDirActions())
-            }
-
-            else -> arrayListOf()
-        }
-        intent.getJsonArray<SelectItem<Int>>("otherActions")?.let {
-            selectList.addAll(it)
-        }
-        val title = intent.getStringExtra("title") ?: let {
-            when (mode) {
-                HandleFileContract.EXPORT -> return@let getString(R.string.export)
-                HandleFileContract.DIR -> return@let getString(R.string.select_folder)
-                else -> return@let getString(R.string.select_file)
-            }
-        }
-        alert(title) {
-            items(selectList) { _, item, _ ->
-                when (item.value) {
-                    HandleFileContract.DIR -> kotlin.runCatching {
-                        selectDocTree.launch()
-                    }.onFailure {
-                        AppLog.put(getString(R.string.open_sys_dir_picker_error), it, true)
-                        checkPermissions {
-                            FilePickerDialog.show(
-                                supportFragmentManager,
-                                mode = HandleFileContract.DIR
-                            )
-                        }
-                    }
-
-                    HandleFileContract.FILE -> kotlin.runCatching {
-                        selectDoc.launch(typesOfExtensions(allowExtensions))
-                    }.onFailure {
-                        AppLog.put(getString(R.string.open_sys_dir_picker_error), it, true)
-                        checkPermissions {
-                            FilePickerDialog.show(
-                                supportFragmentManager,
-                                mode = HandleFileContract.FILE,
-                                allowExtensions = allowExtensions
-                            )
-                        }
-                    }
-
-                    10 -> checkPermissions {
-                        @Suppress("DEPRECATION")
-                        lifecycleScope.launchWhenResumed {
-                            FilePickerDialog.show(
-                                supportFragmentManager,
-                                mode = HandleFileContract.DIR
-                            )
-                        }
-                    }
-
-                    11 -> checkPermissions {
-                        @Suppress("DEPRECATION")
-                        lifecycleScope.launchWhenResumed {
-                            FilePickerDialog.show(
-                                supportFragmentManager,
-                                mode = HandleFileContract.FILE,
-                                allowExtensions = allowExtensions
-                            )
-                        }
-                    }
-
-                    111 -> getFileData()?.let {
-                        viewModel.upload(it.first, it.second, it.third) { url ->
-                            val uri = Uri.parse(url)
-                            setResult(RESULT_OK, Intent().setData(uri))
-                            finish()
-                        }
-                    }
-
-                    112 -> checkPermissions { // 手动输入目录路径
-                        showInputDirectoryDialog()
-                    }
-
-                    else -> {
-                        val path = item.title
-                        val uri = if (path.isContentScheme()) {
-                            Uri.parse(path)
-                        } else {
-                            Uri.fromFile(File(path))
-                        }
-                        onResult(Intent().setData(uri))
-                    }
-                }
-            }
-            onCancelled {
-                finish()
-            }
-        }
-    }
-
-    private fun showInputDirectoryDialog() {
-        val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
-            editView.hint = getString(R.string.enter_directory_path)
-        }
-
-        alert(getString(R.string.manual_input)) {
-            customView { alertBinding.root }
-            okButton {
-                val inputPath = alertBinding.editView.text.toString()
-                if (inputPath.isBlank()) {
-                    toastOnUi(getString(R.string.empty_directory_input))
-                    return@okButton
-                }
-                val file = File(inputPath)
-                if (file.exists() &&
-                    file.isDirectory &&
-                    isExternalStorage(file) &&
-                    file.checkWrite()
-                ) {
-                    onResult(Intent().setData(Uri.fromFile(file)))
-                } else {
-                    toastOnUi(getString(R.string.invalid_directory))
-                }
-            }
-            onDismiss {
-                finish()
-            }
-            cancelButton()
-        }
-    }
-
-    private fun isExternalStorage(path: File): Boolean {
-        if (path.canonicalPath.startsWith(appCtx.externalFiles.parent!!)) {
-            return false
-        }
-        try {
-            if (Environment.isExternalStorageEmulated(path)) {
-                return true
-            }
-        } catch (_: IllegalArgumentException) {
-        }
-        try {
-            if (Environment.isExternalStorageRemovable(path)) {
-                return true
-            }
-        } catch (_: IllegalArgumentException) {
-        }
-        return false
-    }
-
-    private fun getFileData(): Triple<String, Any, String>? {
-        val fileName = intent.getStringExtra("fileName")
-        val file = intent.getStringExtra("fileKey")?.let {
-            IntentData.get<Any>(it)
-        }
-        val contentType = intent.getStringExtra("contentType")
-        if (fileName != null && file != null && contentType != null) {
-            return Triple(fileName, file, contentType)
-        }
-        return null
-    }
-
-    private fun getDirActions(onlySys: Boolean = false): ArrayList<SelectItem<Int>> {
-        return if (onlySys) {
-            arrayListOf(
-                SelectItem(getString(R.string.sys_folder_picker), HandleFileContract.DIR),
-                SelectItem(getString(R.string.manual_input), 112) // 添加手动输入选项
-            )
-        } else {
-            arrayListOf(
-                SelectItem(getString(R.string.sys_folder_picker), HandleFileContract.DIR),
-                SelectItem(getString(R.string.app_folder_picker), 10),
-                SelectItem(getString(R.string.manual_input), 112) // 添加手动输入选项
-            )
-        }
-    }
-
-    private fun getFileActions(): ArrayList<SelectItem<Int>> {
-        return arrayListOf(
-            SelectItem(getString(R.string.sys_file_picker), HandleFileContract.FILE),
-            SelectItem(getString(R.string.app_file_picker), 11)
-        )
-    }
-
-    private fun checkPermissions(success: (() -> Unit)? = null) {
-        PermissionsCompat.Builder()
-            .addPermissions(*Permissions.Group.STORAGE)
-            .rationale(R.string.tip_perm_request_storage)
-            .onGranted {
-                success?.invoke()
-            }
-            .onDenied {
-                finish()
-            }
-            .onError {
-                finish()
-            }
-            .request()
-    }
-
-    private fun typesOfExtensions(allowExtensions: Array<String>?): Array<String> {
-        val types = hashSetOf<String>()
-        if (allowExtensions.isNullOrEmpty()) {
-            types.add("*/*")
-        } else {
-            allowExtensions.forEach {
-                when (it) {
-                    "*" -> types.add("*/*")
-                    "txt", "xml" -> types.add("text/*")
-                    else -> {
-                        val mime = MimeTypeMap.getSingleton()
-                            .getMimeTypeFromExtension(it)
-                            ?: "application/octet-stream"
-                        types.add(mime)
-                    }
-                }
-            }
-        }
-        return types.toTypedArray()
-    }
-
-    override fun onResult(data: Intent) {
-        val uri = data.data
-        uri ?: let {
-            finish()
-            return
-        }
-        if (mode == HandleFileContract.EXPORT) {
-            getFileData()?.let { fileData ->
-                viewModel.saveToLocal(uri, fileData.first, fileData.second) { savedUri ->
-                    setResult(RESULT_OK, Intent().setData(savedUri))
-                    finish()
-                }
-            }
-        } else {
-            data.putExtra("value", intent.getStringExtra("value"))
-            setResult(RESULT_OK, data)
-            finish()
-        }
-    }
-}

+ 0 - 90
app/src/main/java/io/legado/app/ui/file/HandleFileContract.kt

@@ -1,90 +0,0 @@
-package io.legado.app.ui.file
-
-import android.app.Activity.RESULT_OK
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import androidx.activity.result.contract.ActivityResultContract
-import io.legado.app.help.IntentData
-import io.legado.app.lib.dialogs.SelectItem
-import io.legado.app.utils.RealPathUtil
-import io.legado.app.utils.externalFiles
-import io.legado.app.utils.putJson
-import splitties.init.appCtx
-
-@Suppress("unused")
-class HandleFileContract :
-    ActivityResultContract<(HandleFileContract.HandleFileParam.() -> Unit)?, HandleFileContract.Result>() {
-
-    private var requestCode: Int = 0
-
-    override fun createIntent(context: Context, input: (HandleFileParam.() -> Unit)?): Intent {
-        val intent = Intent(context, HandleFileActivity::class.java)
-        val handleFileParam = HandleFileParam()
-        input?.let {
-            handleFileParam.apply(input)
-        }
-        handleFileParam.let {
-            requestCode = it.requestCode
-            intent.putExtra("mode", it.mode)
-            intent.putExtra("title", it.title)
-            intent.putExtra("allowExtensions", it.allowExtensions)
-            intent.putJson("otherActions", it.otherActions)
-            it.fileData?.let { fileData ->
-                intent.putExtra("fileName", fileData.name)
-                intent.putExtra("fileKey", IntentData.put(fileData.data))
-                intent.putExtra("contentType", fileData.type)
-            }
-            intent.putExtra("value", it.value)
-        }
-        return intent
-    }
-
-    override fun parseResult(resultCode: Int, intent: Intent?): Result {
-        if (resultCode != RESULT_OK || intent == null) {
-            return Result(null, requestCode, null)
-        }
-
-        val dataUri = intent.data
-        val uri = if (dataUri == null ||
-            RealPathUtil.getTreePath(dataUri)?.startsWith(appCtx.externalFiles.parent!!) == true
-        ) {
-            null
-        } else {
-            dataUri
-        }
-
-        return Result(uri, requestCode, intent.getStringExtra("value"))
-    }
-
-    companion object {
-        const val DIR = 0
-        const val FILE = 1
-        const val DIR_SYS = 2
-        const val EXPORT = 3
-    }
-
-    @Suppress("ArrayInDataClass")
-    data class HandleFileParam(
-        var mode: Int = DIR,
-        var title: String? = null,
-        var allowExtensions: Array<String> = arrayOf(),
-        var otherActions: ArrayList<SelectItem<Int>>? = null,
-        var fileData: FileData? = null,
-        var requestCode: Int = 0,
-        var value: String? = null
-    )
-
-    data class Result(
-        val uri: Uri?,
-        val requestCode: Int,
-        val value: String?
-    )
-
-    data class FileData(
-        val name: String,
-        val data: Any,
-        val type: String
-    )
-
-}

+ 0 - 63
app/src/main/java/io/legado/app/ui/file/HandleFileViewModel.kt

@@ -1,63 +0,0 @@
-package io.legado.app.ui.file
-
-import android.app.Application
-import android.net.Uri
-import androidx.documentfile.provider.DocumentFile
-import androidx.lifecycle.MutableLiveData
-import io.legado.app.base.BaseViewModel
-import io.legado.app.constant.AppLog
-import io.legado.app.help.DirectLinkUpload
-import io.legado.app.utils.*
-
-import java.io.File
-
-class HandleFileViewModel(application: Application) : BaseViewModel(application) {
-
-    val errorLiveData = MutableLiveData<String>()
-
-    fun upload(
-        fileName: String,
-        file: Any,
-        contentType: String,
-        success: (url: String) -> Unit
-    ) {
-        execute {
-            DirectLinkUpload.upLoad(fileName, file, contentType)
-        }.onSuccess {
-            success.invoke(it)
-        }.onError {
-            AppLog.put("上传文件失败\n${it.localizedMessage}", it)
-            it.printOnDebug()
-            errorLiveData.postValue(it.localizedMessage)
-        }
-    }
-
-    fun saveToLocal(uri: Uri, fileName: String, data: Any, success: (uri: Uri) -> Unit) {
-        execute {
-            val bytes = when (data) {
-                is File -> data.readBytes()
-                is ByteArray -> data
-                is String -> data.toByteArray()
-                else -> GSON.toJson(data).toByteArray()
-            }
-            return@execute if (uri.isContentScheme()) {
-                val doc = DocumentFile.fromTreeUri(context, uri)!!
-                doc.findFile(fileName)?.delete()
-                val newDoc = doc.createFile("", fileName)
-                newDoc!!.writeBytes(context, bytes)
-                newDoc.uri
-            } else {
-                val file = File(uri.path ?: uri.toString())
-                val newFile = FileUtils.createFileIfNotExist(file, fileName)
-                newFile.writeBytes(bytes)
-                Uri.fromFile(newFile)
-            }
-        }.onError {
-            it.printOnDebug()
-            errorLiveData.postValue(it.localizedMessage)
-        }.onSuccess {
-            success.invoke(it)
-        }
-    }
-
-}

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

@@ -38,9 +38,6 @@ import io.legado.app.BuildConfig
 import io.legado.app.R
 import io.legado.app.base.BaseComposeActivity
 import io.legado.app.constant.AppConst.appInfo
-import io.legado.app.data.entities.Book
-import io.legado.app.data.entities.BookChapter
-import io.legado.app.data.entities.BookSource
 import io.legado.app.domain.gateway.BackupSettingsGateway
 import io.legado.app.domain.gateway.MangaSettingsGateway
 import io.legado.app.domain.gateway.OtherSettingsGateway
@@ -52,22 +49,21 @@ import io.legado.app.help.update.AppUpdateGitHub
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.model.AudioPlay
 import io.legado.app.service.WebService
-import io.legado.app.ui.about.CrashLogsDialog
+import io.legado.app.ui.about.MarkdownSheet
 import io.legado.app.ui.about.UpdateDialog
 import io.legado.app.ui.book.audio.AudioPlayViewModel
-import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
 import io.legado.app.ui.book.read.ReadBookInputHandler
 import io.legado.app.ui.book.read.ReadBookRouteHost
 import io.legado.app.ui.book.read.page.entities.PageDirection
 import io.legado.app.ui.theme.LocalAppUiConfiguration
 import io.legado.app.ui.welcome.WelcomeActivity
-import io.legado.app.ui.widget.dialog.TextDialog
 import io.legado.app.utils.LogUtils
 import io.legado.app.utils.showDialogFragment
 import io.legado.app.utils.startActivity
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
@@ -80,8 +76,7 @@ import kotlin.coroutines.suspendCoroutine
 /**
  * 主界面
  */
-open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
-    ChangeBookSourceDialog.CallBack {
+open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack {
 
     private data class RouteEvent(
         val route: NavKey,
@@ -91,6 +86,13 @@ open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
     /** 当前激活的有声书播放器 ViewModel(由有声书路由在生命周期内设置/清理) */
     internal var activeAudioPlayViewModel: AudioPlayViewModel? = null
 
+    /** 全局 Compose 文本弹层状态,供遗留命令式路径展示 Markdown/文本内容 */
+    private val textSheetFlow = MutableStateFlow<TextSheetData?>(null)
+
+    fun showTextSheet(title: String, content: String, onDismiss: (() -> Unit)? = null) {
+        textSheetFlow.value = TextSheetData(title, content, onDismiss)
+    }
+
     companion object {
         private const val KEY_RESTORE_READ_ROUTE = "restoreReadRoute"
         private const val KEY_RESTORE_READ_BOOK_URL = "restoreReadBookUrl"
@@ -310,8 +312,6 @@ open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
         lifecycleScope.launch {
             //版本更新
             upVersion()
-            //设置本地密码
-            notifyAppCrash()
             //备份同步
             backupSync()
             //自动更新书籍
@@ -495,6 +495,23 @@ open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
                 MainNavigator.navigateBack(this@MainActivity, backStack)
             }
         }
+        TextSheetHost()
+    }
+
+    @Composable
+    private fun TextSheetHost() {
+        val sheet by textSheetFlow.collectAsStateWithLifecycle()
+        sheet?.let { data ->
+            MarkdownSheet(
+                show = true,
+                title = data.title,
+                content = data.content,
+                onDismissRequest = {
+                    textSheetFlow.value = null
+                    data.onDismiss?.invoke()
+                },
+            )
+        }
     }
 
     private fun checkStartupRoute(): Boolean {
@@ -524,13 +541,6 @@ open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
             return@suspendCoroutine
         }
         LocalConfig.versionCode = appInfo.versionCode
-        if (LocalConfig.isFirstOpenApp) {
-            val help = String(assets.open("web/help/md/appHelp.md").readBytes())
-            val dialog = TextDialog(getString(R.string.help), help, TextDialog.Mode.MD)
-            dialog.setOnDismissListener { block.resume(null) }
-            showDialogFragment(dialog)
-            return@suspendCoroutine
-        }
         if (!BuildConfig.DEBUG) {
             lifecycleScope.launch {
                 try {
@@ -540,17 +550,11 @@ open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
                         dialog.setOnDismissListener { block.resume(null) }
                         showDialogFragment(dialog)
                     } else {
-                        val fallback = String(assets.open("updateLog.md").readBytes())
-                        val dialog = TextDialog(getString(R.string.update_log), fallback, TextDialog.Mode.MD)
-                        dialog.setOnDismissListener { block.resume(null) }
-                        showDialogFragment(dialog)
+                        block.resume(null)
                     }
                 } catch (e: Exception) {
                     e.printStackTrace()
-                    val fallback = String(assets.open("updateLog.md").readBytes())
-                    val dialog = TextDialog(getString(R.string.update_log), fallback, TextDialog.Mode.MD)
-                    dialog.setOnDismissListener { block.resume(null) }
-                    showDialogFragment(dialog)
+                    block.resume(null)
                 }
             }
         } else {
@@ -558,19 +562,6 @@ open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
         }
     }
 
-    private fun notifyAppCrash() {
-        if (!LocalConfig.appCrash || BuildConfig.DEBUG) {
-            return
-        }
-        LocalConfig.appCrash = false
-        alert(getString(R.string.draw), "检测到阅读发生了崩溃,是否打开崩溃日志以便报告问题?") {
-            yesButton {
-                showDialogFragment<CrashLogsDialog>()
-            }
-            noButton()
-        }
-    }
-
     /**
      * 备份同步
      */
@@ -708,21 +699,14 @@ open class MainActivity : BaseComposeActivity(), AudioPlay.CallBack,
         // 歌词暂不在界面展示
     }
 
-    // ===== ChangeBookSourceDialog.CallBack(有声书换源对话框,转发给当前播放器)=====
-
-    override val oldBook: Book?
-        get() = AudioPlay.book
-
-    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {
-        activeAudioPlayViewModel?.changeTo(source, book, toc)
-    }
-
-    override fun addToBookshelf(book: Book, toc: List<BookChapter>) {
-        activeAudioPlayViewModel?.addToBookshelf(book, toc)
-    }
-
 }
 
+data class TextSheetData(
+    val title: String,
+    val content: String,
+    val onDismiss: (() -> Unit)? = null,
+)
+
 class LauncherW : MainActivity()
 class Launcher1 : MainActivity()
 class Launcher2 : MainActivity()

+ 0 - 1
app/src/main/java/io/legado/app/ui/main/MainContract.kt

@@ -52,7 +52,6 @@ sealed interface MainUiIntent {
 sealed interface MainEffect {
     data class OpenUrl(val url: String) : MainEffect
     data class CopyUrl(val url: String) : MainEffect
-    data class ShowMarkdown(val title: String, val path: String) : MainEffect
     data class StartActivity(val destination: Class<*>, val configTag: String? = null) : MainEffect
     data object ExitApp : MainEffect
     data object NavigateToReadRecord : MainEffect

+ 17 - 4
app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt

@@ -50,7 +50,7 @@ import io.legado.app.ui.book.audio.AudioPlayIntent
 import io.legado.app.ui.book.audio.AudioPlayScreenContent
 import io.legado.app.ui.book.audio.AudioPlayViewModel
 import io.legado.app.ui.book.cache.manage.BookCacheManageRouteScreen
-import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
+import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet
 import io.legado.app.ui.book.explore.ExploreShowIntent
 import io.legado.app.ui.book.explore.ExploreShowRouteScreen
 import io.legado.app.ui.book.explore.ExploreShowViewModel
@@ -772,6 +772,7 @@ fun MainActivity.mainEntryProvider(
         )
         val lifecycleOwner = LocalLifecycleOwner.current
         val uiState by audioPlayViewModel.uiState.collectAsStateWithLifecycle()
+        var showAudioChangeSource by remember { mutableStateOf(false) }
         val imageLoader: ImageLoader = koinInject()
         val sourceEditResult = rememberLauncherForActivityResult(
             ActivityResultContracts.StartActivityForResult()
@@ -831,9 +832,7 @@ fun MainActivity.mainEntryProvider(
         LaunchedEffect(audioPlayViewModel) {
             audioPlayViewModel.effects.collectLatest { effect ->
                 when (effect) {
-                    is AudioPlayEffect.OpenChangeSource -> showDialogFragment(
-                        ChangeBookSourceDialog(effect.bookName, effect.author)
-                    )
+                    is AudioPlayEffect.OpenChangeSource -> showAudioChangeSource = true
 
                     is AudioPlayEffect.OpenLogin -> startActivity(
                         MainActivity.createSourceLoginIntent(
@@ -886,6 +885,20 @@ fun MainActivity.mainEntryProvider(
                 onBack = { audioPlayViewModel.onIntent(AudioPlayIntent.BackPressed) },
             )
         }
+        val audioBook = AudioPlay.book
+        if (showAudioChangeSource && audioBook != null) {
+            ChangeSourceSheet(
+                show = true,
+                oldBook = audioBook,
+                onDismissRequest = { showAudioChangeSource = false },
+                onReplace = { source, book, toc, _ ->
+                    audioPlayViewModel.changeTo(source, book, toc)
+                },
+                onAddAsNew = { book, toc ->
+                    audioPlayViewModel.addToBookshelf(book, toc)
+                },
+            )
+        }
     }
 
     entry<MainRouteSearchContent> { route ->

+ 0 - 18
app/src/main/java/io/legado/app/ui/main/MainScreen.kt

@@ -3,7 +3,6 @@ package io.legado.app.ui.main
 import android.content.Intent
 import android.os.Build
 import androidx.activity.ComponentActivity
-import androidx.appcompat.app.AppCompatActivity
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.ExperimentalSharedTransitionApi
 import androidx.compose.animation.SharedTransitionScope
@@ -99,14 +98,10 @@ import io.legado.app.ui.widget.components.navigation.AppNavigationBar
 import io.legado.app.ui.widget.components.navigation.AppNavigationBarItem
 import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
 import io.legado.app.ui.widget.components.text.AppText
-import io.legado.app.ui.widget.dialog.TextDialog
 import io.legado.app.utils.sendToClip
-import io.legado.app.utils.showDialogFragment
 import io.legado.app.utils.startActivityForBook
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 import org.koin.androidx.compose.koinViewModel
 import top.yukonga.miuix.kmp.basic.FloatingActionButton
 import top.yukonga.miuix.kmp.basic.NavigationRailDefaults
@@ -161,7 +156,6 @@ fun MainScreen(
 ) {
     val context = LocalContext.current
     val coroutineScope = rememberCoroutineScope()
-    val defaultHelpTitle = stringResource(R.string.help)
 
     LaunchedEffect(effects, context) {
         effects.collectLatest { effect ->
@@ -173,18 +167,6 @@ fun MainScreen(
                 }
 
                 is MainEffect.CopyUrl -> context.sendToClip(effect.url)
-                is MainEffect.ShowMarkdown -> {
-                    val activity = context as? AppCompatActivity ?: return@collectLatest
-                    val title = effect.title.ifBlank { defaultHelpTitle }
-                    val mdText = withContext(Dispatchers.IO) {
-                        context.assets
-                            .open("web/help/md/${effect.path}.md")
-                            .bufferedReader()
-                            .use { it.readText() }
-                    }
-                    activity.showDialogFragment(TextDialog(title, mdText, TextDialog.Mode.MD))
-                }
-
                 is MainEffect.StartActivity -> {
                     context.startActivity(Intent(context, effect.destination).apply {
                         effect.configTag?.let { putExtra("configTag", it) }

+ 0 - 3
app/src/main/java/io/legado/app/ui/main/MainViewModel.kt

@@ -76,9 +76,6 @@ class MainViewModel(
         when (event) {
             is PrefClickEvent.OpenUrl -> _effects.tryEmit(MainEffect.OpenUrl(event.url))
             is PrefClickEvent.CopyUrl -> _effects.tryEmit(MainEffect.CopyUrl(event.url))
-            is PrefClickEvent.ShowMd -> _effects.tryEmit(
-                MainEffect.ShowMarkdown(title = event.title, path = event.path)
-            )
             is PrefClickEvent.StartActivity -> _effects.tryEmit(
                 MainEffect.StartActivity(
                     destination = event.destination,

+ 1 - 15
app/src/main/java/io/legado/app/ui/main/my/MyScreen.kt

@@ -58,7 +58,6 @@ import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
 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.TopBarActionButton
 import org.koin.androidx.compose.koinViewModel
 
 
@@ -97,20 +96,7 @@ fun MyScreen(
         topBar = {
             GlassMediumFlexibleTopAppBar(
                 title = stringResource(R.string.my),
-                actions = {
-                    TopBarActionButton(
-                        onClick = {
-                            onNavigate(
-                                PrefClickEvent.ShowMd(
-                                    title = "",
-                                    path = "appHelp"
-                                )
-                            )
-                        },
-                        imageVector = Icons.AutoMirrored.Filled.HelpOutline,
-                        contentDescription = stringResource(R.string.help)
-                    )
-                },
+                actions = {},
                 scrollBehavior = scrollBehavior
             )
         }

+ 0 - 1
app/src/main/java/io/legado/app/ui/main/my/MyViewModel.kt

@@ -24,7 +24,6 @@ data class MyUiState(
 sealed class PrefClickEvent {
     data class OpenUrl(val url: String) : PrefClickEvent()
     data class CopyUrl(val url: String) : PrefClickEvent()
-    data class ShowMd(val title: String, val path: String) : PrefClickEvent()
     data class StartActivity(val destination: Class<*>, val configTag: String? = null) : PrefClickEvent()
     object OpenReadRecord : PrefClickEvent()
     object OpenBookCacheManage : PrefClickEvent()

+ 0 - 5
app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt

@@ -59,7 +59,6 @@ import io.legado.app.ui.widget.components.lazylist.FastScrollLazyColumn
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.rules.RuleListScaffold
 import io.legado.app.ui.widget.components.tabRow.AppTabRow
-import io.legado.app.utils.showHelp
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
@@ -363,10 +362,6 @@ fun ReplaceRuleScreen(
                 text = stringResource(R.string.group_management),
                 onClick = { showGroupManageSheet = true; dismiss() }
             )
-            RoundDropdownMenuItem(
-                text = stringResource(R.string.help),
-                onClick = { dismiss(); (context as? AppCompatActivity)?.showHelp("replaceRuleHelp") }
-            )
             PillDivider()
             RoundDropdownMenuItem(
                 text = stringResource(R.string.sort_old_first),

+ 0 - 97
app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubAdapter.kt

@@ -1,97 +0,0 @@
-package io.legado.app.ui.rss.subscription
-
-import android.content.Context
-import android.view.View
-import android.view.ViewGroup
-import android.widget.PopupMenu
-import androidx.recyclerview.widget.RecyclerView
-import io.legado.app.R
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.data.entities.RuleSub
-import io.legado.app.databinding.ItemRuleSubBinding
-import io.legado.app.ui.widget.recycler.ItemTouchCallback
-
-
-class RuleSubAdapter(context: Context, val callBack: Callback) :
-    RecyclerAdapter<RuleSub, ItemRuleSubBinding>(context),
-    ItemTouchCallback.Callback {
-
-    private val typeArray = context.resources.getStringArray(R.array.rule_type)
-
-    override fun convert(
-        holder: ItemViewHolder,
-        binding: ItemRuleSubBinding,
-        item: RuleSub,
-        payloads: MutableList<Any>
-    ) {
-        binding.tvType.text = typeArray[item.type]
-        binding.tvName.text = item.name
-        binding.tvUrl.text = item.url
-    }
-
-    override fun registerListener(holder: ItemViewHolder, binding: ItemRuleSubBinding) {
-        binding.root.setOnClickListener {
-            callBack.openSubscription(getItem(holder.layoutPosition)!!)
-        }
-        binding.ivEdit.setOnClickListener {
-            callBack.editSubscription(getItem(holder.layoutPosition)!!)
-        }
-        binding.ivMenuMore.setOnClickListener {
-            showMenu(binding.ivMenuMore, holder.layoutPosition)
-        }
-    }
-
-    private fun showMenu(view: View, position: Int) {
-        val source = getItem(position) ?: return
-        val popupMenu = PopupMenu(context, view)
-        popupMenu.inflate(R.menu.source_sub_item)
-        popupMenu.setOnMenuItemClickListener { menuItem ->
-            when (menuItem.itemId) {
-                R.id.menu_del -> callBack.delSubscription(source)
-            }
-            true
-        }
-        popupMenu.show()
-    }
-
-    override fun getViewBinding(parent: ViewGroup): ItemRuleSubBinding {
-        return ItemRuleSubBinding.inflate(inflater, parent, false)
-    }
-
-    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
-        val srcItem = getItem(srcPosition)
-        val targetItem = getItem(targetPosition)
-        if (srcItem != null && targetItem != null) {
-            if (srcItem.customOrder == targetItem.customOrder) {
-                callBack.upOrder()
-            } else {
-                val srcOrder = srcItem.customOrder
-                srcItem.customOrder = targetItem.customOrder
-                targetItem.customOrder = srcOrder
-                movedItems.add(srcItem)
-                movedItems.add(targetItem)
-            }
-        }
-        swapItem(srcPosition, targetPosition)
-        return true
-    }
-
-    private val movedItems = hashSetOf<RuleSub>()
-
-    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
-        if (movedItems.isNotEmpty()) {
-            callBack.updateSourceSub(*movedItems.toTypedArray())
-            movedItems.clear()
-        }
-    }
-
-    interface Callback {
-        fun openSubscription(ruleSub: RuleSub)
-        fun editSubscription(ruleSub: RuleSub)
-        fun delSubscription(ruleSub: RuleSub)
-        fun updateSourceSub(vararg ruleSub: RuleSub)
-        fun upOrder()
-    }
-
-}

+ 0 - 193
app/src/main/java/io/legado/app/ui/widget/BatteryView.kt

@@ -1,193 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.content.Context
-import android.graphics.PorterDuff
-import android.graphics.Typeface
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.view.LayoutInflater
-import androidx.annotation.ColorInt
-import androidx.constraintlayout.widget.ConstraintLayout
-import io.legado.app.databinding.ViewBatteryBinding
-import io.legado.app.utils.dpToPx
-
-class BatteryView @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null
-) : ConstraintLayout(context, attrs) {
-
-    private val binding: ViewBatteryBinding =
-        ViewBatteryBinding.inflate(LayoutInflater.from(context), this, true)
-
-    enum class BatteryMode { OUTER, INNER, ICON, ARROW, TIME, CLASSIC, NO_BATTERY, EMPTY }
-
-    private var battery: Int = 0
-
-    var text: CharSequence?
-        get() = binding.batteryText.text
-        set(value) {
-            binding.batteryText.text = value
-            binding.batteryTextInner.text = value
-            binding.batteryTextEnd.text = value
-        }
-
-    var batteryMode: BatteryMode = BatteryMode.OUTER
-        set(value) {
-            field = value
-            updateMode()
-        }
-
-    var typeface: Typeface?
-        get() = binding.batteryText.typeface
-        set(value) {
-            binding.batteryText.typeface = value ?: Typeface.DEFAULT
-            binding.batteryTextInner.typeface = value ?: Typeface.DEFAULT
-            binding.batteryTextEnd.typeface = value ?: Typeface.DEFAULT
-        }
-
-    var textSize: Float
-        get() = binding.batteryText.textSize / binding.batteryText.resources.displayMetrics.density
-        set(value) {
-            binding.batteryText.setTextSize(TypedValue.COMPLEX_UNIT_SP, value)
-        }
-
-    fun setTextColor(@ColorInt color: Int) {
-        binding.batteryText.setTextColor(color)
-        binding.batteryTextInner.setTextColor(color)
-    }
-
-    fun setBattery(battery: Int, text: String? = null) {
-        this.battery = battery.coerceIn(0, 100)
-
-        when (batteryMode) {
-            BatteryMode.TIME -> {
-                val timePart = text ?: ""
-                val batteryPart = "$battery"
-                binding.batteryText.text = timePart
-                binding.batteryTextInner.text = batteryPart
-            }
-
-            BatteryMode.CLASSIC -> {
-                binding.batteryClassic.isBattery = true
-                binding.batteryClassic.textSize = 11f
-                binding.batteryClassic.setBattery(battery, text)
-            }
-
-            else -> {
-                val displayText = text?.let { "$it  $battery" } ?: battery.toString()
-                binding.batteryText.text = displayText
-                binding.batteryTextEnd.text = displayText
-                binding.batteryTextInner.text = displayText
-            }
-        }
-
-        updateFill()
-    }
-
-    fun setColor(@ColorInt color: Int) {
-        binding.batteryText.setTextColor(color)
-        binding.batteryTextEnd.setTextColor(color)
-        binding.batteryTextInner.setTextColor(color)
-        binding.batteryFill.setCardBackgroundColor(color)
-        binding.batteryClassic.setColor(color)
-        binding.arrowIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN)
-        binding.batteryIcon.setColorFilter(color, PorterDuff.Mode.SRC_IN)
-        binding.batteryIcon.alpha = 0.76f
-        binding.arrowIcon.alpha = 0.76f
-    }
-
-    fun setTextIfNotEqual(newText: String?) {
-        if (binding.batteryText.text?.toString() != newText) {
-            binding.batteryText.text = newText
-        }
-    }
-
-
-    private fun updateMode() {
-        when (batteryMode) {
-            BatteryMode.OUTER -> {
-                binding.batteryText.visibility = GONE
-                binding.batteryTextEnd.visibility = VISIBLE
-                binding.batteryTextInner.visibility = GONE
-                binding.batteryFill.visibility = VISIBLE
-                binding.batteryIcon.visibility = VISIBLE
-                binding.arrowIcon.visibility = GONE
-                binding.batteryClassic.visibility = GONE
-            }
-            BatteryMode.INNER -> {
-                binding.batteryText.visibility = GONE
-                binding.batteryTextEnd.visibility = GONE
-                binding.batteryTextInner.visibility = VISIBLE
-                binding.batteryFill.visibility = GONE
-                binding.batteryIcon.visibility = VISIBLE
-                binding.arrowIcon.visibility = GONE
-                binding.batteryClassic.visibility = GONE
-            }
-            BatteryMode.ICON -> {
-                binding.batteryText.visibility = GONE
-                binding.batteryTextEnd.visibility = GONE
-                binding.batteryTextInner.visibility = GONE
-                binding.batteryFill.visibility = VISIBLE
-                binding.batteryIcon.visibility = VISIBLE
-                binding.arrowIcon.visibility = GONE
-                binding.batteryClassic.visibility = GONE
-            }
-            BatteryMode.ARROW -> {
-                binding.batteryText.visibility = VISIBLE
-                binding.batteryTextEnd.visibility = GONE
-                binding.batteryTextInner.visibility = GONE
-                binding.batteryFill.visibility = GONE
-                binding.batteryIcon.visibility = GONE
-                binding.arrowIcon.visibility = VISIBLE
-                binding.batteryClassic.visibility = GONE
-            }
-            BatteryMode.TIME -> {
-                binding.batteryText.visibility = VISIBLE
-                binding.batteryTextEnd.visibility = GONE
-                binding.batteryTextInner.visibility = VISIBLE
-                binding.batteryFill.visibility = GONE
-                binding.batteryIcon.visibility = VISIBLE
-                binding.arrowIcon.visibility = GONE
-                binding.batteryClassic.visibility = GONE
-            }
-            BatteryMode.CLASSIC -> {
-                binding.batteryText.visibility = GONE
-                binding.batteryTextEnd.visibility = GONE
-                binding.batteryTextInner.visibility = GONE
-                binding.batteryFill.visibility = GONE
-                binding.batteryIcon.visibility = GONE
-                binding.arrowIcon.visibility = GONE
-                binding.batteryClassic.visibility = VISIBLE
-            }
-            BatteryMode.NO_BATTERY -> {
-                binding.batteryFill.visibility = GONE
-                binding.batteryIcon.visibility = GONE
-                binding.batteryText.visibility = VISIBLE
-                binding.batteryTextEnd.visibility = GONE
-                binding.batteryTextInner.visibility = GONE
-                binding.arrowIcon.visibility = GONE
-                binding.batteryClassic.visibility = GONE
-            }
-            BatteryMode.EMPTY -> {
-                binding.batteryFill.visibility = GONE
-                binding.batteryIcon.visibility = GONE
-                binding.batteryText.visibility = GONE
-                binding.batteryTextEnd.visibility = GONE
-                binding.batteryTextInner.visibility = GONE
-                binding.arrowIcon.visibility = INVISIBLE
-                binding.batteryClassic.visibility = GONE
-            }
-        }
-        updateFill()
-    }
-
-    private fun updateFill() {
-        post {
-            val maxWidth = 17.dpToPx()
-            val params = binding.batteryFill.layoutParams
-            params.width = (maxWidth * battery / 100f).toInt()
-            binding.batteryFill.layoutParams = params
-        }
-    }
-
-}

+ 0 - 122
app/src/main/java/io/legado/app/ui/widget/BatteryViewOrgin.kt

@@ -1,122 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Paint
-import android.graphics.Rect
-import android.graphics.Typeface
-import android.os.Build
-import android.text.StaticLayout
-import android.util.AttributeSet
-import androidx.annotation.ColorInt
-import androidx.appcompat.widget.AppCompatTextView
-import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory
-import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw
-import io.legado.app.utils.dpToPx
-
-class BatteryViewOrgin @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null
-) : AppCompatTextView(context, attrs) {
-    private val readSettingsGateway get() = org.koin.core.context.GlobalContext.get().get<io.legado.app.domain.gateway.ReadSettingsGateway>()
-    private val batteryTypeface by lazy {
-        Typeface.createFromAsset(context.assets, "font/number.ttf")
-    }
-    private val batteryPaint = Paint()
-    private val outFrame = Rect()
-    private val polar = Rect()
-    private val canvasRecorder = CanvasRecorderFactory.create()
-    var isBattery = true
-        set(value) {
-            field = value
-            if (value && !isInEditMode) {
-                super.setTypeface(batteryTypeface)
-                postInvalidate()
-            }
-        }
-    private var battery: Int = 0
-
-    init {
-        setPadding(4.dpToPx(), 3.dpToPx(), 6.dpToPx(), 3.dpToPx())
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-            isFallbackLineSpacing = false
-        }
-        batteryPaint.strokeWidth = 1f.dpToPx()
-        batteryPaint.isAntiAlias = true
-        batteryPaint.color = paint.color
-    }
-
-    override fun setTypeface(tf: Typeface?) {
-        if (!isBattery) {
-            super.setTypeface(tf)
-        }
-    }
-
-    fun setColor(@ColorInt color: Int) {
-        setTextColor(color)
-        batteryPaint.color = color
-        invalidate()
-    }
-
-    @SuppressLint("SetTextI18n")
-    fun setBattery(battery: Int, text: String? = null) {
-        this.battery = battery
-        if (text.isNullOrEmpty()) {
-            setText(battery.toString())
-        } else {
-            setText("$text  $battery")
-        }
-    }
-
-    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
-        super.onLayout(changed, left, top, right, bottom)
-        canvasRecorder.invalidate()
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        if (readSettingsGateway.currentSettings.optimizeRender) {
-            canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {
-                super.onDraw(this)
-                drawBattery(this)
-            }
-        } else {
-            super.onDraw(canvas)
-            drawBattery(canvas)
-        }
-    }
-
-    private fun drawBattery(canvas: Canvas) {
-        if (!isBattery) return
-        layout.getLineBounds(0, outFrame)
-        val batteryStart = layout
-            .getPrimaryHorizontal(text.length - battery.toString().length)
-            .toInt() + 2.dpToPx()
-        val batteryEnd = batteryStart +
-                StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx()
-        outFrame.set(
-            batteryStart,
-            2.dpToPx(),
-            batteryEnd,
-            height - 2.dpToPx()
-        )
-        val dj = (outFrame.bottom - outFrame.top) / 3
-        polar.set(
-            batteryEnd,
-            outFrame.top + dj,
-            batteryEnd + 2.dpToPx(),
-            outFrame.bottom - dj
-        )
-        batteryPaint.style = Paint.Style.STROKE
-        canvas.drawRect(outFrame, batteryPaint)
-        batteryPaint.style = Paint.Style.FILL
-        canvas.drawRect(polar, batteryPaint)
-    }
-
-    @Suppress("UNNECESSARY_SAFE_CALL")
-    override fun invalidate() {
-        super.invalidate()
-        canvasRecorder?.invalidate()
-    }
-
-}

+ 0 - 66
app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt

@@ -1,66 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.util.AttributeSet
-import com.google.android.material.chip.Chip
-import com.google.android.material.chip.ChipGroup
-import io.legado.app.utils.themeColor
-
-@Suppress("unused", "MemberVisibilityCanBePrivate")
-class LabelsBar @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null
-) : ChipGroup(context, attrs) {
-
-    private val unUsedChips = arrayListOf<Chip>()
-    private val usedChips = arrayListOf<Chip>()
-
-    fun clear() {
-        unUsedChips.addAll(usedChips)
-        usedChips.clear()
-        removeAllViews()
-    }
-
-    fun setLabels(labels: List<String>, style: (Chip) -> Unit) {
-        clear()
-        labels.forEach {
-            addLabel(it, style)
-        }
-    }
-
-    fun addLabel(label: String, style: (Chip) -> Unit) {
-        val chip = if (unUsedChips.isEmpty()) {
-            Chip(context).apply {
-                isClickable = false
-                isCheckable = false
-                chipStartPadding = 8f
-                chipEndPadding = 8f
-                chipStrokeWidth = 0f
-                chipBackgroundColor = ColorStateList.valueOf(
-                    context.themeColor(com.google.android.material.R.attr.colorSurfaceContainerHighest)
-                )
-                usedChips.add(this)
-            }
-        } else {
-            unUsedChips.removeAt(unUsedChips.lastIndex).also {
-                usedChips.add(it)
-            }
-        }
-        style(chip)
-        chip.text = label
-        addView(chip)
-    }
-
-    fun applyColorScheme(backgroundColor: Int, textColor: Int) {
-        usedChips.forEach { chip ->
-            chip.chipBackgroundColor = ColorStateList.valueOf(backgroundColor)
-            chip.setTextColor(textColor)
-        }
-        unUsedChips.forEach { chip ->
-            chip.chipBackgroundColor = ColorStateList.valueOf(backgroundColor)
-            chip.setTextColor(textColor)
-        }
-    }
-
-}

+ 0 - 65
app/src/main/java/io/legado/app/ui/widget/NestedScrollableHost.kt

@@ -1,65 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewConfiguration
-import android.widget.FrameLayout
-import androidx.viewpager2.widget.ViewPager2
-import kotlin.math.abs
-import androidx.core.view.isNotEmpty
-
-class NestedScrollableHost @JvmOverloads constructor(
-    context: Context, attrs: AttributeSet? = null
-) : FrameLayout(context, attrs) {
-
-    private var touchSlop = 0
-    private var initialX = 0f
-    private var initialY = 0f
-
-    private val parentViewPager: ViewPager2?
-        get() {
-            var v: View? = parent as? View
-            while (v != null && v !is ViewPager2) {
-                v = v.parent as? View
-            }
-            return v
-        }
-
-    private val child: View?
-        get() = if (isNotEmpty()) getChildAt(0) else null
-
-    init {
-        touchSlop = ViewConfiguration.get(context).scaledTouchSlop
-    }
-
-    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
-        handleInterceptTouchEvent(ev)
-        return super.onInterceptTouchEvent(ev)
-    }
-
-    private fun handleInterceptTouchEvent(e: MotionEvent) {
-        val parentVp = parentViewPager ?: return
-
-        when (e.action) {
-            MotionEvent.ACTION_DOWN -> {
-                initialX = e.x
-                initialY = e.y
-                parent.requestDisallowInterceptTouchEvent(true)
-            }
-            MotionEvent.ACTION_MOVE -> {
-                val dx = e.x - initialX
-                val dy = e.y - initialY
-                val isHorizontal = abs(dx) > abs(dy)
-
-                if (isHorizontal) {
-                    val canScroll = child?.canScrollHorizontally(-dx.toInt()) ?: false
-                    parent.requestDisallowInterceptTouchEvent(canScroll)
-                } else {
-                    parent.requestDisallowInterceptTouchEvent(false)
-                }
-            }
-        }
-    }
-}

+ 0 - 22
app/src/main/java/io/legado/app/ui/widget/NoChildScrollNestedScrollView.kt

@@ -1,22 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.content.Context
-import android.graphics.Rect
-import android.util.AttributeSet
-import android.view.View
-import androidx.core.widget.NestedScrollView
-
-class NoChildScrollNestedScrollView @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-) : NestedScrollView(context, attrs) {
-
-    override fun requestChildRectangleOnScreen(
-        child: View,
-        rectangle: Rect?,
-        immediate: Boolean
-    ): Boolean {
-        return false
-    }
-
-}

+ 0 - 194
app/src/main/java/io/legado/app/ui/widget/ReaderInfoBarView.kt

@@ -1,194 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Rect
-import android.util.AttributeSet
-import android.view.View
-import androidx.annotation.AttrRes
-import androidx.core.content.ContextCompat
-import androidx.core.content.res.use
-import androidx.core.graphics.ColorUtils
-import androidx.core.view.WindowInsetsCompat
-import io.legado.app.utils.dpToPx
-import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
-import java.text.SimpleDateFormat
-import java.util.Date
-import kotlin.math.min
-import com.google.android.material.R as materialR
-
-class ReaderInfoBarView @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    @AttrRes defStyleAttr: Int = 0,
-) : View(context, attrs, defStyleAttr) {
-
-    companion object {
-        const val ALIGN_LEFT = 0
-        const val ALIGN_CENTER = 1
-    }
-
-    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
-    private val textBounds = Rect()
-    private val timeFormat = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT)
-    private val timeReceiver = TimeReceiver()
-    private var insetLeft: Int = 0
-    private var insetRight: Int = 0
-    private var insetTop: Int = 0
-    private var cutoutInsetLeft = 0
-    private var cutoutInsetRight = 0
-    private val colorText = ColorUtils.setAlphaComponent(
-        context.obtainStyledAttributes(intArrayOf(materialR.attr.colorOnSurface)).use {
-            it.getColor(0, Color.BLACK)
-        },
-        200,
-    )
-    private val colorOutline = ColorUtils.setAlphaComponent(
-        context.obtainStyledAttributes(intArrayOf(materialR.attr.colorSurface)).use {
-            it.getColor(0, Color.WHITE)
-        },
-        200,
-    )
-
-    var textInfoAlignment: Int = ALIGN_CENTER
-        set(value) {
-            field = value
-            updateTextSize()
-            invalidate()
-        }
-    private var timeText = timeFormat.format(Date())
-    private var text: String = ""
-    private val innerHeight
-        get() = height - paddingTop - paddingBottom - insetTop
-    private val innerWidth
-        get() = width - paddingLeft - paddingRight - insetLeft - insetRight
-
-    init {
-        val insetStart = 10.dpToPx()
-        val insetEnd = 10.dpToPx()
-        paint.strokeWidth = 2f.dpToPx()
-        paint.setShadowLayer(2f, 1f, 1f, Color.GRAY)
-        insetLeft = insetStart
-        insetRight = insetEnd
-        insetTop = minOf(insetLeft, insetRight)
-        setOnApplyWindowInsetsListenerCompat { _, windowInsets ->
-            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
-            if (insets.left >= paddingLeft) {
-                cutoutInsetLeft = insets.left
-            }
-            if (insets.right >= paddingRight) {
-                cutoutInsetRight = insets.right
-            }
-            windowInsets
-        }
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        super.onDraw(canvas)
-        val ty = innerHeight / 2f + textBounds.height() / 2f - textBounds.bottom
-
-        val textX = when (textInfoAlignment) {
-            ALIGN_CENTER -> {
-                val textWidth = paint.measureText(text)
-                (width / 2f).coerceIn(
-                    paddingLeft + insetLeft + cutoutInsetLeft + textWidth / 2,
-                    width - paddingRight - insetRight - cutoutInsetRight - textWidth / 2
-                )
-            }
-
-            else -> (paddingLeft + insetLeft + cutoutInsetLeft).toFloat()
-        }
-        paint.textAlign = when (textInfoAlignment) {
-            ALIGN_CENTER -> Paint.Align.CENTER
-            else -> Paint.Align.LEFT
-        }
-
-        canvas.drawTextOutline(
-            text,
-            textX,
-            paddingTop + insetTop + ty,
-        )
-
-        paint.textAlign = Paint.Align.RIGHT
-        canvas.drawTextOutline(
-            timeText,
-            (width - paddingRight - insetRight - cutoutInsetRight).toFloat(),
-            paddingTop + insetTop + ty,
-        )
-    }
-
-    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
-        super.onSizeChanged(w, h, oldw, oldh)
-        updateTextSize()
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        ContextCompat.registerReceiver(
-            context,
-            timeReceiver,
-            IntentFilter(Intent.ACTION_TIME_TICK),
-            ContextCompat.RECEIVER_EXPORTED,
-        )
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        context.unregisterReceiver(timeReceiver)
-    }
-
-    fun update(label: String) {
-        text = label
-        updateTextSize()
-        invalidate()
-    }
-
-    private fun updateTextSize() {
-        val testTextSize = 48f
-        paint.textSize = testTextSize
-        paint.getTextBounds(text, 0, text.length, textBounds)
-
-        val maxTextHeight = innerHeight * 0.8f
-        val scaleFactor = min(
-            maxTextHeight / textBounds.height(),
-            calculateMaxWidthScale()
-        )
-        paint.textSize = testTextSize * scaleFactor
-
-        paint.getTextBounds(text, 0, text.length, textBounds)
-    }
-
-    private fun calculateMaxWidthScale(): Float {
-        return when (textInfoAlignment) {
-            ALIGN_CENTER -> {
-                val availableWidth = innerWidth - cutoutInsetLeft - cutoutInsetRight
-                val requiredWidth = paint.measureText(text)
-                if (requiredWidth > availableWidth) availableWidth / requiredWidth else 1f
-            }
-
-            else -> 1f
-        }
-    }
-
-    private fun Canvas.drawTextOutline(text: String, x: Float, y: Float) {
-        paint.color = colorOutline
-        paint.style = Paint.Style.STROKE
-        drawText(text, x, y, paint)
-        paint.color = colorText
-        paint.style = Paint.Style.FILL
-        drawText(text, x, y, paint)
-    }
-
-    private inner class TimeReceiver : BroadcastReceiver() {
-
-        override fun onReceive(context: Context?, intent: Intent?) {
-            timeText = timeFormat.format(Date())
-            invalidate()
-        }
-    }
-}

+ 0 - 102
app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt

@@ -1,102 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.Menu
-import android.widget.FrameLayout
-import androidx.annotation.MenuRes
-import androidx.annotation.StringRes
-import androidx.appcompat.widget.PopupMenu
-import io.legado.app.databinding.ViewSelectActionBarBinding
-import io.legado.app.utils.visible
-
-
-@Suppress("unused")
-class SelectActionBar @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null
-) : FrameLayout(context, attrs) {
-
-    private var callBack: CallBack? = null
-    private var selMenu: PopupMenu? = null
-    private val binding = ViewSelectActionBarBinding
-        .inflate(LayoutInflater.from(context), this, true)
-
-    init {
-        if (!isInEditMode) {
-            binding.cbSelectedAll.addOnCheckedChangeListener { buttonView, isChecked ->
-                if (buttonView.isPressed) {
-                    callBack?.selectAll(isChecked)
-                }
-            }
-            binding.btnRevertSelection.setOnClickListener { callBack?.revertSelection() }
-            binding.btnSelectActionMain.setOnClickListener { callBack?.onClickSelectBarMainAction() }
-            binding.ivMenuMore.addOnCheckedChangeListener { button, isChecked ->
-                if (isChecked) {
-                    selMenu?.show()
-                }
-            }
-        }
-    }
-
-    fun setMainActionText(text: String) = binding.run {
-        btnSelectActionMain.text = text
-        btnSplit.visible()
-    }
-
-    fun setMainActionText(@StringRes id: Int) = binding.run {
-        btnSelectActionMain.setText(id)
-        btnSplit.visible()
-    }
-
-    fun inflateMenu(@MenuRes resId: Int): Menu? {
-        selMenu = PopupMenu(context, binding.ivMenuMore).apply {
-            inflate(resId)
-
-            setOnDismissListener {
-                binding.ivMenuMore.isChecked = false
-            }
-        }
-        return selMenu?.menu
-    }
-
-
-    fun setCallBack(callBack: CallBack) {
-        this.callBack = callBack
-    }
-
-    fun setOnMenuItemClickListener(listener: PopupMenu.OnMenuItemClickListener) {
-        selMenu?.setOnMenuItemClickListener(listener)
-    }
-
-    fun upCountView(selectCount: Int, allCount: Int) = binding.run {
-        if (selectCount == 0) {
-            cbSelectedAll.isChecked = false
-        } else {
-            cbSelectedAll.isChecked = selectCount >= allCount
-        }
-
-        //重置全选的文字
-        tvCount.text = selectCount.toString()
-        tvCountAll.text = allCount.toString()
-
-        setMenuClickable(selectCount > 0)
-    }
-
-    private fun setMenuClickable(isClickable: Boolean) = binding.run {
-        btnRevertSelection.isEnabled = isClickable
-        btnRevertSelection.isClickable = isClickable
-        btnSplit.isEnabled = isClickable
-        btnSplit.isClickable = isClickable
-    }
-
-    interface CallBack {
-
-        fun selectAll(selectAll: Boolean)
-
-        fun revertSelection()
-
-        fun onClickSelectBarMainAction() {}
-    }
-}

+ 0 - 185
app/src/main/java/io/legado/app/ui/widget/SimpleSliderView.kt

@@ -1,185 +0,0 @@
-package io.legado.app.ui.widget
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.os.Handler
-import android.os.Looper
-import android.util.AttributeSet
-import android.view.View
-import android.view.LayoutInflater
-import android.widget.FrameLayout
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.appcompat.widget.TooltipCompat
-import androidx.core.view.ViewCompat
-import io.legado.app.R
-import io.legado.app.databinding.ViewSimpleSliderBinding
-import io.legado.app.lib.dialogs.alert
-
-@SuppressLint("ViewConstructor", "ClickableViewAccessibility")
-class SimpleSliderView @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null
-) : LinearLayout(context, attrs) {
-
-    private val binding = ViewSimpleSliderBinding.inflate(LayoutInflater.from(context), this, true)
-    private var _orientation: Int = 1
-    private var _progress = 0
-    private var _max = 100
-    private var _min = 0
-    private val isBottomBackground: Boolean
-
-    var valueFormat: ((Int) -> String)? = null
-    var onChanged: ((Int) -> Unit)? = null
-
-    private val handler = Handler(Looper.getMainLooper())
-
-    private val repeatInterval = 320L
-
-    private val tvTit: TextView
-    private var titleText: CharSequence? = null
-
-    var progress: Int
-        get() = _progress
-        set(value) {
-            _progress = value.coerceIn(_min, _max)
-            updateValue()
-        }
-
-    var max: Int
-        get() = _max
-        set(value) {
-            _max = value
-            _progress = _progress.coerceAtMost(_max)
-            updateValue()
-        }
-
-    var min: Int
-        get() = _min
-        set(value) {
-            _min = value
-            _progress = _progress.coerceAtLeast(_min)
-            updateValue()
-        }
-
-    init {
-        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailSeekBar)
-        isBottomBackground = typedArray.getBoolean(R.styleable.DetailSeekBar_isBottomBackground, false)
-        val title = typedArray.getText(R.styleable.DetailSeekBar_title)
-        titleText = title
-        _max = typedArray.getInt(R.styleable.DetailSeekBar_max, 100)
-        _min = typedArray.getInt(R.styleable.DetailSeekBar_min, 0)
-        _progress = _progress.coerceIn(_min, _max)
-
-        val ta = context.obtainStyledAttributes(attrs, R.styleable.SimpleCounterView)
-        _orientation = ta.getInt(R.styleable.SimpleCounterView_orientation, 1)
-        ta.recycle()
-
-        val slider = binding.slider
-        slider.valueFrom = _min.toFloat()
-        slider.valueTo = _max.toFloat()
-        slider.value = _progress.toFloat()
-
-        slider.addOnChangeListener { _, value, fromUser ->
-            if (fromUser) {
-                _progress = value.toInt()
-                updateValue()
-                onChanged?.invoke(_progress)
-            }
-        }
-
-        tvTit = binding.tvSeekTitleVertical
-        typedArray.recycle()
-
-        tvTit.text = title
-        tvTit.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
-        slider.contentDescription = title
-        TooltipCompat.setTooltipText(tvTit, title)
-
-        binding.tvSeekValue.setOnClickListener {
-            showSeekBarDialog()
-        }
-
-        updateValue()
-    }
-
-    private fun showSeekBarDialog() {
-        context.alert(title = null, message = null) {
-            customView {
-                val detailSeekBar = SimpleCounterView(ctx).apply {
-                    max = _max
-                    min = _min
-                    progress = _progress
-                    valueFormat = this@SimpleSliderView.valueFormat
-                    setTitle(tvTit.text)
-                    onChanged = {
-                        _progress = it
-                        updateValue()
-                        this@SimpleSliderView.onChanged?.invoke(it)
-                    }
-                }
-
-                FrameLayout(ctx).apply {
-                    setPadding(64, 0, 64, 0)
-                    addView(detailSeekBar, LayoutParams(
-                        LayoutParams.MATCH_PARENT,
-                        LayoutParams.WRAP_CONTENT
-                    ))
-                }
-            }
-
-            okButton()
-            cancelButton()
-        }
-    }
-
-    private fun updateValue() {
-        val displayValue = valueFormat?.invoke(_progress) ?: _progress.toString()
-        binding.tvSeekValue.text = displayValue
-        binding.tvSeekValue.contentDescription = context.getString(
-            R.string.a11y_slider_value,
-            titleText ?: tvTit.text,
-            displayValue,
-        )
-        ViewCompat.setStateDescription(binding.slider, displayValue)
-        if (binding.slider.value.toInt() != _progress) {
-            binding.slider.value = _progress.toFloat()
-        }
-    }
-
-
-    private val incrementRunnable = object : Runnable {
-        override fun run() {
-            if (_progress < _max) {
-                _progress++
-                updateValue()
-                onChanged?.invoke(_progress)
-                handler.postDelayed(this, repeatInterval)
-            }
-        }
-    }
-
-    private val decrementRunnable = object : Runnable {
-        override fun run() {
-            if (_progress > _min) {
-                _progress--
-                updateValue()
-                onChanged?.invoke(_progress)
-                handler.postDelayed(this, repeatInterval)
-            }
-        }
-    }
-
-    override fun onDetachedFromWindow() {
-        handler.removeCallbacks(incrementRunnable)
-        handler.removeCallbacks(decrementRunnable)
-        super.onDetachedFromWindow()
-    }
-
-    override fun setEnabled(enabled: Boolean) {
-        super.setEnabled(enabled)
-        tvTit.isEnabled = enabled
-        binding.tvSeekValue.isEnabled = enabled
-    }
-
-}

+ 0 - 152
app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt

@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2015 tyrantgit
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.legado.app.ui.widget.anima.explosion_field
-
-import android.animation.ValueAnimator
-import android.annotation.SuppressLint
-import android.graphics.*
-import android.view.View
-import android.view.animation.AccelerateInterpolator
-import java.util.*
-import kotlin.math.pow
-
-@SuppressLint("Recycle")
-class ExplosionAnimator(private val mContainer: View, bitmap: Bitmap, bound: Rect) :
-    ValueAnimator() {
-
-    private val mPaint: Paint = Paint()
-    private val mParticles: Array<Particle?>
-    private val mBound: Rect = Rect(bound)
-
-    init {
-        val partLen = 15
-        mParticles = arrayOfNulls(partLen * partLen)
-        val random = Random(System.currentTimeMillis())
-        val w = bitmap.width / (partLen + 2)
-        val h = bitmap.height / (partLen + 2)
-        for (i in 0 until partLen) {
-            for (j in 0 until partLen) {
-                mParticles[i * partLen + j] =
-                    generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random)
-            }
-        }
-        setFloatValues(0f, END_VALUE)
-        interpolator = DEFAULT_INTERPOLATOR
-        duration = DEFAULT_DURATION
-    }
-
-    private fun generateParticle(color: Int, random: Random): Particle {
-        val particle = Particle()
-        particle.color = color
-        particle.radius = V
-        if (random.nextFloat() < 0.2f) {
-            particle.baseRadius = V + (X - V) * random.nextFloat()
-        } else {
-            particle.baseRadius = W + (V - W) * random.nextFloat()
-        }
-        val nextFloat = random.nextFloat()
-        particle.top = mBound.height() * (0.18f * random.nextFloat() + 0.2f)
-        particle.top =
-            if (nextFloat < 0.2f) particle.top else particle.top + particle.top * 0.2f * random.nextFloat()
-        particle.bottom = mBound.height() * (random.nextFloat() - 0.5f) * 1.8f
-        var f =
-            if (nextFloat < 0.2f) particle.bottom else if (nextFloat < 0.8f) particle.bottom * 0.6f else particle.bottom * 0.3f
-        particle.bottom = f
-        particle.mag = 4.0f * particle.top / particle.bottom
-        particle.neg = -particle.mag / particle.bottom
-        f = mBound.centerX() + Y * (random.nextFloat() - 0.5f)
-        particle.baseCx = f
-        particle.cx = f
-        f = mBound.centerY() + Y * (random.nextFloat() - 0.5f)
-        particle.baseCy = f
-        particle.cy = f
-        particle.life = END_VALUE / 10 * random.nextFloat()
-        particle.overflow = 0.4f * random.nextFloat()
-        particle.alpha = 1f
-        return particle
-    }
-
-    fun draw(canvas: Canvas): Boolean {
-        if (!isStarted) {
-            return false
-        }
-        for (particle in mParticles) {
-            particle?.let {
-                particle.advance(animatedValue as Float)
-                if (particle.alpha > 0f) {
-                    mPaint.color = particle.color
-                    mPaint.alpha = (Color.alpha(particle.color) * particle.alpha).toInt()
-                    canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint)
-                }
-            }
-        }
-        mContainer.invalidate()
-        return true
-    }
-
-    override fun start() {
-        super.start()
-        mContainer.invalidate()
-    }
-
-    private inner class Particle {
-        var alpha: Float = 0.toFloat()
-        var color: Int = 0
-        var cx: Float = 0.toFloat()
-        var cy: Float = 0.toFloat()
-        var radius: Float = 0.toFloat()
-        var baseCx: Float = 0.toFloat()
-        var baseCy: Float = 0.toFloat()
-        var baseRadius: Float = 0.toFloat()
-        var top: Float = 0.toFloat()
-        var bottom: Float = 0.toFloat()
-        var mag: Float = 0.toFloat()
-        var neg: Float = 0.toFloat()
-        var life: Float = 0.toFloat()
-        var overflow: Float = 0.toFloat()
-
-
-        fun advance(factor: Float) {
-            var f = 0f
-            var normalization = factor / END_VALUE
-            if (normalization < life || normalization > 1f - overflow) {
-                alpha = 0f
-                return
-            }
-            normalization = (normalization - life) / (1f - life - overflow)
-            val f2 = normalization * END_VALUE
-            if (normalization >= 0.7f) {
-                f = (normalization - 0.7f) / 0.3f
-            }
-            alpha = 1f - f
-            f = bottom * f2
-            cx = baseCx + f
-            cy = (baseCy - this.neg * f.toDouble().pow(2.0)).toFloat() - f * mag
-            radius = V + (baseRadius - V) * f2
-        }
-    }
-
-    companion object {
-
-        internal var DEFAULT_DURATION: Long = 0x400
-        private val DEFAULT_INTERPOLATOR = AccelerateInterpolator(0.6f)
-        private const val END_VALUE = 1.4f
-        private val X = Utils.dp2Px(5).toFloat()
-        private val Y = Utils.dp2Px(20).toFloat()
-        private val V = Utils.dp2Px(2).toFloat()
-        private val W = Utils.dp2Px(1).toFloat()
-    }
-}

+ 0 - 21
app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt

@@ -1,21 +0,0 @@
-package io.legado.app.ui.widget.anima.explosion_field
-
-import android.app.Activity
-import android.view.View
-import android.view.ViewGroup
-import android.view.Window
-
-object ExplosionField {
-
-    fun attach2Window(activity: Activity): ExplosionView {
-        val rootView = activity.findViewById<View>(Window.ID_ANDROID_CONTENT) as ViewGroup
-        val explosionField = ExplosionView(activity)
-        rootView.addView(
-            explosionField, ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
-            )
-        )
-        return explosionField
-    }
-
-}

+ 0 - 156
app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionView.kt

@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2015 tyrantgit
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.legado.app.ui.widget.anima.explosion_field
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ValueAnimator
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Rect
-import android.media.MediaPlayer
-import android.util.AttributeSet
-import android.view.View
-import io.legado.app.utils.DebugLog
-
-import java.util.*
-
-
-@Suppress("unused")
-class ExplosionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
-    View(context, attrs) {
-
-    private var customDuration = ExplosionAnimator.DEFAULT_DURATION
-    private var idPlayAnimationEffect = 0
-    private var mZAnimatorListener: OnAnimatorListener? = null
-    private var mOnClickListener: OnClickListener? = null
-
-    private val mExplosions = ArrayList<ExplosionAnimator>()
-    private val mExpandInset = IntArray(2)
-
-    init {
-        Arrays.fill(mExpandInset, Utils.dp2Px(32))
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        super.onDraw(canvas)
-        for (explosion in mExplosions) {
-            explosion.draw(canvas)
-        }
-    }
-
-    fun playSoundAnimationEffect(id: Int) {
-        this.idPlayAnimationEffect = id
-    }
-
-    fun setCustomDuration(customDuration: Long) {
-        this.customDuration = customDuration
-    }
-
-    fun addActionEvent(iEvents: OnAnimatorListener) {
-        this.mZAnimatorListener = iEvents
-    }
-
-
-    fun expandExplosionBound(dx: Int, dy: Int) {
-        mExpandInset[0] = dx
-        mExpandInset[1] = dy
-    }
-
-    @JvmOverloads
-    fun explode(bitmap: Bitmap?, bound: Rect, startDelay: Long, view: View? = null) {
-        val currentDuration = customDuration
-        val explosion = ExplosionAnimator(this, bitmap!!, bound)
-        explosion.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator) {
-                mExplosions.remove(animation)
-                view?.let {
-                    view.scaleX = 1f
-                    view.scaleY = 1f
-                    view.alpha = 1f
-                    //view.setOnClickListener(mOnClickListener)//set event
-                }
-            }
-        })
-        explosion.startDelay = startDelay
-        explosion.duration = currentDuration
-        mExplosions.add(explosion)
-        explosion.start()
-    }
-
-    @JvmOverloads
-    fun explode(view: View, restartState: Boolean? = false) {
-
-        val r = Rect()
-        view.getGlobalVisibleRect(r)
-        val location = IntArray(2)
-        getLocationOnScreen(location)
-        r.offset(-location[0], -location[1])
-        r.inset(-mExpandInset[0], -mExpandInset[1])
-        val startDelay = 100
-        val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150)
-        animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
-
-            var random = Random()
-
-            override fun onAnimationUpdate(animation: ValueAnimator) {
-                view.translationX = (random.nextFloat() - 0.5f) * view.width.toFloat() * 0.05f
-                view.translationY = (random.nextFloat() - 0.5f) * view.height.toFloat() * 0.05f
-            }
-        })
-
-        animator.addListener(object : Animator.AnimatorListener {
-            override fun onAnimationStart(animator: Animator) {
-                if (idPlayAnimationEffect != 0)
-                    MediaPlayer.create(context, idPlayAnimationEffect).start()
-            }
-
-            override fun onAnimationEnd(animator: Animator) {
-                if (mZAnimatorListener != null) {
-                    mZAnimatorListener!!.onAnimationEnd(animator, this@ExplosionView)
-                }
-            }
-
-            override fun onAnimationCancel(animator: Animator) {
-                DebugLog.i(javaClass.name, "CANCEL")
-            }
-
-            override fun onAnimationRepeat(animator: Animator) {
-                DebugLog.i(javaClass.name, "REPEAT")
-            }
-        })
-
-        animator.start()
-        view.animate().setDuration(150).setStartDelay(startDelay.toLong()).scaleX(0f).scaleY(0f)
-            .alpha(0f).start()
-        if (restartState!!)
-            explode(Utils.createBitmapFromView(view), r, startDelay.toLong(), view)
-        else
-            explode(Utils.createBitmapFromView(view), r, startDelay.toLong())
-
-    }
-
-    fun clear() {
-        mExplosions.clear()
-        invalidate()
-    }
-
-    override fun setOnClickListener(mOnClickListener: OnClickListener?) {
-        this.mOnClickListener = mOnClickListener
-    }
-
-}

+ 0 - 8
app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/OnAnimatorListener.kt

@@ -1,8 +0,0 @@
-package io.legado.app.ui.widget.anima.explosion_field
-
-import android.animation.Animator
-import android.view.View
-
-interface OnAnimatorListener {
-    fun onAnimationEnd(animator: Animator, view: View)
-}

+ 0 - 80
app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/Utils.kt

@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 tyrantgit
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.legado.app.ui.widget.anima.explosion_field
-
-
-import android.content.res.Resources
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.drawable.BitmapDrawable
-import android.view.View
-import android.widget.ImageView
-import io.legado.app.utils.printOnDebug
-
-
-import kotlin.math.roundToInt
-
-object Utils {
-
-    private val DENSITY = Resources.getSystem().displayMetrics.density
-    private val sCanvas = Canvas()
-
-    fun dp2Px(dp: Int): Int {
-        return (dp * DENSITY).roundToInt()
-    }
-
-    fun createBitmapFromView(view: View): Bitmap? {
-        if (view is ImageView) {
-            val drawable = view.drawable
-            if (drawable != null && drawable is BitmapDrawable) {
-                return drawable.bitmap
-            }
-        }
-        view.clearFocus()
-        val bitmap = createBitmapSafely(
-            view.width,
-            view.height, Bitmap.Config.ARGB_8888, 1
-        )
-        if (bitmap != null) {
-            synchronized(sCanvas) {
-                val canvas = sCanvas
-                canvas.setBitmap(bitmap)
-                view.draw(canvas)
-                canvas.setBitmap(null)
-            }
-        }
-        return bitmap
-    }
-
-    private fun createBitmapSafely(
-        width: Int,
-        height: Int,
-        config: Bitmap.Config,
-        retryCount: Int
-    ): Bitmap? {
-        try {
-            return Bitmap.createBitmap(width, height, config)
-        } catch (e: OutOfMemoryError) {
-            e.printOnDebug()
-            if (retryCount > 0) {
-                System.gc()
-                return createBitmapSafely(width, height, config, retryCount - 1)
-            }
-            return null
-        }
-
-    }
-}

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/components/explore/ExploreKindItemState.kt

@@ -13,7 +13,7 @@ import io.legado.app.data.entities.rule.ExploreKind
 import io.legado.app.domain.usecase.ExploreKindUiUseCase
 import io.legado.app.help.source.getExploreInfoMap
 import io.legado.app.utils.InfoMap
-import io.legado.app.utils.showDialogFragment
+import io.legado.app.ui.main.MainActivity
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.launch
@@ -71,7 +71,7 @@ class ExploreKindItemState(
     }
 
     fun showError(error: String) {
-        activity?.showDialogFragment(io.legado.app.ui.widget.dialog.TextDialog("ERROR", error))
+        (activity as? MainActivity)?.showTextSheet("ERROR", error)
     }
 }
 

+ 21 - 14
app/src/main/java/io/legado/app/ui/widget/dialog/BottomWebViewDialog.kt

@@ -64,7 +64,7 @@ import io.legado.app.lib.dialogs.SelectItem
 import io.legado.app.lib.dialogs.selector
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import io.legado.app.ui.association.OnLineImportActivity
-import io.legado.app.ui.file.HandleFileContract
+import androidx.activity.result.contract.ActivityResultContracts
 import io.legado.app.utils.ACache
 import io.legado.app.utils.GSON
 import io.legado.app.utils.applyDayNight
@@ -136,12 +136,15 @@ class BottomWebViewDialog() : BottomSheetDialogFragment(R.layout.dialog_web_view
             else -> sysConfiguration.isNightMode
         }
     private var appliedDarkTheme: Boolean? = null
-    private val selectImageDir = registerForActivityResult(HandleFileContract()) {
-        it.uri?.let { uri ->
-            ACache.get().put(imagePathKey, uri.toString())
-            saveImage(it.value, uri)
+    private val selectImageDir =
+        registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
+            if (uri != null) {
+                ACache.get().put(imagePathKey, uri.toString())
+                saveImage(pendingSaveImage, uri)
+            }
         }
-    }
+
+    private var pendingSaveImage: String? = null
 
     private lateinit var currentWebView: WebView
     private var source: BaseSource? = null
@@ -582,14 +585,18 @@ class BottomWebViewDialog() : BottomSheetDialogFragment(R.layout.dialog_web_view
     }
 
     private fun selectSaveFolder(webPic: String?) {
-        val default = arrayListOf<SelectItem<Int>>()
-        val path = ACache.get().getAsString(imagePathKey)
-        if (!path.isNullOrEmpty()) {
-            default.add(SelectItem(path, -1))
-        }
-        selectImageDir.launch {
-            otherActions = default
-            value = webPic
+        val lastPath = ACache.get().getAsString(imagePathKey)
+        val options = buildList {
+            if (!lastPath.isNullOrEmpty()) add(lastPath)
+            add(getString(R.string.sys_folder_picker))
+        }
+        requireContext().selector(R.string.select_folder, options) { _, index ->
+            if (!lastPath.isNullOrEmpty() && index == 0) {
+                saveImage(webPic, lastPath.toUri())
+            } else {
+                pendingSaveImage = webPic
+                selectImageDir.launch(null)
+            }
         }
     }
 

+ 0 - 118
app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt

@@ -1,118 +0,0 @@
-package io.legado.app.ui.widget.dialog
-
-import android.os.Build
-import android.os.Bundle
-import android.view.View
-import android.view.textclassifier.TextClassifier
-import androidx.lifecycle.lifecycleScope
-import com.bumptech.glide.Glide
-import io.legado.app.R
-import io.legado.app.base.BaseBottomSheetDialogFragment
-import io.legado.app.databinding.DialogTextViewBinding
-import io.legado.app.help.IntentData
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.utils.setHtml
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.noties.markwon.Markwon
-import io.noties.markwon.ext.tables.TablePlugin
-import io.noties.markwon.html.HtmlPlugin
-import io.noties.markwon.image.glide.GlideImagesPlugin
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-
-class TextDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_text_view) {
-
-    enum class Mode {
-        MD, HTML, TEXT
-    }
-
-    constructor(
-        title: String,
-        content: String?,
-        mode: Mode = Mode.TEXT,
-        time: Long = 0,
-        autoClose: Boolean = false
-    ) : this() {
-        arguments = Bundle().apply {
-            putString("title", title)
-            putString("content", IntentData.put(content))
-            putString("mode", mode.name)
-            putLong("time", time)
-        }
-    }
-
-    private val binding by viewBinding(DialogTextViewBinding::bind)
-    private var time = 0L
-    private var autoClose: Boolean = false
-
-    override fun onStart() {
-        super.onStart()
-        //setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 0.9f)
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        //binding.toolBar.inflateMenu(R.menu.dialog_text)
-        //binding.toolBar.menu.applyTint(requireContext())
-//        binding.toolBar.setOnMenuItemClickListener {
-//            when (it.itemId) {
-//                R.id.menu_close -> dismissAllowingStateLoss()
-//            }
-//            true
-//        }
-        arguments?.let {
-            //binding.toolBar.title = it.getString("title")
-            val content = IntentData.get(it.getString("content")) ?: ""
-            when (it.getString("mode")) {
-                Mode.MD.name -> viewLifecycleOwner.lifecycleScope.launch {
-                    binding.textView.setTextClassifier(TextClassifier.NO_OP)
-                    val markwon: Markwon
-                    val markdown = withContext(IO) {
-                        markwon = Markwon.builder(requireContext())
-                            .usePlugin(GlideImagesPlugin.create(Glide.with(requireContext())))
-                            .usePlugin(HtmlPlugin.create())
-                            .usePlugin(TablePlugin.create(requireContext()))
-                            .build()
-                        markwon.toMarkdown(content)
-                    }
-                    markwon.setParsedMarkdown(binding.textView, markdown)
-                }
-
-                Mode.HTML.name -> binding.textView.setHtml(content)
-                else -> {
-                    if (content.length >= 32 * 1024) {
-                        val truncatedContent =
-                            content.substring(0, 32 * 1024) + "\n\n数据太大,无法全部显示…"
-                        binding.textView.text = truncatedContent
-                    } else {
-                        binding.textView.text = content
-                    }
-                }
-            }
-            time = it.getLong("time", 0L)
-        }
-//        if (time > 0) {
-//            binding.badgeView.setBadgeCount((time / 1000).toInt())
-//            lifecycleScope.launch {
-//                while (time > 0) {
-//                    delay(1000)
-//                    time -= 1000
-//                    binding.badgeView.setBadgeCount((time / 1000).toInt())
-//                    if (time <= 0) {
-//                        view.post {
-//                            dialog?.setCancelable(true)
-//                            if (autoClose) dialog?.cancel()
-//                        }
-//                    }
-//                }
-//            }
-//        } else {
-//            view.post {
-//                dialog?.setCancelable(true)
-//            }
-//        }
-    }
-
-}

+ 0 - 81
app/src/main/java/io/legado/app/ui/widget/dialog/TextListDialog.kt

@@ -1,81 +0,0 @@
-package io.legado.app.ui.widget.dialog
-
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup
-import androidx.recyclerview.widget.LinearLayoutManager
-import io.legado.app.R
-import io.legado.app.base.BaseDialogFragment
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.databinding.DialogRecyclerViewBinding
-import io.legado.app.databinding.ItemLogBinding
-import io.legado.app.utils.setLayout
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-
-@Suppress("unused")
-class TextListDialog() : BaseDialogFragment(R.layout.dialog_recycler_view) {
-
-    constructor(title: String, values: ArrayList<String>) : this() {
-        arguments = Bundle().apply {
-            putString("title", title)
-            putStringArrayList("values", values)
-        }
-    }
-
-    private val binding by viewBinding(DialogRecyclerViewBinding::bind)
-    private val adapter by lazy { TextAdapter(requireContext()) }
-    private var values: ArrayList<String>? = null
-
-    override fun onStart() {
-        super.onStart()
-        setLayout(0.9f, 0.9f)
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
-        arguments?.let {
-            toolBar.title = it.getString("title")
-            values = it.getStringArrayList("values")
-        }
-        recyclerView.layoutManager = LinearLayoutManager(requireContext())
-        recyclerView.adapter = adapter
-        adapter.setItems(values)
-    }
-
-    class TextAdapter(context: Context) :
-        RecyclerAdapter<String, ItemLogBinding>(context) {
-
-        override fun getViewBinding(parent: ViewGroup): ItemLogBinding {
-            return ItemLogBinding.inflate(inflater, parent, false)
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemLogBinding,
-            item: String,
-            payloads: MutableList<Any>
-        ) {
-            binding.apply {
-                if (textView.getTag(R.id.tag1) == null) {
-                    val listener = object : View.OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(v: View) {
-                            textView.isCursorVisible = false
-                            textView.isCursorVisible = true
-                        }
-
-                        override fun onViewDetachedFromWindow(v: View) {}
-                    }
-                    textView.addOnAttachStateChangeListener(listener)
-                    textView.setTag(R.id.tag1, listener)
-                }
-                textView.text = item
-            }
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemLogBinding) {
-            //nothing
-        }
-    }
-
-}

+ 0 - 51
app/src/main/java/io/legado/app/ui/widget/dialog/UrlOptionDialog.kt

@@ -1,51 +0,0 @@
-package io.legado.app.ui.widget.dialog
-
-import android.app.Dialog
-import android.content.Context
-import android.os.Bundle
-import android.view.ViewGroup
-import io.legado.app.R
-import io.legado.app.constant.AppConst
-import io.legado.app.databinding.DialogUrlOptionEditBinding
-import io.legado.app.model.analyzeRule.AnalyzeUrl
-import io.legado.app.utils.GSON
-import io.legado.app.utils.setLayout
-
-class UrlOptionDialog(context: Context, private val success: (String) -> Unit) : Dialog(context) {
-
-    val binding = DialogUrlOptionEditBinding.inflate(layoutInflater)
-
-    override fun onStart() {
-        super.onStart()
-        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)
-        window?.setBackgroundDrawableResource(R.color.transparent)
-    }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(binding.root)
-        binding.root.setOnClickListener { dismiss() }
-        binding.vwBg.setOnClickListener(null)
-        binding.editMethod.setFilterValues("POST", "GET")
-        binding.editCharset.setFilterValues(AppConst.charsets)
-        binding.tvOk.setOnClickListener {
-            success.invoke(GSON.toJson(getUrlOption()))
-            dismiss()
-        }
-    }
-
-    private fun getUrlOption(): AnalyzeUrl.UrlOption {
-        val urlOption = AnalyzeUrl.UrlOption()
-        urlOption.useWebView(binding.cbUseWebView.isChecked)
-        urlOption.setMethod(binding.editMethod.text.toString())
-        urlOption.setCharset(binding.editCharset.text.toString())
-        urlOption.setHeaders(binding.editHeaders.text.toString())
-        urlOption.setBody(binding.editBody.text.toString())
-        urlOption.setRetry(binding.editRetry.text.toString())
-        urlOption.setType(binding.editType.text.toString())
-        urlOption.setWebJs(binding.editWebJs.text.toString())
-        urlOption.setJs(binding.editJs.text.toString())
-        return urlOption
-    }
-
-}

+ 0 - 181
app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardAssistsConfig.kt

@@ -1,181 +0,0 @@
-package io.legado.app.ui.widget.keyboard
-
-import android.content.Context
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.Toolbar
-import androidx.core.view.setPadding
-import androidx.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.ItemTouchHelper
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import io.legado.app.R
-import io.legado.app.base.BaseDialogFragment
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.constant.AppLog
-import io.legado.app.data.appDb
-import io.legado.app.data.entities.KeyboardAssist
-import io.legado.app.databinding.DialogMultipleEditTextBinding
-import io.legado.app.databinding.DialogRecyclerViewBinding
-import io.legado.app.databinding.Item1lineTextAndDelBinding
-import io.legado.app.lib.dialogs.alert
-//import io.legado.app.lib.theme.backgroundColor
-//import io.legado.app.lib.theme.primaryColor
-import io.legado.app.ui.widget.recycler.ItemTouchCallback
-import io.legado.app.ui.widget.recycler.VerticalDivider
-import io.legado.app.utils.applyTint
-import io.legado.app.utils.dpToPx
-import io.legado.app.utils.setLayout
-import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
-
-/**
- * 辅助按键配置
- */
-class KeyboardAssistsConfig : BaseDialogFragment(R.layout.dialog_recycler_view),
-    Toolbar.OnMenuItemClickListener {
-
-    private val binding by viewBinding(DialogRecyclerViewBinding::bind)
-    private val adapter by lazy { KeyAdapter(requireContext()) }
-
-    override fun onStart() {
-        super.onStart()
-        setLayout(0.9f, 0.9f)
-    }
-
-    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
-        //binding.toolBar.setBackgroundColor(primaryColor)
-        binding.toolBar.setTitle(R.string.assists_key_config)
-        initView()
-        initMenu()
-        initData()
-    }
-
-    private fun initView() {
-        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
-        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
-        binding.recyclerView.adapter = adapter
-        val itemTouchCallback = ItemTouchCallback(adapter)
-        itemTouchCallback.isCanDrag = true
-        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)
-
-    }
-
-    private fun initMenu() {
-        binding.toolBar.setOnMenuItemClickListener(this)
-        binding.toolBar.inflateMenu(R.menu.keyboard_assists_config)
-        binding.toolBar.menu.applyTint(requireContext())
-    }
-
-    private fun initData() {
-        lifecycleScope.launch {
-            appDb.keyboardAssistsDao.flowAll.catch {
-                AppLog.put("辅助按键配置获取数据失败\n${it.localizedMessage}", it)
-            }.flowOn(IO).collect {
-                adapter.setItems(it)
-            }
-        }
-    }
-
-    override fun onMenuItemClick(item: MenuItem?): Boolean {
-        when (item?.itemId) {
-            R.id.menu_add -> editKey(null)
-        }
-        return false
-    }
-
-    private fun editKey(keyboardAssist: KeyboardAssist?) {
-        alert {
-            setTitle("辅助按键")
-            val alertBinding = DialogMultipleEditTextBinding.inflate(layoutInflater).apply {
-                layout1.hint = "key"
-                edit1.setText(keyboardAssist?.key)
-                layout2.hint = "value"
-                layout2.visible()
-                edit2.setText(keyboardAssist?.value)
-            }
-            setCustomView(alertBinding.root)
-            cancelButton()
-            okButton {
-                lifecycleScope.launch(IO) {
-                    val newKeyboardAssist = KeyboardAssist(
-                        key = alertBinding.edit1.text.toString(),
-                        value = alertBinding.edit2.text.toString()
-                    )
-                    if (keyboardAssist == null) {
-                        newKeyboardAssist.serialNo = appDb.keyboardAssistsDao.maxSerialNo + 1
-                        appDb.keyboardAssistsDao.insert(newKeyboardAssist)
-                    } else {
-                        newKeyboardAssist.serialNo = keyboardAssist.serialNo
-                        appDb.keyboardAssistsDao.delete(keyboardAssist)
-                        appDb.keyboardAssistsDao.insert(newKeyboardAssist)
-                    }
-                }
-            }
-        }
-    }
-
-    private inner class KeyAdapter(context: Context) :
-        RecyclerAdapter<KeyboardAssist, Item1lineTextAndDelBinding>(context),
-        ItemTouchCallback.Callback {
-
-        private var isMoved = false
-
-        override fun getViewBinding(parent: ViewGroup): Item1lineTextAndDelBinding {
-            return Item1lineTextAndDelBinding.inflate(inflater, parent, false).apply {
-                root.setPadding(16.dpToPx())
-                ivDelete.visible()
-            }
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: Item1lineTextAndDelBinding,
-            item: KeyboardAssist,
-            payloads: MutableList<Any>
-        ) {
-            //binding.root.setBackgroundColor(context.backgroundColor)
-            binding.textView.text = item.key
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextAndDelBinding) {
-            binding.root.setOnClickListener {
-                getItemByLayoutPosition(holder.layoutPosition)?.let { keyboardAssist ->
-                    editKey(keyboardAssist)
-                }
-            }
-            binding.ivDelete.setOnClickListener {
-                getItemByLayoutPosition(holder.layoutPosition)?.let { keyboardAssist ->
-                    lifecycleScope.launch(IO) {
-                        appDb.keyboardAssistsDao.delete(keyboardAssist)
-                    }
-                }
-            }
-        }
-
-        override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
-            swapItem(srcPosition, targetPosition)
-            isMoved = true
-            return true
-        }
-
-        override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
-            if (isMoved) {
-                for ((index, item) in getItems().withIndex()) {
-                    item.serialNo = index + 1
-                }
-                lifecycleScope.launch(IO) {
-                    appDb.keyboardAssistsDao.update(*getItems().toTypedArray())
-                }
-            }
-            isMoved = false
-        }
-    }
-}

+ 0 - 182
app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardToolPop.kt

@@ -1,182 +0,0 @@
-package io.legado.app.ui.widget.keyboard
-
-import android.content.Context
-import android.graphics.Rect
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewTreeObserver
-import android.view.Window
-import android.widget.PopupWindow
-import io.legado.app.R
-import io.legado.app.base.adapter.ItemViewHolder
-import io.legado.app.base.adapter.RecyclerAdapter
-import io.legado.app.constant.AppLog
-import io.legado.app.data.appDb
-import io.legado.app.data.entities.KeyboardAssist
-import io.legado.app.databinding.ItemFilletTextBinding
-import io.legado.app.databinding.PopupKeyboardToolBinding
-import io.legado.app.lib.dialogs.SelectItem
-import io.legado.app.lib.dialogs.selector
-import io.legado.app.utils.activity
-import io.legado.app.utils.dpToPx
-import io.legado.app.utils.showDialogFragment
-import io.legado.app.utils.windowSize
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.launch
-import splitties.systemservices.layoutInflater
-import splitties.systemservices.windowManager
-import kotlin.math.abs
-
-/**
- * 键盘帮助浮窗
- */
-class KeyboardToolPop(
-    private val context: Context,
-    private val scope: CoroutineScope,
-    private val rootView: View,
-    private val callBack: CallBack
-) : PopupWindow(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT),
-    ViewTreeObserver.OnGlobalLayoutListener {
-
-    private val helpChar = "❓"
-
-    private val binding = PopupKeyboardToolBinding.inflate(LayoutInflater.from(context))
-    private val adapter = Adapter(context)
-    private var mIsSoftKeyBoardShowing = false
-    var initialPadding = 0
-
-    init {
-        contentView = binding.root
-
-        isTouchable = true
-        isOutsideTouchable = false
-        isFocusable = false
-        inputMethodMode = INPUT_METHOD_NEEDED //解决遮盖输入法
-        initRecyclerView()
-        upAdapterData()
-    }
-
-    fun attachToWindow(window: Window) {
-        window.decorView.viewTreeObserver.addOnGlobalLayoutListener(this)
-        contentView.measure(
-            View.MeasureSpec.UNSPECIFIED,
-            View.MeasureSpec.UNSPECIFIED,
-        )
-    }
-
-    override fun onGlobalLayout() {
-        val rect = Rect()
-        // 获取当前页面窗口的显示范围
-        rootView.getWindowVisibleDisplayFrame(rect)
-        val screenHeight = windowManager.windowSize.heightPixels
-        val keyboardHeight = screenHeight - rect.bottom // 输入法的高度
-        val preShowing = mIsSoftKeyBoardShowing
-        if (abs(keyboardHeight) > screenHeight / 5) {
-            mIsSoftKeyBoardShowing = true // 超过屏幕五分之一则表示弹出了输入法
-            rootView.setPadding(0, 0, 0, initialPadding + contentView.measuredHeight)
-            if (!isShowing) {
-                showAtLocation(rootView, Gravity.BOTTOM, 0, 0)
-            }
-        } else {
-            mIsSoftKeyBoardShowing = false
-            rootView.setPadding(0, 0, 0, 0)
-            if (preShowing) {
-                dismiss()
-            }
-        }
-    }
-
-    private fun initRecyclerView() {
-        binding.recyclerView.adapter = adapter
-        adapter.addHeaderView {
-            ItemFilletTextBinding.inflate(context.layoutInflater, it, false).apply {
-                textView.text = helpChar
-                root.setOnClickListener {
-                    helpAlert()
-                }
-                val lp = root.layoutParams as? ViewGroup.MarginLayoutParams
-                lp?.setMargins(0, 0, 4.dpToPx(), 0)
-                root.layoutParams = lp
-            }
-        }
-    }
-
-    @Suppress("MemberVisibilityCanBePrivate")
-    fun upAdapterData() {
-        scope.launch {
-            appDb.keyboardAssistsDao.flowByType(0).catch {
-                AppLog.put("键盘帮助浮窗获取数据失败\n${it.localizedMessage}", it)
-            }.flowOn(IO).collect {
-                adapter.setItems(it)
-            }
-        }
-    }
-
-    private fun helpAlert() {
-        val items = arrayListOf(
-            SelectItem(context.getString(R.string.assists_key_config), "keyConfig")
-        )
-        items.addAll(callBack.helpActions())
-        context.selector(context.getString(R.string.help), items) { _, selectItem, _ ->
-            when (selectItem.value) {
-                "keyConfig" -> config()
-                else -> callBack.onHelpActionSelect(selectItem.value)
-            }
-        }
-    }
-
-    private fun config() {
-        contentView.activity?.showDialogFragment<KeyboardAssistsConfig>()
-    }
-
-    inner class Adapter(context: Context) :
-        RecyclerAdapter<KeyboardAssist, ItemFilletTextBinding>(context) {
-
-        override fun getViewBinding(parent: ViewGroup): ItemFilletTextBinding {
-            return ItemFilletTextBinding.inflate(inflater, parent, false).apply {
-                val lp = root.layoutParams as ViewGroup.MarginLayoutParams
-                lp.setMargins(0, 0, 4.dpToPx(), 0)
-                root.layoutParams = lp
-            }
-        }
-
-        override fun convert(
-            holder: ItemViewHolder,
-            binding: ItemFilletTextBinding,
-            item: KeyboardAssist,
-            payloads: MutableList<Any>
-        ) {
-            binding.run {
-                textView.text = item.key
-            }
-        }
-
-        override fun registerListener(holder: ItemViewHolder, binding: ItemFilletTextBinding) {
-            holder.itemView.apply {
-                setOnClickListener {
-                    getItemByLayoutPosition(holder.layoutPosition)?.let {
-                        callBack.sendText(it.value)
-                    }
-                }
-            }
-        }
-    }
-
-    interface CallBack {
-
-        fun helpActions(): List<SelectItem<String>> = arrayListOf()
-
-        fun onHelpActionSelect(action: String)
-
-        fun sendText(text: String)
-
-        fun onUndoClicked() {}
-        fun onRedoClicked() {}
-    }
-
-}

+ 0 - 112
app/src/main/java/io/legado/app/ui/widget/number/NumberPickerDialog.kt

@@ -1,112 +0,0 @@
-package io.legado.app.ui.widget.number
-
-import android.app.AlertDialog
-import android.content.Context
-import android.view.View
-import android.widget.NumberPicker
-import com.google.android.material.button.MaterialButton
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.textfield.TextInputEditText
-import com.google.android.material.textfield.TextInputLayout
-import io.legado.app.R
-
-
-class NumberPickerDialog(context: Context) {
-    private val builder = MaterialAlertDialogBuilder(context)
-    private var numberPicker: NumberPicker? = null
-    private var editText: TextInputEditText? = null
-    private var inputLayout: TextInputLayout? = null
-    private var maxValue: Int? = null
-    private var minValue: Int? = null
-    private var value: Int? = null
-    private var useInputMode = false
-
-    init {
-        builder.setView(R.layout.dialog_number_picker)
-    }
-
-    fun setTitle(title: String): NumberPickerDialog {
-        builder.setTitle(title)
-        return this
-    }
-
-    fun setMaxValue(value: Int): NumberPickerDialog {
-        maxValue = value
-        return this
-    }
-
-    fun setMinValue(value: Int): NumberPickerDialog {
-        minValue = value
-        return this
-    }
-
-    fun setValue(value: Int): NumberPickerDialog {
-        this.value = value
-        return this
-    }
-
-    fun setCustomButton(textId: Int, listener: (() -> Unit)?): NumberPickerDialog {
-        builder.setNeutralButton(textId) { _, _ ->
-            numberPicker?.clearFocus()
-            listener?.invoke()
-        }
-        return this
-    }
-
-    fun show(callBack: ((value: Int) -> Unit)?) {
-        builder.setPositiveButton(R.string.ok, null)
-        builder.setNegativeButton(R.string.cancel, null)
-        val dialog = builder.show()
-
-        numberPicker = dialog.findViewById(R.id.number_picker)
-        inputLayout = dialog.findViewById(R.id.til_input)
-        editText = dialog.findViewById(R.id.et_input)
-        val btnSwitch = dialog.findViewById<MaterialButton>(R.id.btn_switch_input)
-
-        numberPicker?.let { np ->
-            minValue?.let { np.minValue = it }
-            maxValue?.let { np.maxValue = it }
-            value?.let { np.value = it }
-        }
-
-        btnSwitch?.setOnClickListener {
-            useInputMode = !useInputMode
-            if (useInputMode) {
-                numberPicker?.visibility = View.GONE
-                inputLayout?.visibility = View.VISIBLE
-
-                val min = minValue ?: numberPicker?.minValue
-                val max = maxValue ?: numberPicker?.maxValue
-                inputLayout?.hint = "输入范围: $min - $max"
-
-                editText?.setText(numberPicker?.value?.toString() ?: value?.toString())
-            } else {
-                numberPicker?.visibility = View.VISIBLE
-                inputLayout?.visibility = View.GONE
-                inputLayout?.error = null
-                inputLayout?.hint = null
-            }
-        }
-
-        dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
-            if (useInputMode) {
-                val num = editText?.text?.toString()?.toIntOrNull()
-                val min = minValue ?: numberPicker?.minValue ?: Int.MIN_VALUE
-                val max = maxValue ?: numberPicker?.maxValue ?: Int.MAX_VALUE
-                if (num == null || num !in min..max) {
-                    inputLayout?.error = "请输入 $min - $max 范围内的数字"
-                } else {
-                    inputLayout?.error = null
-                    callBack?.invoke(num)
-                    dialog.dismiss()
-                }
-            } else {
-                numberPicker?.let {
-                    it.clearFocus()
-                    callBack?.invoke(it.value)
-                    dialog.dismiss()
-                }
-            }
-        }
-    }
-}

+ 0 - 170
app/src/main/java/io/legado/app/ui/widget/recycler/DividerNoLast.kt

@@ -1,170 +0,0 @@
-package io.legado.app.ui.widget.recycler
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.view.View
-import android.widget.LinearLayout
-import androidx.recyclerview.widget.RecyclerView
-import io.legado.app.utils.DebugLog
-
-import kotlin.math.roundToInt
-
-
-/**
- * 不画最后一条分隔线
- */
-@Suppress("MemberVisibilityCanBePrivate", "RedundantRequireNotNullCall", "unused")
-class DividerNoLast(context: Context, orientation: Int) :
-    RecyclerView.ItemDecoration() {
-
-    companion object {
-        const val HORIZONTAL = LinearLayout.HORIZONTAL
-        const val VERTICAL = LinearLayout.VERTICAL
-    }
-
-    private val attrs = intArrayOf(android.R.attr.listDivider)
-
-    private var mDivider: Drawable? = null
-
-    /**
-     * Current orientation. Either [.HORIZONTAL] or [.VERTICAL].
-     */
-    private var mOrientation = 0
-
-    private val mBounds = Rect()
-
-    init {
-        val a = context.obtainStyledAttributes(attrs)
-        mDivider = a.getDrawable(0)
-        if (mDivider == null) {
-            DebugLog.w(
-                javaClass.name,
-                "@android:attr/listDivider was not set in the theme used for this DividerItemDecoration. Please set that attribute all call setDrawable()"
-            )
-        }
-        a.recycle()
-        setOrientation(orientation)
-    }
-
-    /**
-     * Sets the orientation for this divider. This should be called if
-     * [RecyclerView.LayoutManager] changes orientation.
-     *
-     * @param orientation [.HORIZONTAL] or [.VERTICAL]
-     */
-    fun setOrientation(orientation: Int) {
-        require(!(orientation != HORIZONTAL && orientation != VERTICAL)) { "Invalid orientation. It should be either HORIZONTAL or VERTICAL" }
-        mOrientation = orientation
-    }
-
-    /**
-     * Sets the [Drawable] for this divider.
-     *
-     * @param drawable Drawable that should be used as a divider.
-     */
-    fun setDrawable(drawable: Drawable) {
-        requireNotNull(drawable) { "Drawable cannot be null." }
-        mDivider = drawable
-    }
-
-    /**
-     * @return the [Drawable] for this divider.
-     */
-    fun getDrawable(): Drawable? {
-        return mDivider
-    }
-
-    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
-        if (parent.layoutManager == null || mDivider == null) {
-            return
-        }
-        if (mOrientation == VERTICAL) {
-            drawVertical(c, parent)
-        } else {
-            drawHorizontal(c, parent)
-        }
-    }
-
-    private fun drawVertical(
-        canvas: Canvas,
-        parent: RecyclerView
-    ) {
-        canvas.save()
-        val left: Int
-        val right: Int
-        if (parent.clipToPadding) {
-            left = parent.paddingLeft
-            right = parent.width - parent.paddingRight
-            canvas.clipRect(
-                left, parent.paddingTop, right,
-                parent.height - parent.paddingBottom
-            )
-        } else {
-            left = 0
-            right = parent.width
-        }
-        val childCount = parent.childCount
-        for (i in 0 until childCount - 1) {
-            val child = parent.getChildAt(i)
-            parent.getDecoratedBoundsWithMargins(child, mBounds)
-            val bottom = mBounds.bottom + child.translationY.roundToInt()
-            val top = bottom - mDivider!!.intrinsicHeight
-            mDivider!!.setBounds(left, top, right, bottom)
-            mDivider!!.draw(canvas)
-        }
-        canvas.restore()
-    }
-
-    private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {
-        canvas.save()
-        val top: Int
-        val bottom: Int
-        if (parent.clipToPadding) {
-            top = parent.paddingTop
-            bottom = parent.height - parent.paddingBottom
-            canvas.clipRect(
-                parent.paddingLeft, top,
-                parent.width - parent.paddingRight, bottom
-            )
-        } else {
-            top = 0
-            bottom = parent.height
-        }
-        val childCount = parent.childCount
-        for (i in 0 until childCount - 1) {
-            val child = parent.getChildAt(i)
-            parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds)
-            val right = mBounds.right + child.translationX.roundToInt()
-            val left = right - mDivider!!.intrinsicWidth
-            mDivider!!.setBounds(left, top, right, bottom)
-            mDivider!!.draw(canvas)
-        }
-        canvas.restore()
-    }
-
-    override fun getItemOffsets(
-        outRect: Rect, view: View, parent: RecyclerView,
-        state: RecyclerView.State
-    ) {
-        if (mDivider == null) {
-            outRect[0, 0, 0] = 0
-            return
-        }
-
-        if (mOrientation == VERTICAL) {
-            outRect[0, 0, 0] = mDivider!!.intrinsicHeight
-        } else {
-            val childAdapterPosition = parent.getChildAdapterPosition(view)
-            val lastCount = parent.adapter!!.itemCount - 1
-            //如果不是最后一条 正常赋值 如果是最后一条 赋值为0
-            if (childAdapterPosition != lastCount) {
-                outRect[0, 0, mDivider!!.intrinsicWidth] = 0
-            } else {
-                outRect[0, 0, 0] = 0
-            }
-        }
-    }
-
-}

+ 0 - 34
app/src/main/java/io/legado/app/ui/widget/recycler/HeaderAdapterDataObserver.kt

@@ -1,34 +0,0 @@
-package io.legado.app.ui.widget.recycler
-
-import androidx.recyclerview.widget.RecyclerView
-
-@Suppress("unused")
-internal class HeaderAdapterDataObserver(
-    private var adapterDataObserver: RecyclerView.AdapterDataObserver,
-    private var headerCount: Int
-) : RecyclerView.AdapterDataObserver() {
-    override fun onChanged() {
-        adapterDataObserver.onChanged()
-    }
-
-    override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
-        adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount)
-    }
-
-    override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
-        adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount, payload)
-    }
-
-    // 当第n个数据被获取,更新第n+1个position
-    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
-        adapterDataObserver.onItemRangeInserted(positionStart + headerCount, itemCount)
-    }
-
-    override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
-        adapterDataObserver.onItemRangeRemoved(positionStart + headerCount, itemCount)
-    }
-
-    override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
-        super.onItemRangeMoved(fromPosition + headerCount, toPosition + headerCount, itemCount)
-    }
-}

+ 0 - 36
app/src/main/java/io/legado/app/ui/widget/recycler/NoChildScrollLinearLayoutManager.kt

@@ -1,36 +0,0 @@
-package io.legado.app.ui.widget.recycler
-
-import android.content.Context
-import android.graphics.Rect
-import android.util.AttributeSet
-import android.view.View
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-
-class NoChildScrollLinearLayoutManager @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
-) : LinearLayoutManager(context, attrs, defStyleAttr, defStyleRes) {
-
-    override fun requestChildRectangleOnScreen(
-        parent: RecyclerView,
-        child: View,
-        rect: Rect,
-        immediate: Boolean,
-        focusedChildVisible: Boolean
-    ): Boolean {
-        return false
-    }
-
-    override fun requestChildRectangleOnScreen(
-        parent: RecyclerView,
-        child: View,
-        rect: Rect,
-        immediate: Boolean
-    ): Boolean {
-        return false
-    }
-
-}

+ 0 - 48
app/src/main/java/io/legado/app/ui/widget/recycler/RecyclerViewAtPager2.kt

@@ -1,48 +0,0 @@
-package io.legado.app.ui.widget.recycler
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.MotionEvent
-import androidx.recyclerview.widget.RecyclerView
-import kotlin.math.abs
-
-class RecyclerViewAtPager2 : RecyclerView {
-
-    constructor(context: Context) : super(context)
-    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
-    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
-        context,
-        attrs,
-        defStyleAttr
-    )
-
-    private var startX = 0
-    private var startY = 0
-
-    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
-        when (ev.action) {
-            MotionEvent.ACTION_DOWN -> {
-                startX = ev.x.toInt()
-                startY = ev.y.toInt()
-                parent.requestDisallowInterceptTouchEvent(true)
-            }
-            MotionEvent.ACTION_MOVE -> {
-                val endX = ev.x.toInt()
-                val endY = ev.y.toInt()
-                val disX = abs(endX - startX)
-                val disY = abs(endY - startY)
-                if (disX > disY) {
-                    if (disX > 50) {
-                        parent.requestDisallowInterceptTouchEvent(false)
-                    }
-                } else {
-                    parent.requestDisallowInterceptTouchEvent(true)
-                }
-            }
-            MotionEvent.ACTION_UP,
-            MotionEvent.ACTION_CANCEL -> parent.requestDisallowInterceptTouchEvent(false)
-        }
-        return super.dispatchTouchEvent(ev)
-    }
-
-}

+ 0 - 46
app/src/main/java/io/legado/app/ui/widget/recycler/UpLinearLayoutManager.kt

@@ -1,46 +0,0 @@
-package io.legado.app.ui.widget.recycler
-
-import android.content.Context
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.LinearSmoothScroller
-
-@Suppress("MemberVisibilityCanBePrivate", "unused")
-class UpLinearLayoutManager(val context: Context) : LinearLayoutManager(context) {
-
-    fun smoothScrollToPosition(position: Int) {
-        smoothScrollToPosition(position, 0)
-    }
-
-    fun smoothScrollToPosition(position: Int, offset: Int) {
-        val scroller = UpLinearSmoothScroller(context)
-        scroller.targetPosition = position
-        scroller.offset = offset
-        startSmoothScroll(scroller)
-    }
-
-    class UpLinearSmoothScroller(context: Context?) : LinearSmoothScroller(context) {
-        var offset = 0
-
-        override fun getVerticalSnapPreference(): Int {
-            return SNAP_TO_START
-        }
-
-        override fun getHorizontalSnapPreference(): Int {
-            return SNAP_TO_START
-        }
-
-        override fun calculateDtToFit(
-            viewStart: Int,
-            viewEnd: Int,
-            boxStart: Int,
-            boxEnd: Int,
-            snapPreference: Int
-        ): Int {
-            if (snapPreference == SNAP_TO_START) {
-                return boxStart - viewStart + offset
-            }
-            throw IllegalArgumentException("snap preference should be SNAP_TO_START")
-        }
-    }
-
-}

+ 0 - 31
app/src/main/java/io/legado/app/ui/widget/text/EditEntity.kt

@@ -1,31 +0,0 @@
-package io.legado.app.ui.widget.text
-
-import splitties.init.appCtx
-
-data class EditEntity(
-    var key: String,
-    var value: String?,
-    var hint: String,
-    val viewType: Int = 0
-) {
-
-    constructor(
-        key: String,
-        value: String?,
-        hint: Int,
-        viewType: Int = 0
-    ) : this(
-        key,
-        value,
-        appCtx.getString(hint),
-        viewType
-    )
-
-    @Suppress("unused")
-    object ViewType {
-
-        const val checkBox = 1
-
-    }
-
-}

+ 0 - 28
app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt

@@ -1,28 +0,0 @@
-package io.legado.app.ui.widget.text
-
-import android.content.Context
-import android.os.Build
-import android.util.AttributeSet
-import androidx.appcompat.widget.AppCompatTextView
-
-class MultilineTextView(context: Context, attrs: AttributeSet?) :
-    AppCompatTextView(context, attrs) {
-
-    init {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-            isFallbackLineSpacing = false
-        }
-    }
-
-    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
-        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
-        calculateLines(heightSize)
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
-    }
-
-    private fun calculateLines(measuredHeight: Int) {
-        val lHeight = lineHeight
-        val lines = measuredHeight / lHeight
-        setLines(lines)
-    }
-}

+ 0 - 17
app/src/main/java/io/legado/app/ui/widget/text/PrimaryTextView.kt

@@ -1,17 +0,0 @@
-package io.legado.app.ui.widget.text
-
-import android.content.Context
-import android.util.AttributeSet
-import androidx.appcompat.widget.AppCompatTextView
-
-/**
- * @author Aidan Follestad (afollestad)
- */
-@Suppress("unused")
-class PrimaryTextView(context: Context, attrs: AttributeSet) :
-    AppCompatTextView(context, attrs) {
-
-//    init {
-//        setTextColor(ThemeStore.textColorPrimary(context))
-//    }
-}

+ 0 - 18
app/src/main/java/io/legado/app/ui/widget/text/SecondaryTextView.kt

@@ -1,18 +0,0 @@
-package io.legado.app.ui.widget.text
-
-import android.content.Context
-import android.util.AttributeSet
-import androidx.appcompat.widget.AppCompatTextView
-//import io.legado.app.lib.theme.secondaryTextColor
-
-/**
- * @author Aidan Follestad (afollestad)
- */
-@Suppress("unused")
-class SecondaryTextView(context: Context, attrs: AttributeSet) :
-    AppCompatTextView(context, attrs) {
-
-    init {
-        //setTextColor(context.secondaryTextColor)
-    }
-}

+ 0 - 9
app/src/main/java/io/legado/app/utils/ActivityExtensions.kt

@@ -21,7 +21,6 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
 import androidx.fragment.app.DialogFragment
 import io.legado.app.R
-import io.legado.app.ui.widget.dialog.TextDialog
 
 inline fun <reified T : DialogFragment> AppCompatActivity.showDialogFragment(
     arguments: Bundle.() -> Unit = {}
@@ -209,11 +208,3 @@ val Activity.navigationBarGravity: Int
         val gravity = (navigationBar?.layoutParams as? FrameLayout.LayoutParams)?.gravity
         return gravity ?: Gravity.BOTTOM
     }
-
-/**
- * 显示目录help下的帮助文档
- */
-fun AppCompatActivity.showHelp(fileName: String) {
-    val mdText = String(assets.open("web/help/md/${fileName}.md").readBytes())
-    showDialogFragment(TextDialog(getString(R.string.help), mdText, TextDialog.Mode.MD))
-}

+ 0 - 6
app/src/main/java/io/legado/app/utils/FragmentExtensions.kt

@@ -19,7 +19,6 @@ import io.legado.app.help.book.isImage
 import io.legado.app.help.book.isLocal
 import io.legado.app.domain.gateway.MangaSettingsGateway
 import io.legado.app.ui.main.MainActivity
-import io.legado.app.ui.widget.dialog.TextDialog
 import org.koin.core.context.GlobalContext
 
 private val mangaSettingsGateway
@@ -72,10 +71,5 @@ fun Fragment.startActivityForBook(
     startActivity(intent)
 }
 
-fun Fragment.showHelp(fileName: String) {
-    val mdText = String(requireContext().assets.open("web/help/md/${fileName}.md").readBytes())
-    showDialogFragment(TextDialog(getString(R.string.help), mdText, TextDialog.Mode.MD))
-}
-
 val Fragment.isCreated
     get() = lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)

+ 0 - 150
app/src/main/res/layout/dialog_book_change_source.xml

@@ -1,150 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <androidx.appcompat.widget.Toolbar
-        android:id="@+id/tool_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:theme="?attr/actionBarStyle"
-        app:contentInsetStartWithNavigation="0dp"
-        app:displayHomeAsUp="false"
-        app:fitStatusBar="false" />
-
-    <com.google.android.material.progressindicator.LinearProgressIndicator
-        android:id="@+id/refresh_progress_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:indeterminate="true"
-        android:visibility="invisible"
-        app:waveAmplitude="2dp"
-        app:wavelength="32dp"
-        app:waveSpeed="24dp"
-        app:layout_constraintTop_toBottomOf="@+id/tool_bar" />
-
-    <LinearLayout
-        android:id="@+id/ll_info"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginVertical="4dp"
-        android:layout_marginStart="16dp"
-        android:orientation="horizontal"
-        android:visibility="gone">
-
-        <com.google.android.material.card.MaterialCardView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:scaleType="centerCrop"
-            app:cardBackgroundColor="?attr/colorTertiaryContainer"
-            app:cardCornerRadius="4dp"
-            app:strokeWidth="0dp">
-
-            <TextView
-                android:id="@+id/tv_progress"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingHorizontal="4dp"
-                android:paddingVertical="2dp"
-                android:text="@string/text"
-                android:textColor="?attr/colorTertiary"
-                android:textSize="11sp"
-                android:textStyle="bold"
-                tools:ignore="SmallSp" />
-
-        </com.google.android.material.card.MaterialCardView>
-
-        <com.google.android.material.card.MaterialCardView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="4dp"
-            android:scaleType="centerCrop"
-            app:cardBackgroundColor="?attr/colorPrimary"
-            app:cardCornerRadius="4dp"
-            app:strokeWidth="0dp">
-
-            <TextView
-                android:id="@+id/tv_result"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:paddingHorizontal="4dp"
-                android:paddingVertical="2dp"
-                android:text="@string/text"
-                android:textColor="?attr/colorOnPrimary"
-                android:textSize="11sp"
-                android:textStyle="bold"
-                tools:ignore="SmallSp" />
-
-        </com.google.android.material.card.MaterialCardView>
-
-    </LinearLayout>
-
-    <io.legado.app.ui.widget.EmptyMessageView
-        android:id="@+id/tv_empty_msg"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:layout_gravity="center"
-        app:messageText="还没有源!"/>
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/recycler_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
-
-    <LinearLayout
-        android:id="@+id/ll_bottom_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:orientation="horizontal"
-        android:paddingHorizontal="16dp"
-        android:paddingVertical="8dp">
-
-        <TextView
-            android:id="@+id/tv_dur"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:background="?attr/selectableItemBackgroundBorderless"
-            android:text="@string/text"
-            android:textStyle="bold"
-            android:ellipsize="end"
-            android:gravity="center_vertical"
-            android:singleLine="true"
-            android:textSize="12sp" />
-
-        <com.google.android.material.button.MaterialButtonGroup
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content">
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/iv_top"
-                style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
-                android:layout_width="40dp"
-                android:layout_height="40dp"
-                android:contentDescription="@string/go_to_top"
-                android:tooltipText="@string/go_to_top"
-                app:icon="@drawable/ic_arrow_drop_up"
-                app:iconGravity="textStart"
-                tools:ignore="UnusedAttribute" />
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/iv_bottom"
-                style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
-                android:layout_width="40dp"
-                android:layout_height="40dp"
-                android:contentDescription="@string/go_to_bottom"
-                android:tooltipText="@string/go_to_bottom"
-                app:icon="@drawable/ic_arrow_drop_down"
-                app:iconGravity="textStart"
-                tools:ignore="UnusedAttribute" />
-        </com.google.android.material.button.MaterialButtonGroup>
-
-    </LinearLayout>
-
-</LinearLayout>

+ 0 - 178
app/src/main/res/layout/dialog_book_group_edit.xml

@@ -1,178 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:fillViewport="true"
-    android:paddingHorizontal="16dp"
-    android:paddingBottom="16dp"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:orientation="vertical"
-        android:paddingBottom="8dp">
-
-        <com.google.android.material.bottomsheet.BottomSheetDragHandleView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingVertical="16dp"
-            android:minHeight="0dp"
-            android:layout_gravity="center_horizontal" />
-
-        <TextView
-            android:id="@+id/bottom_sheet_title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/group_edit"
-            android:textSize="18sp"
-            android:textStyle="bold" />
-
-    </LinearLayout>
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:gravity="center_horizontal"
-        android:paddingVertical="16dp">
-
-        <io.legado.app.ui.widget.image.CoverImageView
-            android:id="@+id/iv_cover"
-            android:layout_width="90dp"
-            android:layout_height="130dp"
-            android:layout_marginEnd="16dp"
-            android:layout_marginTop="4dp"
-            android:contentDescription="@string/img_cover"
-            android:scaleType="centerCrop"
-            android:src="@drawable/image_cover_default"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            tools:ignore="ImageContrastCheck" />
-
-            <com.google.android.material.textfield.TextInputLayout
-                android:id="@+id/til_group_name"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
-                android:layout_marginStart="8dp"
-                android:hint="@string/group_name"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@id/iv_cover"
-                app:layout_constraintTop_toTopOf="@id/iv_cover">
-
-                <com.google.android.material.textfield.TextInputEditText
-                    android:id="@+id/tie_group_name"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:ellipsize="end"
-                    android:maxLines="1" />
-            </com.google.android.material.textfield.TextInputLayout>
-
-        <com.google.android.material.textfield.TextInputLayout
-            android:id="@+id/til_sort_menu"
-            style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
-            android:hint="@string/sort"
-            app:layout_constraintBottom_toBottomOf="@id/iv_cover"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toEndOf="@id/iv_cover">
-
-            <com.google.android.material.textfield.MaterialAutoCompleteTextView
-                android:id="@+id/actv_sort_menu"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:cursorVisible="false"
-                android:ellipsize="end"
-                android:focusable="false"
-                android:inputType="none"
-                android:singleLine="true" />
-        </com.google.android.material.textfield.TextInputLayout>
-
-            <com.google.android.material.materialswitch.MaterialSwitch
-                android:id="@+id/cb_enable_refresh"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/allow_drop_down_refresh"
-                android:checked="true"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/iv_cover" />
-
-        <com.google.android.material.materialswitch.MaterialSwitch
-            android:id="@+id/cb_private"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/private_group"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/cb_enable_refresh" />
-
-        <TextView
-            android:id="@+id/tv_private_desc"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:text="@string/private_group_desc"
-            android:textAppearance="?attr/textAppearanceBodySmall"
-            android:textColor="?attr/colorOnSurfaceVariant"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="@id/cb_private"
-            app:layout_constraintTop_toBottomOf="@id/cb_private" />
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:gravity="center_vertical">
-
-        <com.google.android.material.button.MaterialButtonGroup
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content">
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btn_delete"
-                style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:icon="@drawable/ic_clear_all" />
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btn_reset"
-                style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:icon="@drawable/ic_restore" />
-        </com.google.android.material.button.MaterialButtonGroup>
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="1dp"
-            android:layout_weight="1" />
-
-        <com.google.android.material.button.MaterialButtonGroup
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content">
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btn_cancel"
-                style="@style/Widget.Material3Expressive.Button.TonalButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/cancel" />
-
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btn_ok"
-                style="@style/Widget.Material3Expressive.Button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/ok" />
-        </com.google.android.material.button.MaterialButtonGroup>
-
-    </LinearLayout>
-</LinearLayout>

+ 0 - 48
app/src/main/res/layout/dialog_book_group_picker.xml

@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <androidx.appcompat.widget.Toolbar
-        android:id="@+id/tool_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:theme="?attr/actionBarStyle" />
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/recycler_view"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1" />
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-        <Space
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" />
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/tv_cancel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/Widget.Material3Expressive.Button.OutlinedButton"
-            android:layout_marginEnd="8dp"
-            android:text="@string/cancel"
-            tools:ignore="RtlHardcoded" />
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/tv_ok"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@style/Widget.Material3Expressive.Button.TonalButton"
-            android:layout_marginEnd="16dp"
-            android:text="@string/ok"
-            tools:ignore="RtlHardcoded" />
-
-    </LinearLayout>
-</LinearLayout>

+ 0 - 130
app/src/main/res/layout/dialog_bookmark.xml

@@ -1,130 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:id="@+id/vw_bg"
-    android:layout_gravity="center"
-    android:orientation="vertical"
-    tools:ignore="UselessParent">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:orientation="vertical"
-        android:paddingHorizontal="16dp"
-        android:paddingBottom="8dp">
-
-        <com.google.android.material.bottomsheet.BottomSheetDragHandleView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingVertical="16dp"
-            android:minHeight="0dp"
-            android:layout_gravity="center_horizontal" />
-
-        <TextView
-            android:id="@+id/bottom_sheet_title"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:text="@string/bookmark"
-            android:textSize="18sp"
-            android:textStyle="bold" />
-
-    </LinearLayout>
-
-        <TextView
-            android:id="@+id/tv_chapter_name"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingHorizontal="16dp" />
-
-        <androidx.core.widget.NestedScrollView
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:padding="16dp"
-            android:overScrollMode="ifContentScrolls">
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="vertical">
-
-                <com.google.android.material.textfield.TextInputLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <com.google.android.material.textfield.TextInputEditText
-                        android:id="@+id/edit_book_text"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="@string/content"
-                        tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </com.google.android.material.textfield.TextInputLayout>
-
-                <com.google.android.material.textfield.TextInputLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:paddingTop="3dp">
-
-                    <com.google.android.material.textfield.TextInputEditText
-                        android:id="@+id/edit_content"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="@string/note_content"
-                        tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </com.google.android.material.textfield.TextInputLayout>
-
-            </LinearLayout>
-
-        </androidx.core.widget.NestedScrollView>
-
-        <androidx.constraintlayout.widget.ConstraintLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:padding="16dp">
-
-            <!-- 删除按钮靠左 -->
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btn_delete"
-                style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:icon="@drawable/ic_clear_all"
-                android:visibility="invisible"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toTopOf="parent"
-                app:layout_constraintBottom_toBottomOf="parent" />
-
-            <!-- 取消按钮 -->
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btn_cancel"
-                style="@style/Widget.Material3Expressive.Button.TonalButton"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:minWidth="0dp"
-                android:text="@string/cancel"
-                app:layout_constraintEnd_toStartOf="@id/btn_ok"
-                app:layout_constraintTop_toTopOf="parent"
-                app:layout_constraintBottom_toBottomOf="parent"
-                android:layout_marginEnd="8dp"/>
-
-            <!-- 确认按钮靠右 -->
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btn_ok"
-                style="@style/Widget.Material3Expressive.Button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:minWidth="0dp"
-                android:text="@string/ok"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toTopOf="parent"
-                app:layout_constraintBottom_toBottomOf="parent" />
-
-        </androidx.constraintlayout.widget.ConstraintLayout>
-
-</LinearLayout>

+ 0 - 30
app/src/main/res/layout/dialog_change_cover.xml

@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <androidx.appcompat.widget.Toolbar
-        android:id="@+id/tool_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:elevation="5dp"
-        android:theme="?attr/actionBarStyle"
-
-        app:displayHomeAsUp="false"
-        app:fitStatusBar="false" />
-
-    <com.google.android.material.progressindicator.LinearProgressIndicator
-        android:id="@+id/refresh_progress_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:indeterminate="true"
-        android:visibility="gone" />
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/recycler_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-</LinearLayout>

+ 0 - 24
app/src/main/res/layout/dialog_edit_text.xml

@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:overScrollMode="ifContentScrolls"
-    android:paddingVertical="8dp"
-    android:paddingHorizontal="16dp">
-
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/edit_layout"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/Widget.Material3.TextInputLayout.FilledBox"
-        android:layout_marginTop="8dp">
-
-        <io.legado.app.ui.widget.text.AutoCompleteTextView
-            android:id="@+id/edit_view"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:padding="16dp" />
-
-    </com.google.android.material.textfield.TextInputLayout>
-
-</androidx.core.widget.NestedScrollView>

+ 0 - 47
app/src/main/res/layout/dialog_file_chooser.xml

@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <com.google.android.material.appbar.MaterialToolbar
-        android:id="@+id/tool_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/rv_path"
-        android:layout_width="match_parent"
-        android:layout_height="24dp"
-        android:paddingLeft="12dp"
-        android:paddingRight="12dp" />
-
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1">
-
-        <androidx.recyclerview.widget.RecyclerView
-            android:id="@+id/rv_file"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-
-        <TextView
-            android:id="@+id/tv_empty"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:gravity="center"
-            android:visibility="gone" />
-
-    </FrameLayout>
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/tv_ok"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="@style/Widget.Material3Expressive.Button.TonalButton"
-        android:layout_margin="8dp"
-        android:text="@string/ok"
-        android:gravity="center" />
-
-</LinearLayout>

+ 0 - 44
app/src/main/res/layout/dialog_multiple_edit_text.xml

@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:overScrollMode="ifContentScrolls"
-    android:padding="16dp">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
-
-        <com.google.android.material.textfield.TextInputLayout
-            android:id="@+id/layout_1"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content">
-
-            <io.legado.app.ui.widget.text.AutoCompleteTextView
-                android:id="@+id/edit_1"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-        </com.google.android.material.textfield.TextInputLayout>
-
-        <com.google.android.material.textfield.TextInputLayout
-            android:id="@+id/layout_2"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone">
-
-            <io.legado.app.ui.widget.text.AutoCompleteTextView
-                android:id="@+id/edit_2"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-        </com.google.android.material.textfield.TextInputLayout>
-
-
-    </LinearLayout>
-
-</androidx.core.widget.NestedScrollView>

+ 0 - 48
app/src/main/res/layout/dialog_number_picker.xml

@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="24dp"
-    android:gravity="center">
-
-    <NumberPicker
-        android:id="@+id/number_picker"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:scrollbars="none"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="@id/btn_switch_input"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"/>
-
-    <com.google.android.material.textfield.TextInputLayout
-        android:id="@+id/til_input"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        style="@style/Widget.Material3.TextInputLayout.FilledBox.Dense"
-        android:visibility="gone"
-        android:layout_marginEnd="16dp"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/btn_switch_input"
-        app:layout_constraintTop_toBottomOf="@id/btn_switch_input"
-        app:layout_constraintBottom_toTopOf="@id/btn_switch_input">
-
-        <com.google.android.material.textfield.TextInputEditText
-            android:id="@+id/et_input"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:inputType="number" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/btn_switch_input"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:icon="@drawable/ic_swap_horiz"
-        style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"/>
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 48
app/src/main/res/layout/dialog_text_view.xml

@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:gravity="center"
-        android:paddingBottom="8dp"
-        android:paddingHorizontal="16dp">
-
-        <com.google.android.material.bottomsheet.BottomSheetDragHandleView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:paddingVertical="16dp"
-            android:minHeight="0dp"
-            android:layout_gravity="center_horizontal" />
-
-        <TextView
-            android:id="@+id/bottom_sheet_title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/help"
-            android:textSize="18sp"
-            android:textStyle="bold" />
-
-    </LinearLayout>
-
-
-    <androidx.core.widget.NestedScrollView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fillViewport="true">
-        <io.legado.app.ui.widget.text.ScrollTextView
-            android:id="@+id/text_view"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:padding="16dp"
-            android:scrollbarStyle="outsideOverlay"
-            android:textIsSelectable="true" />
-    </androidx.core.widget.NestedScrollView>
-
-</LinearLayout>
-
-

+ 0 - 184
app/src/main/res/layout/dialog_url_option_edit.xml

@@ -1,184 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:padding="16dp">
-
-    <LinearLayout
-        android:id="@+id/vw_bg"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="@drawable/shape_card_view"
-        android:orientation="vertical"
-        android:layout_gravity="center"
-        android:padding="16dp"
-        tools:ignore="UselessParent">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_vertical"
-            android:orientation="horizontal"
-            android:paddingBottom="10dp">
-
-            <TextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/url_option"
-                android:textSize="18sp"
-                android:textStyle="bold" />
-
-            <Space
-                android:layout_width="0dp"
-                android:layout_height="1dp"
-                android:layout_weight="1" />
-
-            <TextView
-                android:id="@+id/tv_ok"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:background="?attr/selectableItemBackgroundBorderless"
-                android:padding="12dp"
-                android:text="@string/ok" />
-
-        </LinearLayout>
-
-        <androidx.core.widget.NestedScrollView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:overScrollMode="ifContentScrolls">
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="vertical">
-
-                <io.legado.app.lib.theme.view.ThemeCheckBox
-                    android:id="@+id/cb_use_web_view"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="useWebView"
-                    tools:ignore="HardcodedText" />
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_method"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_method"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="method"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_charset"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_charset"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="charset"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_headers"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_headers"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="headers"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_body"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_body"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="body"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_type"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_type"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="type"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_retry"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_retry"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="retry"
-                        android:inputType="number"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_web_js"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_web_js"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="webJs"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-                <io.legado.app.ui.widget.text.TextInputLayout
-                    android:id="@+id/layout_js"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-
-                    <io.legado.app.ui.widget.text.AutoCompleteTextView
-                        android:id="@+id/edit_js"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:hint="js"
-                        tools:ignore="HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-
-                </io.legado.app.ui.widget.text.TextInputLayout>
-
-            </LinearLayout>
-
-        </androidx.core.widget.NestedScrollView>
-
-
-    </LinearLayout>
-
-</FrameLayout>

+ 0 - 84
app/src/main/res/layout/dialog_webdav_server.xml

@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <androidx.appcompat.widget.Toolbar
-        android:id="@+id/tool_bar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="3dp"
-        android:elevation="5dp"
-        android:theme="?attr/actionBarStyle"
-        app:title="@string/server_config"
-        app:displayHomeAsUp="false"
-        app:fitStatusBar="false" />
-
-    <LinearLayout
-        android:id="@+id/vw_bg"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="16dp"
-        android:layout_gravity="center"
-        android:orientation="vertical"
-        tools:ignore="UselessParent">
-
-        <io.legado.app.ui.widget.text.TextInputLayout
-            android:id="@+id/til_name"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:hint="@string/name">
-
-            <io.legado.app.lib.theme.view.ThemeEditText
-                android:id="@+id/et_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
-        </io.legado.app.ui.widget.text.TextInputLayout>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:gravity="center_vertical">
-
-            <io.legado.app.ui.widget.text.AccentTextView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:padding="6dp"
-                android:text="TYPE"
-                tools:ignore="HardcodedText" />
-
-            <androidx.appcompat.widget.AppCompatSpinner
-                android:id="@+id/sp_type"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:entries="@array/server_type"
-                tools:ignore="TouchTargetSizeCheck" />
-
-        </LinearLayout>
-
-        <androidx.core.widget.NestedScrollView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:overScrollMode="ifContentScrolls"
-            tools:ignore="SpeakableTextPresentCheck">
-
-            <com.google.android.flexbox.FlexboxLayout
-                android:id="@+id/flexbox"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="8dp"
-                android:clipToPadding="false"
-                app:dividerDrawable="@drawable/shape_space_divider"
-                app:flexDirection="row"
-                app:flexWrap="wrap"
-                app:showDivider="middle" />
-
-        </androidx.core.widget.NestedScrollView>
-
-    </LinearLayout>
-</LinearLayout>

+ 0 - 22
app/src/main/res/layout/item_app_log.xml

@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:padding="6dp"
-    android:background="?attr/selectableItemBackground"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/text_time"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
-
-    <TextView
-        android:id="@+id/text_message"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:contentDescription="@string/log"
-        android:textIsSelectable="true"
-        android:autoLink="web" />
-
-</LinearLayout>

+ 0 - 72
app/src/main/res/layout/item_book_group_manage.xml

@@ -1,72 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_margin="8dp"
-    app:strokeWidth="0dp">
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:clickable="true"
-        android:focusable="true"
-        android:background="?attr/colorSurface"
-        android:foreground="?attr/selectableItemBackground"
-        android:gravity="center_vertical"
-        android:orientation="horizontal"
-        android:padding="8dp">
-
-        <TextView
-            android:id="@+id/tv_group"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
-            android:textStyle="bold"
-            android:textSize="14sp"
-            tools:text="group" />
-
-        <com.google.android.material.card.MaterialCardView
-            android:id="@+id/cd_group"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:cardCornerRadius="8dp"
-            app:strokeColor="?attr/colorSurfaceContainerHigh"
-            app:cardBackgroundColor="?attr/colorSurfaceContainerHigh"
-            android:layout_marginStart="8dp">
-            <TextView
-                android:id="@+id/tv_suffix"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginVertical="4dp"
-                android:layout_marginHorizontal="8dp"
-                android:textStyle="bold"
-                android:textSize="12sp"
-                tools:text="group" />
-        </com.google.android.material.card.MaterialCardView>
-
-        <View
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"/>
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/tv_edit"
-            android:layout_width="40dp"
-            android:layout_height="40dp"
-            app:iconSize="20dp"
-            app:iconPadding="0dp"
-            app:iconGravity="textStart"
-            android:layout_marginEnd="8dp"
-            style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
-            app:icon="@drawable/ic_edit" />
-
-        <io.legado.app.lib.theme.view.ThemeSwitch
-            android:id="@+id/sw_show"
-            android:minWidth="48dp"
-            android:minHeight="48dp"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-
-    </LinearLayout>
-</com.google.android.material.card.MaterialCardView>

+ 0 - 60
app/src/main/res/layout/item_bookmark.xml

@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingStart="16dp"
-    android:paddingEnd="10dp"
-    android:paddingVertical="12dp"
-    android:background="?attr/selectableItemBackground">
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/btn_locate"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        app:iconSize="16dp"
-        app:iconGravity="textStart"
-        app:icon="@drawable/ic_locate"
-        style="@style/Widget.Material3Expressive.Button.IconButton.Outlined"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"/>
-
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:layout_marginEnd="8dp"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toStartOf="@id/btn_locate"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent">
-        <TextView
-            android:id="@+id/tv_chapter_name"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingBottom="2dp"
-            android:textSize="16sp"
-            android:textStyle="bold" />
-
-        <TextView
-            android:id="@+id/tv_book_text"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:paddingBottom="2dp"
-            android:textSize="13sp"
-            android:singleLine="true" />
-
-        <TextView
-            android:id="@+id/tv_content"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:textColor="?attr/colorSecondary"
-            android:textSize="12sp"
-            android:singleLine="true" />
-    </LinearLayout>
-
-
-
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 220
app/src/main/res/layout/item_bookshelf_list.xml

@@ -1,220 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/cv_content"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    app:cardBackgroundColor="@android:color/transparent"
-    android:clickable="true"
-    android:focusable="true"
-    app:strokeWidth="0dp">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:ignore="UnusedAttribute">
-
-        <com.google.android.material.card.MaterialCardView
-            android:id="@+id/cd_cover"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:cardBackgroundColor="@android:color/transparent"
-            app:cardCornerRadius="6dp"
-            app:strokeWidth="0dp"
-            android:layout_margin="8dp"
-            android:transitionName="img_cover"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent">
-            <androidx.constraintlayout.widget.ConstraintLayout
-                android:id="@+id/cl_cover"
-                android:layout_width="70dp"
-                android:layout_height="98dp">
-
-                <io.legado.app.ui.widget.image.CoverImageView
-                    android:id="@+id/iv_cover"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
-                    android:scaleType="centerCrop"
-                    android:src="@drawable/image_cover_default"
-                    app:layout_constraintTop_toTopOf="parent"
-                    app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintEnd_toEndOf="parent"
-                    tools:ignore="ImageContrastCheck" />
-
-                <com.google.android.material.progressindicator.LinearProgressIndicator
-                    android:id="@+id/rl_loading"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_marginVertical="4dp"
-                    android:layout_marginHorizontal="6dp"
-                    android:indeterminate="true"
-                    android:visibility="invisible"
-                    android:layout_gravity="top"
-                    app:layout_constraintTop_toTopOf="@id/iv_cover"
-                    app:layout_constraintStart_toStartOf="@id/iv_cover"
-                    app:layout_constraintEnd_toEndOf="@id/iv_cover" />
-
-                <com.google.android.material.card.MaterialCardView
-                    android:id="@+id/cd_unread"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_margin="4dp"
-                    android:visibility="visible"
-                    app:cardCornerRadius="4dp"
-                    app:strokeWidth="0dp"
-                    app:cardBackgroundColor="?attr/colorSecondaryVariant"
-                    app:layout_constraintEnd_toEndOf="parent"
-                    app:layout_constraintTop_toTopOf="parent">
-
-                    <LinearLayout
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:gravity="center_vertical"
-                        android:orientation="horizontal">
-
-                        <com.google.android.material.circularreveal.cardview.CircularRevealCardView
-                            android:id="@+id/new_chapter"
-                            android:layout_width="6dp"
-                            android:layout_height="6dp"
-                            android:visibility="gone"
-                            android:layout_marginStart="6dp"
-                            app:strokeWidth="0dp"
-                            app:cardBackgroundColor="?attr/colorSecondaryContainer" />
-
-                        <TextView
-                            android:id="@+id/tv_unread"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:textColor="?attr/colorOnSecondary"
-                            android:textSize="9sp"
-                            android:padding="4dp"
-                            android:textStyle="bold"
-                            android:includeFontPadding="false"
-                            tools:text="5"
-                            tools:ignore="SmallSp" />
-                    </LinearLayout>
-
-                </com.google.android.material.card.MaterialCardView>
-
-                <com.google.android.material.card.MaterialCardView
-                    android:id="@+id/cd_tip"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_margin="4dp"
-                    android:visibility="visible"
-                    app:cardCornerRadius="4dp"
-                    app:strokeWidth="0dp"
-                    app:cardBackgroundColor="?attr/colorSecondaryVariant"
-                    app:layout_constraintStart_toStartOf="parent"
-                    app:layout_constraintBottom_toBottomOf="parent">
-
-                    <TextView
-                        android:id="@+id/tv_tip"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:textColor="?attr/colorOnSecondary"
-                        android:textSize="8sp"
-                        android:padding="3dp"
-                        android:textStyle="bold"
-                        android:includeFontPadding="false"
-                        tools:text="5"
-                        tools:ignore="SmallSp" />
-
-                </com.google.android.material.card.MaterialCardView>
-
-            </androidx.constraintlayout.widget.ConstraintLayout>
-        </com.google.android.material.card.MaterialCardView>
-
-        <LinearLayout
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            android:layout_marginHorizontal="8dp"
-            android:gravity="center_vertical"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintStart_toEndOf="@id/cd_cover"
-            app:layout_constraintEnd_toEndOf="parent">
-
-            <TextView
-                android:id="@+id/tv_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginBottom="2dp"
-                android:scrollbars="none"
-                android:maxLines="2"
-                android:ellipsize="end"
-                android:text="@string/book_name"
-                android:textSize="16sp"
-                android:textStyle="bold"
-                tools:ignore="RtlHardcoded,RtlSymmetry"
-                app:layout_constraintStart_toEndOf="@+id/cd_cover"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintTop_toTopOf="@id/cd_cover" />
-
-            <TextView
-                android:id="@+id/tv_author"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginBottom="2dp"
-                android:maxLines="1"
-                android:scrollbars="none"
-                android:singleLine="true"
-                android:text="@string/author"
-                android:textSize="13sp"
-                tools:ignore="RtlSymmetry,TextContrastCheck"
-                app:layout_constraintStart_toEndOf="@+id/iv_author"
-                app:layout_constraintBottom_toTopOf="@+id/iv_author"
-                app:layout_constraintTop_toBottomOf="@+id/iv_author" />
-
-            <TextView
-                android:id="@+id/tv_read"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginBottom="2dp"
-                android:scrollbars="none"
-                android:singleLine="true"
-                android:text="@string/read_dur_progress"
-                android:textSize="13sp"
-                tools:ignore="TextContrastCheck"
-                app:layout_constraintBottom_toTopOf="@id/iv_read"
-                app:layout_constraintStart_toEndOf="@+id/iv_read"
-                app:layout_constraintTop_toBottomOf="@+id/iv_read"
-                app:layout_constraintEnd_toEndOf="parent"/>
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal">
-
-                <TextView
-                    android:id="@+id/tv_last_update_time"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
-                    android:layout_marginEnd="4dp"
-                    android:maxLines="1"
-                    android:scrollbars="none"
-                    android:singleLine="true"
-                    android:text="@string/text"
-                    android:textSize="13sp"
-                    android:textColor="?attr/colorPrimary" />
-
-                <TextView
-                    android:id="@+id/tv_last"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:scrollbars="none"
-                    android:singleLine="true"
-                    android:text="@string/lasted_show"
-                    android:textSize="13sp" />
-
-            </LinearLayout>
-
-        </LinearLayout>
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-</com.google.android.material.card.MaterialCardView>

+ 0 - 31
app/src/main/res/layout/item_cover.xml

@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:padding="12dp"
-    android:clickable="true"
-    android:focusable="true"
-    android:orientation="vertical">
-
-    <io.legado.app.ui.widget.image.CoverImageView
-        android:id="@+id/iv_cover"
-        android:layout_width="match_parent"
-        android:layout_height="100dp"
-        android:contentDescription="@string/img_cover"
-        android:scaleType="centerCrop"
-        android:transitionName="img_cover"
-        tools:ignore="UnusedAttribute" />
-
-    <TextView
-        android:id="@+id/tv_source"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:gravity="center"
-        android:singleLine="true"
-        android:text="@string/book_source"
-        android:textSize="16sp"
-        tools:ignore="RtlHardcoded,RtlSymmetry" />
-
-</LinearLayout>

+ 0 - 123
app/src/main/res/layout/item_download.xml

@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="?attr/selectableItemBackground"
-    android:clickable="true"
-    android:focusable="true"
-    android:padding="16dp">
-
-    <TextView
-        android:id="@+id/tv_name"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:singleLine="true"
-        style="@style/ChapterNameText"
-        android:layout_marginEnd="8dp"
-        app:layout_constraintEnd_toStartOf="@id/cd_download"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        tools:text="@string/name" />
-
-    <TextView
-        android:id="@+id/tv_author"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:paddingBottom="2dp"
-        android:singleLine="true"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toLeftOf="@+id/iv_download"
-        app:layout_constraintTop_toBottomOf="@+id/tv_name"
-        tools:text="@string/author" />
-
-    <com.google.android.material.card.MaterialCardView
-        android:id="@+id/cd_download"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="2dp"
-        app:cardBackgroundColor="?attr/colorSecondaryContainer"
-        app:layout_constraintBottom_toBottomOf="@id/tv_name"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="@id/tv_name"
-        app:strokeWidth="0dp">
-
-        <TextView
-            android:id="@+id/tv_download"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginHorizontal="6dp"
-            android:layout_marginVertical="2dp"
-            android:singleLine="true"
-            android:textColor="?attr/colorPrimaryVariant"
-            android:textSize="10sp"
-            android:textStyle="bold"
-            tools:ignore="SmallSp"
-            tools:text="@string/action_download" />
-    </com.google.android.material.card.MaterialCardView>
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/iv_delete"
-        style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:contentDescription="@string/start"
-        app:icon="@drawable/ic_clear_all"
-        app:iconGravity="textStart"
-        app:iconSize="20dp"
-        app:layout_constraintRight_toLeftOf="@+id/iv_download"
-        app:layout_constraintTop_toTopOf="@id/tv_author" />
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/iv_download"
-        style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
-        android:layout_width="40dp"
-        android:layout_height="40dp"
-        android:contentDescription="@string/start"
-        android:layout_marginEnd="4dp"
-        app:icon="@drawable/ic_play"
-        app:iconGravity="textStart"
-        app:layout_constraintRight_toLeftOf="@+id/tv_export"
-        app:layout_constraintTop_toTopOf="@id/tv_author" />
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/tv_export"
-        style="@style/Widget.Material3Expressive.Button.OutlinedButton"
-        android:layout_height="40dp"
-        android:layout_width="wrap_content"
-        android:minWidth="0dp"
-        android:textSize="12sp"
-        android:padding="0dp"
-        android:text="@string/export"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="@id/tv_author" />
-
-    <TextView
-        android:id="@+id/tv_msg"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@id/tv_author" />
-
-    <com.google.android.material.progressindicator.LinearProgressIndicator
-        android:id="@+id/progress_download"
-        style="@style/Widget.Material3.LinearProgressIndicator"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:waveAmplitude="2dp"
-        app:waveSpeed="50dp"
-        app:wavelength="40dp"
-        android:layout_marginTop="2dp"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@id/iv_download" />
-
-    <com.google.android.material.progressindicator.LinearProgressIndicator
-        android:id="@+id/progress_export"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        app:layout_constraintTop_toBottomOf="@id/iv_download" />
-
-</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 30
app/src/main/res/layout/item_file_picker.xml

@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content">
-
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_vertical"
-        android:background="?attr/selectableItemBackground"
-        android:padding="5dp"
-        tools:ignore="UseCompoundDrawables,UselessParent">
-
-        <ImageView
-            android:id="@+id/image_view"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            tools:ignore="ContentDescription" />
-
-        <TextView
-            android:id="@+id/text_view"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:gravity="center_vertical" />
-
-    </LinearLayout>
-
-</FrameLayout>

+ 0 - 26
app/src/main/res/layout/item_fillet_text.xml

@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    app:cardBackgroundColor="?attr/colorSecondaryContainer"
-    app:strokeWidth="0dp"
-    android:clickable="true"
-    android:focusable="true">
-    <TextView
-        xmlns:tools="http://schemas.android.com/tools"
-        android:id="@+id/text_view"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:ellipsize="end"
-        android:gravity="center"
-        android:paddingTop="8dp"
-        android:paddingBottom="8dp"
-        android:paddingStart="16dp"
-        android:paddingEnd="16dp"
-        android:maxLines="1"
-        android:textSize="14sp"
-        android:textStyle="bold"
-        android:textColor="?attr/colorOnSecondaryContainer" />
-</com.google.android.material.card.MaterialCardView>
-

+ 0 - 74
app/src/main/res/layout/item_find_book.xml

@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/ll_title"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    android:orientation="vertical"
-    android:clickable="true"
-    android:focusable="true"
-    android:paddingHorizontal="16dp"
-    android:paddingVertical="4dp"
-    android:animateLayoutChanges="true"
-    android:background="?attr/selectableItemBackground">
-
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:gravity="center_vertical"
-        android:paddingVertical="8dp">
-
-        <TextView
-            android:id="@+id/tv_name"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
-            tools:text="起点中文"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toStartOf="@id/iv_status"/>
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/iv_status"
-            style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:checkable="true"
-            app:icon="@drawable/m3_split_button_chevron_avd"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"/>
-
-        <com.google.android.material.loadingindicator.LoadingIndicator
-            android:id="@+id/rotate_loading"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:indicatorColor="?attr/colorSurface"
-            app:indicatorSize="24dp"
-            android:visibility="gone"
-            app:layout_constraintTop_toTopOf="@id/iv_status"
-            app:layout_constraintBottom_toBottomOf="@id/iv_status"
-            app:layout_constraintStart_toStartOf="@id/iv_status"
-            app:layout_constraintEnd_toEndOf="@id/iv_status"/>
-
-    </androidx.constraintlayout.widget.ConstraintLayout>
-
-    <com.google.android.flexbox.FlexboxLayout
-        android:id="@+id/flexbox"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:clipToPadding="false"
-        android:overScrollMode="never"
-        android:layout_marginTop="4dp"
-        android:layout_marginBottom="16dp"
-        android:visibility="gone"
-        app:dividerDrawable="@drawable/shape_space_divider"
-        app:flexDirection="row"
-        app:flexWrap="wrap"
-        app:showDivider="middle" />
-
-</LinearLayout>

+ 0 - 27
app/src/main/res/layout/item_group_select.xml

@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:paddingVertical="8dp"
-    android:paddingStart="12dp"
-    android:paddingEnd="16dp"
-    android:gravity="center_vertical"
-    android:orientation="horizontal">
-
-    <io.legado.app.lib.theme.view.ThemeCheckBox
-        android:id="@+id/cb_group"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_weight="1"
-        android:singleLine="true" />
-
-    <com.google.android.material.button.MaterialButton
-        android:id="@+id/tv_edit"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        style="@style/Widget.Material3Expressive.Button.IconButton.Tonal"
-        app:icon="@drawable/ic_edit" />
-
-</LinearLayout>

+ 0 - 89
app/src/main/res/layout/item_import_book.xml

@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="60dp"
-    android:background="?attr/selectableItemBackground"
-    android:orientation="horizontal"
-    android:baselineAligned="false">
-
-    <FrameLayout
-        android:layout_width="60dp"
-        android:layout_height="match_parent">
-        <!--选择是否添加文件-->
-        <io.legado.app.lib.theme.view.ThemeCheckBox
-            android:id="@+id/cb_select"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:clickable="false"
-            android:enabled="true"
-            android:focusable="true" />
-        <!--文件夹标识或已选文件标识-->
-        <androidx.appcompat.widget.AppCompatImageView
-            android:id="@+id/iv_icon"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:scaleType="center"
-            android:visibility="invisible"
-            android:src="@drawable/ic_folder" />
-    </FrameLayout>
-
-    <LinearLayout
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:paddingTop="5dp"
-        android:paddingBottom="5dp"
-        android:orientation="vertical">
-
-        <TextView
-            android:id="@+id/tv_name"
-            android:layout_width="wrap_content"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:singleLine="true"
-            android:text="@string/app_name"
-            android:textSize="16sp"
-            android:gravity="center_vertical"
-            tools:ignore="NestedWeights" />
-
-        <LinearLayout
-            android:id="@+id/ll_brief"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:gravity="center_vertical"
-            android:orientation="horizontal">
-
-            <io.legado.app.ui.widget.text.AccentBgTextView
-                android:id="@+id/tv_tag"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="16dp"
-                android:paddingStart="8dp"
-                android:paddingEnd="8dp"
-                android:text="TXT"
-                android:maxLines="1"
-                android:maxWidth="50dp"
-                app:radius="2dp"
-                tools:ignore="HardcodedText,RtlHardcoded" />
-
-            <TextView
-                android:id="@+id/tv_size"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginEnd="16dp"
-                tools:ignore="RtlHardcoded"
-                tools:text="324kb" />
-
-            <TextView
-                android:id="@+id/tv_date"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                tools:text="2017-05-22" />
-        </LinearLayout>
-
-    </LinearLayout>
-</LinearLayout>

Некоторые файлы не были показаны из-за большого количества измененных файлов