1
0
mirror of https://github.com/fumiama/copymanga.git synced 2026-06-11 02:50:28 +08:00
优化
1. 主页加载速度
2. 默认UA判定
3. TCP Client
This commit is contained in:
源文雨
2025-06-16 23:57:33 +09:00
parent e53db6762f
commit 036fdac4a9
24 changed files with 260 additions and 392 deletions

View File

@@ -12,8 +12,8 @@ android {
minSdkVersion 23
//noinspection OldTargetApi
targetSdkVersion 34
versionCode 80
versionName '2.5.7'
versionCode 81
versionName '2.5.8'
resourceConfigurations += ['zh', 'zh-rCN']
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -124,4 +124,5 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'com.airbnb.android:lottie:6.6.6'
implementation 'net.java.dev.jna:jna:5.17.0@aar'
implementation 'top.fumiama:sdict:0.1.0'
}

View File

@@ -63,7 +63,7 @@ import top.fumiama.copymanga.api.update.Update
import top.fumiama.copymanga.api.user.Member
import top.fumiama.copymanga.lib.Comancry
import top.fumiama.copymanga.lib.Comandy
import top.fumiama.copymanga.storage.DataLoader
import top.fumiama.copymanga.storage.ConfigLoader
import top.fumiama.copymanga.strings.Base16384
import top.fumiama.dmzj.copymanga.BuildConfig
import top.fumiama.dmzj.copymanga.R
@@ -225,7 +225,7 @@ class MainActivity : AppCompatActivity() {
toolsBox.buildInfo("备份管理", "可选择导出或导入base16384格式配置项",
"导出", "导入", "取消", { // ok
MaterialDialog(this).show {
input(prefill = Base16384.encode(DataLoader().toByteArray()))
input(prefill = Base16384.encode(ConfigLoader().toByteArray()))
positiveButton(android.R.string.ok)
title(null, "请复制配置文本并保存")
}
@@ -233,7 +233,7 @@ class MainActivity : AppCompatActivity() {
MaterialDialog(this).show {
input { _, c ->
try {
DataLoader(Base16384.decode(c.toString())).settings.export()
ConfigLoader(Base16384.decode(c.toString())).settings.export()
navController?.apply {
currentDestination?.id?.let {
popBackStack()

View File

@@ -58,6 +58,7 @@ object Config {
val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!!
val pc_ua get() = MainActivity.mainWeakReference?.get()?.getString(R.string.pc_ua)?.format(app_ver.value)?:""
val default_ua get() = MainActivity.mainWeakReference?.get()?.getString(R.string.default_ua)?:""
val referer get() = MainActivity.mainWeakReference?.get()?.getString(R.string.referer)?.format(app_ver.value)?:""
val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo)
@@ -95,7 +96,7 @@ object Config {
val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID)
val net_umstring = PreferenceString("settings_cat_net_et_umstring")
val net_source = PreferenceString("settings_cat_net_et_source", R.string.source)
val net_ua = PreferenceString("settings_cat_net_et_ua", "__default_ua__")
val net_ua = PreferenceString("settings_cat_net_et_ua", R.string.default_ua)
val view_manga_inverse_chapters = PreferenceBoolean("settings_cat_vm_sw_inverse_chapters", false)
val view_manga_always_dark_bg = PreferenceBoolean("settings_cat_vm_sw_always_dark_bg", false)

View File

@@ -1,27 +0,0 @@
package top.fumiama.copymanga.api.update
//Fumiama 20210601
//ByteArrayQueue.kt
//FIFO队列
class ByteArrayQueue {
private var elements = byteArrayOf()
val size get() = elements.size
fun append(items: ByteArray) {
elements += items
}
fun pop(num: Int = 1): ByteArray? {
return if(num <= elements.size) {
val re = elements.copyOfRange(0, num)
elements = elements.copyOfRange(num, elements.size)
re
} else null
}
fun clear() {
elements = byteArrayOf()
}
fun popAll(): ByteArray {
val re = elements
clear()
return re
}
operator fun plusAssign(items: ByteArray) = append(items)
}

View File

@@ -1,7 +1,7 @@
package top.fumiama.copymanga.api.update
import android.util.Log
import top.fumiama.copymanga.net.Client
import top.fumiama.sdict.io.Client
class SimpleKanban(private val client: Client, private val pwd: String) { //must run in thread
private val raw: ByteArray?
@@ -51,13 +51,13 @@ class SimpleKanban(private val client: Client, private val pwd: String) { //mu
client.sendMessage("${pwd}get${version}quit")
client.receiveRawMessage(36) //Welcome to simple kanban server. get
val r = try {
val firstRecv = client.receiveRawMessage(4)
if(firstRecv.decodeToString() == "null") "null"
val firstReceive = client.receiveRawMessage(4)
if(firstReceive.decodeToString() == "null") "null"
else {
val length = convert2Int(firstRecv)
val length = convert2Int(firstReceive)
Log.d("MySK", "Msg len: $length")
var re = byteArrayOf()
if(firstRecv.size > 4) re += firstRecv.copyOfRange(4, firstRecv.size)
if(firstReceive.size > 4) re += firstReceive.copyOfRange(4, firstReceive.size)
re += client.receiveRawMessage(length - re.size)
if(re.isNotEmpty()) re.decodeToString() else "null"
}

View File

@@ -16,10 +16,11 @@ import kotlinx.android.synthetic.main.dialog_progress.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.net.Client
import top.fumiama.sdict.io.Client
import top.fumiama.copymanga.view.interaction.UITools
import top.fumiama.dmzj.copymanga.BuildConfig
import top.fumiama.dmzj.copymanga.R
import top.fumiama.sdict.utils.Utils.toHexStr
import java.io.File
import java.security.MessageDigest
@@ -63,7 +64,7 @@ object Update {
fetch(client, kanban, this@apply) {
lifecycleScope.launch {
val md5 = msg.substringAfterLast("md5:")
if (md5 == UITools.toHexStr(
if (md5 == toHexStr(
MessageDigest.getInstance("MD5").digest(it)
)
) {

View File

@@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.json.ComandyVersion
import top.fumiama.copymanga.net.Client
import top.fumiama.sdict.io.Client
import top.fumiama.copymanga.net.DownloadTools
import top.fumiama.copymanga.storage.PreferenceBoolean
import top.fumiama.copymanga.storage.UserPreferenceInt

View File

@@ -1,124 +0,0 @@
package top.fumiama.copymanga.net
//Fumiama 20210601
//Client.kt
import android.util.Log
import top.fumiama.copymanga.api.update.ByteArrayQueue
import java.io.*
import java.lang.Thread.sleep
import java.net.Socket
class Client(private val ip: String, private val port: Int) {
//普通数据交互接口
private var sc: Socket? = null
//普通交互流
private var dout: OutputStream? = null
private var din: InputStream? = null
//已连接标记
private val isConnect get() = sc != null && din != null && dout != null
/**
* 初始化普通交互连接
*/
fun initConnect(depth: Int = 0): Boolean{
if(depth > 3) Log.d("MyC", "connect server failed after $depth tries")
else try {
sc = Socket(ip, port) //通过socket连接服务器
din = sc?.getInputStream() //获取输入流并转换为StreamReader约定编码格式
dout = sc?.getOutputStream() //获取输出流
sc?.soTimeout = 10000 //设置连接超时限制
return if (isConnect) {
Log.d("MyC", "connect server successful")
true
} else {
Log.d("MyC", "connect server failed, now retry...")
initConnect(depth + 1)
}
} catch (e: IOException) { //获取输入输出流是可能报IOException的所以必须try-catch
e.printStackTrace()
}
return false
}
/**
* 发送数据至服务器
* @param message 要发送至服务器的字符串
*/
fun sendMessage(message: String?): Boolean = sendMessage(message?.toByteArray())
fun sendMessage(message: ByteArray?): Boolean {
try {
if (isConnect) {
if (message != null) { //判断输出流或者消息是否为空为空的话会产生null pointer错误
dout?.write(message)
dout?.flush()
Log.d("MyC", "Send msg: ${message.decodeToString()}")
return true
} else Log.d("MyC", "The message to be sent is empty")
Log.d("MyC", "send message succeed")
} else Log.d("MyC", "send message failed: no connect")
} catch (e: IOException) {
Log.d("MyC", "send message failed: crash")
e.printStackTrace()
}
return false
}
fun read(): Char? = din?.read()?.toChar()
private var buffer = ByteArrayQueue()
private val receiveBuffer = ByteArray(65536)
fun receiveRawMessage(totalSize: Int, setProgress: Boolean = false) : ByteArray {
if(totalSize == buffer.size) return buffer.popAll()
else {
try {
if (isConnect) {
Log.d("MyC", "开始接收服务端信息")
var prevP = 0
while(totalSize > buffer.size) {
val count = din?.read(receiveBuffer)?:0
if(count > 0) {
buffer += receiveBuffer.copyOfRange(0, count)
Log.d("MyC", "reply length:$count")
val p = 100 * buffer.size / totalSize
if(setProgress && totalSize > 0 && prevP != p) {
progress?.notify(p)
prevP = p
}
} else sleep(10)
}
} else Log.d("MyC", "no connect to receive message")
} catch (e: IOException) {
Log.d("MyC", "receive message failed")
e.printStackTrace()
}
return if(totalSize > 0) buffer.pop(totalSize)?:byteArrayOf() else buffer.popAll()
}
}
fun receiveMessage(totalSize: Int) = receiveRawMessage(totalSize).decodeToString()
/**
* 关闭连接
*/
fun closeConnect() = try {
din?.close()
dout?.close()
sc?.close()
sc = null
din = null
dout = null
true
} catch (e: IOException) {
e.printStackTrace()
false
}
var progress: Progress? = null
interface Progress {
fun notify(progressPercentage: Int)
}
}

View File

@@ -7,6 +7,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.json.JSONObject
import top.fumiama.sdict.io.Client
import top.fumiama.copymanga.api.Config
import top.fumiama.copymanga.api.Config.proxyUrl
import top.fumiama.copymanga.json.ComandyCapsule
@@ -34,7 +35,7 @@ object DownloadTools {
connection.apply {
Config.net_ua.value.let {
if (it.isEmpty()) return@let
setRequestProperty("user-agent", if (it == "__default_ua__") Config.pc_ua else it)
setRequestProperty("user-agent", if (it == Config.default_ua) Config.pc_ua else it)
}
Config.net_source.value.let { if(it.isNotEmpty()) setRequestProperty("source", it) }
// deviceinfo
@@ -43,7 +44,7 @@ object DownloadTools {
if (Config.net_use_gzip.value) setRequestProperty("accept-encoding", "gzip")
setRequestProperty("authorization", "Token${Config.token.value?.let { tk ->
if (tk.isNotEmpty()) " $tk" else ""
}}")
}?:""}")
if (Config.net_platform.value) setRequestProperty("platform", Config.platform.value)
if (Config.net_referer.value) setRequestProperty("referer", Config.referer)
if (Config.net_use_json.value) setRequestProperty("accept", "application/json")
@@ -66,7 +67,7 @@ object DownloadTools {
capsule.headers = hashMapOf()
Config.net_ua.value.let {
if (it.isEmpty()) return@let
capsule.headers["user-agent"] = if (it == "__default_ua__") Config.pc_ua else it
capsule.headers["user-agent"] = if (it == Config.default_ua) Config.pc_ua else it
}
Config.net_source.value.let { if(it.isNotEmpty()) capsule.headers["source"] = it }
// deviceinfo
@@ -75,7 +76,7 @@ object DownloadTools {
if (Config.net_use_gzip.value) capsule.headers["accept-encoding"] = "gzip"
capsule.headers["authorization"] = "Token${Config.token.value?.let { tk ->
if (tk.isNotEmpty()) " $tk" else ""
}}"
}?:""}"
if (Config.net_platform.value) capsule.headers["platform"] = Config.platform.value
if (Config.net_referer.value) capsule.headers["referer"] = Config.referer
if (Config.net_use_json.value) capsule.headers["accept"] = "application/json"

View File

@@ -11,11 +11,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference
import top.fumiama.copymanga.api.Config
import top.fumiama.copymanga.json.ReturnBase
import java.io.File
import java.security.MessageDigest
open class AutoDownloadHandler(
private val url: () -> String, private val jsonClass: Class<*>,
@@ -24,6 +22,7 @@ open class AutoDownloadHandler(
private val customCacheFile: File? = null): Handler(Looper.myLooper()!!) {
private var checkTimes = 0
var exit = false
var raw: String? = null
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when(msg.what){
@@ -45,26 +44,16 @@ open class AutoDownloadHandler(
downloadCoroutine()
check()
}
private fun toHexStr(byteArray: ByteArray) =
with(StringBuilder()) {
byteArray.forEach {
val hex = it.toInt() and (0xFF)
val hexStr = Integer.toHexString(hex)
if (hexStr.length == 1) append("0").append(hexStr)
else append(hexStr)
}
toString()
}
private suspend fun downloadCoroutine() = withContext(Dispatchers.IO) {
val cacheName = toHexStr(MessageDigest.getInstance("MD5").digest(url().encodeToByteArray()))
val cacheFile = customCacheFile?:(mainWeakReference?.get()?.externalCacheDir?.let { File(it, cacheName) })
if(loadFromCache) {
cacheFile?.let {
customCacheFile?.let {
if (it.exists()) {
var pass = true
it.inputStream().use { fi->
try {
pass = setGsonItem(Gson().fromJson(fi.reader(), jsonClass))
val data = fi.readBytes().decodeToString()
raw = data
pass = setGsonItem(Gson().fromJson(data, jsonClass))
} catch (e: Exception) {
e.printStackTrace()
}
@@ -77,10 +66,11 @@ open class AutoDownloadHandler(
while (cnt++ <= 3) {
try {
val data = Config.api.get(url())
raw = data
if(exit) return@withContext
val pass = setGsonItem(Gson().fromJson(data, jsonClass))
if (pass && loadFromCache) {
cacheFile?.writeText(data)
customCacheFile?.writeText(data)
}
if(!pass) {
delay(2000)

View File

@@ -4,7 +4,7 @@ import top.fumiama.copymanga.api.Config
import java.nio.ByteBuffer
import java.util.zip.CRC32
class DataLoader {
class ConfigLoader {
data class Settings(
var appVer: String,
var platform: String,

View File

@@ -21,7 +21,7 @@ import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.api.Config.manga_dl_show_0m_manga
import top.fumiama.copymanga.api.manga.Downloader
import top.fumiama.copymanga.api.manga.Reader
import top.fumiama.copymanga.net.Client
import top.fumiama.sdict.io.Client
import top.fumiama.copymanga.storage.FileUtils
import top.fumiama.copymanga.storage.FileUtils.compressToUserFile
import top.fumiama.copymanga.view.interaction.Navigate

View File

@@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageButton
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
@@ -24,7 +25,6 @@ import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.line_word.view.*
import kotlinx.android.synthetic.main.viewpage_horizonal.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity
@@ -40,150 +40,176 @@ import java.lang.ref.WeakReference
import java.net.URLEncoder
import java.nio.charset.Charset
class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home, true) {
lateinit var homeHandler: HomeHandler
val vm: HomeViewModel by viewModels()
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if(isFirstInflate) {
val tb = (activity as MainActivity).toolsBox
val netInfo = tb.netInfo
if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError)
MainActivity.member?.apply { lifecycleScope.launch {
withContext(Dispatchers.IO) {
Config.api.init()
try {
info()
} catch (e: Exception) {
Snackbar
.make(view, "${e::class.simpleName} ${e.message}", Snackbar.LENGTH_LONG)
.setTextMaxLines(10)
.show()
}
}
} }
homeHandler = HomeHandler(WeakReference(this))
val theme = resources.newTheme()
swiperefresh?.setColorSchemeColors(
resources.getColor(R.color.colorAccent, theme),
resources.getColor(R.color.colorBlue2, theme),
resources.getColor(R.color.colorGreen, theme))
swiperefresh?.isEnabled = true
val theme = resources.newTheme()
swiperefresh?.setColorSchemeColors(
resources.getColor(R.color.colorAccent, theme),
resources.getColor(R.color.colorBlue2, theme),
resources.getColor(R.color.colorGreen, theme)
)
fhl?.setPadding(0, 0, 0, navBarHeight)
fhl?.setPadding(0, 0, 0, navBarHeight)
fhs?.apply {
isNestedScrollingEnabled = true
val recyclerView = findViewById<RecyclerView>(R.id.search_recycler_view)
recyclerView.isNestedScrollingEnabled = true
recyclerView.setPadding(0, 0, 0, navBarHeight)
setAdapterLayoutManager(LinearLayoutManager(context))
val adapter = ListViewHolder(recyclerView).RecyclerViewAdapter()
setAdapter(adapter)
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
setMicIconImageResource(R.drawable.ic_setting_search)
val micView = findViewById<ImageButton>(R.id.search_image_view_mic)
setClearFocusOnBackPressed(true)
setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener {
override fun onNavigationClick(hasFocus: Boolean) {
if (hasFocus()) {
clearFocus()
}
else requestFocus()
}
})
setTextHint(android.R.string.search_go)
var lastSearch = ""
setOnQueryTextListener(object : SearchLayout.OnQueryTextListener {
var lastChangeTime = 0L
override fun onQueryTextChange(newText: CharSequence): Boolean {
if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true
lastSearch = newText.toString()
postDelayed({
lifecycleScope.launch {
if (!newText.contentEquals(lastSearch)) return@launch
val diff = System.currentTimeMillis() - lastChangeTime
if(diff > 500) {
if (newText.isNotEmpty()) {
Log.d("MyHF", "new text: $newText")
adapter.refresh(newText)
lifecycleScope.launch {
withContext(Dispatchers.Main) {
vm.indexStructure.observe(viewLifecycleOwner) {
Log.d("MyHF", "get observed: $it")
if (it == null) { // init
val tb = (activity as MainActivity).toolsBox
val netInfo = tb.netInfo
lifecycleScope.launch net@ {
if (netInfo == tb.transportStringNull || netInfo == tb.transportStringError) {
(activity as MainActivity).toolsBox.toastError(getString(R.string.web_error))
fhov?.swipeRefreshLayout = swiperefresh
swiperefresh.isRefreshing = false
swiperefresh.setOnRefreshListener {
Log.d("MyHFH", "Refresh items.")
vm.saveIndexStructure(null)
}
hideKanban()
return@net
}
MainActivity.member?.apply {
withContext(Dispatchers.IO) {
Config.api.init()
try {
info()
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Snackbar
.make(
view,
"${e::class.simpleName} ${e.message}",
Snackbar.LENGTH_LONG
)
.setTextMaxLines(10)
.show()
}
}
}
}
}, 1024)
lastChangeTime = System.currentTimeMillis()
return true
}
} }
override fun onQueryTextSubmit(query: CharSequence): Boolean {
/*if(query.isNotEmpty()) {
val key = query.toString()
Toast.makeText(context, key, Toast.LENGTH_SHORT).show()
}*/
Log.d("MyHF", "recover text: $lastSearch")
setTextQuery(lastSearch, false)
return true
}
})
homeHandler = HomeHandler(WeakReference(this@HomeFragment))
homeHandler.obtainMessage(-1, true).sendToTarget()
homeHandler.startLoad()
setOnMicClickListener(object : SearchLayout.OnMicClickListener {
val types = arrayOf("", "name", "author", "local")
var i = 0
override fun onMicClick() {
val typeNames = resources.getStringArray(R.array.search_types)
AlertDialog.Builder(context)
.setTitle(R.string.set_search_types)
.setIcon(R.mipmap.ic_launcher)
.setSingleChoiceItems(ArrayAdapter(context, R.layout.line_choice_list, typeNames), i){ d, p ->
adapter.type = types[p]
i = p
d.cancel()
}.show()
return@observe
}
})
if(homeHandler.exit) return@observe
var isInFocusWaiting = false
setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener {
override fun onFocusChange(hasFocus: Boolean) {
Log.d("MyHF", "fhs onFocusChange: $hasFocus")
if (isInFocusWaiting) return
isInFocusWaiting = true
postDelayed({
navigationIconSupport = if (hasFocus) {
setTextQuery("__notice_focus_change__", true)
SearchLayout.NavigationIconSupport.ARROW
}
else {
if (lastSearch.isNotEmpty()) {
micView?.visibility = View.VISIBLE
swiperefresh?.isEnabled = true
fhs?.apply {
isNestedScrollingEnabled = true
val recyclerView = findViewById<RecyclerView>(R.id.search_recycler_view)
recyclerView.isNestedScrollingEnabled = true
recyclerView.setPadding(0, 0, 0, navBarHeight)
setAdapterLayoutManager(LinearLayoutManager(context))
val adapter = ListViewHolder(recyclerView).RecyclerViewAdapter()
setAdapter(adapter)
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
setMicIconImageResource(R.drawable.ic_setting_search)
val micView = findViewById<ImageButton>(R.id.search_image_view_mic)
setClearFocusOnBackPressed(true)
setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener {
override fun onNavigationClick(hasFocus: Boolean) {
if (hasFocus()) {
clearFocus()
}
SearchLayout.NavigationIconSupport.SEARCH
else requestFocus()
}
isInFocusWaiting = false
}, 300)
}
})
})
setTextHint(android.R.string.search_go)
setOnTouchListener { _, e ->
Log.d("MyHF", "fhns on touch")
if (e.action == MotionEvent.ACTION_UP && mSearchEditText?.text?.isNotEmpty() == true) {
ime?.hideSoftInputFromWindow(activity?.window?.decorView?.windowToken, 0)
}
false
}
}
var lastSearch = ""
setOnQueryTextListener(object : SearchLayout.OnQueryTextListener {
var lastChangeTime = 0L
override fun onQueryTextChange(newText: CharSequence): Boolean {
if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true
lastSearch = newText.toString()
postDelayed({
lifecycleScope.launch {
if (!newText.contentEquals(lastSearch)) return@launch
val diff = System.currentTimeMillis() - lastChangeTime
if(diff > 500) {
if (newText.isNotEmpty()) {
Log.d("MyHF", "new text: $newText")
adapter.refresh(newText)
}
}
}
}, 1024)
lastChangeTime = System.currentTimeMillis()
return true
}
lifecycleScope.launch{
withContext(Dispatchers.IO) {
homeHandler.obtainMessage(-1, true).sendToTarget()
while(!MainActivity.isDrawerClosed) delay(233)
//homeHandler.sendEmptyMessage(6) //removeAllViews
//homeHandler.fhib = null
delay(300)
homeHandler.startLoad()
override fun onQueryTextSubmit(query: CharSequence): Boolean {
/*if(query.isNotEmpty()) {
val key = query.toString()
Toast.makeText(context, key, Toast.LENGTH_SHORT).show()
}*/
Log.d("MyHF", "recover text: $lastSearch")
setTextQuery(lastSearch, false)
return true
}
})
setOnMicClickListener(object : SearchLayout.OnMicClickListener {
val types = arrayOf("", "name", "author", "local")
var i = 0
override fun onMicClick() {
val typeNames = resources.getStringArray(R.array.search_types)
AlertDialog.Builder(context)
.setTitle(R.string.set_search_types)
.setIcon(R.mipmap.ic_launcher)
.setSingleChoiceItems(ArrayAdapter(context, R.layout.line_choice_list, typeNames), i){ d, p ->
adapter.type = types[p]
i = p
d.cancel()
}.show()
}
})
var isInFocusWaiting = false
setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener {
override fun onFocusChange(hasFocus: Boolean) {
Log.d("MyHF", "fhs onFocusChange: $hasFocus")
if (isInFocusWaiting) return
isInFocusWaiting = true
postDelayed({
navigationIconSupport = if (hasFocus) {
setTextQuery("__notice_focus_change__", true)
SearchLayout.NavigationIconSupport.ARROW
}
else {
if (lastSearch.isNotEmpty()) {
micView?.visibility = View.VISIBLE
}
SearchLayout.NavigationIconSupport.SEARCH
}
isInFocusWaiting = false
}, 300)
}
})
setOnTouchListener { _, e ->
Log.d("MyHF", "fhns on touch")
if (e.action == MotionEvent.ACTION_UP && mSearchEditText?.text?.isNotEmpty() == true) {
ime?.hideSoftInputFromWindow(activity?.window?.decorView?.windowToken, 0)
}
false
}
}
homeHandler.sendEmptyMessage(2) //setSwipe
homeHandler.sendEmptyMessage(7) //inflateBanner
homeHandler.sendEmptyMessage(1) //inflateCardLines
}
}
}
@@ -300,6 +326,7 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
override fun getItemCount() = (results?.results?.list?.size?:0) + if (query?.isNotEmpty() == true) 1 else 0
@SuppressLint("NotifyDataSetChanged")
suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) {
query = q.toString()
activity?.apply {

View File

@@ -8,7 +8,6 @@ import android.os.Message
import android.util.Log
import android.view.View
import android.view.ViewTreeObserver
import android.widget.LinearLayout
import android.widget.Toast
import androidx.cardview.widget.CardView
import androidx.constraintlayout.widget.ConstraintLayout
@@ -28,13 +27,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.api.Config
import top.fumiama.copymanga.json.ComicStructure
import top.fumiama.copymanga.json.IndexStructure
import top.fumiama.copymanga.net.template.AutoDownloadHandler
import top.fumiama.copymanga.api.Config
import top.fumiama.copymanga.view.operation.GlideHideLottieViewListener
import top.fumiama.copymanga.view.interaction.Navigate
import top.fumiama.copymanga.view.interaction.UITools
import top.fumiama.copymanga.view.operation.GlideHideLottieViewListener
import top.fumiama.dmzj.copymanga.R
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicInteger
@@ -47,7 +46,7 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
) {
private val homeF get() = that.get()
var index: IndexStructure? = null
var fhib: Banner? = null
private var fhib: Banner? = null
get() {
Log.d("MyHH", "Get fhib.")
if (field == null) {
@@ -56,7 +55,6 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
}
return field
}
private var indexLines = arrayOf<View>()
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
@@ -72,16 +70,6 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
2 -> homeF?.swiperefresh?.let { setSwipe(it) }
3 -> setBanner(fhib!!)
5 -> setBannerInfo(msg.obj as Banner)
6 -> {
homeF?.fhl?.let {
val oa = ObjectAnimator.ofFloat(it, "alpha", 1f, 0f).setDuration(233)
oa.doOnEnd { _ ->
it.removeAllViews()
it.alpha = 1f
}
oa.start()
}
}
7 -> inflateBanner()
}
}
@@ -108,15 +96,22 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
Toast.makeText(homeF?.context, R.string.web_error, Toast.LENGTH_SHORT).show()
}
}
override suspend fun doWhenFinishDownload() = withContext(Dispatchers.IO) {
override suspend fun doWhenFinishDownload(): Unit = withContext(Dispatchers.IO) {
super.doWhenFinishDownload()
if(exit) return@withContext
sendEmptyMessage(2) //setSwipe
sendEmptyMessage(7) //inflateBanner
sendEmptyMessage(1) //inflateCardLines
raw?.let {
Log.d("MyHFH", "save raw: $it")
homeF?.apply { activity?.runOnUiThread {
vm.saveIndexStructure(it)
} }
}
}
private fun inflateBanner() = homeF?.fhl?.addView(fhib)
private fun inflateBanner() {
homeF?.fhl?.let { it.post {
fhib = null
it.addView(fhib)
} }
}
private suspend fun inflateTopics() {
index?.results?.topics?.list?.let {
@@ -242,29 +237,13 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
private fun inflateCardLines() {
homeF?.lifecycleScope?.launch {
withContext(Dispatchers.IO) {
if (indexLines.isNotEmpty()) indexLines = arrayOf()
inflateRec()
inflateTopics()
inflateHot()
inflateNew()
inflateFinish()
inflateRank()
homeF?.fhl?.apply { post {
for (i in indexLines.indices) {
try {
addView(indexLines[i])
} catch (e: Exception) {
e.printStackTrace()
(indexLines[i].parent as LinearLayout).apply {
post {
removeAllViews()
homeF?.fhl?.addView(indexLines[i])
}
}
}
}
obtainMessage(-1, false).sendToTarget() //closeLoad
} }
obtainMessage(-1, false).sendToTarget() //closeLoad
}
}
}
@@ -306,14 +285,24 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
homeF?.lifecycleScope?.launch {
withContext(Dispatchers.IO) {
homeF?.showKanban()
fhib?.isAutoPlay = false
index = null
fhib?.adapter?.notifyDataSetChanged()
fhib?.let {
it.isAutoPlay = false
index = null
it.adapter?.notifyDataSetChanged()
}
fhib = null
indexLines = arrayOf()
this@HomeHandler.sendEmptyMessage(6) //removeAllViews
delay(300)
this@HomeHandler.sendEmptyMessage(0) //setLayouts
withContext(Dispatchers.Main) {
homeF?.fhl?.let {
val oa = ObjectAnimator.ofFloat(it, "alpha", 1f, 0f).setDuration(233)
oa.doOnEnd { _ ->
it.removeAllViews()
it.alpha = 1f
homeF?.vm?.saveIndexStructure(null) // reload
}
oa.start()
}
}
}
}
}
@@ -322,15 +311,14 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
private suspend fun allocateLine(
title: String, iconResId: Int, comics: Array<ComicStructure>,
finish: Boolean = false, isTopic: Boolean = false, onClick: (() -> Unit)? = null
): Int = withContext(Dispatchers.IO) {
val p = indexLines.size
): Unit = withContext(Dispatchers.IO) {
val c = comics.size / 3
homeF?.layoutInflater?.inflate(
when(c){
1 -> R.layout.line_1bookline
2 -> R.layout.line_2bookline
3 -> R.layout.line_3bookline
else -> return@withContext -1
else -> return@withContext
}, null, false)?.apply {
withContext(Dispatchers.Main) {
scanCards(this@apply, comics, finish, isTopic)
@@ -341,9 +329,9 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
if(onClick != null) setOnClickListener { onClick() }
}
}
indexLines += this
homeF?.fhl?.let { it.post { it.addView(this) } }
}
return@withContext p
return@withContext
}
private suspend fun scanCards(v: View, comics: Array<ComicStructure>, finish: Boolean, isTopic: Boolean) = withContext(Dispatchers.IO) {

View File

@@ -0,0 +1,12 @@
package top.fumiama.copymanga.ui.home
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class HomeViewModel : ViewModel() {
val indexStructure = MutableLiveData<String?>(null)
fun saveIndexStructure(index: String?) {
indexStructure.value = index
}
}

View File

@@ -123,16 +123,6 @@ class UITools(that: Context?, w: WeakReference<Activity>? = null) {
return listOf(numPerRow, w, totalWidth)
}
companion object {
fun toHexStr(byteArray: ByteArray) =
with(StringBuilder()) {
byteArray.forEach {
val hex = it.toInt() and (0xFF)
val hexStr = Integer.toHexString(hex)
if (hexStr.length == 1) append("0").append(hexStr)
else append(hexStr)
}
toString()
}
@SuppressLint("DiscouragedApi", "InternalInsetResource")
fun getNavigationBarHeight(context: Context): Int {
val resources = context.resources

View File

@@ -14,9 +14,8 @@ import top.fumiama.copymanga.api.Config
import top.fumiama.copymanga.view.interaction.UITools
import java.util.concurrent.atomic.AtomicBoolean
open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
open class NoBackRefreshFragment(private val layoutToLoad: Int, private val noCacheView: Boolean = false): Fragment() {
private var _rootView: View? = null
val rootView: View get() = _rootView!!
var isFirstInflate = true
var navBarHeight = 0
private val disableAnimation get() = Config.general_disable_kanban_animation.value
@@ -25,7 +24,10 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//TODO: 支持自动重建
navBarHeight = context?.let { UITools.getNavigationBarHeight(it) } ?: 0
if (noCacheView) return inflater.inflate(layoutToLoad, container, false)
if(_rootView == null) {
isFirstInflate = true
_rootView = inflater.inflate(layoutToLoad, container, false)
@@ -34,8 +36,7 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
isFirstInflate = false
Log.d("MyNBRF", "not first inflate")
}
navBarHeight = context?.let { UITools.getNavigationBarHeight(it) } ?: 0
return rootView
return _rootView!!
}
override fun onDestroy() {
hideKanban()

View File

@@ -115,6 +115,7 @@
<string name="TRANSPORT_ERROR">网络错误</string>
<string name="pc_ua">COPY/%1$s</string>
<string name="default_ua">__default_ua__</string>
<string name="app_ver">&appver;</string>
<string name="referer">com.copymanga.app-%1$s</string>
<string name="platform">&platform;</string>

View File

@@ -185,6 +185,7 @@
<EditTextPreference
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:defaultValue="@string/default_ua"
android:selectAllOnFocus="false"
android:singleLine="true"
android:title="@string/settings_cat_net_et_title_ua"