mirror of
https://github.com/fumiama/copymanga.git
synced 2026-06-19 00:20:29 +08:00
v2.5.8
优化 1. 主页加载速度 2. 默认UA判定 3. TCP Client
This commit is contained in:
1
.idea/dictionaries/fumiama.xml
generated
1
.idea/dictionaries/fumiama.xml
generated
@@ -3,6 +3,7 @@
|
|||||||
<words>
|
<words>
|
||||||
<w>alphae</w>
|
<w>alphae</w>
|
||||||
<w>azurewebsites</w>
|
<w>azurewebsites</w>
|
||||||
|
<w>catquit</w>
|
||||||
<w>comancry</w>
|
<w>comancry</w>
|
||||||
<w>comandy</w>
|
<w>comandy</w>
|
||||||
<w>deviceinfo</w>
|
<w>deviceinfo</w>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
//noinspection OldTargetApi
|
//noinspection OldTargetApi
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 80
|
versionCode 81
|
||||||
versionName '2.5.7'
|
versionName '2.5.8'
|
||||||
resourceConfigurations += ['zh', 'zh-rCN']
|
resourceConfigurations += ['zh', 'zh-rCN']
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@@ -124,4 +124,5 @@ dependencies {
|
|||||||
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.6'
|
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'
|
||||||
|
implementation 'top.fumiama:sdict:0.1.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import top.fumiama.copymanga.api.update.Update
|
|||||||
import top.fumiama.copymanga.api.user.Member
|
import top.fumiama.copymanga.api.user.Member
|
||||||
import top.fumiama.copymanga.lib.Comancry
|
import top.fumiama.copymanga.lib.Comancry
|
||||||
import top.fumiama.copymanga.lib.Comandy
|
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.copymanga.strings.Base16384
|
||||||
import top.fumiama.dmzj.copymanga.BuildConfig
|
import top.fumiama.dmzj.copymanga.BuildConfig
|
||||||
import top.fumiama.dmzj.copymanga.R
|
import top.fumiama.dmzj.copymanga.R
|
||||||
@@ -225,7 +225,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
toolsBox.buildInfo("备份管理", "可选择导出或导入base16384格式配置项",
|
toolsBox.buildInfo("备份管理", "可选择导出或导入base16384格式配置项",
|
||||||
"导出", "导入", "取消", { // ok
|
"导出", "导入", "取消", { // ok
|
||||||
MaterialDialog(this).show {
|
MaterialDialog(this).show {
|
||||||
input(prefill = Base16384.encode(DataLoader().toByteArray()))
|
input(prefill = Base16384.encode(ConfigLoader().toByteArray()))
|
||||||
positiveButton(android.R.string.ok)
|
positiveButton(android.R.string.ok)
|
||||||
title(null, "请复制配置文本并保存")
|
title(null, "请复制配置文本并保存")
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
MaterialDialog(this).show {
|
MaterialDialog(this).show {
|
||||||
input { _, c ->
|
input { _, c ->
|
||||||
try {
|
try {
|
||||||
DataLoader(Base16384.decode(c.toString())).settings.export()
|
ConfigLoader(Base16384.decode(c.toString())).settings.export()
|
||||||
navController?.apply {
|
navController?.apply {
|
||||||
currentDestination?.id?.let {
|
currentDestination?.id?.let {
|
||||||
popBackStack()
|
popBackStack()
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ object Config {
|
|||||||
|
|
||||||
val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!!
|
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 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 referer get() = MainActivity.mainWeakReference?.get()?.getString(R.string.referer)?.format(app_ver.value)?:""
|
||||||
|
|
||||||
val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo)
|
val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo)
|
||||||
@@ -95,7 +96,7 @@ object Config {
|
|||||||
val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID)
|
val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID)
|
||||||
val net_umstring = PreferenceString("settings_cat_net_et_umstring")
|
val net_umstring = PreferenceString("settings_cat_net_et_umstring")
|
||||||
val net_source = PreferenceString("settings_cat_net_et_source", R.string.source)
|
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_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)
|
val view_manga_always_dark_bg = PreferenceBoolean("settings_cat_vm_sw_always_dark_bg", false)
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package top.fumiama.copymanga.api.update
|
package top.fumiama.copymanga.api.update
|
||||||
|
|
||||||
import android.util.Log
|
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
|
class SimpleKanban(private val client: Client, private val pwd: String) { //must run in thread
|
||||||
private val raw: ByteArray?
|
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.sendMessage("${pwd}get${version}quit")
|
||||||
client.receiveRawMessage(36) //Welcome to simple kanban server. get
|
client.receiveRawMessage(36) //Welcome to simple kanban server. get
|
||||||
val r = try {
|
val r = try {
|
||||||
val firstRecv = client.receiveRawMessage(4)
|
val firstReceive = client.receiveRawMessage(4)
|
||||||
if(firstRecv.decodeToString() == "null") "null"
|
if(firstReceive.decodeToString() == "null") "null"
|
||||||
else {
|
else {
|
||||||
val length = convert2Int(firstRecv)
|
val length = convert2Int(firstReceive)
|
||||||
Log.d("MySK", "Msg len: $length")
|
Log.d("MySK", "Msg len: $length")
|
||||||
var re = byteArrayOf()
|
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)
|
re += client.receiveRawMessage(length - re.size)
|
||||||
if(re.isNotEmpty()) re.decodeToString() else "null"
|
if(re.isNotEmpty()) re.decodeToString() else "null"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import kotlinx.android.synthetic.main.dialog_progress.view.*
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
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.copymanga.view.interaction.UITools
|
||||||
import top.fumiama.dmzj.copymanga.BuildConfig
|
import top.fumiama.dmzj.copymanga.BuildConfig
|
||||||
import top.fumiama.dmzj.copymanga.R
|
import top.fumiama.dmzj.copymanga.R
|
||||||
|
import top.fumiama.sdict.utils.Utils.toHexStr
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ object Update {
|
|||||||
fetch(client, kanban, this@apply) {
|
fetch(client, kanban, this@apply) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
val md5 = msg.substringAfterLast("md5:")
|
val md5 = msg.substringAfterLast("md5:")
|
||||||
if (md5 == UITools.toHexStr(
|
if (md5 == toHexStr(
|
||||||
MessageDigest.getInstance("MD5").digest(it)
|
MessageDigest.getInstance("MD5").digest(it)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.fumiama.copymanga.MainActivity
|
import top.fumiama.copymanga.MainActivity
|
||||||
import top.fumiama.copymanga.json.ComandyVersion
|
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.net.DownloadTools
|
||||||
import top.fumiama.copymanga.storage.PreferenceBoolean
|
import top.fumiama.copymanga.storage.PreferenceBoolean
|
||||||
import top.fumiama.copymanga.storage.UserPreferenceInt
|
import top.fumiama.copymanga.storage.UserPreferenceInt
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import top.fumiama.sdict.io.Client
|
||||||
import top.fumiama.copymanga.api.Config
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.api.Config.proxyUrl
|
import top.fumiama.copymanga.api.Config.proxyUrl
|
||||||
import top.fumiama.copymanga.json.ComandyCapsule
|
import top.fumiama.copymanga.json.ComandyCapsule
|
||||||
@@ -34,7 +35,7 @@ object DownloadTools {
|
|||||||
connection.apply {
|
connection.apply {
|
||||||
Config.net_ua.value.let {
|
Config.net_ua.value.let {
|
||||||
if (it.isEmpty()) return@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) }
|
Config.net_source.value.let { if(it.isNotEmpty()) setRequestProperty("source", it) }
|
||||||
// deviceinfo
|
// deviceinfo
|
||||||
@@ -43,7 +44,7 @@ object DownloadTools {
|
|||||||
if (Config.net_use_gzip.value) setRequestProperty("accept-encoding", "gzip")
|
if (Config.net_use_gzip.value) setRequestProperty("accept-encoding", "gzip")
|
||||||
setRequestProperty("authorization", "Token${Config.token.value?.let { tk ->
|
setRequestProperty("authorization", "Token${Config.token.value?.let { tk ->
|
||||||
if (tk.isNotEmpty()) " $tk" else ""
|
if (tk.isNotEmpty()) " $tk" else ""
|
||||||
}}")
|
}?:""}")
|
||||||
if (Config.net_platform.value) setRequestProperty("platform", Config.platform.value)
|
if (Config.net_platform.value) setRequestProperty("platform", Config.platform.value)
|
||||||
if (Config.net_referer.value) setRequestProperty("referer", Config.referer)
|
if (Config.net_referer.value) setRequestProperty("referer", Config.referer)
|
||||||
if (Config.net_use_json.value) setRequestProperty("accept", "application/json")
|
if (Config.net_use_json.value) setRequestProperty("accept", "application/json")
|
||||||
@@ -66,7 +67,7 @@ object DownloadTools {
|
|||||||
capsule.headers = hashMapOf()
|
capsule.headers = hashMapOf()
|
||||||
Config.net_ua.value.let {
|
Config.net_ua.value.let {
|
||||||
if (it.isEmpty()) return@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 }
|
Config.net_source.value.let { if(it.isNotEmpty()) capsule.headers["source"] = it }
|
||||||
// deviceinfo
|
// deviceinfo
|
||||||
@@ -75,7 +76,7 @@ object DownloadTools {
|
|||||||
if (Config.net_use_gzip.value) capsule.headers["accept-encoding"] = "gzip"
|
if (Config.net_use_gzip.value) capsule.headers["accept-encoding"] = "gzip"
|
||||||
capsule.headers["authorization"] = "Token${Config.token.value?.let { tk ->
|
capsule.headers["authorization"] = "Token${Config.token.value?.let { tk ->
|
||||||
if (tk.isNotEmpty()) " $tk" else ""
|
if (tk.isNotEmpty()) " $tk" else ""
|
||||||
}}"
|
}?:""}"
|
||||||
if (Config.net_platform.value) capsule.headers["platform"] = Config.platform.value
|
if (Config.net_platform.value) capsule.headers["platform"] = Config.platform.value
|
||||||
if (Config.net_referer.value) capsule.headers["referer"] = Config.referer
|
if (Config.net_referer.value) capsule.headers["referer"] = Config.referer
|
||||||
if (Config.net_use_json.value) capsule.headers["accept"] = "application/json"
|
if (Config.net_use_json.value) capsule.headers["accept"] = "application/json"
|
||||||
|
|||||||
@@ -11,11 +11,9 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference
|
|
||||||
import top.fumiama.copymanga.api.Config
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.json.ReturnBase
|
import top.fumiama.copymanga.json.ReturnBase
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
open class AutoDownloadHandler(
|
open class AutoDownloadHandler(
|
||||||
private val url: () -> String, private val jsonClass: Class<*>,
|
private val url: () -> String, private val jsonClass: Class<*>,
|
||||||
@@ -24,6 +22,7 @@ open class AutoDownloadHandler(
|
|||||||
private val customCacheFile: File? = null): Handler(Looper.myLooper()!!) {
|
private val customCacheFile: File? = null): Handler(Looper.myLooper()!!) {
|
||||||
private var checkTimes = 0
|
private var checkTimes = 0
|
||||||
var exit = false
|
var exit = false
|
||||||
|
var raw: String? = null
|
||||||
override fun handleMessage(msg: Message) {
|
override fun handleMessage(msg: Message) {
|
||||||
super.handleMessage(msg)
|
super.handleMessage(msg)
|
||||||
when(msg.what){
|
when(msg.what){
|
||||||
@@ -45,26 +44,16 @@ open class AutoDownloadHandler(
|
|||||||
downloadCoroutine()
|
downloadCoroutine()
|
||||||
check()
|
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) {
|
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) {
|
if(loadFromCache) {
|
||||||
cacheFile?.let {
|
customCacheFile?.let {
|
||||||
if (it.exists()) {
|
if (it.exists()) {
|
||||||
var pass = true
|
var pass = true
|
||||||
it.inputStream().use { fi->
|
it.inputStream().use { fi->
|
||||||
try {
|
try {
|
||||||
pass = setGsonItem(Gson().fromJson(fi.reader(), jsonClass))
|
val data = fi.readBytes().decodeToString()
|
||||||
|
raw = data
|
||||||
|
pass = setGsonItem(Gson().fromJson(data, jsonClass))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@@ -77,10 +66,11 @@ open class AutoDownloadHandler(
|
|||||||
while (cnt++ <= 3) {
|
while (cnt++ <= 3) {
|
||||||
try {
|
try {
|
||||||
val data = Config.api.get(url())
|
val data = Config.api.get(url())
|
||||||
|
raw = data
|
||||||
if(exit) return@withContext
|
if(exit) return@withContext
|
||||||
val pass = setGsonItem(Gson().fromJson(data, jsonClass))
|
val pass = setGsonItem(Gson().fromJson(data, jsonClass))
|
||||||
if (pass && loadFromCache) {
|
if (pass && loadFromCache) {
|
||||||
cacheFile?.writeText(data)
|
customCacheFile?.writeText(data)
|
||||||
}
|
}
|
||||||
if(!pass) {
|
if(!pass) {
|
||||||
delay(2000)
|
delay(2000)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import top.fumiama.copymanga.api.Config
|
|||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
|
|
||||||
class DataLoader {
|
class ConfigLoader {
|
||||||
data class Settings(
|
data class Settings(
|
||||||
var appVer: String,
|
var appVer: String,
|
||||||
var platform: String,
|
var platform: String,
|
||||||
@@ -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.Config.manga_dl_show_0m_manga
|
||||||
import top.fumiama.copymanga.api.manga.Downloader
|
import top.fumiama.copymanga.api.manga.Downloader
|
||||||
import top.fumiama.copymanga.api.manga.Reader
|
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
|
||||||
import top.fumiama.copymanga.storage.FileUtils.compressToUserFile
|
import top.fumiama.copymanga.storage.FileUtils.compressToUserFile
|
||||||
import top.fumiama.copymanga.view.interaction.Navigate
|
import top.fumiama.copymanga.view.interaction.Navigate
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
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.line_word.view.*
|
||||||
import kotlinx.android.synthetic.main.viewpage_horizonal.view.*
|
import kotlinx.android.synthetic.main.viewpage_horizonal.view.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.fumiama.copymanga.MainActivity
|
import top.fumiama.copymanga.MainActivity
|
||||||
@@ -40,150 +40,176 @@ import java.lang.ref.WeakReference
|
|||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
|
class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home, true) {
|
||||||
lateinit var homeHandler: HomeHandler
|
lateinit var homeHandler: HomeHandler
|
||||||
|
val vm: HomeViewModel by viewModels()
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
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()
|
val theme = resources.newTheme()
|
||||||
swiperefresh?.setColorSchemeColors(
|
swiperefresh?.setColorSchemeColors(
|
||||||
resources.getColor(R.color.colorAccent, theme),
|
resources.getColor(R.color.colorAccent, theme),
|
||||||
resources.getColor(R.color.colorBlue2, theme),
|
resources.getColor(R.color.colorBlue2, theme),
|
||||||
resources.getColor(R.color.colorGreen, theme))
|
resources.getColor(R.color.colorGreen, theme)
|
||||||
swiperefresh?.isEnabled = true
|
)
|
||||||
|
|
||||||
fhl?.setPadding(0, 0, 0, navBarHeight)
|
fhl?.setPadding(0, 0, 0, navBarHeight)
|
||||||
|
|
||||||
fhs?.apply {
|
lifecycleScope.launch {
|
||||||
isNestedScrollingEnabled = true
|
withContext(Dispatchers.Main) {
|
||||||
val recyclerView = findViewById<RecyclerView>(R.id.search_recycler_view)
|
vm.indexStructure.observe(viewLifecycleOwner) {
|
||||||
recyclerView.isNestedScrollingEnabled = true
|
Log.d("MyHF", "get observed: $it")
|
||||||
recyclerView.setPadding(0, 0, 0, navBarHeight)
|
if (it == null) { // init
|
||||||
setAdapterLayoutManager(LinearLayoutManager(context))
|
val tb = (activity as MainActivity).toolsBox
|
||||||
val adapter = ListViewHolder(recyclerView).RecyclerViewAdapter()
|
val netInfo = tb.netInfo
|
||||||
setAdapter(adapter)
|
lifecycleScope.launch net@ {
|
||||||
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
|
if (netInfo == tb.transportStringNull || netInfo == tb.transportStringError) {
|
||||||
setMicIconImageResource(R.drawable.ic_setting_search)
|
(activity as MainActivity).toolsBox.toastError(getString(R.string.web_error))
|
||||||
val micView = findViewById<ImageButton>(R.id.search_image_view_mic)
|
fhov?.swipeRefreshLayout = swiperefresh
|
||||||
setClearFocusOnBackPressed(true)
|
swiperefresh.isRefreshing = false
|
||||||
setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener {
|
swiperefresh.setOnRefreshListener {
|
||||||
override fun onNavigationClick(hasFocus: Boolean) {
|
Log.d("MyHFH", "Refresh items.")
|
||||||
if (hasFocus()) {
|
vm.saveIndexStructure(null)
|
||||||
clearFocus()
|
}
|
||||||
}
|
hideKanban()
|
||||||
else requestFocus()
|
return@net
|
||||||
}
|
}
|
||||||
})
|
MainActivity.member?.apply {
|
||||||
setTextHint(android.R.string.search_go)
|
withContext(Dispatchers.IO) {
|
||||||
|
Config.api.init()
|
||||||
var lastSearch = ""
|
try {
|
||||||
setOnQueryTextListener(object : SearchLayout.OnQueryTextListener {
|
info()
|
||||||
var lastChangeTime = 0L
|
} catch (e: Exception) {
|
||||||
override fun onQueryTextChange(newText: CharSequence): Boolean {
|
withContext(Dispatchers.Main) {
|
||||||
if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true
|
Snackbar
|
||||||
lastSearch = newText.toString()
|
.make(
|
||||||
postDelayed({
|
view,
|
||||||
lifecycleScope.launch {
|
"${e::class.simpleName} ${e.message}",
|
||||||
if (!newText.contentEquals(lastSearch)) return@launch
|
Snackbar.LENGTH_LONG
|
||||||
val diff = System.currentTimeMillis() - lastChangeTime
|
)
|
||||||
if(diff > 500) {
|
.setTextMaxLines(10)
|
||||||
if (newText.isNotEmpty()) {
|
.show()
|
||||||
Log.d("MyHF", "new text: $newText")
|
}
|
||||||
adapter.refresh(newText)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} }
|
||||||
}, 1024)
|
|
||||||
lastChangeTime = System.currentTimeMillis()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onQueryTextSubmit(query: CharSequence): Boolean {
|
homeHandler = HomeHandler(WeakReference(this@HomeFragment))
|
||||||
/*if(query.isNotEmpty()) {
|
homeHandler.obtainMessage(-1, true).sendToTarget()
|
||||||
val key = query.toString()
|
homeHandler.startLoad()
|
||||||
Toast.makeText(context, key, Toast.LENGTH_SHORT).show()
|
|
||||||
}*/
|
|
||||||
Log.d("MyHF", "recover text: $lastSearch")
|
|
||||||
setTextQuery(lastSearch, false)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setOnMicClickListener(object : SearchLayout.OnMicClickListener {
|
return@observe
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
})
|
if(homeHandler.exit) return@observe
|
||||||
|
|
||||||
var isInFocusWaiting = false
|
swiperefresh?.isEnabled = true
|
||||||
setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener {
|
|
||||||
override fun onFocusChange(hasFocus: Boolean) {
|
fhs?.apply {
|
||||||
Log.d("MyHF", "fhs onFocusChange: $hasFocus")
|
isNestedScrollingEnabled = true
|
||||||
if (isInFocusWaiting) return
|
val recyclerView = findViewById<RecyclerView>(R.id.search_recycler_view)
|
||||||
isInFocusWaiting = true
|
recyclerView.isNestedScrollingEnabled = true
|
||||||
postDelayed({
|
recyclerView.setPadding(0, 0, 0, navBarHeight)
|
||||||
navigationIconSupport = if (hasFocus) {
|
setAdapterLayoutManager(LinearLayoutManager(context))
|
||||||
setTextQuery("__notice_focus_change__", true)
|
val adapter = ListViewHolder(recyclerView).RecyclerViewAdapter()
|
||||||
SearchLayout.NavigationIconSupport.ARROW
|
setAdapter(adapter)
|
||||||
}
|
navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH
|
||||||
else {
|
setMicIconImageResource(R.drawable.ic_setting_search)
|
||||||
if (lastSearch.isNotEmpty()) {
|
val micView = findViewById<ImageButton>(R.id.search_image_view_mic)
|
||||||
micView?.visibility = View.VISIBLE
|
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 ->
|
var lastSearch = ""
|
||||||
Log.d("MyHF", "fhns on touch")
|
setOnQueryTextListener(object : SearchLayout.OnQueryTextListener {
|
||||||
if (e.action == MotionEvent.ACTION_UP && mSearchEditText?.text?.isNotEmpty() == true) {
|
var lastChangeTime = 0L
|
||||||
ime?.hideSoftInputFromWindow(activity?.window?.decorView?.windowToken, 0)
|
override fun onQueryTextChange(newText: CharSequence): Boolean {
|
||||||
}
|
if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true
|
||||||
false
|
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{
|
override fun onQueryTextSubmit(query: CharSequence): Boolean {
|
||||||
withContext(Dispatchers.IO) {
|
/*if(query.isNotEmpty()) {
|
||||||
homeHandler.obtainMessage(-1, true).sendToTarget()
|
val key = query.toString()
|
||||||
while(!MainActivity.isDrawerClosed) delay(233)
|
Toast.makeText(context, key, Toast.LENGTH_SHORT).show()
|
||||||
//homeHandler.sendEmptyMessage(6) //removeAllViews
|
}*/
|
||||||
//homeHandler.fhib = null
|
Log.d("MyHF", "recover text: $lastSearch")
|
||||||
delay(300)
|
setTextQuery(lastSearch, false)
|
||||||
homeHandler.startLoad()
|
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
|
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) {
|
suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) {
|
||||||
query = q.toString()
|
query = q.toString()
|
||||||
activity?.apply {
|
activity?.apply {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import android.os.Message
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
@@ -28,13 +27,13 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.json.ComicStructure
|
import top.fumiama.copymanga.json.ComicStructure
|
||||||
import top.fumiama.copymanga.json.IndexStructure
|
import top.fumiama.copymanga.json.IndexStructure
|
||||||
import top.fumiama.copymanga.net.template.AutoDownloadHandler
|
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.Navigate
|
||||||
import top.fumiama.copymanga.view.interaction.UITools
|
import top.fumiama.copymanga.view.interaction.UITools
|
||||||
|
import top.fumiama.copymanga.view.operation.GlideHideLottieViewListener
|
||||||
import top.fumiama.dmzj.copymanga.R
|
import top.fumiama.dmzj.copymanga.R
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
@@ -47,7 +46,7 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
|||||||
) {
|
) {
|
||||||
private val homeF get() = that.get()
|
private val homeF get() = that.get()
|
||||||
var index: IndexStructure? = null
|
var index: IndexStructure? = null
|
||||||
var fhib: Banner? = null
|
private var fhib: Banner? = null
|
||||||
get() {
|
get() {
|
||||||
Log.d("MyHH", "Get fhib.")
|
Log.d("MyHH", "Get fhib.")
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
@@ -56,7 +55,6 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
|||||||
}
|
}
|
||||||
return field
|
return field
|
||||||
}
|
}
|
||||||
private var indexLines = arrayOf<View>()
|
|
||||||
|
|
||||||
override fun handleMessage(msg: Message) {
|
override fun handleMessage(msg: Message) {
|
||||||
super.handleMessage(msg)
|
super.handleMessage(msg)
|
||||||
@@ -72,16 +70,6 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
|||||||
2 -> homeF?.swiperefresh?.let { setSwipe(it) }
|
2 -> homeF?.swiperefresh?.let { setSwipe(it) }
|
||||||
3 -> setBanner(fhib!!)
|
3 -> setBanner(fhib!!)
|
||||||
5 -> setBannerInfo(msg.obj as Banner)
|
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()
|
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()
|
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()
|
super.doWhenFinishDownload()
|
||||||
if(exit) return@withContext
|
raw?.let {
|
||||||
sendEmptyMessage(2) //setSwipe
|
Log.d("MyHFH", "save raw: $it")
|
||||||
sendEmptyMessage(7) //inflateBanner
|
homeF?.apply { activity?.runOnUiThread {
|
||||||
sendEmptyMessage(1) //inflateCardLines
|
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() {
|
private suspend fun inflateTopics() {
|
||||||
index?.results?.topics?.list?.let {
|
index?.results?.topics?.list?.let {
|
||||||
@@ -242,29 +237,13 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
|||||||
private fun inflateCardLines() {
|
private fun inflateCardLines() {
|
||||||
homeF?.lifecycleScope?.launch {
|
homeF?.lifecycleScope?.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (indexLines.isNotEmpty()) indexLines = arrayOf()
|
|
||||||
inflateRec()
|
inflateRec()
|
||||||
inflateTopics()
|
inflateTopics()
|
||||||
inflateHot()
|
inflateHot()
|
||||||
inflateNew()
|
inflateNew()
|
||||||
inflateFinish()
|
inflateFinish()
|
||||||
inflateRank()
|
inflateRank()
|
||||||
homeF?.fhl?.apply { post {
|
obtainMessage(-1, false).sendToTarget() //closeLoad
|
||||||
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
|
|
||||||
} }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,14 +285,24 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
|||||||
homeF?.lifecycleScope?.launch {
|
homeF?.lifecycleScope?.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
homeF?.showKanban()
|
homeF?.showKanban()
|
||||||
fhib?.isAutoPlay = false
|
fhib?.let {
|
||||||
index = null
|
it.isAutoPlay = false
|
||||||
fhib?.adapter?.notifyDataSetChanged()
|
index = null
|
||||||
|
it.adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
fhib = null
|
fhib = null
|
||||||
indexLines = arrayOf()
|
|
||||||
this@HomeHandler.sendEmptyMessage(6) //removeAllViews
|
|
||||||
delay(300)
|
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(
|
private suspend fun allocateLine(
|
||||||
title: String, iconResId: Int, comics: Array<ComicStructure>,
|
title: String, iconResId: Int, comics: Array<ComicStructure>,
|
||||||
finish: Boolean = false, isTopic: Boolean = false, onClick: (() -> Unit)? = null
|
finish: Boolean = false, isTopic: Boolean = false, onClick: (() -> Unit)? = null
|
||||||
): Int = withContext(Dispatchers.IO) {
|
): Unit = withContext(Dispatchers.IO) {
|
||||||
val p = indexLines.size
|
|
||||||
val c = comics.size / 3
|
val c = comics.size / 3
|
||||||
homeF?.layoutInflater?.inflate(
|
homeF?.layoutInflater?.inflate(
|
||||||
when(c){
|
when(c){
|
||||||
1 -> R.layout.line_1bookline
|
1 -> R.layout.line_1bookline
|
||||||
2 -> R.layout.line_2bookline
|
2 -> R.layout.line_2bookline
|
||||||
3 -> R.layout.line_3bookline
|
3 -> R.layout.line_3bookline
|
||||||
else -> return@withContext -1
|
else -> return@withContext
|
||||||
}, null, false)?.apply {
|
}, null, false)?.apply {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
scanCards(this@apply, comics, finish, isTopic)
|
scanCards(this@apply, comics, finish, isTopic)
|
||||||
@@ -341,9 +329,9 @@ class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadH
|
|||||||
if(onClick != null) setOnClickListener { onClick() }
|
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) {
|
private suspend fun scanCards(v: View, comics: Array<ComicStructure>, finish: Boolean, isTopic: Boolean) = withContext(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -123,16 +123,6 @@ class UITools(that: Context?, w: WeakReference<Activity>? = null) {
|
|||||||
return listOf(numPerRow, w, totalWidth)
|
return listOf(numPerRow, w, totalWidth)
|
||||||
}
|
}
|
||||||
companion object {
|
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")
|
@SuppressLint("DiscouragedApi", "InternalInsetResource")
|
||||||
fun getNavigationBarHeight(context: Context): Int {
|
fun getNavigationBarHeight(context: Context): Int {
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
|
|||||||
@@ -14,9 +14,8 @@ import top.fumiama.copymanga.api.Config
|
|||||||
import top.fumiama.copymanga.view.interaction.UITools
|
import top.fumiama.copymanga.view.interaction.UITools
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
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
|
private var _rootView: View? = null
|
||||||
val rootView: View get() = _rootView!!
|
|
||||||
var isFirstInflate = true
|
var isFirstInflate = true
|
||||||
var navBarHeight = 0
|
var navBarHeight = 0
|
||||||
private val disableAnimation get() = Config.general_disable_kanban_animation.value
|
private val disableAnimation get() = Config.general_disable_kanban_animation.value
|
||||||
@@ -25,7 +24,10 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
//TODO: 支持自动重建
|
navBarHeight = context?.let { UITools.getNavigationBarHeight(it) } ?: 0
|
||||||
|
|
||||||
|
if (noCacheView) return inflater.inflate(layoutToLoad, container, false)
|
||||||
|
|
||||||
if(_rootView == null) {
|
if(_rootView == null) {
|
||||||
isFirstInflate = true
|
isFirstInflate = true
|
||||||
_rootView = inflater.inflate(layoutToLoad, container, false)
|
_rootView = inflater.inflate(layoutToLoad, container, false)
|
||||||
@@ -34,8 +36,7 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
|
|||||||
isFirstInflate = false
|
isFirstInflate = false
|
||||||
Log.d("MyNBRF", "not first inflate")
|
Log.d("MyNBRF", "not first inflate")
|
||||||
}
|
}
|
||||||
navBarHeight = context?.let { UITools.getNavigationBarHeight(it) } ?: 0
|
return _rootView!!
|
||||||
return rootView
|
|
||||||
}
|
}
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
hideKanban()
|
hideKanban()
|
||||||
|
|||||||
@@ -115,6 +115,7 @@
|
|||||||
<string name="TRANSPORT_ERROR">网络错误</string>
|
<string name="TRANSPORT_ERROR">网络错误</string>
|
||||||
|
|
||||||
<string name="pc_ua">COPY/%1$s</string>
|
<string name="pc_ua">COPY/%1$s</string>
|
||||||
|
<string name="default_ua">__default_ua__</string>
|
||||||
<string name="app_ver">&appver;</string>
|
<string name="app_ver">&appver;</string>
|
||||||
<string name="referer">com.copymanga.app-%1$s</string>
|
<string name="referer">com.copymanga.app-%1$s</string>
|
||||||
<string name="platform">&platform;</string>
|
<string name="platform">&platform;</string>
|
||||||
|
|||||||
@@ -185,6 +185,7 @@
|
|||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:defaultValue="@string/default_ua"
|
||||||
android:selectAllOnFocus="false"
|
android:selectAllOnFocus="false"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:title="@string/settings_cat_net_et_title_ua"
|
android:title="@string/settings_cat_net_et_title_ua"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
Third-party client for copymanga with better reading/downloading experience.
|
A third-party app for CopyManga, offering only basic features. For a more complete experience, please refer to the official version.
|
||||||
<h3>Features</h3>
|
<h3>Features</h3>
|
||||||
1. Browse homepage, category, rank, my downloads, tag, and author.
|
1. Browse the homepage, categories, rankings, downloads, subscriptions, reading history, tags, and authors.
|
||||||
2. View, search and read manga; log reading progress.
|
2. View and search for manga, and read them directly. Reading progress is saved both locally and in the cloud (cloud sync does not track the exact page).
|
||||||
3. Download manga. But due to force majeure, the downloading is slow and error-prone. This is not due to bad optimization, absolutely not.
|
3. Download manga. However, due to circumstances beyond our control, download speeds may be slow and errors can occur—definitely not because of optimization, absolutely not.
|
||||||
4. Read downloaded manga.
|
4. Read and delete downloaded manga, or jump to the manga detail page directly from your downloads.
|
||||||
5. Check update.
|
5. Check for updates.
|
||||||
|
6. Log in and log out.
|
||||||
|
7. Subscribe or unsubscribe to manga.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Third-party client for copymanga with better reading/downloading experience
|
A third-party app for CopyManga, offering only basic features. For a more complete experience, please refer to the official version.
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
拷贝漫画的第三方 APP,优化阅读/下载体验
|
拷贝漫画的第三方APP,仅提供基础功能,更多丰富功能请移步官方版本
|
||||||
<h3>功能</h3>
|
<h3>功能</h3>
|
||||||
1. 浏览主页、分类、排行、我的下载、标签、作者。
|
1. 浏览主页、分类、排行、我的下载、我的订阅、浏览历史、标签、作者。
|
||||||
2. 查看、搜索漫画并直接阅读;记录漫画与章节的阅读进度。
|
2. 查看、搜索漫画并直接阅读;在本地和云端记录漫画与章节的阅读进度 (云端不能精确到页)。
|
||||||
3. 下载漫画。但是由于不可抗力,下载速度较慢且容易出错,这绝对不是优化的原因,绝对不是。
|
3. 下载漫画。但是由于不可抗力,下载速度较慢且容易出错,这绝对不是优化的原因,绝对不是。
|
||||||
4. 阅读下载的漫画。
|
4. 阅读、删除下载的漫画,或从我的下载页面直接导航到漫画详情页。
|
||||||
5. 检查更新。
|
5. 检查更新。
|
||||||
|
6. 登录,注销。
|
||||||
|
7. 订阅、取消订阅。
|
||||||
@@ -1 +1 @@
|
|||||||
拷贝漫画的第三方 APP,优化阅读/下载体验
|
拷贝漫画的第三方APP,仅提供基础功能,更多丰富功能请移步官方版本
|
||||||
Reference in New Issue
Block a user