Merge remote-tracking branch 'origin/master'

This commit is contained in:
kunfei 2023-06-11 19:08:25 +08:00
commit ba90882998
22 changed files with 399 additions and 148 deletions

View File

@ -5,6 +5,7 @@ import android.content.Context
import android.util.SparseArray import android.util.SparseArray
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.postDelayed
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -160,6 +161,12 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
onCurrentListChanged() onCurrentListChanged()
} }
} }
handler.postDelayed(1000) {
if (diffJob?.isCompleted == false) {
diffJob?.cancel()
setItems(items)
}
}
} }
} }

View File

@ -130,6 +130,7 @@ object PreferKey {
const val clearWebViewData = "clearWebViewData" const val clearWebViewData = "clearWebViewData"
const val onlyLatestBackup = "onlyLatestBackup" const val onlyLatestBackup = "onlyLatestBackup"
const val brightnessVwPos = "brightnessVwPos" const val brightnessVwPos = "brightnessVwPos"
const val shrinkDatabase = "shrinkDatabase"
const val cPrimary = "colorPrimary" const val cPrimary = "colorPrimary"
const val cAccent = "colorAccent" const val cAccent = "colorAccent"

View File

@ -3,6 +3,7 @@ package io.legado.app.data.dao
import androidx.room.* import androidx.room.*
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.utils.cnCompare import io.legado.app.utils.cnCompare
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -11,21 +12,29 @@ import kotlinx.coroutines.flow.map
@Dao @Dao
interface BookSourceDao { interface BookSourceDao {
@Query("select * from book_sources order by customOrder asc") @Query(
fun flowAll(): Flow<List<BookSource>> """select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources order by customOrder asc"""
)
fun flowAll(): Flow<List<BookSourcePart>>
@Query( @Query(
"""select * from book_sources """select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources
where bookSourceName like '%' || :searchKey || '%' where bookSourceName like '%' || :searchKey || '%'
or bookSourceGroup like '%' || :searchKey || '%' or bookSourceGroup like '%' || :searchKey || '%'
or bookSourceUrl like '%' || :searchKey || '%' or bookSourceUrl like '%' || :searchKey || '%'
or bookSourceComment like '%' || :searchKey || '%' or bookSourceComment like '%' || :searchKey || '%'
order by customOrder asc""" order by customOrder asc"""
) )
fun flowSearch(searchKey: String): Flow<List<BookSource>> fun flowSearch(searchKey: String): Flow<List<BookSourcePart>>
@Query( @Query(
"""select * from book_sources """select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources
where enabled = 1 and where enabled = 1 and
(bookSourceName like '%' || :searchKey || '%' (bookSourceName like '%' || :searchKey || '%'
or bookSourceGroup like '%' || :searchKey || '%' or bookSourceGroup like '%' || :searchKey || '%'
@ -33,32 +42,42 @@ interface BookSourceDao {
or bookSourceComment like '%' || :searchKey || '%') or bookSourceComment like '%' || :searchKey || '%')
order by customOrder asc""" order by customOrder asc"""
) )
fun flowSearchEnabled(searchKey: String): Flow<List<BookSource>> fun flowSearchEnabled(searchKey: String): Flow<List<BookSourcePart>>
@Query( @Query(
"""select * from book_sources """select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources
where bookSourceGroup = :searchKey where bookSourceGroup = :searchKey
or bookSourceGroup like :searchKey || ',%' or bookSourceGroup like :searchKey || ',%'
or bookSourceGroup like '%,' || :searchKey or bookSourceGroup like '%,' || :searchKey
or bookSourceGroup like '%,' || :searchKey || ',%' or bookSourceGroup like '%,' || :searchKey || ',%'
order by customOrder asc""" order by customOrder asc"""
) )
fun flowGroupSearch(searchKey: String): Flow<List<BookSource>> fun flowGroupSearch(searchKey: String): Flow<List<BookSourcePart>>
@Query("select * from book_sources where enabled = 1 order by customOrder asc") @Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
fun flowEnabled(): Flow<List<BookSource>> trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources where enabled = 1 order by customOrder asc""")
fun flowEnabled(): Flow<List<BookSourcePart>>
@Query("select * from book_sources where enabled = 0 order by customOrder asc") @Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
fun flowDisabled(): Flow<List<BookSource>> trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources where enabled = 0 order by customOrder asc""")
fun flowDisabled(): Flow<List<BookSourcePart>>
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc") @Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc")
fun flowExplore(): Flow<List<BookSource>> fun flowExplore(): Flow<List<BookSource>>
@Query("select * from book_sources where loginUrl is not null and loginUrl != ''") @Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
fun flowLogin(): Flow<List<BookSource>> trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources where loginUrl is not null and loginUrl != ''""")
fun flowLogin(): Flow<List<BookSourcePart>>
@Query("select * from book_sources where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'") @Query("""select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore,
fun flowNoGroup(): Flow<List<BookSource>> trim(loginUrl) <> '' hasLoginUrl, lastUpdateTime, respondTime, weight, trim(exploreUrl) <> '' hasExploreUrl
from book_sources where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'""")
fun flowNoGroup(): Flow<List<BookSourcePart>>
@Query( @Query(
"""select * from book_sources """select * from book_sources
@ -155,12 +174,59 @@ interface BookSourceDao {
@Query("delete from book_sources where bookSourceUrl = :key") @Query("delete from book_sources where bookSourceUrl = :key")
fun delete(key: String) fun delete(key: String)
@Transaction
fun delete(bookSources: List<BookSourcePart>) {
for (bs in bookSources) {
delete(bs.bookSourceUrl)
}
}
@get:Query("select min(customOrder) from book_sources") @get:Query("select min(customOrder) from book_sources")
val minOrder: Int val minOrder: Int
@get:Query("select max(customOrder) from book_sources") @get:Query("select max(customOrder) from book_sources")
val maxOrder: Int val maxOrder: Int
@Query("update book_sources set enabled = :enable where bookSourceUrl = :bookSourceUrl")
fun enable(bookSourceUrl: String, enable: Boolean)
@Transaction
fun enable(enable: Boolean, bookSources: List<BookSourcePart>) {
for (bs in bookSources) {
enable(bs.bookSourceUrl, enable)
}
}
@Query("update book_sources set enabledExplore = :enable where bookSourceUrl = :bookSourceUrl")
fun enableExplore(bookSourceUrl: String, enable: Boolean)
@Transaction
fun enableExplore(enable: Boolean, bookSources: List<BookSourcePart>) {
for (bs in bookSources) {
enableExplore(bs.bookSourceUrl, enable)
}
}
@Query("update book_sources set customOrder = :customOrder where bookSourceUrl = :bookSourceUrl")
fun upOrder(bookSourceUrl: String, customOrder: Int)
@Transaction
fun upOrder(bookSources: List<BookSourcePart>) {
for (bs in bookSources) {
upOrder(bs.bookSourceUrl, bs.customOrder)
}
}
@Query("update book_sources set bookSourceGroup = :bookSourceGroup where bookSourceUrl = :bookSourceUrl")
fun upGroup(bookSourceUrl: String, bookSourceGroup: String)
@Transaction
fun upGroup(bookSources: List<BookSourcePart>) {
for (bs in bookSources) {
bs.bookSourceGroup?.let { upGroup(bs.bookSourceUrl, it) }
}
}
private fun dealGroups(list: List<String>): List<String> { private fun dealGroups(list: List<String>): List<String> {
val groups = linkedSetOf<String>() val groups = linkedSetOf<String>()
list.forEach { list.forEach {

View File

@ -0,0 +1,73 @@
package io.legado.app.data.entities
import android.text.TextUtils
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.utils.splitNotBlank
data class BookSourcePart(
// 地址,包括 http/https
var bookSourceUrl: String = "",
// 名称
var bookSourceName: String = "",
// 分组
var bookSourceGroup: String? = null,
// 手动排序编号
var customOrder: Int = 0,
// 是否启用
var enabled: Boolean = true,
// 启用发现
var enabledExplore: Boolean = true,
// 是否有登录地址
var hasLoginUrl: Boolean = false,
// 最后更新时间,用于排序
var lastUpdateTime: Long = 0,
// 响应时间,用于排序
var respondTime: Long = 180000L,
// 智能排序的权重
var weight: Int = 0,
// 是否有发现url
var hasExploreUrl: Boolean = false
) {
override fun hashCode(): Int {
return bookSourceUrl.hashCode()
}
override fun equals(other: Any?): Boolean {
return if (other is BookSourcePart) other.bookSourceUrl == bookSourceUrl else false
}
fun getDisPlayNameGroup(): String {
return if (bookSourceGroup.isNullOrBlank()) {
bookSourceName
} else {
String.format("%s (%s)", bookSourceName, bookSourceGroup)
}
}
fun getBookSource(): BookSource? {
return appDb.bookSourceDao.getBookSource(bookSourceUrl)
}
fun addGroup(groups: String) {
bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
it.addAll(groups.splitNotBlank(AppPattern.splitGroupRegex))
bookSourceGroup = TextUtils.join(",", it)
}
if (bookSourceGroup.isNullOrBlank()) bookSourceGroup = groups
}
fun removeGroup(groups: String) {
bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {
it.removeAll(groups.splitNotBlank(AppPattern.splitGroupRegex).toSet())
bookSourceGroup = TextUtils.join(",", it)
}
}
}
fun List<BookSourcePart>.toBookSource(): List<BookSource> {
return mapNotNull { it.getBookSource() }
}

View File

@ -13,6 +13,7 @@ import android.webkit.WebViewClient
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.utils.runOnUI import io.legado.app.utils.runOnUI
import kotlinx.coroutines.Runnable import kotlinx.coroutines.Runnable
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
@ -80,6 +81,7 @@ class BackstageWebView(
} else { } else {
webView.loadDataWithBaseURL(url, html, "text/html", getEncoding(), url) webView.loadDataWithBaseURL(url, html, "text/html", getEncoding(), url)
} }
else -> if (headerMap == null) { else -> if (headerMap == null) {
webView.loadUrl(url!!) webView.loadUrl(url!!)
} else { } else {
@ -131,10 +133,15 @@ class BackstageWebView(
private inner class HtmlWebViewClient : WebViewClient() { private inner class HtmlWebViewClient : WebViewClient() {
var runnable: EvalJsRunnable? = null
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
setCookie(url) setCookie(url)
val runnable = EvalJsRunnable(view, url, getJs()) if (runnable == null) {
mHandler.postDelayed(runnable, 1000) runnable = EvalJsRunnable(view, url, getJs())
}
mHandler.removeCallbacks(runnable!!)
mHandler.postDelayed(runnable!!, 1000)
} }
@SuppressLint("WebViewClientOnReceivedSslError") @SuppressLint("WebViewClientOnReceivedSslError")
@ -157,30 +164,35 @@ class BackstageWebView(
private val mWebView: WeakReference<WebView> = WeakReference(webView) private val mWebView: WeakReference<WebView> = WeakReference(webView)
override fun run() { override fun run() {
mWebView.get()?.evaluateJavascript(mJavaScript) { mWebView.get()?.evaluateJavascript(mJavaScript) {
if (it.isNotEmpty() && it != "null") { handleResult(it)
val content = StringEscapeUtils.unescapeJson(it)
.replace("^\"|\"$".toRegex(), "")
try {
val response = StrResponse(url, content)
callback?.onResult(response)
} catch (e: Exception) {
callback?.onError(e)
}
mHandler.removeCallbacks(this)
destroy()
return@evaluateJavascript
}
if (retry > 30) {
callback?.onError(NoStackTraceException("js执行超时"))
mHandler.removeCallbacks(this)
destroy()
return@evaluateJavascript
}
retry++
mHandler.removeCallbacks(this)
mHandler.postDelayed(this, 1000)
} }
} }
private fun handleResult(result: String) = Coroutine.async {
if (result.isNotEmpty() && result != "null") {
val content = StringEscapeUtils.unescapeJson(result)
.replace(quoteRegex, "")
try {
val response = StrResponse(url, content)
callback?.onResult(response)
} catch (e: Exception) {
callback?.onError(e)
}
mHandler.post {
destroy()
}
return@async
}
if (retry > 30) {
callback?.onError(NoStackTraceException("js执行超时"))
mHandler.post {
destroy()
}
return@async
}
retry++
mHandler.postDelayed(this@EvalJsRunnable, 1000)
}
} }
private inner class SnifferWebClient : WebViewClient() { private inner class SnifferWebClient : WebViewClient() {
@ -231,6 +243,7 @@ class BackstageWebView(
companion object { companion object {
const val JS = "document.documentElement.outerHTML" const val JS = "document.documentElement.outerHTML"
private val quoteRegex = "^\"|\"$".toRegex()
} }
abstract class Callback { abstract class Callback {

View File

@ -4,6 +4,7 @@ import android.content.Context
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.help.CacheManager import io.legado.app.help.CacheManager
import io.legado.app.help.IntentData import io.legado.app.help.IntentData
import io.legado.app.service.CheckSourceService import io.legado.app.service.CheckSourceService
@ -22,7 +23,7 @@ object CheckSource {
var checkContent = CacheManager.get("checkContent")?.toBoolean() ?: true var checkContent = CacheManager.get("checkContent")?.toBoolean() ?: true
val summary get() = upSummary() val summary get() = upSummary()
fun start(context: Context, sources: List<BookSource>) { fun start(context: Context, sources: List<BookSourcePart>) {
val selectedIds = sources.map { val selectedIds = sources.map {
it.bookSourceUrl it.bookSourceUrl
} }

View File

@ -56,18 +56,17 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application
fun explore() { fun explore() {
val source = bookSource val source = bookSource
val url = exploreUrl val url = exploreUrl
if (source != null && url != null) { if (source == null || url == null) return
WebBook.exploreBook(viewModelScope, source, url, page) WebBook.exploreBook(viewModelScope, source, url, page)
.timeout(if (BuildConfig.DEBUG) 0L else 30000L) .timeout(if (BuildConfig.DEBUG) 0L else 30000L)
.onSuccess(IO) { searchBooks -> .onSuccess(IO) { searchBooks ->
booksData.postValue(searchBooks) booksData.postValue(searchBooks)
appDb.searchBookDao.insert(*searchBooks.toTypedArray()) appDb.searchBookDao.insert(*searchBooks.toTypedArray())
page++ page++
}.onError { }.onError {
it.printOnDebug() it.printOnDebug()
errorLiveData.postValue(it.stackTraceStr) errorLiveData.postValue(it.stackTraceStr)
} }
}
} }
} }

View File

@ -13,6 +13,7 @@ import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.databinding.DialogSourcePickerBinding import io.legado.app.databinding.DialogSourcePickerBinding
import io.legado.app.databinding.Item1lineTextBinding import io.legado.app.databinding.Item1lineTextBinding
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
@ -81,7 +82,7 @@ class SourcePickerDialog : BaseDialogFragment(R.layout.dialog_source_picker) {
} }
inner class SourceAdapter(context: Context) : inner class SourceAdapter(context: Context) :
RecyclerAdapter<BookSource, Item1lineTextBinding>(context) { RecyclerAdapter<BookSourcePart, Item1lineTextBinding>(context) {
override fun getViewBinding(parent: ViewGroup): Item1lineTextBinding { override fun getViewBinding(parent: ViewGroup): Item1lineTextBinding {
return Item1lineTextBinding.inflate(inflater, parent, false).apply { return Item1lineTextBinding.inflate(inflater, parent, false).apply {
@ -92,7 +93,7 @@ class SourcePickerDialog : BaseDialogFragment(R.layout.dialog_source_picker) {
override fun convert( override fun convert(
holder: ItemViewHolder, holder: ItemViewHolder,
binding: Item1lineTextBinding, binding: Item1lineTextBinding,
item: BookSource, item: BookSourcePart,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
binding.textView.text = item.getDisPlayNameGroup() binding.textView.text = item.getDisPlayNameGroup()
@ -101,7 +102,9 @@ class SourcePickerDialog : BaseDialogFragment(R.layout.dialog_source_picker) {
override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextBinding) { override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextBinding) {
binding.root.onClick { binding.root.onClick {
getItemByLayoutPosition(holder.layoutPosition)?.let { getItemByLayoutPosition(holder.layoutPosition)?.let {
callback?.sourceOnClick(it) it.getBookSource()?.let { source ->
callback?.sourceOnClick(source)
}
dismissAllowingStateLoss() dismissAllowingStateLoss()
} }
} }

View File

@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import splitties.init.appCtx import splitties.init.appCtx
@ -20,6 +21,10 @@ data class SearchScope(private var scope: String) {
"${source.bookSourceName.replace(":", "")}::${source.bookSourceUrl}" "${source.bookSourceName.replace(":", "")}::${source.bookSourceUrl}"
) )
constructor(source: BookSourcePart) : this(
"${source.bookSourceName.replace(":", "")}::${source.bookSourceUrl}"
)
override fun toString(): String { override fun toString(): String {
return scope return scope
} }

View File

@ -17,6 +17,8 @@ import io.legado.app.constant.AppLog
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.data.entities.toBookSource
import io.legado.app.databinding.ActivityBookSourceBinding import io.legado.app.databinding.ActivityBookSourceBinding
import io.legado.app.databinding.DialogEditTextBinding import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.help.DirectLinkUpload import io.legado.app.help.DirectLinkUpload
@ -143,54 +145,66 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
mode = HandleFileContract.FILE mode = HandleFileContract.FILE
allowExtensions = arrayOf("txt", "json") allowExtensions = arrayOf("txt", "json")
} }
R.id.menu_import_onLine -> showImportDialog() R.id.menu_import_onLine -> showImportDialog()
R.id.menu_sort_manual -> { R.id.menu_sort_manual -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Default) sortCheck(Sort.Default)
upBookSource(searchView.query?.toString()) upBookSource(searchView.query?.toString())
} }
R.id.menu_sort_auto -> { R.id.menu_sort_auto -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Weight) sortCheck(Sort.Weight)
upBookSource(searchView.query?.toString()) upBookSource(searchView.query?.toString())
} }
R.id.menu_sort_name -> { R.id.menu_sort_name -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Name) sortCheck(Sort.Name)
upBookSource(searchView.query?.toString()) upBookSource(searchView.query?.toString())
} }
R.id.menu_sort_url -> { R.id.menu_sort_url -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Url) sortCheck(Sort.Url)
upBookSource(searchView.query?.toString()) upBookSource(searchView.query?.toString())
} }
R.id.menu_sort_time -> { R.id.menu_sort_time -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Update) sortCheck(Sort.Update)
upBookSource(searchView.query?.toString()) upBookSource(searchView.query?.toString())
} }
R.id.menu_sort_respondTime -> { R.id.menu_sort_respondTime -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Respond) sortCheck(Sort.Respond)
upBookSource(searchView.query?.toString()) upBookSource(searchView.query?.toString())
} }
R.id.menu_sort_enable -> { R.id.menu_sort_enable -> {
item.isChecked = true item.isChecked = true
sortCheck(Sort.Enable) sortCheck(Sort.Enable)
upBookSource(searchView.query?.toString()) upBookSource(searchView.query?.toString())
} }
R.id.menu_enabled_group -> { R.id.menu_enabled_group -> {
searchView.setQuery(getString(R.string.enabled), true) searchView.setQuery(getString(R.string.enabled), true)
} }
R.id.menu_disabled_group -> { R.id.menu_disabled_group -> {
searchView.setQuery(getString(R.string.disabled), true) searchView.setQuery(getString(R.string.disabled), true)
} }
R.id.menu_group_login -> { R.id.menu_group_login -> {
searchView.setQuery(getString(R.string.need_login), true) searchView.setQuery(getString(R.string.need_login), true)
} }
R.id.menu_group_null -> { R.id.menu_group_null -> {
searchView.setQuery(getString(R.string.no_group), true) searchView.setQuery(getString(R.string.no_group), true)
} }
R.id.menu_help -> showHelp() R.id.menu_help -> showHelp()
} }
if (item.groupId == R.id.source_group) { if (item.groupId == R.id.source_group) {
@ -228,22 +242,28 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
searchKey.isNullOrEmpty() -> { searchKey.isNullOrEmpty() -> {
appDb.bookSourceDao.flowAll() appDb.bookSourceDao.flowAll()
} }
searchKey == getString(R.string.enabled) -> { searchKey == getString(R.string.enabled) -> {
appDb.bookSourceDao.flowEnabled() appDb.bookSourceDao.flowEnabled()
} }
searchKey == getString(R.string.disabled) -> { searchKey == getString(R.string.disabled) -> {
appDb.bookSourceDao.flowDisabled() appDb.bookSourceDao.flowDisabled()
} }
searchKey == getString(R.string.need_login) -> { searchKey == getString(R.string.need_login) -> {
appDb.bookSourceDao.flowLogin() appDb.bookSourceDao.flowLogin()
} }
searchKey == getString(R.string.no_group) -> { searchKey == getString(R.string.no_group) -> {
appDb.bookSourceDao.flowNoGroup() appDb.bookSourceDao.flowNoGroup()
} }
searchKey.startsWith("group:") -> { searchKey.startsWith("group:") -> {
val key = searchKey.substringAfter("group:") val key = searchKey.substringAfter("group:")
appDb.bookSourceDao.flowGroupSearch(key) appDb.bookSourceDao.flowGroupSearch(key)
} }
else -> { else -> {
appDb.bookSourceDao.flowSearch(searchKey) appDb.bookSourceDao.flowSearch(searchKey)
} }
@ -253,6 +273,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
Sort.Name -> data.sortedWith { o1, o2 -> Sort.Name -> data.sortedWith { o1, o2 ->
o1.bookSourceName.cnCompare(o2.bookSourceName) o1.bookSourceName.cnCompare(o2.bookSourceName)
} }
Sort.Url -> data.sortedBy { it.bookSourceUrl } Sort.Url -> data.sortedBy { it.bookSourceUrl }
Sort.Update -> data.sortedByDescending { it.lastUpdateTime } Sort.Update -> data.sortedByDescending { it.lastUpdateTime }
Sort.Respond -> data.sortedBy { it.respondTime } Sort.Respond -> data.sortedBy { it.respondTime }
@ -263,6 +284,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
} }
sort sort
} }
else -> data else -> data
} }
else when (sort) { else when (sort) {
@ -270,6 +292,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
Sort.Name -> data.sortedWith { o1, o2 -> Sort.Name -> data.sortedWith { o1, o2 ->
o2.bookSourceName.cnCompare(o1.bookSourceName) o2.bookSourceName.cnCompare(o1.bookSourceName)
} }
Sort.Url -> data.sortedByDescending { it.bookSourceUrl } Sort.Url -> data.sortedByDescending { it.bookSourceUrl }
Sort.Update -> data.sortedBy { it.lastUpdateTime } Sort.Update -> data.sortedBy { it.lastUpdateTime }
Sort.Respond -> data.sortedByDescending { it.respondTime } Sort.Respond -> data.sortedByDescending { it.respondTime }
@ -280,6 +303,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
} }
sort sort
} }
else -> data.reversed() else -> data.reversed()
} }
}.catch { }.catch {
@ -330,7 +354,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
override fun onClickSelectBarMainAction() { override fun onClickSelectBarMainAction() {
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) { alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
yesButton { viewModel.del(*adapter.selection.toTypedArray()) } yesButton { viewModel.del(adapter.selection) }
noButton() noButton()
} }
} }
@ -353,7 +377,7 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray()) R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray())
R.id.menu_add_group -> selectionAddToGroups() R.id.menu_add_group -> selectionAddToGroups()
R.id.menu_remove_group -> selectionRemoveFromGroups() R.id.menu_remove_group -> selectionRemoveFromGroups()
R.id.menu_export_selection -> viewModel.saveToFile(adapter.selection) { file -> R.id.menu_export_selection -> viewModel.saveToFile(adapter.selection.toBookSource()) { file ->
exportDir.launch { exportDir.launch {
mode = HandleFileContract.EXPORT mode = HandleFileContract.EXPORT
fileData = HandleFileContract.FileData( fileData = HandleFileContract.FileData(
@ -363,9 +387,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
) )
} }
} }
R.id.menu_share_source -> viewModel.saveToFile(adapter.selection) {
R.id.menu_share_source -> viewModel.saveToFile(adapter.selection.toBookSource()) {
share(it) share(it)
} }
R.id.menu_check_selected_interval -> adapter.checkSelectedInterval() R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()
} }
return true return true
@ -386,9 +412,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
CheckSource.keyword = it CheckSource.keyword = it
} }
} }
CheckSource.start(this@BookSourceActivity, adapter.selection) val selectItems = adapter.selection
val firstItem = adapter.getItems().indexOf(adapter.selection.firstOrNull()) CheckSource.start(this@BookSourceActivity, selectItems)
val lastItem = adapter.getItems().indexOf(adapter.selection.lastOrNull()) val adapterItems = adapter.getItems()
val firstItem = adapterItems.indexOf(selectItems.firstOrNull())
val lastItem = adapterItems.indexOf(selectItems.lastOrNull())
Debug.isChecking = firstItem >= 0 && lastItem >= 0 Debug.isChecking = firstItem >= 0 && lastItem >= 0
checkMessageRefreshJob(firstItem, lastItem).start() checkMessageRefreshJob(firstItem, lastItem).start()
} }
@ -587,45 +615,49 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
return false return false
} }
override fun del(bookSource: BookSource) { override fun del(bookSource: BookSourcePart) {
alert(R.string.draw) { alert(R.string.draw) {
setMessage(getString(R.string.sure_del) + "\n" + bookSource.bookSourceName) setMessage(getString(R.string.sure_del) + "\n" + bookSource.bookSourceName)
noButton() noButton()
yesButton { yesButton {
viewModel.del(bookSource) viewModel.del(listOf(bookSource))
} }
} }
} }
override fun update(vararg bookSource: BookSource) { override fun edit(bookSource: BookSourcePart) {
viewModel.update(*bookSource)
}
override fun edit(bookSource: BookSource) {
startActivity<BookSourceEditActivity> { startActivity<BookSourceEditActivity> {
putExtra("sourceUrl", bookSource.bookSourceUrl) putExtra("sourceUrl", bookSource.bookSourceUrl)
} }
} }
override fun upOrder(items: List<BookSource>) { override fun upOrder(items: List<BookSourcePart>) {
viewModel.upOrder(items) viewModel.upOrder(items)
} }
override fun toTop(bookSource: BookSource) { override fun enable(enable: Boolean, bookSource: BookSourcePart) {
viewModel.enable(enable, listOf(bookSource))
}
override fun enableExplore(enable: Boolean, bookSource: BookSourcePart) {
viewModel.enableExplore(enable, listOf(bookSource))
}
override fun toTop(bookSource: BookSourcePart) {
viewModel.topSource(bookSource) viewModel.topSource(bookSource)
} }
override fun toBottom(bookSource: BookSource) { override fun toBottom(bookSource: BookSourcePart) {
viewModel.bottomSource(bookSource) viewModel.bottomSource(bookSource)
} }
override fun searchBook(bookSource: BookSource) { override fun searchBook(bookSource: BookSourcePart) {
startActivity<SearchActivity> { startActivity<SearchActivity> {
putExtra("searchScope", SearchScope(bookSource).toString()) putExtra("searchScope", SearchScope(bookSource).toString())
} }
} }
override fun debug(bookSource: BookSource) { override fun debug(bookSource: BookSourcePart) {
startActivity<BookSourceDebugActivity> { startActivity<BookSourceDebugActivity> {
putExtra("key", bookSource.bookSourceUrl) putExtra("key", bookSource.bookSourceUrl)
} }

View File

@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSourcePart
import io.legado.app.databinding.ItemBookSourceBinding import io.legado.app.databinding.ItemBookSourceBinding
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.model.Debug import io.legado.app.model.Debug
@ -28,34 +28,34 @@ import java.util.*
class BookSourceAdapter(context: Context, val callBack: CallBack) : class BookSourceAdapter(context: Context, val callBack: CallBack) :
RecyclerAdapter<BookSource, ItemBookSourceBinding>(context), RecyclerAdapter<BookSourcePart, ItemBookSourceBinding>(context),
ItemTouchCallback.Callback { ItemTouchCallback.Callback {
private val selected = linkedSetOf<BookSource>() private val selected = linkedSetOf<BookSourcePart>()
private val finalMessageRegex = Regex("成功|失败") private val finalMessageRegex = Regex("成功|失败")
val selection: List<BookSource> val selection: List<BookSourcePart>
get() { get() {
return getItems().filter { return getItems().filter {
selected.contains(it) selected.contains(it)
} }
} }
val diffItemCallback = object : DiffUtil.ItemCallback<BookSource>() { val diffItemCallback = object : DiffUtil.ItemCallback<BookSourcePart>() {
override fun areItemsTheSame(oldItem: BookSource, newItem: BookSource): Boolean { override fun areItemsTheSame(oldItem: BookSourcePart, newItem: BookSourcePart): Boolean {
return oldItem.bookSourceUrl == newItem.bookSourceUrl return oldItem.bookSourceUrl == newItem.bookSourceUrl
} }
override fun areContentsTheSame(oldItem: BookSource, newItem: BookSource): Boolean { override fun areContentsTheSame(oldItem: BookSourcePart, newItem: BookSourcePart): Boolean {
return oldItem.bookSourceName == newItem.bookSourceName return oldItem.bookSourceName == newItem.bookSourceName
&& oldItem.bookSourceGroup == newItem.bookSourceGroup && oldItem.bookSourceGroup == newItem.bookSourceGroup
&& oldItem.enabled == newItem.enabled && oldItem.enabled == newItem.enabled
&& oldItem.enabledExplore == newItem.enabledExplore && oldItem.enabledExplore == newItem.enabledExplore
&& oldItem.exploreUrl == newItem.exploreUrl && oldItem.hasExploreUrl == newItem.hasExploreUrl
} }
override fun getChangePayload(oldItem: BookSource, newItem: BookSource): Any? { override fun getChangePayload(oldItem: BookSourcePart, newItem: BookSourcePart): Any? {
val payload = Bundle() val payload = Bundle()
if (oldItem.bookSourceName != newItem.bookSourceName if (oldItem.bookSourceName != newItem.bookSourceName
|| oldItem.bookSourceGroup != newItem.bookSourceGroup || oldItem.bookSourceGroup != newItem.bookSourceGroup
@ -66,7 +66,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
payload.putBoolean("enabled", newItem.enabled) payload.putBoolean("enabled", newItem.enabled)
} }
if (oldItem.enabledExplore != newItem.enabledExplore || if (oldItem.enabledExplore != newItem.enabledExplore ||
oldItem.exploreUrl != newItem.exploreUrl oldItem.hasExploreUrl != newItem.hasExploreUrl
) { ) {
payload.putBoolean("upExplore", true) payload.putBoolean("upExplore", true)
} }
@ -85,7 +85,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
override fun convert( override fun convert(
holder: ItemViewHolder, holder: ItemViewHolder,
binding: ItemBookSourceBinding, binding: ItemBookSourceBinding,
item: BookSource, item: BookSourcePart,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
binding.run { binding.run {
@ -117,7 +117,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
getItem(holder.layoutPosition)?.let { getItem(holder.layoutPosition)?.let {
if (view.isPressed) { if (view.isPressed) {
it.enabled = checked it.enabled = checked
callBack.update(it) callBack.enable(checked, it)
} }
} }
} }
@ -153,7 +153,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
val popupMenu = PopupMenu(context, view) val popupMenu = PopupMenu(context, view)
popupMenu.inflate(R.menu.book_source_item) popupMenu.inflate(R.menu.book_source_item)
val qyMenu = popupMenu.menu.findItem(R.id.menu_enable_explore) val qyMenu = popupMenu.menu.findItem(R.id.menu_enable_explore)
if (source.exploreUrl.isNullOrEmpty()) { if (!source.hasExploreUrl) {
qyMenu.isVisible = false qyMenu.isVisible = false
} else { } else {
if (source.enabledExplore) { if (source.enabledExplore) {
@ -163,7 +163,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
} }
} }
val loginMenu = popupMenu.menu.findItem(R.id.menu_login) val loginMenu = popupMenu.menu.findItem(R.id.menu_login)
loginMenu.isVisible = !source.loginUrl.isNullOrBlank() loginMenu.isVisible = source.hasLoginUrl
popupMenu.setOnMenuItemClickListener { menuItem -> popupMenu.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.menu_top -> callBack.toTop(source) R.id.menu_top -> callBack.toTop(source)
@ -172,14 +172,16 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
putExtra("type", "bookSource") putExtra("type", "bookSource")
putExtra("key", source.bookSourceUrl) putExtra("key", source.bookSourceUrl)
} }
R.id.menu_search -> callBack.searchBook(source) R.id.menu_search -> callBack.searchBook(source)
R.id.menu_debug_source -> callBack.debug(source) R.id.menu_debug_source -> callBack.debug(source)
R.id.menu_del -> { R.id.menu_del -> {
callBack.del(source) callBack.del(source)
selected.remove(source) selected.remove(source)
} }
R.id.menu_enable_explore -> { R.id.menu_enable_explore -> {
callBack.update(source.copy(enabledExplore = !source.enabledExplore)) callBack.enableExplore(!source.enabledExplore, source)
} }
} }
true true
@ -187,16 +189,18 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
popupMenu.show() popupMenu.show()
} }
private fun upShowExplore(iv: ImageView, source: BookSource) { private fun upShowExplore(iv: ImageView, source: BookSourcePart) {
when { when {
source.exploreUrl.isNullOrEmpty() -> { !source.hasExploreUrl -> {
iv.invisible() iv.invisible()
} }
source.enabledExplore -> { source.enabledExplore -> {
iv.setColorFilter(Color.GREEN) iv.setColorFilter(Color.GREEN)
iv.visible() iv.visible()
iv.contentDescription = context.getString(R.string.tag_explore_enabled) iv.contentDescription = context.getString(R.string.tag_explore_enabled)
} }
else -> { else -> {
iv.setColorFilter(Color.RED) iv.setColorFilter(Color.RED)
iv.visible() iv.visible()
@ -205,7 +209,10 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
} }
} }
private fun upCheckSourceMessage(binding: ItemBookSourceBinding, item: BookSource) = binding.run { private fun upCheckSourceMessage(
binding: ItemBookSourceBinding,
item: BookSourcePart
) = binding.run {
val msg = Debug.debugMessageMap[item.bookSourceUrl] ?: "" val msg = Debug.debugMessageMap[item.bookSourceUrl] ?: ""
ivDebugText.text = msg ivDebugText.text = msg
val isEmpty = msg.isEmpty() val isEmpty = msg.isEmpty()
@ -274,7 +281,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
return true return true
} }
private val movedItems = hashSetOf<BookSource>() private val movedItems = hashSetOf<BookSourcePart>()
override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (movedItems.isNotEmpty()) { if (movedItems.isNotEmpty()) {
@ -285,19 +292,19 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
if (movedItems.size > sortNumberSet.size) { if (movedItems.size > sortNumberSet.size) {
callBack.upOrder(getItems()) callBack.upOrder(getItems())
} else { } else {
callBack.update(*movedItems.toTypedArray()) callBack.upOrder(movedItems.toList())
} }
movedItems.clear() movedItems.clear()
} }
} }
val dragSelectCallback: DragSelectTouchHelper.Callback = val dragSelectCallback: DragSelectTouchHelper.Callback =
object : DragSelectTouchHelper.AdvanceCallback<BookSource>(Mode.ToggleAndReverse) { object : DragSelectTouchHelper.AdvanceCallback<BookSourcePart>(Mode.ToggleAndReverse) {
override fun currentSelectedId(): MutableSet<BookSource> { override fun currentSelectedId(): MutableSet<BookSourcePart> {
return selected return selected
} }
override fun getItemId(position: Int): BookSource { override fun getItemId(position: Int): BookSourcePart {
return getItem(position)!! return getItem(position)!!
} }
@ -317,14 +324,15 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
} }
interface CallBack { interface CallBack {
fun del(bookSource: BookSource) fun del(bookSource: BookSourcePart)
fun edit(bookSource: BookSource) fun edit(bookSource: BookSourcePart)
fun update(vararg bookSource: BookSource) fun toTop(bookSource: BookSourcePart)
fun toTop(bookSource: BookSource) fun toBottom(bookSource: BookSourcePart)
fun toBottom(bookSource: BookSource) fun searchBook(bookSource: BookSourcePart)
fun searchBook(bookSource: BookSource) fun debug(bookSource: BookSourcePart)
fun debug(bookSource: BookSource) fun upOrder(items: List<BookSourcePart>)
fun upOrder(items: List<BookSource>) fun enable(enable: Boolean, bookSource: BookSourcePart)
fun enableExplore(enable: Boolean, bookSource: BookSourcePart)
fun upCountView() fun upCountView()
} }
} }

View File

@ -5,6 +5,7 @@ import android.text.TextUtils
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.help.config.SourceConfig import io.legado.app.help.config.SourceConfig
import io.legado.app.utils.* import io.legado.app.utils.*
import java.io.File import java.io.File
@ -16,31 +17,31 @@ import java.io.FileOutputStream
*/ */
class BookSourceViewModel(application: Application) : BaseViewModel(application) { class BookSourceViewModel(application: Application) : BaseViewModel(application) {
fun topSource(vararg sources: BookSource) { fun topSource(vararg sources: BookSourcePart) {
execute { execute {
sources.sortBy { it.customOrder } sources.sortBy { it.customOrder }
val minOrder = appDb.bookSourceDao.minOrder - 1 val minOrder = appDb.bookSourceDao.minOrder - 1
val array = Array(sources.size) { val array = sources.mapIndexed { index, it ->
sources[it].copy(customOrder = minOrder - it) it.copy(customOrder = minOrder - index)
} }
appDb.bookSourceDao.update(*array) appDb.bookSourceDao.upOrder(array)
} }
} }
fun bottomSource(vararg sources: BookSource) { fun bottomSource(vararg sources: BookSourcePart) {
execute { execute {
sources.sortBy { it.customOrder } sources.sortBy { it.customOrder }
val maxOrder = appDb.bookSourceDao.maxOrder + 1 val maxOrder = appDb.bookSourceDao.maxOrder + 1
val array = Array(sources.size) { val array = sources.mapIndexed { index, it ->
sources[it].copy(customOrder = maxOrder + it) it.copy(customOrder = maxOrder + index)
} }
appDb.bookSourceDao.update(*array) appDb.bookSourceDao.upOrder(array)
} }
} }
fun del(vararg sources: BookSource) { fun del(sources: List<BookSourcePart>) {
execute { execute {
appDb.bookSourceDao.delete(*sources) appDb.bookSourceDao.delete(sources)
sources.forEach { sources.forEach {
SourceConfig.removeSource(it.bookSourceUrl) SourceConfig.removeSource(it.bookSourceUrl)
} }
@ -51,68 +52,72 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
execute { appDb.bookSourceDao.update(*bookSource) } execute { appDb.bookSourceDao.update(*bookSource) }
} }
fun upOrder(items: List<BookSource>) { fun upOrder(items: List<BookSourcePart>) {
if (items.isEmpty()) return if (items.isEmpty()) return
execute { execute {
val firstSortNumber = items[0].customOrder val firstSortNumber = items[0].customOrder
items.forEachIndexed { index, bookSource -> items.forEachIndexed { index, bookSource ->
bookSource.customOrder = firstSortNumber + index bookSource.customOrder = firstSortNumber + index
appDb.bookSourceDao.update(bookSource)
} }
appDb.bookSourceDao.upOrder(items)
} }
} }
fun enableSelection(sources: List<BookSource>) { fun enable(enable: Boolean, items: List<BookSourcePart>) {
execute { execute {
val array = Array(sources.size) { appDb.bookSourceDao.enable(enable, items)
sources[it].copy(enabled = true)
}
appDb.bookSourceDao.update(*array)
} }
} }
fun disableSelection(sources: List<BookSource>) { fun enableSelection(sources: List<BookSourcePart>) {
execute { execute {
val array = Array(sources.size) { appDb.bookSourceDao.enable(true, sources)
sources[it].copy(enabled = false)
}
appDb.bookSourceDao.update(*array)
} }
} }
fun enableSelectExplore(sources: List<BookSource>) { fun disableSelection(sources: List<BookSourcePart>) {
execute { execute {
val array = Array(sources.size) { appDb.bookSourceDao.enable(false, sources)
sources[it].copy(enabledExplore = true)
}
appDb.bookSourceDao.update(*array)
} }
} }
fun disableSelectExplore(sources: List<BookSource>) { fun enableExplore(enable: Boolean, items: List<BookSourcePart>) {
execute { execute {
val array = Array(sources.size) { appDb.bookSourceDao.enableExplore(enable, items)
sources[it].copy(enabledExplore = false)
}
appDb.bookSourceDao.update(*array)
} }
} }
fun selectionAddToGroups(sources: List<BookSource>, groups: String) { fun enableSelectExplore(sources: List<BookSourcePart>) {
execute { execute {
val array = Array(sources.size) { appDb.bookSourceDao.enableExplore(true, sources)
sources[it].copy().addGroup(groups)
}
appDb.bookSourceDao.update(*array)
} }
} }
fun selectionRemoveFromGroups(sources: List<BookSource>, groups: String) { fun disableSelectExplore(sources: List<BookSourcePart>) {
execute { execute {
val array = Array(sources.size) { appDb.bookSourceDao.enableExplore(false, sources)
sources[it].copy().removeGroup(groups) }
}
fun selectionAddToGroups(sources: List<BookSourcePart>, groups: String) {
execute {
val array = sources.map {
it.copy().apply {
addGroup(groups)
}
} }
appDb.bookSourceDao.update(*array) appDb.bookSourceDao.upGroup(array)
}
}
fun selectionRemoveFromGroups(sources: List<BookSourcePart>, groups: String) {
execute {
val array = sources.map {
it.copy().apply {
removeGroup(groups)
}
}
appDb.bookSourceDao.upGroup(array)
} }
} }

View File

@ -4,6 +4,7 @@ import android.app.Application
import android.content.Context import android.content.Context
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb
import io.legado.app.help.AppWebDav import io.legado.app.help.AppWebDav
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
@ -34,5 +35,12 @@ class ConfigViewModel(application: Application) : BaseViewModel(application) {
} }
} }
fun shrinkDatabase() {
execute {
appDb.openHelper.writableDatabase.execSQL("VACUUM")
}.onSuccess {
context.toastOnUi(R.string.success)
}
}
} }

View File

@ -135,6 +135,7 @@ class OtherConfigFragment : PreferenceFragment(),
PreferKey.clearWebViewData -> clearWebViewData() PreferKey.clearWebViewData -> clearWebViewData()
"localPassword" -> alertLocalPassword() "localPassword" -> alertLocalPassword()
PreferKey.shrinkDatabase -> shrinkDatabase()
} }
return super.onPreferenceTreeClick(preference) return super.onPreferenceTreeClick(preference)
} }
@ -234,6 +235,15 @@ class OtherConfigFragment : PreferenceFragment(),
} }
} }
private fun shrinkDatabase() {
alert(R.string.sure, R.string.shrink_database) {
okButton {
viewModel.shrinkDatabase()
}
noButton()
}
}
private fun clearWebViewData() { private fun clearWebViewData() {
alert(R.string.clear_webview_data, R.string.sure_del) { alert(R.string.clear_webview_data, R.string.sure_del) {
okButton { okButton {

View File

@ -1110,4 +1110,6 @@
<string name="custom_export">Custom Export</string> <string name="custom_export">Custom Export</string>
<string name="file_contains_number">The number of chapters contained in each file</string> <string name="file_contains_number">The number of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string> <string name="export_chapter_index">The section index that needs to be exported</string>
<string name="shrink_database">压缩数据库</string>
<string name="shrink_database_summary">减小数据库文件的大小</string>
</resources> </resources>

View File

@ -1113,4 +1113,6 @@
<string name="custom_export">Custom Export</string> <string name="custom_export">Custom Export</string>
<string name="file_contains_number">The number of chapters contained in each file</string> <string name="file_contains_number">The number of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string> <string name="export_chapter_index">The section index that needs to be exported</string>
<string name="shrink_database">压缩数据库</string>
<string name="shrink_database_summary">减小数据库文件的大小</string>
</resources> </resources>

View File

@ -1113,4 +1113,6 @@
<string name="custom_export">Custom Export</string> <string name="custom_export">Custom Export</string>
<string name="file_contains_number">The index of chapters contained in each file</string> <string name="file_contains_number">The index of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string> <string name="export_chapter_index">The section index that needs to be exported</string>
<string name="shrink_database">压缩数据库</string>
<string name="shrink_database_summary">减小数据库文件的大小</string>
</resources> </resources>

View File

@ -1110,4 +1110,6 @@
<string name="custom_export">自定義導出</string> <string name="custom_export">自定義導出</string>
<string name="file_contains_number">每個文件包含的章節數量</string> <string name="file_contains_number">每個文件包含的章節數量</string>
<string name="export_chapter_index">需要輸出的章節</string> <string name="export_chapter_index">需要輸出的章節</string>
<string name="shrink_database">压缩数据库</string>
<string name="shrink_database_summary">减小数据库文件的大小</string>
</resources> </resources>

View File

@ -1112,4 +1112,6 @@
<string name="custom_export">自定義導出</string> <string name="custom_export">自定義導出</string>
<string name="file_contains_number">每個文件包含的章節數量</string> <string name="file_contains_number">每個文件包含的章節數量</string>
<string name="export_chapter_index">需要輸出的章節</string> <string name="export_chapter_index">需要輸出的章節</string>
<string name="shrink_database">压缩数据库</string>
<string name="shrink_database_summary">减小数据库文件的大小</string>
</resources> </resources>

View File

@ -1112,4 +1112,6 @@
<string name="custom_export">自定义导出</string> <string name="custom_export">自定义导出</string>
<string name="file_contains_number">每个文件包含的章节数量</string> <string name="file_contains_number">每个文件包含的章节数量</string>
<string name="export_chapter_index">需要输出的章节</string> <string name="export_chapter_index">需要输出的章节</string>
<string name="shrink_database">压缩数据库</string>
<string name="shrink_database_summary">减小数据库文件的大小</string>
</resources> </resources>

View File

@ -1113,4 +1113,6 @@
<string name="custom_export">Custom Export</string> <string name="custom_export">Custom Export</string>
<string name="file_contains_number">The number of chapters contained in each file</string> <string name="file_contains_number">The number of chapters contained in each file</string>
<string name="export_chapter_index">The section index that needs to be exported</string> <string name="export_chapter_index">The section index that needs to be exported</string>
<string name="shrink_database">压缩数据库</string>
<string name="shrink_database_summary">减小数据库文件的大小</string>
</resources> </resources>

View File

@ -156,6 +156,12 @@
android:title="@string/clear_webview_data" android:title="@string/clear_webview_data"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.lib.prefs.Preference
android:key="shrinkDatabase"
android:summary="@string/shrink_database_summary"
android:title="@string/shrink_database"
app:iconSpaceReserved="false" />
<io.legado.app.lib.prefs.Preference <io.legado.app.lib.prefs.Preference
android:key="threadCount" android:key="threadCount"
android:title="@string/threads_num_title" android:title="@string/threads_num_title"