优化
This commit is contained in:
@@ -19,6 +19,7 @@ import androidx.lifecycle.LifecycleRegistry
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.base.VMBaseActivity
|
import io.legado.app.base.VMBaseActivity
|
||||||
@@ -49,6 +50,7 @@ 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.ui.widget.recycler.VerticalDivider
|
import io.legado.app.ui.widget.recycler.VerticalDivider
|
||||||
import io.legado.app.utils.ACache
|
import io.legado.app.utils.ACache
|
||||||
|
import io.legado.app.utils.NetworkUtils
|
||||||
import io.legado.app.utils.applyTint
|
import io.legado.app.utils.applyTint
|
||||||
import io.legado.app.utils.cnCompare
|
import io.legado.app.utils.cnCompare
|
||||||
import io.legado.app.utils.dpToPx
|
import io.legado.app.utils.dpToPx
|
||||||
@@ -103,6 +105,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
override var sortAscending = true
|
override var sortAscending = true
|
||||||
private set
|
private set
|
||||||
private var snackBar: Snackbar? = null
|
private var snackBar: Snackbar? = null
|
||||||
|
private var showDuplicationSource = false
|
||||||
|
private val bookSourceDecoration by lazy {
|
||||||
|
BookSourceDecoration(adapter)
|
||||||
|
}
|
||||||
|
private val hostMap = hashMapOf<String, String>()
|
||||||
private val qrResult = registerForActivityResult(QrCodeResult()) {
|
private val qrResult = registerForActivityResult(QrCodeResult()) {
|
||||||
it ?: return@registerForActivityResult
|
it ?: return@registerForActivityResult
|
||||||
showDialogFragment(ImportBookSourceDialog(it))
|
showDialogFragment(ImportBookSourceDialog(it))
|
||||||
@@ -265,6 +272,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
searchView.setQuery(getString(R.string.disabled_explore), true)
|
searchView.setQuery(getString(R.string.disabled_explore), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.menu_show_same_source -> {
|
||||||
|
item.isChecked = !item.isChecked
|
||||||
|
toggleDuplicationSourceView(item.isChecked)
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_help -> showHelp("SourceMBookHelp")
|
R.id.menu_help -> showHelp("SourceMBookHelp")
|
||||||
}
|
}
|
||||||
if (item.groupId == R.id.source_group) {
|
if (item.groupId == R.id.source_group) {
|
||||||
@@ -335,7 +347,12 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
appDb.bookSourceDao.flowSearch(searchKey)
|
appDb.bookSourceDao.flowSearch(searchKey)
|
||||||
}
|
}
|
||||||
}.map { data ->
|
}.map { data ->
|
||||||
if (sortAscending) {
|
hostMap.clear()
|
||||||
|
if (showDuplicationSource) {
|
||||||
|
data.sortedWith(
|
||||||
|
compareBy<BookSourcePart> { getSourceHost(it.bookSourceUrl) == "#" }
|
||||||
|
.thenBy { getSourceHost(it.bookSourceUrl) })
|
||||||
|
} else if (sortAscending) {
|
||||||
when (sort) {
|
when (sort) {
|
||||||
BookSourceSort.Weight -> data.sortedBy { it.weight }
|
BookSourceSort.Weight -> data.sortedBy { it.weight }
|
||||||
BookSourceSort.Name -> data.sortedWith { o1, o2 ->
|
BookSourceSort.Name -> data.sortedWith { o1, o2 ->
|
||||||
@@ -383,7 +400,8 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
AppLog.put("书源界面更新书源出错", it)
|
AppLog.put("书源界面更新书源出错", it)
|
||||||
}.flowOn(IO).conflate().collect { data ->
|
}.flowOn(IO).conflate().collect { data ->
|
||||||
adapter.setItems(data, adapter.diffItemCallback, !Debug.isChecking)
|
adapter.setItems(data, adapter.diffItemCallback, !Debug.isChecking)
|
||||||
itemTouchCallback.isCanDrag = sort == BookSourceSort.Default
|
itemTouchCallback.isCanDrag =
|
||||||
|
sort == BookSourceSort.Default && !showDuplicationSource
|
||||||
delay(500)
|
delay(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -667,6 +685,21 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun toggleDuplicationSourceView(enable: Boolean) {
|
||||||
|
showDuplicationSource = enable
|
||||||
|
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onChanged() {
|
||||||
|
if (enable) {
|
||||||
|
binding.recyclerView.addItemDecoration(bookSourceDecoration)
|
||||||
|
} else {
|
||||||
|
binding.recyclerView.removeItemDecoration(bookSourceDecoration)
|
||||||
|
}
|
||||||
|
adapter.unregisterAdapterDataObserver(this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
upBookSource(searchView.query?.toString())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保持亮屏
|
* 保持亮屏
|
||||||
*/
|
*/
|
||||||
@@ -686,6 +719,12 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
|
|||||||
.upCountView(adapter.selection.size, adapter.itemCount)
|
.upCountView(adapter.selection.size, adapter.itemCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getSourceHost(origin: String): String {
|
||||||
|
return hostMap.getOrPut(origin) {
|
||||||
|
NetworkUtils.getSubDomainOrNull(origin) ?: "#"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
newText?.let {
|
newText?.let {
|
||||||
upBookSource(it)
|
upBookSource(it)
|
||||||
|
|||||||
@@ -272,6 +272,18 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
|
|||||||
callBack.upCountView()
|
callBack.upCountView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getHeaderText(position: Int): String {
|
||||||
|
val source = getItem(position)!!
|
||||||
|
return callBack.getSourceHost(source.bookSourceUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isItemHeader(position: Int): Boolean {
|
||||||
|
if (position == 0) return true
|
||||||
|
val lastHost = getHeaderText(position - 1)
|
||||||
|
val curHost = getHeaderText(position)
|
||||||
|
return lastHost != curHost
|
||||||
|
}
|
||||||
|
|
||||||
override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
|
override fun swap(srcPosition: Int, targetPosition: Int): Boolean {
|
||||||
val srcItem = getItem(srcPosition)
|
val srcItem = getItem(srcPosition)
|
||||||
val targetItem = getItem(targetPosition)
|
val targetItem = getItem(targetPosition)
|
||||||
@@ -344,5 +356,6 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
|
|||||||
fun enable(enable: Boolean, bookSource: BookSourcePart)
|
fun enable(enable: Boolean, bookSource: BookSourcePart)
|
||||||
fun enableExplore(enable: Boolean, bookSource: BookSourcePart)
|
fun enableExplore(enable: Boolean, bookSource: BookSourcePart)
|
||||||
fun upCountView()
|
fun upCountView()
|
||||||
|
fun getSourceHost(origin: String): String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package io.legado.app.ui.book.source.manage
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.text.TextPaint
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
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 splitties.init.appCtx
|
||||||
|
|
||||||
|
class BookSourceDecoration(val adapter: BookSourceAdapter): RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
|
private val headerLeft = 16f.dpToPx()
|
||||||
|
private val headerHeight = 32f.dpToPx()
|
||||||
|
|
||||||
|
private val headerPaint = Paint().apply {
|
||||||
|
color = appCtx.backgroundColor
|
||||||
|
}
|
||||||
|
private val textPaint = TextPaint().apply {
|
||||||
|
textSize = 16f.spToPx()
|
||||||
|
color = appCtx.accentColor
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
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 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -178,6 +178,19 @@ object NetworkUtils {
|
|||||||
}.getOrDefault(baseUrl)
|
}.getOrDefault(baseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSubDomainOrNull(url: String): String? {
|
||||||
|
val baseUrl = getBaseUrl(url) ?: return null
|
||||||
|
return kotlin.runCatching {
|
||||||
|
val mURL = URL(baseUrl)
|
||||||
|
val host: String = mURL.host
|
||||||
|
//mURL.scheme https/http
|
||||||
|
//判断是否为ip
|
||||||
|
if (isIPAddress(host)) return host
|
||||||
|
//PublicSuffixDatabase处理域名
|
||||||
|
PublicSuffixDatabase.get().getEffectiveTldPlusOne(host) ?: host
|
||||||
|
}.getOrDefault(null)
|
||||||
|
}
|
||||||
|
|
||||||
fun getDomain(url: String): String {
|
fun getDomain(url: String): String {
|
||||||
val baseUrl = getBaseUrl(url) ?: return url
|
val baseUrl = getBaseUrl(url) ?: return url
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
@@ -219,14 +232,17 @@ object NetworkUtils {
|
|||||||
* @return True if the input parameter is a valid IPv4 address.
|
* @return True if the input parameter is a valid IPv4 address.
|
||||||
*/
|
*/
|
||||||
fun isIPv4Address(input: String?): Boolean {
|
fun isIPv4Address(input: String?): Boolean {
|
||||||
return input != null && Validator.isIpv4(input)
|
return input != null && input.isNotEmpty()
|
||||||
|
&& input[0] in '1'..'9'
|
||||||
|
&& input.count { it == '.' } == 3
|
||||||
|
&& Validator.isIpv4(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if valid IPV6 address.
|
* Check if valid IPV6 address.
|
||||||
*/
|
*/
|
||||||
fun isIPv6Address(input: String?): Boolean {
|
fun isIPv6Address(input: String?): Boolean {
|
||||||
return input != null && Validator.isIpv6(input)
|
return input != null && input.contains(":") && Validator.isIpv6(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -132,6 +132,12 @@
|
|||||||
android:title="@string/import_by_qr_code"
|
android:title="@string/import_by_qr_code"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_show_same_source"
|
||||||
|
android:checkable="true"
|
||||||
|
android:title="@string/show_same_source"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_help"
|
android:id="@+id/menu_help"
|
||||||
android:icon="@drawable/ic_help"
|
android:icon="@drawable/ic_help"
|
||||||
|
|||||||
@@ -1174,4 +1174,5 @@
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1177,4 +1177,5 @@
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1177,4 +1177,5 @@
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
|
</resources>
|
||||||
@@ -1173,4 +1173,5 @@ Còn </string>
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1174,4 +1174,5 @@
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1176,4 +1176,5 @@
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1176,4 +1176,5 @@
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1177,4 +1177,5 @@
|
|||||||
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
<string name="read_aloud_read_phone_state_permission_rationale">阅读需要读取手机状态实现来电期间暂停朗读功能</string>
|
||||||
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
<string name="read_aloud_by_media_button_title">耳机按键启动朗读</string>
|
||||||
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
<string name="read_aloud_by_media_button_summary">通过耳机按键来启动朗读</string>
|
||||||
|
<string name="show_same_source">显示重复书源</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user