Explorar o código

Merge pull request #2178 from 821938089/little-fix

一些优化
kunfei %!s(int64=4) %!d(string=hai) anos
pai
achega
c7adf677dc
Modificáronse 30 ficheiros con 203 adicións e 19 borrados
  1. 1 0
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  2. 12 0
      app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
  3. 3 1
      app/src/main/java/io/legado/app/help/config/AppConfig.kt
  4. 5 2
      app/src/main/java/io/legado/app/model/localBook/EpubFile.kt
  5. 13 3
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  6. 2 2
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  7. 21 0
      app/src/main/java/io/legado/app/ui/book/manage/BookAdapter.kt
  8. 1 0
      app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageActivity.kt
  9. 11 6
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  10. 6 0
      app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
  11. 9 0
      app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt
  12. 2 0
      app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt
  13. 5 1
      app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt
  14. 5 1
      app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt
  15. 29 2
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  16. 20 0
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt
  17. 7 0
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  18. 3 0
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  19. 4 0
      app/src/main/res/menu/bookshelf_menage_sel.xml
  20. 4 0
      app/src/main/res/menu/rss_main_item.xml
  21. 16 0
      app/src/main/res/menu/rss_source.xml
  22. 4 1
      app/src/main/res/menu/rss_source_sel.xml
  23. 2 0
      app/src/main/res/values-es-rES/strings.xml
  24. 2 0
      app/src/main/res/values-ja-rJP/strings.xml
  25. 2 0
      app/src/main/res/values-pt-rBR/strings.xml
  26. 2 0
      app/src/main/res/values-zh-rHK/strings.xml
  27. 2 0
      app/src/main/res/values-zh-rTW/strings.xml
  28. 2 0
      app/src/main/res/values-zh/strings.xml
  29. 2 0
      app/src/main/res/values/strings.xml
  30. 6 0
      app/src/main/res/xml/pref_config_other.xml

+ 1 - 0
app/src/main/java/io/legado/app/constant/PreferKey.kt

@@ -109,6 +109,7 @@ object PreferKey {
     const val welcomeShowIcon = "welcomeShowIcon"
     const val welcomeShowIcon = "welcomeShowIcon"
     const val welcomeShowIconDark = "welcomeShowIconDark"
     const val welcomeShowIconDark = "welcomeShowIconDark"
     const val pageTouchSlop = "pageTouchSlop"
     const val pageTouchSlop = "pageTouchSlop"
+    const val showAddToShelfAlert = "showAddToShelfAlert"
 
 
     const val cPrimary = "colorPrimary"
     const val cPrimary = "colorPrimary"
     const val cAccent = "colorAccent"
     const val cAccent = "colorAccent"

+ 12 - 0
app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt

@@ -44,6 +44,15 @@ interface RssSourceDao {
     @Query("SELECT * FROM rssSources where enabled = 1 order by customOrder")
     @Query("SELECT * FROM rssSources where enabled = 1 order by customOrder")
     fun flowEnabled(): Flow<List<RssSource>>
     fun flowEnabled(): Flow<List<RssSource>>
 
 
+    @Query("SELECT * FROM rssSources where enabled = 0 order by customOrder")
+    fun flowDisabled(): Flow<List<RssSource>>
+
+    @Query("select * from rssSources where loginUrl is not null and loginUrl != ''")
+    fun flowLogin(): Flow<List<RssSource>>
+
+    @Query("select * from rssSources where sourceGroup is null or sourceGroup = '' or sourceGroup like '%未分组%'")
+    fun flowNoGroup(): Flow<List<RssSource>>
+
     @Query(
     @Query(
         """SELECT * FROM rssSources 
         """SELECT * FROM rssSources 
         where enabled = 1 
         where enabled = 1 
@@ -67,6 +76,9 @@ interface RssSourceDao {
     @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
     @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
     fun flowGroup(): Flow<List<String>>
     fun flowGroup(): Flow<List<String>>
 
 
+    @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1")
+    fun flowGroupEnabled(): Flow<List<String>>
+
     @get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
     @get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
     val allGroup: List<String>
     val allGroup: List<String>
 
 

+ 3 - 1
app/src/main/java/io/legado/app/help/config/AppConfig.kt

@@ -310,7 +310,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
 
 
     val recordLog get() = appCtx.getPrefBoolean(PreferKey.recordLog)
     val recordLog get() = appCtx.getPrefBoolean(PreferKey.recordLog)
 
 
-    val loadCoverOnlyWifi = appCtx.getPrefBoolean(PreferKey.loadCoverOnlyWifi, false)
+    val loadCoverOnlyWifi get() = appCtx.getPrefBoolean(PreferKey.loadCoverOnlyWifi, false)
+
+    val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true)
 
 
     val doublePageHorizontal: String?
     val doublePageHorizontal: String?
         get() = appCtx.getPrefString(PreferKey.doublePageHorizontal)
         get() = appCtx.getPrefString(PreferKey.doublePageHorizontal)

+ 5 - 2
app/src/main/java/io/legado/app/model/localBook/EpubFile.kt

@@ -18,6 +18,7 @@ import java.io.File
 import java.io.FileOutputStream
 import java.io.FileOutputStream
 import java.io.IOException
 import java.io.IOException
 import java.io.InputStream
 import java.io.InputStream
+import java.net.URLDecoder
 import java.nio.charset.Charset
 import java.nio.charset.Charset
 
 
 class EpubFile(var book: Book) {
 class EpubFile(var book: Book) {
@@ -117,7 +118,9 @@ class EpubFile(var book: Book) {
          * <image width="1038" height="670" xlink:href="..."/>
          * <image width="1038" height="670" xlink:href="..."/>
          * ...titlepage.xhtml
          * ...titlepage.xhtml
          */
          */
-        if (chapter.url.contains("titlepage.xhtml")) {
+        if (chapter.url.contains("titlepage.xhtml") ||
+            chapter.url.contains("cover.xhtml")
+        ) {
             return "<img src=\"cover.jpeg\" />"
             return "<img src=\"cover.jpeg\" />"
         }
         }
         /*获取当前章节文本*/
         /*获取当前章节文本*/
@@ -190,7 +193,7 @@ class EpubFile(var book: Book) {
 
 
     private fun getImage(href: String): InputStream? {
     private fun getImage(href: String): InputStream? {
         if (href == "cover.jpeg") return epubBook?.coverImage?.inputStream
         if (href == "cover.jpeg") return epubBook?.coverImage?.inputStream
-        val abHref = href.replace("../", "")
+        val abHref = URLDecoder.decode(href.replace("../", ""), "UTF-8")
         return epubBook?.resources?.getByHref(abHref)?.inputStream
         return epubBook?.resources?.getByHref(abHref)?.inputStream
     }
     }
 
 

+ 13 - 3
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -6,6 +6,7 @@ import androidx.documentfile.provider.DocumentFile
 import com.script.SimpleBindings
 import com.script.SimpleBindings
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppConst
+import io.legado.app.constant.AppLog
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.BaseSource
 import io.legado.app.data.entities.BaseSource
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
@@ -16,6 +17,7 @@ import io.legado.app.help.BookHelp
 import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.AppConfig
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import io.legado.app.utils.*
 import io.legado.app.utils.*
+import org.jsoup.nodes.Entities
 import splitties.init.appCtx
 import splitties.init.appCtx
 import java.io.*
 import java.io.*
 import java.util.regex.Pattern
 import java.util.regex.Pattern
@@ -77,11 +79,12 @@ object LocalBook {
             throw TocEmptyException(appCtx.getString(R.string.chapter_list_empty))
             throw TocEmptyException(appCtx.getString(R.string.chapter_list_empty))
         }
         }
         val lh = LinkedHashSet(chapters)
         val lh = LinkedHashSet(chapters)
+        lh.forEachIndexed { index, bookChapter -> bookChapter.index = index }
         return ArrayList(lh)
         return ArrayList(lh)
     }
     }
 
 
     fun getContent(book: Book, chapter: BookChapter): String? {
     fun getContent(book: Book, chapter: BookChapter): String? {
-        return try {
+        val content = try {
             when {
             when {
                 book.isEpub() -> {
                 book.isEpub() -> {
                     EpubFile.getContent(book, chapter)
                     EpubFile.getContent(book, chapter)
@@ -95,8 +98,15 @@ object LocalBook {
             }
             }
         } catch (e: Exception) {
         } catch (e: Exception) {
             e.printOnDebug()
             e.printOnDebug()
-            e.localizedMessage
-        }
+            AppLog.put("获取本地书籍内容失败\n${e.localizedMessage}", e)
+            "获取本地书籍内容失败\n${e.localizedMessage}"
+        }?.replace("&lt;img", "&lt; img", true)
+        content ?: return null
+        return kotlin.runCatching {
+            Entities.unescape(content)
+        }.onFailure {
+            AppLog.put("HTML实体解码失败\n${it.localizedMessage}", it)
+        }.getOrElse { content }
     }
     }
 
 
     /**
     /**

+ 2 - 2
app/src/main/java/io/legado/app/service/CheckSourceService.kt

@@ -136,7 +136,7 @@ class CheckSourceService : BaseService() {
             source.bookSourceComment = source.bookSourceComment
             source.bookSourceComment = source.bookSourceComment
                 ?.split("\n\n")
                 ?.split("\n\n")
                 ?.filterNot {
                 ?.filterNot {
-                    it.startsWith("Error: ")
+                    it.startsWith("// Error: ")
                 }?.joinToString("\n")
                 }?.joinToString("\n")
             //校验搜索书籍
             //校验搜索书籍
             if (CheckSource.checkSearch) {
             if (CheckSource.checkSearch) {
@@ -186,7 +186,7 @@ class CheckSourceService : BaseService() {
                     !is NoStackTraceException -> source.addGroup("网站失效")
                     !is NoStackTraceException -> source.addGroup("网站失效")
                 }
                 }
                 source.bookSourceComment =
                 source.bookSourceComment =
-                    "Error: ${it.localizedMessage}" + if (source.bookSourceComment.isNullOrBlank())
+                    "// Error: ${it.localizedMessage}" + if (source.bookSourceComment.isNullOrBlank())
                         "" else "\n\n${source.bookSourceComment}"
                         "" else "\n\n${source.bookSourceComment}"
                 Debug.updateFinalMessage(source.bookSourceUrl, "校验失败:${it.localizedMessage}")
                 Debug.updateFinalMessage(source.bookSourceUrl, "校验失败:${it.localizedMessage}")
             }.onSuccess(searchCoroutine) {
             }.onSuccess(searchCoroutine) {

+ 21 - 0
app/src/main/java/io/legado/app/ui/book/manage/BookAdapter.kt

@@ -14,6 +14,8 @@ import io.legado.app.databinding.ItemArrangeBookBinding
 import io.legado.app.lib.theme.backgroundColor
 import io.legado.app.lib.theme.backgroundColor
 import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
 import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
 import io.legado.app.ui.widget.recycler.ItemTouchCallback
 import io.legado.app.ui.widget.recycler.ItemTouchCallback
+import java.util.*
+import kotlin.collections.HashSet
 
 
 class BookAdapter(context: Context, val callBack: CallBack) :
 class BookAdapter(context: Context, val callBack: CallBack) :
     RecyclerAdapter<Book, ItemArrangeBookBinding>(context),
     RecyclerAdapter<Book, ItemArrangeBookBinding>(context),
@@ -121,6 +123,25 @@ class BookAdapter(context: Context, val callBack: CallBack) :
         callBack.upSelectCount()
         callBack.upSelectCount()
     }
     }
 
 
+    fun checkSelectedInterval() {
+        val selectedPosition = linkedSetOf<Int>()
+        getItems().forEachIndexed { index, it ->
+            if (selectedBooks.contains(it)) {
+                selectedPosition.add(index)
+            }
+        }
+        val minPosition = Collections.min(selectedPosition)
+        val maxPosition = Collections.max(selectedPosition)
+        val itemCount = maxPosition - minPosition + 1
+        for (i in minPosition..maxPosition) {
+            getItem(i)?.let {
+                selectedBooks.add(it)
+            }
+        }
+        notifyItemRangeChanged(minPosition, itemCount, bundleOf(Pair("selected", null)))
+        callBack.upSelectCount()
+    }
+
     private fun getGroupList(groupId: Long): List<String> {
     private fun getGroupList(groupId: Long): List<String> {
         val groupNames = arrayListOf<String>()
         val groupNames = arrayListOf<String>()
         callBack.groupList.forEach {
         callBack.groupList.forEach {

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

@@ -192,6 +192,7 @@ class BookshelfManageActivity :
                 viewModel.upCanUpdate(adapter.selection, false)
                 viewModel.upCanUpdate(adapter.selection, false)
             R.id.menu_add_to_group -> selectGroup(addToGroupRequestCode, 0)
             R.id.menu_add_to_group -> selectGroup(addToGroupRequestCode, 0)
             R.id.menu_change_source -> showDialogFragment<SourcePickerDialog>()
             R.id.menu_change_source -> showDialogFragment<SourcePickerDialog>()
+            R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()
         }
         }
         return false
         return false
     }
     }

+ 11 - 6
app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt

@@ -25,6 +25,7 @@ import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.AppWebDav
 import io.legado.app.help.AppWebDav
 import io.legado.app.help.BookHelp
 import io.legado.app.help.BookHelp
 import io.legado.app.help.IntentData
 import io.legado.app.help.IntentData
+import io.legado.app.help.config.AppConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadBookConfig
 import io.legado.app.help.config.ReadTipConfig
 import io.legado.app.help.config.ReadTipConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
@@ -1148,13 +1149,17 @@ class ReadBookActivity : BaseReadBookActivity(),
     override fun finish() {
     override fun finish() {
         ReadBook.book?.let {
         ReadBook.book?.let {
             if (!ReadBook.inBookshelf) {
             if (!ReadBook.inBookshelf) {
-                alert(title = getString(R.string.add_to_shelf)) {
-                    setMessage(getString(R.string.check_add_bookshelf, it.name))
-                    okButton {
-                        ReadBook.inBookshelf = true
-                        setResult(Activity.RESULT_OK)
+                if (!AppConfig.showAddToShelfAlert) {
+                    viewModel.removeFromBookshelf { super.finish() }
+                } else {
+                    alert(title = getString(R.string.add_to_shelf)) {
+                        setMessage(getString(R.string.check_add_bookshelf, it.name))
+                        okButton {
+                            ReadBook.inBookshelf = true
+                            setResult(Activity.RESULT_OK)
+                        }
+                        noButton { viewModel.removeFromBookshelf { super.finish() } }
                     }
                     }
-                    noButton { viewModel.removeFromBookshelf { super.finish() } }
                 }
                 }
             } else {
             } else {
                 super.finish()
                 super.finish()

+ 6 - 0
app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt

@@ -66,6 +66,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
     private var menu: Menu? = null
     private var menu: Menu? = null
     private var precisionSearchMenuItem: MenuItem? = null
     private var precisionSearchMenuItem: MenuItem? = null
     private var groups = linkedSetOf<String>()
     private var groups = linkedSetOf<String>()
+    private var isManualStopSearch = false
     private val searchFinishCallback: (isEmpty: Boolean) -> Unit = searchFinish@{ isEmpty ->
     private val searchFinishCallback: (isEmpty: Boolean) -> Unit = searchFinish@{ isEmpty ->
         val searchGroup = AppConfig.searchGroup
         val searchGroup = AppConfig.searchGroup
         if (!isEmpty || searchGroup.isEmpty()) return@searchFinish
         if (!isEmpty || searchGroup.isEmpty()) return@searchFinish
@@ -155,6 +156,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
             override fun onQueryTextSubmit(query: String?): Boolean {
             override fun onQueryTextSubmit(query: String?): Boolean {
                 searchView.clearFocus()
                 searchView.clearFocus()
                 query?.let {
                 query?.let {
+                    isManualStopSearch = false
                     viewModel.saveSearchKey(query)
                     viewModel.saveSearchKey(query)
                     viewModel.searchKey = ""
                     viewModel.searchKey = ""
                     viewModel.search(it)
                     viewModel.search(it)
@@ -221,6 +223,7 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
                 .setPressedColor(ColorUtils.darkenColor(accentColor))
                 .setPressedColor(ColorUtils.darkenColor(accentColor))
                 .create()
                 .create()
         binding.fbStop.setOnClickListener {
         binding.fbStop.setOnClickListener {
+            isManualStopSearch = true
             viewModel.stop()
             viewModel.stop()
             binding.refreshProgressBar.isAutoLoading = false
             binding.refreshProgressBar.isAutoLoading = false
         }
         }
@@ -266,6 +269,9 @@ class SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel
      * 滚动到底部事件
      * 滚动到底部事件
      */
      */
     private fun scrollToBottom() {
     private fun scrollToBottom() {
+        if (isManualStopSearch) {
+            return
+        }
         if (viewModel.isSearchLiveData.value == false
         if (viewModel.isSearchLiveData.value == false
             && viewModel.searchKey.isNotEmpty()
             && viewModel.searchKey.isNotEmpty()
             && loadMoreView.hasMore
             && loadMoreView.hasMore

+ 9 - 0
app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt

@@ -3,6 +3,7 @@ package io.legado.app.ui.config
 import android.content.SharedPreferences
 import android.content.SharedPreferences
 import android.net.Uri
 import android.net.Uri
 import android.os.Bundle
 import android.os.Bundle
+import android.text.Editable
 import android.text.InputType
 import android.text.InputType
 import android.view.Menu
 import android.view.Menu
 import android.view.MenuInflater
 import android.view.MenuInflater
@@ -116,6 +117,14 @@ class BackupConfigFragment : PreferenceFragment(),
                     InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT
                     InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT
             }
             }
         }
         }
+        findPreference<EditTextPreference>(PreferKey.webDavDir)?.let {
+            it.setOnBindEditTextListener { editText ->
+                editText.text = AppConfig.webDavDir?.toEditable()
+            }
+            it.setOnPreferenceChangeListener { _, newValue ->
+                (newValue as String).isNotBlank()
+            }
+        }
         upPreferenceSummary(PreferKey.webDavUrl, getPrefString(PreferKey.webDavUrl))
         upPreferenceSummary(PreferKey.webDavUrl, getPrefString(PreferKey.webDavUrl))
         upPreferenceSummary(PreferKey.webDavAccount, getPrefString(PreferKey.webDavAccount))
         upPreferenceSummary(PreferKey.webDavAccount, getPrefString(PreferKey.webDavAccount))
         upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword))
         upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword))

+ 2 - 0
app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt

@@ -63,6 +63,7 @@ class RssAdapter(context: Context, val callBack: CallBack) :
                 R.id.menu_top -> callBack.toTop(rssSource)
                 R.id.menu_top -> callBack.toTop(rssSource)
                 R.id.menu_edit -> callBack.edit(rssSource)
                 R.id.menu_edit -> callBack.edit(rssSource)
                 R.id.menu_del -> callBack.del(rssSource)
                 R.id.menu_del -> callBack.del(rssSource)
+                R.id.menu_disable -> callBack.disable(rssSource)
             }
             }
             true
             true
         }
         }
@@ -74,5 +75,6 @@ class RssAdapter(context: Context, val callBack: CallBack) :
         fun toTop(rssSource: RssSource)
         fun toTop(rssSource: RssSource)
         fun edit(rssSource: RssSource)
         fun edit(rssSource: RssSource)
         fun del(rssSource: RssSource)
         fun del(rssSource: RssSource)
+        fun disable(rssSource: RssSource)
     }
     }
 }
 }

+ 5 - 1
app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt

@@ -123,7 +123,7 @@ class RssFragment : VMBaseFragment<RssSourceViewModel>(R.layout.fragment_rss),
     private fun initGroupData() {
     private fun initGroupData() {
         groupsFlowJob?.cancel()
         groupsFlowJob?.cancel()
         groupsFlowJob = launch {
         groupsFlowJob = launch {
-            appDb.rssSourceDao.flowGroup().conflate().collect {
+            appDb.rssSourceDao.flowGroupEnabled().conflate().collect {
                 groups.clear()
                 groups.clear()
                 it.map { group ->
                 it.map { group ->
                     groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
                     groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
@@ -181,4 +181,8 @@ class RssFragment : VMBaseFragment<RssSourceViewModel>(R.layout.fragment_rss),
     override fun del(rssSource: RssSource) {
     override fun del(rssSource: RssSource) {
         viewModel.del(rssSource)
         viewModel.del(rssSource)
     }
     }
+
+    override fun disable(rssSource: RssSource) {
+        viewModel.disable(rssSource)
+    }
 }
 }

+ 5 - 1
app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt

@@ -65,7 +65,11 @@ class GroupManageDialog : BaseDialogFragment(R.layout.dialog_recycler_view),
                 it.map { group ->
                 it.map { group ->
                     groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
                     groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
                 }
                 }
-                adapter.setItems(groups.toList())
+                adapter.setItems(
+                    groups.sortedWith { o1, o2 ->
+                        o1.cnCompare(o2)
+                    }
+                )
             }
             }
         }
         }
     }
     }

+ 29 - 2
app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt

@@ -50,6 +50,9 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
     override val viewModel by viewModels<RssSourceViewModel>()
     override val viewModel by viewModels<RssSourceViewModel>()
     private val importRecordKey = "rssSourceRecordKey"
     private val importRecordKey = "rssSourceRecordKey"
     private val adapter by lazy { RssSourceAdapter(this, this) }
     private val adapter by lazy { RssSourceAdapter(this, this) }
+    private val searchView: SearchView by lazy {
+        binding.titleBar.findViewById(R.id.search_view)
+    }
     private var sourceFlowJob: Job? = null
     private var sourceFlowJob: Job? = null
     private var groups = hashSetOf<String>()
     private var groups = hashSetOf<String>()
     private var groupMenu: SubMenu? = null
     private var groupMenu: SubMenu? = null
@@ -120,10 +123,21 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
             R.id.menu_import_qr -> qrCodeResult.launch()
             R.id.menu_import_qr -> qrCodeResult.launch()
             R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()
             R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()
             R.id.menu_import_default -> viewModel.importDefault()
             R.id.menu_import_default -> viewModel.importDefault()
+            R.id.menu_enabled_group -> {
+                searchView.setQuery(getString(R.string.enabled), true)
+            }
+            R.id.menu_disabled_group -> {
+                searchView.setQuery(getString(R.string.disabled), true)
+            }
+            R.id.menu_group_login -> {
+                searchView.setQuery(getString(R.string.need_login), true)
+            }
+            R.id.menu_group_null -> {
+                searchView.setQuery(getString(R.string.no_group), true)
+            }
             R.id.menu_help -> showHelp()
             R.id.menu_help -> showHelp()
             else -> if (item.groupId == R.id.source_group) {
             else -> if (item.groupId == R.id.source_group) {
-                binding.titleBar.findViewById<SearchView>(R.id.search_view)
-                    .setQuery("group:${item.title}", true)
+                searchView.setQuery("group:${item.title}", true)
             }
             }
         }
         }
         return super.onCompatOptionsItemSelected(item)
         return super.onCompatOptionsItemSelected(item)
@@ -144,6 +158,7 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
             R.id.menu_share_source -> viewModel.saveToFile(adapter.selection) {
             R.id.menu_share_source -> viewModel.saveToFile(adapter.selection) {
                 share(it)
                 share(it)
             }
             }
+            R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()
         }
         }
         return true
         return true
     }
     }
@@ -240,6 +255,18 @@ class RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceView
                 searchKey.isNullOrBlank() -> {
                 searchKey.isNullOrBlank() -> {
                     appDb.rssSourceDao.flowAll()
                     appDb.rssSourceDao.flowAll()
                 }
                 }
+                searchKey == getString(R.string.enabled) -> {
+                    appDb.rssSourceDao.flowEnabled()
+                }
+                searchKey == getString(R.string.disabled) -> {
+                    appDb.rssSourceDao.flowDisabled()
+                }
+                searchKey == getString(R.string.need_login) -> {
+                    appDb.rssSourceDao.flowLogin()
+                }
+                searchKey == getString(R.string.no_group) -> {
+                    appDb.rssSourceDao.flowNoGroup()
+                }
                 searchKey.startsWith("group:") -> {
                 searchKey.startsWith("group:") -> {
                     val key = searchKey.substringAfter("group:")
                     val key = searchKey.substringAfter("group:")
                     appDb.rssSourceDao.flowGroupSearch(key)
                     appDb.rssSourceDao.flowGroupSearch(key)

+ 20 - 0
app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt

@@ -17,6 +17,7 @@ import io.legado.app.lib.theme.backgroundColor
 import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
 import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
 import io.legado.app.ui.widget.recycler.ItemTouchCallback
 import io.legado.app.ui.widget.recycler.ItemTouchCallback
 import io.legado.app.utils.ColorUtils
 import io.legado.app.utils.ColorUtils
+import java.util.*
 
 
 
 
 class RssSourceAdapter(context: Context, val callBack: CallBack) :
 class RssSourceAdapter(context: Context, val callBack: CallBack) :
@@ -151,6 +152,25 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) :
         callBack.upCountView()
         callBack.upCountView()
     }
     }
 
 
+    fun checkSelectedInterval() {
+        val selectedPosition = linkedSetOf<Int>()
+        getItems().forEachIndexed { index, it ->
+            if (selected.contains(it)) {
+                selectedPosition.add(index)
+            }
+        }
+        val minPosition = Collections.min(selectedPosition)
+        val maxPosition = Collections.max(selectedPosition)
+        val itemCount = maxPosition - minPosition + 1
+        for (i in minPosition..maxPosition) {
+            getItem(i)?.let {
+                selected.add(it)
+            }
+        }
+        notifyItemRangeChanged(minPosition, itemCount, bundleOf(Pair("selected", null)))
+        callBack.upCountView()
+    }
+
     private fun showMenu(view: View, position: Int) {
     private fun showMenu(view: View, position: Int) {
         val source = getItem(position) ?: return
         val source = getItem(position) ?: return
         val popupMenu = PopupMenu(context, view)
         val popupMenu = PopupMenu(context, view)

+ 7 - 0
app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt

@@ -133,4 +133,11 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
         }
         }
     }
     }
 
 
+    fun disable(rssSource: RssSource) {
+        execute {
+            rssSource.enabled = false
+            appDb.rssSourceDao.update(rssSource)
+        }
+    }
+
 }
 }

+ 3 - 0
app/src/main/java/io/legado/app/utils/StringExtensions.kt

@@ -6,6 +6,7 @@ import io.legado.app.constant.AppPattern.dataUriRegex
 import android.icu.text.Collator
 import android.icu.text.Collator
 import android.icu.util.ULocale
 import android.icu.util.ULocale
 import android.net.Uri
 import android.net.Uri
+import android.text.Editable
 import java.io.File
 import java.io.File
 import java.util.*
 import java.util.*
 
 
@@ -13,6 +14,8 @@ fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
 
 
 fun String?.isContentScheme(): Boolean = this?.startsWith("content://") == true
 fun String?.isContentScheme(): Boolean = this?.startsWith("content://") == true
 
 
+fun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this)
+
 fun String.parseToUri(): Uri {
 fun String.parseToUri(): Uri {
     return if (isContentScheme()) {
     return if (isContentScheme()) {
         Uri.parse(this)
         Uri.parse(this)

+ 4 - 0
app/src/main/res/menu/bookshelf_menage_sel.xml

@@ -21,4 +21,8 @@
         android:id="@+id/menu_change_source"
         android:id="@+id/menu_change_source"
         android:title="@string/change_source_batch" />
         android:title="@string/change_source_batch" />
 
 
+    <item
+        android:id="@+id/menu_check_selected_interval"
+        android:title="@string/check_selected_interval" />
+
 </menu>
 </menu>

+ 4 - 0
app/src/main/res/menu/rss_main_item.xml

@@ -9,6 +9,10 @@
         android:id="@+id/menu_edit"
         android:id="@+id/menu_edit"
         android:title="@string/edit" />
         android:title="@string/edit" />
 
 
+    <item
+        android:id="@+id/menu_disable"
+        android:title="@string/disable_source" />
+
     <item
     <item
         android:id="@+id/menu_del"
         android:id="@+id/menu_del"
         android:title="@string/delete" />
         android:title="@string/delete" />

+ 16 - 0
app/src/main/res/menu/rss_source.xml

@@ -16,6 +16,22 @@
                 android:id="@+id/menu_group_manage"
                 android:id="@+id/menu_group_manage"
                 android:title="@string/group_manage" />
                 android:title="@string/group_manage" />
 
 
+            <item
+                android:id="@+id/menu_enabled_group"
+                android:title="@string/enabled" />
+
+            <item
+                android:id="@+id/menu_disabled_group"
+                android:title="@string/disabled" />
+
+            <item
+                android:id="@+id/menu_group_login"
+                android:title="@string/need_login" />
+
+            <item
+                android:id="@+id/menu_group_null"
+                android:title="@string/no_group" />
+
             <group android:id="@+id/source_group">
             <group android:id="@+id/source_group">
 
 
             </group>
             </group>

+ 4 - 1
app/src/main/res/menu/rss_source_sel.xml

@@ -27,11 +27,14 @@
         android:title="@string/export_selection"
         android:title="@string/export_selection"
         app:showAsAction="never" />
         app:showAsAction="never" />
 
 
-
     <item
     <item
         android:id="@+id/menu_share_source"
         android:id="@+id/menu_share_source"
         android:title="@string/share_selected_source"
         android:title="@string/share_selected_source"
         app:showAsAction="never" />
         app:showAsAction="never" />
 
 
+    <item
+        android:id="@+id/menu_check_selected_interval"
+        android:title="@string/check_selected_interval"
+        app:showAsAction="never" />
 
 
 </menu>
 </menu>

+ 2 - 0
app/src/main/res/values-es-rES/strings.xml

@@ -1008,4 +1008,6 @@
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="example">Ejemplo</string>
     <string name="example">Ejemplo</string>
     <string name="check_selected_interval">选中所选区间</string>
     <string name="check_selected_interval">选中所选区间</string>
+    <string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
+    <string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
 </resources>
 </resources>

+ 2 - 0
app/src/main/res/values-ja-rJP/strings.xml

@@ -1011,4 +1011,6 @@
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="example">例</string>
     <string name="example">例</string>
     <string name="check_selected_interval">选中所选区间</string>
     <string name="check_selected_interval">选中所选区间</string>
+    <string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
+    <string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
 </resources>
 </resources>

+ 2 - 0
app/src/main/res/values-pt-rBR/strings.xml

@@ -1011,4 +1011,6 @@
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="example">Exemplo</string>
     <string name="example">Exemplo</string>
     <string name="check_selected_interval">选中所选区间</string>
     <string name="check_selected_interval">选中所选区间</string>
+    <string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
+    <string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
 </resources>
 </resources>

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

@@ -1008,4 +1008,6 @@
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="example">示例</string>
     <string name="example">示例</string>
     <string name="check_selected_interval">选中所选区间</string>
     <string name="check_selected_interval">选中所选区间</string>
+    <string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
+    <string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
 </resources>
 </resources>

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

@@ -1010,4 +1010,6 @@
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="example">示例</string>
     <string name="example">示例</string>
     <string name="check_selected_interval">选中所选区间</string>
     <string name="check_selected_interval">选中所选区间</string>
+    <string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
+    <string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
 </resources>
 </resources>

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

@@ -1010,4 +1010,6 @@
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="example">示例</string>
     <string name="example">示例</string>
     <string name="check_selected_interval">选中所选区间</string>
     <string name="check_selected_interval">选中所选区间</string>
+    <string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
+    <string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
 </resources>
 </resources>

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

@@ -1011,4 +1011,6 @@
     <string name="page_touch_slop_dialog_title">滑动翻页阈值(0 = 系统默认值)</string>
     <string name="page_touch_slop_dialog_title">滑动翻页阈值(0 = 系统默认值)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="page_touch_slop_summary">滑动多长距离才会触发滑动翻页(系统默认值 %s px)</string>
     <string name="check_selected_interval">选中所选区间</string>
     <string name="check_selected_interval">选中所选区间</string>
+    <string name="show_add_to_shelf_alert_title">返回时提示放入书架</string>
+    <string name="show_add_to_shelf_alert_summary">阅读未放入书架的书籍在返回时提示放入书架</string>
 </resources>
 </resources>

+ 6 - 0
app/src/main/res/xml/pref_config_other.xml

@@ -112,6 +112,12 @@
             android:summary="@string/auto_clear_expired_summary"
             android:summary="@string/auto_clear_expired_summary"
             android:title="@string/auto_clear_expired" />
             android:title="@string/auto_clear_expired" />
 
 
+        <io.legado.app.lib.prefs.SwitchPreference
+            android:defaultValue="true"
+            android:key="showAddToShelfAlert"
+            android:summary="@string/show_add_to_shelf_alert_summary"
+            android:title="@string/show_add_to_shelf_alert_title" />
+
         <io.legado.app.lib.prefs.Preference
         <io.legado.app.lib.prefs.Preference
             android:key="webPort"
             android:key="webPort"
             android:title="@string/web_port_title"
             android:title="@string/web_port_title"