This commit is contained in:
Horis 2024-12-21 18:09:16 +08:00
parent f1eeec99e8
commit 612227502a
14 changed files with 159 additions and 4 deletions

View File

@ -19,6 +19,7 @@ import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import io.legado.app.R
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.VerticalDivider
import io.legado.app.utils.ACache
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare
import io.legado.app.utils.dpToPx
@ -103,6 +105,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
override var sortAscending = true
private set
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()) {
it ?: return@registerForActivityResult
showDialogFragment(ImportBookSourceDialog(it))
@ -265,6 +272,11 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
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")
}
if (item.groupId == R.id.source_group) {
@ -335,7 +347,12 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
appDb.bookSourceDao.flowSearch(searchKey)
}
}.map { data ->
if (sortAscending) {
hostMap.clear()
if (showDuplicationSource) {
data.sortedWith(
compareBy<BookSourcePart> { getSourceHost(it.bookSourceUrl) == "#" }
.thenBy { getSourceHost(it.bookSourceUrl) })
} else if (sortAscending) {
when (sort) {
BookSourceSort.Weight -> data.sortedBy { it.weight }
BookSourceSort.Name -> data.sortedWith { o1, o2 ->
@ -383,7 +400,8 @@ class BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceV
AppLog.put("书源界面更新书源出错", it)
}.flowOn(IO).conflate().collect { data ->
adapter.setItems(data, adapter.diffItemCallback, !Debug.isChecking)
itemTouchCallback.isCanDrag = sort == BookSourceSort.Default
itemTouchCallback.isCanDrag =
sort == BookSourceSort.Default && !showDuplicationSource
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)
}
override fun getSourceHost(origin: String): String {
return hostMap.getOrPut(origin) {
NetworkUtils.getSubDomainOrNull(origin) ?: "#"
}
}
override fun onQueryTextChange(newText: String?): Boolean {
newText?.let {
upBookSource(it)

View File

@ -272,6 +272,18 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
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 {
val srcItem = getItem(srcPosition)
val targetItem = getItem(targetPosition)
@ -344,5 +356,6 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
fun enable(enable: Boolean, bookSource: BookSourcePart)
fun enableExplore(enable: Boolean, bookSource: BookSourcePart)
fun upCountView()
fun getSourceHost(origin: String): String
}
}

View File

@ -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()
}
}
}

View File

@ -178,6 +178,19 @@ object NetworkUtils {
}.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 {
val baseUrl = getBaseUrl(url) ?: return url
return kotlin.runCatching {
@ -219,14 +232,17 @@ object NetworkUtils {
* @return True if the input parameter is a valid IPv4 address.
*/
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.
*/
fun isIPv6Address(input: String?): Boolean {
return input != null && Validator.isIpv6(input)
return input != null && input.contains(":") && Validator.isIpv6(input)
}
/**

View File

@ -132,6 +132,12 @@
android:title="@string/import_by_qr_code"
app:showAsAction="never" />
<item
android:id="@+id/menu_show_same_source"
android:checkable="true"
android:title="@string/show_same_source"
app:showAsAction="never" />
<item
android:id="@+id/menu_help"
android:icon="@drawable/ic_help"

View File

@ -1174,4 +1174,5 @@
<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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -1177,4 +1177,5 @@
<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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -1177,4 +1177,5 @@
<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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -1173,4 +1173,5 @@ Còn </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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -1174,4 +1174,5 @@
<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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -1176,4 +1176,5 @@
<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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -1176,4 +1176,5 @@
<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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>

View File

@ -1177,4 +1177,5 @@
<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_summary">通过耳机按键来启动朗读</string>
<string name="show_same_source">显示重复书源</string>
</resources>