1
0
mirror of https://github.com/fumiama/copymanga.git synced 2026-06-12 19:58:35 +08:00
新增
1. 更改默认 API (fix #131)
2. 按钮颜色随状态改变 (close #123)
修复
1. “我的订阅”中已阅读的话数可能会显示为乱码 (fix #128)
2. 搜索错误显示转码后结果
3. 搜索框输入关键词异常回显 (fix #109)
优化
1. 详情页排版 (fix #124)
升级
1. gson -> 2.13.1
2. lottie -> 6.6.6
This commit is contained in:
源文雨
2025-05-13 17:29:28 +09:00
parent 24eaf48b5f
commit ad11e61eab
9 changed files with 101 additions and 29 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId 'top.fumiama.copymanga' applicationId 'top.fumiama.copymanga'
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 34 targetSdkVersion 34
versionCode 69 versionCode 70
versionName '2.4.2' versionName '2.4.3'
resourceConfigurations += ['zh', 'zh-rCN'] resourceConfigurations += ['zh', 'zh-rCN']
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -114,13 +114,13 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.16.0' implementation 'com.github.bumptech.glide:glide:4.16.0'
//noinspection KaptUsageInsteadOfKsp //noinspection KaptUsageInsteadOfKsp
kapt 'com.github.bumptech.glide:compiler:4.16.0' kapt 'com.github.bumptech.glide:compiler:4.16.0'
implementation 'com.google.code.gson:gson:2.12.1' implementation 'com.google.code.gson:gson:2.13.1'
implementation 'com.github.vovaksenov99:OverscrollableScrollView:1.0' implementation 'com.github.vovaksenov99:OverscrollableScrollView:1.0'
implementation 'com.github.liaoinstan.SpringView:library:0a24d3e9dd' implementation 'com.github.liaoinstan.SpringView:library:0a24d3e9dd'
implementation 'com.github.zawadz88:MaterialPopupMenu:4.1.0' implementation 'com.github.zawadz88:MaterialPopupMenu:4.1.0'
implementation files('libs/com.lapism/search-2.4.1.aar') // https://stackoverflow.com/a/63029110/28801553 implementation files('libs/com.lapism/search-2.4.1.aar') // https://stackoverflow.com/a/63029110/28801553
//noinspection GradleDependency //noinspection GradleDependency
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'com.airbnb.android:lottie:6.6.4' implementation 'com.airbnb.android:lottie:6.6.6'
implementation 'net.java.dev.jna:jna:5.17.0@aar' implementation 'net.java.dev.jna:jna:5.17.0@aar'
} }

View File

@@ -29,7 +29,7 @@ object Config {
if (field != null) return field if (field != null) return field
field = Proxy( field = Proxy(
R.string.apiProxyApiUrl, R.string.apiProxyApiUrl,
Regex("^https://(api|www)\\.(copymanga|mangacopy|copy-manga)\\.\\w+/api/"), Regex("^https://(api|www)\\.(copymanga|mangacopy|copy-manga|copy20)\\.\\w+/api/"),
) )
return field return field
} }

View File

@@ -0,0 +1,36 @@
package top.fumiama.copymanga.strings
object Chinese {
/**
* 如果输入字符串中已包含汉字,直接返回。
* 否则尝试以 UTF8 重新解码。
*/
fun fixEncodingIfNeeded(input: String): String {
// 如果本身包含汉字,直接返回
if (containsChinese(input)) {
return input
}
return try {
val decoded = input.toByteArray(Charsets.ISO_8859_1).decodeToString()
// 检测解码后的是否包含汉字
if (containsChinese(decoded)) {
decoded
} else {
input // 解码后也没有汉字,说明可能不是编码问题
}
} catch (e: Exception) {
input
}
}
/**
* 简单检测字符串是否包含常见 CJK中日韩汉字。
*/
fun containsChinese(text: String): Boolean {
val regex = Regex("[\u4E00-\u9FFF]")
return regex.containsMatchIn(text)
}
}

View File

@@ -3,15 +3,18 @@ package top.fumiama.copymanga.ui.book
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.color.MaterialColors
import kotlinx.android.synthetic.main.app_bar_main.* import kotlinx.android.synthetic.main.app_bar_main.*
import kotlinx.android.synthetic.main.card_book.* import kotlinx.android.synthetic.main.card_book.*
import kotlinx.android.synthetic.main.fragment_book.* import kotlinx.android.synthetic.main.fragment_book.*
import kotlinx.android.synthetic.main.line_bookinfo_text.*
import kotlinx.android.synthetic.main.line_booktandb.* import kotlinx.android.synthetic.main.line_booktandb.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -19,6 +22,7 @@ import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.api.manga.Book import top.fumiama.copymanga.api.manga.Book
import top.fumiama.copymanga.api.manga.Reader import top.fumiama.copymanga.api.manga.Reader
import top.fumiama.copymanga.strings.Chinese
import top.fumiama.copymanga.view.template.NoBackRefreshFragment import top.fumiama.copymanga.view.template.NoBackRefreshFragment
import top.fumiama.copymanga.view.interaction.Navigate import top.fumiama.copymanga.view.interaction.Navigate
import top.fumiama.copymanga.ui.comicdl.ComicDlFragment import top.fumiama.copymanga.ui.comicdl.ComicDlFragment
@@ -98,6 +102,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
activity?.apply { activity?.apply {
toolbar.title = book?.name toolbar.title = book?.name
} }
setReadTo()
setStartRead() setStartRead()
} }
@@ -118,16 +123,13 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
activity?.apply { activity?.apply {
book?.name?.let { name -> book?.name?.let { name ->
getPreferences(MODE_PRIVATE).getInt(name, -1).let { p -> getPreferences(MODE_PRIVATE).getInt(name, -1).let { p ->
this@BookFragment.lbbstart.apply { var i = 0
var i = 0 if(p >= 0) mBookHandler!!.chapterNames.let {
if(p >= 0) mBookHandler!!.chapterNames.let { i = if (p >= it.size) it.size-1 else p
i = if (p >= it.size) it.size-1 else p }
text = it[i] this@BookFragment.lbbstart.setOnClickListener {
} mBookHandler?.apply {
setOnClickListener { Reader.start2viewManga(name, i, urlArray, uuidArray)
mBookHandler?.apply {
Reader.start2viewManga(name, i, urlArray, uuidArray)
}
} }
} }
} }
@@ -135,6 +137,23 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
} }
} }
fun setReadTo() {
var chapter = "未读"
if(!mBookHandler?.chapterNames.isNullOrEmpty()) {
activity?.apply {
book?.name?.let { name ->
getPreferences(MODE_PRIVATE).getInt(name, -1).let { p ->
if(p >= 0) mBookHandler!!.chapterNames.let {
chapter = it[if (p >= it.size) it.size-1 else p]
}
}
}
}
}
chapter = "读至 $chapter"
this@BookFragment.bttag.text = chapter
}
private suspend fun prepareHandler() = withContext(Dispatchers.IO) { private suspend fun prepareHandler() = withContext(Dispatchers.IO) {
arguments?.apply { arguments?.apply {
if (getBoolean("loadJson")) { if (getBoolean("loadJson")) {
@@ -175,7 +194,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
mBookHandler?.collect = b.results?.collect?:-2 mBookHandler?.collect = b.results?.collect?:-2
Log.d("MyBF", "get collect of ${book?.path} = ${mBookHandler?.collect}") Log.d("MyBF", "get collect of ${book?.path} = ${mBookHandler?.collect}")
tic.text = b.results?.browse?.chapter_name?.let { name -> tic.text = b.results?.browse?.chapter_name?.let { name ->
getString(R.string.text_format_cloud_read_to).format(name) getString(R.string.text_format_cloud_read_to).format(Chinese.fixEncodingIfNeeded(name))
} }
} }
} }
@@ -187,7 +206,11 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
queryCollect() queryCollect()
mBookHandler?.collect?.let { collect -> mBookHandler?.collect?.let { collect ->
if (collect > 0) { if (collect > 0) {
this@BookFragment.lbbsub.setText(R.string.button_sub_subscribed) this@BookFragment.lbbsub.apply {
setText(R.string.button_sub_subscribed)
val color = MaterialColors.getColor(this, R.attr.colorButtonNormal)
backgroundTintList = ColorStateList.valueOf(color)
}
} }
} }
book?.uuid?.let { uuid -> book?.uuid?.let { uuid ->
@@ -199,7 +222,11 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
val re = MainActivity.shelf?.del(collect) val re = MainActivity.shelf?.del(collect)
Toast.makeText(context, re, Toast.LENGTH_SHORT).show() Toast.makeText(context, re, Toast.LENGTH_SHORT).show()
if (re == "请求成功") { if (re == "请求成功") {
this@BookFragment.lbbsub.setText(R.string.button_sub) this@BookFragment.lbbsub.apply {
setText(R.string.button_sub)
val color = MaterialColors.getColor(this, R.attr.colorPrimarySurface)
backgroundTintList = ColorStateList.valueOf(color)
}
} }
} }
return@clickLaunch return@clickLaunch
@@ -208,7 +235,11 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
Toast.makeText(context, re, Toast.LENGTH_SHORT).show() Toast.makeText(context, re, Toast.LENGTH_SHORT).show()
if (re == "修改成功") { if (re == "修改成功") {
queryCollect() queryCollect()
this@BookFragment.lbbsub.setText(R.string.button_sub_subscribed) this@BookFragment.lbbsub.apply {
setText(R.string.button_sub_subscribed)
val color = MaterialColors.getColor(this, R.attr.colorButtonNormal)
backgroundTintList = ColorStateList.valueOf(color)
}
} }
} }
} }

View File

@@ -78,6 +78,7 @@ class BookHandler(private val th: WeakReference<BookFragment>): Handler(Looper.m
oa.start() oa.start()
} }
complete = true complete = true
that?.setReadTo()
that?.setStartRead() that?.setStartRead()
that?.setAddToShelf() that?.setAddToShelf()
Log.d("MyBH", "Set complete: true") Log.d("MyBH", "Set complete: true")
@@ -126,7 +127,7 @@ class BookHandler(private val th: WeakReference<BookFragment>): Handler(Looper.m
// tic?.visibility = View.GONE // tic?.visibility = View.GONE
activity?.toolbar?.title = book?.name activity?.toolbar?.title = book?.name
btauth?.text = that?.getString(R.string.text_format_region)?.format(book?.region?:"未知") btauth?.text = that?.getString(R.string.text_format_region)?.format(book?.region?:"未知")
bttag?.text = that?.getString(R.string.text_format_img_type)?.format(book?.imageType?:"未知") // bttag?.text = that?.getString(R.string.text_format_img_type)?.format(book?.imageType?:"未知")
bthit?.text = that?.getString(R.string.text_format_hit)?.format(book?.popular?:-1) bthit?.text = that?.getString(R.string.text_format_hit)?.format(book?.popular?:-1)
btsub?.text = that?.getString(R.string.text_format_stat)?.format(book?.status?:"未知") btsub?.text = that?.getString(R.string.text_format_stat)?.format(book?.status?:"未知")
bttime?.text = book?.updateTime?:"未知" bttime?.text = book?.updateTime?:"未知"

View File

@@ -5,6 +5,8 @@ import android.util.Log
import top.fumiama.copymanga.view.template.InfoCardLoader import top.fumiama.copymanga.view.template.InfoCardLoader
import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.api.Config
import top.fumiama.dmzj.copymanga.R import top.fumiama.dmzj.copymanga.R
import java.net.URLEncoder
import java.nio.charset.Charset
@ExperimentalStdlibApi @ExperimentalStdlibApi
class SearchFragment : InfoCardLoader(R.layout.fragment_search, R.id.action_nav_search_to_nav_book) { class SearchFragment : InfoCardLoader(R.layout.fragment_search, R.id.action_nav_search_to_nav_book) {
@@ -16,7 +18,7 @@ class SearchFragment : InfoCardLoader(R.layout.fragment_search, R.id.action_nav_
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (isFirstInflate) { if (isFirstInflate) {
query = arguments?.getCharSequence("query")?.toString() query = arguments?.getCharSequence("query")?.toString()?.let { q -> URLEncoder.encode(q, Charset.defaultCharset().name()) }
type = arguments?.getString("type") type = arguments?.getString("type")
Log.d("MySF", "get query=$query, type=$type") Log.d("MySF", "get query=$query, type=$type")
} }

View File

@@ -96,13 +96,14 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
var lastChangeTime = 0L var lastChangeTime = 0L
override fun onQueryTextChange(newText: CharSequence): Boolean { override fun onQueryTextChange(newText: CharSequence): Boolean {
if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true
lastSearch = newText.toString()
postDelayed({ postDelayed({
lifecycleScope.launch { lifecycleScope.launch {
if (!newText.contentEquals(lastSearch)) return@launch
val diff = System.currentTimeMillis() - lastChangeTime val diff = System.currentTimeMillis() - lastChangeTime
if(diff > 500) { if(diff > 500) {
if (newText.isNotEmpty()) { if (newText.isNotEmpty()) {
Log.d("MyHF", "new text: $newText") Log.d("MyHF", "new text: $newText")
lastSearch = newText.toString()
adapter.refresh(newText) adapter.refresh(newText)
} }
} }
@@ -295,9 +296,10 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
override fun getItemCount() = (results?.results?.list?.size?:0) + if (query?.isNotEmpty() == true) 1 else 0 override fun getItemCount() = (results?.results?.list?.size?:0) + if (query?.isNotEmpty() == true) 1 else 0
suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) { suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) {
query = URLEncoder.encode(q.toString(), Charset.defaultCharset().name()) query = q.toString()
activity?.apply { activity?.apply {
PausableDownloader(getString(R.string.searchApiUrl).format(Config.myHostApiUrl.value, 0, query, type)) { PausableDownloader(getString(R.string.searchApiUrl).format(Config.myHostApiUrl.value, 0,
URLEncoder.encode(q.toString(), Charset.defaultCharset().name()), type)) {
results = Gson().fromJson(it.decodeToString(), BookListStructure::class.java) results = Gson().fromJson(it.decodeToString(), BookListStructure::class.java)
count = results?.results?.total?:0 count = results?.results?.total?:0
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@@ -16,6 +16,7 @@ import top.fumiama.copymanga.json.HistoryBookListStructure
import top.fumiama.copymanga.json.ShelfStructure import top.fumiama.copymanga.json.ShelfStructure
import top.fumiama.copymanga.json.TypeBookListStructure import top.fumiama.copymanga.json.TypeBookListStructure
import top.fumiama.copymanga.net.template.PausableDownloader import top.fumiama.copymanga.net.template.PausableDownloader
import top.fumiama.copymanga.strings.Chinese
import top.fumiama.copymanga.view.interaction.Navigate import top.fumiama.copymanga.view.interaction.Navigate
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@@ -66,7 +67,7 @@ open class InfoCardLoader(inflateRes:Int, private val navId:Int, private val isT
Log.d("MyICL", "load @ $i") Log.d("MyICL", "load @ $i")
if(ad?.exit == true) return@PausableDownloader if(ad?.exit == true) return@PausableDownloader
cardList?.addCard( cardList?.addCard(
book?.comic?.name?:"null", "\n云读至${book?.last_chapter_name}", book?.comic?.cover, book?.comic?.name?:"null", "\n云读至${book?.last_chapter_name?.let { Chinese.fixEncodingIfNeeded(it) }}", book?.comic?.cover,
book?.comic?.path_word, null, null, book?.comic?.path_word, null, null,
book?.comic?.status==1 book?.comic?.status==1
) )
@@ -88,7 +89,7 @@ open class InfoCardLoader(inflateRes:Int, private val navId:Int, private val isT
Log.d("MyICL", "load @ $i") Log.d("MyICL", "load @ $i")
if(ad?.exit == true) return@PausableDownloader if(ad?.exit == true) return@PausableDownloader
cardList?.addCard( cardList?.addCard(
book?.comic?.name?:"null", "\n${book?.last_browse?.last_browse_name?.let { "读到$it" }?:"未读"}", book?.comic?.cover, book?.comic?.name?:"null", "\n${book?.last_browse?.last_browse_name?.let { "读到${Chinese.fixEncodingIfNeeded(it)}" }?:"未读"}", book?.comic?.cover,
book?.comic?.path_word, null, null, book?.comic?.path_word, null, null,
book?.comic?.status==1, book?.comic?.status==1,
book.comic?.browse?.chapter_uuid != book.comic?.last_chapter_id book.comic?.browse?.chapter_uuid != book.comic?.last_chapter_id

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [ <!DOCTYPE resources [
<!ENTITY hosturl "www.copy-manga.com"> <!ENTITY hosturl "www.copy20.com">
<!ENTITY appver "2.2.6"> <!ENTITY appver "2.2.9">
]> ]>
<resources> <resources>
<string name="app_name">拷贝漫画</string> <string name="app_name">拷贝漫画</string>
@@ -123,7 +123,6 @@
<string name="text_format_hit">热度 %1$d</string> <string name="text_format_hit">热度 %1$d</string>
<string name="text_format_stat">状态 %1$s</string> <string name="text_format_stat">状态 %1$s</string>
<string name="text_format_region">区域 %1$s</string> <string name="text_format_region">区域 %1$s</string>
<string name="text_format_img_type">画幅 %1$s</string>
<string name="text_format_cloud_read_to">云读至%1$s</string> <string name="text_format_cloud_read_to">云读至%1$s</string>
<string name="topics_series">专题系列</string> <string name="topics_series">专题系列</string>