mirror of
https://github.com/fumiama/copymanga.git
synced 2026-06-04 23:10:23 +08:00
v2.5.6
新增 1. 导入/导出设置 优化 1. 错误显示
This commit is contained in:
@@ -12,8 +12,8 @@ android {
|
||||
minSdkVersion 23
|
||||
//noinspection OldTargetApi
|
||||
targetSdkVersion 34
|
||||
versionCode 78
|
||||
versionName '2.5.5'
|
||||
versionCode 79
|
||||
versionName '2.5.6'
|
||||
resourceConfigurations += ['zh', 'zh-rCN']
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -35,8 +35,8 @@ class LoginActivity : AppCompatActivity() {
|
||||
).show()
|
||||
return@launch
|
||||
}
|
||||
val pwd = altpwd.text?.toString()
|
||||
if (pwd.isNullOrEmpty()) {
|
||||
val pwd = altpwd.text?.toString()?:""
|
||||
if (!isLogout && pwd.isEmpty()) {
|
||||
Toast.makeText(this@LoginActivity, R.string.login_null_pwd, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
return@launch
|
||||
@@ -66,7 +66,7 @@ class LoginActivity : AppCompatActivity() {
|
||||
Toast.makeText(
|
||||
this@LoginActivity,
|
||||
"${e::class.simpleName} ${e.message}",
|
||||
Toast.LENGTH_SHORT
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ 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.strings.Base16384
|
||||
import top.fumiama.dmzj.copymanga.BuildConfig
|
||||
import top.fumiama.dmzj.copymanga.R
|
||||
import java.io.File
|
||||
@@ -158,7 +160,7 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.d("MyMain", "start menu waiting")
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
Config.myHostApiUrl.init()
|
||||
Config.api.init()
|
||||
}
|
||||
withContext(Dispatchers.IO) {
|
||||
delay(1000)
|
||||
@@ -206,6 +208,7 @@ class MainActivity : AppCompatActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
@@ -214,10 +217,39 @@ class MainActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
R.id.action_download -> {
|
||||
if (NewDownloadFragment.wn != null) {
|
||||
//TODO: fill it
|
||||
} else {
|
||||
bookHandler.get()?.sendEmptyMessage(BookHandler.NAVIGATE_TO_DOWNLOAD)
|
||||
when (navController?.currentDestination?.id) {
|
||||
R.id.nav_new_download -> {
|
||||
//TODO: fill it
|
||||
}
|
||||
R.id.nav_settings -> {
|
||||
toolsBox.buildInfo("备份管理", "可选择导出或导入base16384格式配置项",
|
||||
"导出", "导入", "取消", { // ok
|
||||
MaterialDialog(this).show {
|
||||
input(prefill = Base16384.encode(DataLoader().toByteArray()))
|
||||
positiveButton(android.R.string.ok)
|
||||
title(null, "请复制配置文本并保存")
|
||||
}
|
||||
}, { // neutral
|
||||
MaterialDialog(this).show {
|
||||
input { _, c ->
|
||||
try {
|
||||
DataLoader(Base16384.decode(c.toString())).settings.export()
|
||||
navController?.apply {
|
||||
currentDestination?.id?.let {
|
||||
popBackStack()
|
||||
navigate(it)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(this@MainActivity, e.message?:e::class.simpleName, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
positiveButton(android.R.string.ok)
|
||||
title(null, "请粘贴配置文本")
|
||||
}
|
||||
})
|
||||
}
|
||||
else -> bookHandler.get()?.sendEmptyMessage(BookHandler.NAVIGATE_TO_DOWNLOAD)
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -338,6 +370,13 @@ class MainActivity : AppCompatActivity() {
|
||||
menuMain?.findItem(R.id.action_sort)?.isVisible = false
|
||||
menuMain?.findItem(R.id.action_del)?.isVisible = true
|
||||
}
|
||||
R.id.nav_settings -> {
|
||||
Log.d("MyMain", "enter settings")
|
||||
menuMain?.findItem(R.id.action_info)?.isVisible = false
|
||||
menuMain?.findItem(R.id.action_download)?.isVisible = true
|
||||
menuMain?.findItem(R.id.action_sort)?.isVisible = false
|
||||
menuMain?.findItem(R.id.action_del)?.isVisible = false
|
||||
}
|
||||
else -> {
|
||||
Log.d("MyMain", "enter others")
|
||||
menuMain?.findItem(R.id.action_info)?.isVisible = false
|
||||
@@ -433,7 +472,7 @@ class MainActivity : AppCompatActivity() {
|
||||
dl.setMessage("${getString(R.string.app_description)}\n" +
|
||||
"\n$comandy\n" +
|
||||
"$comancry\n\n"+ File("/proc/self/cmdline").readText() + "\n" +
|
||||
"当前API: ${Config.myHostApiUrl.getApis().joinToString(", ")}")
|
||||
"当前API: ${Config.api.getApis().joinToString(", ")}")
|
||||
dl.setTitle("${getString(R.string.action_info)} ${BuildConfig.VERSION_NAME}")
|
||||
dl.setIcon(R.mipmap.ic_launcher)
|
||||
dl.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
|
||||
@@ -14,6 +14,8 @@ import top.fumiama.dmzj.copymanga.R
|
||||
import java.io.File
|
||||
|
||||
object Config {
|
||||
val api = Api()
|
||||
|
||||
var imageProxy: Proxy? = null
|
||||
get() {
|
||||
if (!net_use_img_proxy.value) return null
|
||||
@@ -55,16 +57,11 @@ object Config {
|
||||
}
|
||||
|
||||
val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!!
|
||||
val reverseProxyUrl = PreferenceString(R.string.reverseProxyKeyID)
|
||||
val networkApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl)
|
||||
val myHostApiUrl = Api()
|
||||
val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo)
|
||||
val proxy_key = PreferenceString(R.string.imgProxyCodeKeyID)
|
||||
val app_ver = PreferenceString("settings_cat_general_et_app_version", R.string.app_ver)
|
||||
val platform = PreferenceString("settings_cat_general_et_platform", R.string.platform)
|
||||
val token = UserPreferenceString("token", "", null)
|
||||
val pc_ua get() = MainActivity.mainWeakReference?.get()?.getString(R.string.pc_ua)?.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 token = UserPreferenceString("token", "", null)
|
||||
val comandy_version = UserPreferenceInt("comandy_version", 0)
|
||||
val comancry_version = UserPreferenceInt("comancry_version", 0)
|
||||
val user_id = UserPreferenceString("user_id")
|
||||
@@ -72,6 +69,8 @@ object Config {
|
||||
val nickname = UserPreferenceString("nickname")
|
||||
val avatar = UserPreferenceString("avatar")
|
||||
|
||||
val app_ver = PreferenceString("settings_cat_general_et_app_version", R.string.app_ver)
|
||||
val platform = PreferenceString("settings_cat_general_et_platform", R.string.platform)
|
||||
val general_enable_transparent_system_bar = PreferenceBoolean("settings_cat_general_sw_enable_transparent_systembar", false)
|
||||
val general_disable_kanban_animation = PreferenceBoolean("settings_cat_general_sw_disable_kanban_animation", false)
|
||||
val general_card_per_row = PreferenceInt("settings_cat_general_sb_card_per_row", 0)
|
||||
@@ -79,6 +78,9 @@ object Config {
|
||||
val manga_dl_max_batch = PreferenceInt("settings_cat_md_sb_max_batch", 16)
|
||||
val manga_dl_show_0m_manga = PreferenceBoolean("settings_cat_md_sw_show_0m_manga", false)
|
||||
|
||||
val reverseProxyUrl = PreferenceString(R.string.reverseProxyKeyID)
|
||||
val networkApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl)
|
||||
val proxy_key = PreferenceString(R.string.imgProxyCodeKeyID)
|
||||
val net_use_gzip = PreferenceBoolean("settings_cat_net_sw_use_gzip", false)
|
||||
val net_use_json = PreferenceBoolean("settings_cat_net_sw_use_json", false)
|
||||
val net_platform = PreferenceBoolean("settings_cat_net_sw_platform", false)
|
||||
@@ -88,7 +90,7 @@ object Config {
|
||||
val net_no_webp = PreferenceBoolean("settings_cat_net_no_webp", false)
|
||||
val net_use_comandy = PreferenceBoolean("settings_cat_net_sw_use_comandy", false)
|
||||
val net_use_foreign = PreferenceBoolean("settings_cat_net_sw_use_foreign", false)
|
||||
private val net_use_img_proxy = PreferenceBoolean("settings_cat_net_sw_use_img_proxy", false)
|
||||
val net_use_img_proxy = PreferenceBoolean("settings_cat_net_sw_use_img_proxy", false)
|
||||
val net_use_api_proxy = PreferenceBoolean("settings_cat_net_sw_use_api_proxy", false)
|
||||
val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID)
|
||||
val net_umstring = PreferenceString("settings_cat_net_et_umstring")
|
||||
|
||||
@@ -2,7 +2,9 @@ package top.fumiama.copymanga.api.manga
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.android.synthetic.main.app_bar_main.*
|
||||
import kotlinx.android.synthetic.main.card_book.*
|
||||
import kotlinx.android.synthetic.main.line_booktandb.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -56,38 +58,29 @@ class Book(val path: String, private val getString: (Int) -> String, private val
|
||||
* 更新云端最新图书信息并缓存到本地
|
||||
*/
|
||||
suspend fun updateInfo() = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
var isDownload = false
|
||||
val data: String = if (loadCache) {
|
||||
name?.let { loadInfo(it) } ?: run {
|
||||
isDownload = true
|
||||
Config.myHostApiUrl.get(mBookApiUrl)
|
||||
}
|
||||
} else {
|
||||
var isDownload = false
|
||||
val data: String = if (loadCache) {
|
||||
name?.let { loadInfo(it) } ?: run {
|
||||
isDownload = true
|
||||
Config.myHostApiUrl.get(mBookApiUrl)
|
||||
Config.api.get(mBookApiUrl)
|
||||
}
|
||||
mBook = Gson().fromJson(data, BookInfoStructure::class.java)
|
||||
if (isDownload) saveInfo(data)
|
||||
mGroupPathWords = arrayOf()
|
||||
mKeys = arrayOf()
|
||||
mCounts = intArrayOf()
|
||||
mBook?.results?.groups?.values?.forEach {
|
||||
mKeys += it.name
|
||||
mGroupPathWords += it.path_word
|
||||
if (it.count == 0) {
|
||||
it.count = 1
|
||||
}
|
||||
mCounts += it.count
|
||||
Log.d("MyB", "Add caption: ${it.name} @ ${it.path_word} of ${it.count}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
MainActivity.mainWeakReference?.get()?.apply {
|
||||
withContext(Dispatchers.Main) {
|
||||
runOnUiThread { Toast.makeText(this@apply, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show() }
|
||||
}
|
||||
} else {
|
||||
isDownload = true
|
||||
Config.api.get(mBookApiUrl)
|
||||
}
|
||||
mBook = Gson().fromJson(data, BookInfoStructure::class.java)
|
||||
if (isDownload) saveInfo(data)
|
||||
mGroupPathWords = arrayOf()
|
||||
mKeys = arrayOf()
|
||||
mCounts = intArrayOf()
|
||||
mBook?.results?.groups?.values?.forEach {
|
||||
mKeys += it.name
|
||||
mGroupPathWords += it.path_word
|
||||
if (it.count == 0) {
|
||||
it.count = 1
|
||||
}
|
||||
mCounts += it.count
|
||||
Log.d("MyB", "Add caption: ${it.name} @ ${it.path_word} of ${it.count}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class Shelf(private val getString: (Int) -> String) {
|
||||
append("")
|
||||
append(Config.token.value)
|
||||
}
|
||||
val re = Config.myHostApiUrl.request(
|
||||
val re = Config.api.request(
|
||||
addApiUrl, body.encodeToByteArray(), "POST",
|
||||
"application/x-www-form-urlencoded;charset=utf-8")
|
||||
Gson().fromJson(re, ReturnBase::class.java).message
|
||||
@@ -43,7 +43,7 @@ class Shelf(private val getString: (Int) -> String) {
|
||||
append("authorization=Token+")
|
||||
append(Config.token.value)
|
||||
}
|
||||
val re = Config.myHostApiUrl.request(
|
||||
val re = Config.api.request(
|
||||
delApiUrl, body.encodeToByteArray(),
|
||||
"DELETE", "application/x-www-form-urlencoded;charset=utf-8")
|
||||
Gson().fromJson(re, ReturnBase::class.java).message
|
||||
@@ -51,7 +51,7 @@ class Shelf(private val getString: (Int) -> String) {
|
||||
|
||||
suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) {
|
||||
val queryUrl = queryApiUrlTemplate.format(pathWord, Config.platform.value)
|
||||
Config.myHostApiUrl.get(queryUrl).let {
|
||||
Config.api.get(queryUrl).let {
|
||||
Gson().fromJson(it, BookQueryStructure::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,18 +50,14 @@ class Api {
|
||||
e.printStackTrace()
|
||||
mHostApiUrls = mutableListOf(networkApiUrl.value)
|
||||
withContext(Dispatchers.Main) {
|
||||
runOnUiThread {
|
||||
Toast.makeText(this@apply, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Toast.makeText(this@apply, "${e::class.simpleName} ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
if (mHostApiUrls.isEmpty()) {
|
||||
mHostApiUrls = mutableListOf(networkApiUrl.value)
|
||||
Log.d("MyApi", "myHostApiUrl set default ${mHostApiUrls[0]}")
|
||||
withContext(Dispatchers.Main) {
|
||||
runOnUiThread {
|
||||
Toast.makeText(this@apply, "无法获取API列表", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Toast.makeText(this@apply, "无法获取API列表", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,22 +73,12 @@ class Api {
|
||||
var r: ReturnBase? = null
|
||||
apis.forEachIndexed { i, api ->
|
||||
val u = "https://$api$path"
|
||||
var ret = ""
|
||||
try {
|
||||
val ret = (apiProxy?.comancry(u) {
|
||||
ret = (apiProxy?.comancry(u) {
|
||||
DownloadTools.getApiContent(it)
|
||||
}?: DownloadTools.getApiContent(u)).decodeToString()
|
||||
r = Gson().fromJson(ret, ReturnBase::class.java)
|
||||
if (r!!.code != 200) {
|
||||
withContext(Dispatchers.Main) {
|
||||
MainActivity.mainWeakReference?.get()?.apply {
|
||||
runOnUiThread {
|
||||
Toast.makeText(this, "错误码${r?.code?:-1}, 信息: ${r?.message?:"空"}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ret
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
mu.withLock {
|
||||
if (mHostApiUrls.size <= 1) return@withLock
|
||||
@@ -102,11 +88,19 @@ class Api {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
r?.let {
|
||||
if (it.code != 200) {
|
||||
throw IllegalArgumentException("错误码${it.code}, 信息: ${it.message?:"空"}")
|
||||
} else {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
throw IllegalStateException("错误码${r?.code?:-1}, 信息: ${r?.message?:"空"}")
|
||||
throw NoSuchElementException("无可用API")
|
||||
}
|
||||
|
||||
// request throw error on non-json or non-200 or empty apis, path: /api/v3/xxx, return json string
|
||||
suspend fun request(path: String, body: ByteArray, method: String, contentType: String, forceApi: String? = null): String {
|
||||
suspend fun request(path: String, body: ByteArray, method: String, contentType: String, forceApi: String? = null): String {
|
||||
val apis = if (forceApi == null) mu.withLock { mHostApiUrls } else mutableListOf(forceApi)
|
||||
if (apis.isEmpty()) {
|
||||
throw NoSuchElementException("API列表为空")
|
||||
@@ -114,22 +108,12 @@ class Api {
|
||||
var r: ReturnBase? = null
|
||||
apis.forEachIndexed { i, api ->
|
||||
val u = "https://$api$path"
|
||||
var ret = ""
|
||||
try {
|
||||
val ret = (apiProxy?.comancry(u) {
|
||||
ret = (apiProxy?.comancry(u) {
|
||||
DownloadTools.requestApiWithBody(u, method, body, contentType)
|
||||
}?: DownloadTools.requestApiWithBody(u, method, body, contentType)).decodeToString()
|
||||
r = Gson().fromJson(ret, ReturnBase::class.java)
|
||||
if (r!!.code != 200) {
|
||||
withContext(Dispatchers.Main) {
|
||||
MainActivity.mainWeakReference?.get()?.apply {
|
||||
runOnUiThread {
|
||||
Toast.makeText(this, "错误码${r?.code?:-1}, 信息: ${r?.message?:"空"}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ret
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
mu.withLock {
|
||||
@@ -140,7 +124,14 @@ class Api {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
r?.let {
|
||||
if (it.code != 200) {
|
||||
throw IllegalArgumentException("错误码${it.code}, 信息: ${it.message?:"空"}")
|
||||
} else {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
throw IllegalStateException("错误码${r?.code?:-1}, 信息: ${r?.message?:"空"}")
|
||||
throw NoSuchElementException("无可用API")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,15 @@ class Member(private val getString: (Int) -> String) {
|
||||
* - **code**: 449: 未登录, 450: 有 Exception
|
||||
* - **message**: 可以 toast 的信息
|
||||
*/
|
||||
suspend fun info(): LoginInfoStructure = withContext(Dispatchers.IO) {
|
||||
suspend fun info(): LoginInfoStructure {
|
||||
if (!hasLogin) {
|
||||
throw IllegalArgumentException(getString(R.string.noLogin))
|
||||
}
|
||||
val u = getString(R.string.memberInfoApiUrl)
|
||||
.format(Config.platform.value)
|
||||
val l = Gson().fromJson(Config.myHostApiUrl.get(u), LoginInfoStructure::class.java)
|
||||
if (l.code == 200) Config.avatar.value = l.results.avatar
|
||||
l
|
||||
val l = Gson().fromJson(Config.api.get(u), LoginInfoStructure::class.java)
|
||||
if (l.code == 200) Config.avatar.value = l.results.avatar // must be true
|
||||
return l
|
||||
}
|
||||
|
||||
suspend fun logout() = withContext(Dispatchers.IO) {
|
||||
@@ -43,7 +43,7 @@ class Member(private val getString: (Int) -> String) {
|
||||
Config.avatar.value = null
|
||||
}
|
||||
|
||||
private suspend fun saveInfo(data: ByteArray) = data.inputStream().use { dataIn ->
|
||||
private fun saveInfo(data: ByteArray) = data.inputStream().use { dataIn ->
|
||||
try {
|
||||
Gson().fromJson(dataIn.reader(), LoginInfoStructure::class.java)?.let { l ->
|
||||
if (l.code == 200) {
|
||||
@@ -51,9 +51,8 @@ class Member(private val getString: (Int) -> String) {
|
||||
Config.user_id.value = l.results?.user_id
|
||||
Config.username.value = l.results?.username
|
||||
Config.nickname.value = l.results.nickname
|
||||
return@use info()
|
||||
}
|
||||
return@use l
|
||||
l
|
||||
} ?: throw Exception(getString(R.string.login_parse_json_error))
|
||||
} catch (e: JsonSyntaxException) {
|
||||
throw JsonSyntaxException(data.decodeToString(), e)
|
||||
@@ -65,7 +64,7 @@ class Member(private val getString: (Int) -> String) {
|
||||
val r = if (!Config.net_use_foreign.value) "1" else "0"
|
||||
val pwdEncoded =
|
||||
Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString()
|
||||
Config.myHostApiUrl.request(u, "username=${
|
||||
Config.api.request(u, "username=${
|
||||
URLEncoder.encode(
|
||||
username,
|
||||
Charset.defaultCharset().name()
|
||||
|
||||
@@ -6,10 +6,12 @@ import com.google.gson.Gson
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import top.fumiama.copymanga.api.Config
|
||||
import top.fumiama.copymanga.api.Config.proxyUrl
|
||||
import top.fumiama.copymanga.json.ComandyCapsule
|
||||
import top.fumiama.copymanga.lib.Comandy
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
@@ -146,6 +148,19 @@ object DownloadTools {
|
||||
} else ret
|
||||
}
|
||||
|
||||
private fun parseErrorResponse(body: String): String {
|
||||
return try {
|
||||
val json = JSONObject(body)
|
||||
if (json.has("detail")) {
|
||||
json.getString("detail")
|
||||
} else {
|
||||
body // 原始 JSON 返回
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
"非JSON返回: $body"
|
||||
}
|
||||
}
|
||||
|
||||
private fun InputStream.readBytesWithProgress(sz: Int, p: Client.Progress): ByteArray {
|
||||
val buffer = ByteArrayOutputStream(maxOf(DEFAULT_BUFFER_SIZE, this.available()))
|
||||
copyToWithProgress(buffer, sz, p)
|
||||
@@ -184,7 +199,7 @@ object DownloadTools {
|
||||
//Log.d("MyDT", "comandy reply: $result")
|
||||
Gson().fromJson(result, ComandyCapsule::class.java)!!.let {
|
||||
if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${
|
||||
it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() }
|
||||
it.data?.let { d -> parseErrorResponse(Base64.decode(d, Base64.DEFAULT).decodeToString()) }
|
||||
}")
|
||||
coding = it.headers["Content-Encoding"] as String?
|
||||
Base64.decode(it.data, Base64.DEFAULT)
|
||||
@@ -198,10 +213,14 @@ object DownloadTools {
|
||||
}
|
||||
getApiConnection(u, "GET").let { conn ->
|
||||
val sz = conn.getHeaderFieldInt("Content-Length", 0)
|
||||
if (conn.responseCode >= 400)
|
||||
throw IllegalArgumentException("HTTP${conn.responseCode} " +
|
||||
parseErrorResponse(conn.errorStream.bufferedReader().use(BufferedReader::readText))
|
||||
)
|
||||
val ret = if (sz > 0 && p != null) {
|
||||
conn.inputStream.readBytesWithProgress(sz, p)
|
||||
conn.inputStream.use { it.readBytesWithProgress(sz, p) }
|
||||
} else {
|
||||
conn.inputStream.readBytes()
|
||||
conn.inputStream.use(InputStream::readBytes)
|
||||
}
|
||||
conn.disconnect()
|
||||
Log.d("MyDT", "getHttpContent: ${ret.size} bytes")
|
||||
@@ -255,7 +274,9 @@ object DownloadTools {
|
||||
completed = true
|
||||
p?.notify(100)
|
||||
Gson().fromJson(result, ComandyCapsule::class.java)?.let {
|
||||
if (it.code != 200) null
|
||||
if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${
|
||||
it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() }
|
||||
}")
|
||||
else it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) }
|
||||
}
|
||||
}
|
||||
@@ -272,17 +293,17 @@ object DownloadTools {
|
||||
var ret: ByteArray? = null
|
||||
try {
|
||||
val connection = getNormalConnection(u, "GET", Config.pc_ua)
|
||||
val ci = connection.inputStream
|
||||
val sz = connection.getHeaderFieldInt("Content-Length", 0)
|
||||
if(readSize > 0) {
|
||||
ret = ByteArray(readSize)
|
||||
ci?.read(ret, 0, readSize)
|
||||
} else ret = if (sz > 0 && p != null) {
|
||||
ci.readBytesWithProgress(sz, p)
|
||||
} else {
|
||||
ci.readBytes()
|
||||
connection.inputStream.use { ci ->
|
||||
if(readSize > 0) {
|
||||
ret = ByteArray(readSize)
|
||||
ci.read(ret, 0, readSize)
|
||||
} else ret = if (sz > 0 && p != null) {
|
||||
ci.readBytesWithProgress(sz, p)
|
||||
} else {
|
||||
ci.readBytes()
|
||||
}
|
||||
}
|
||||
ci?.close()
|
||||
connection.disconnect()
|
||||
} catch (ex: Exception) {
|
||||
ex.printStackTrace()
|
||||
@@ -313,7 +334,11 @@ object DownloadTools {
|
||||
var coding = ""
|
||||
getApiConnection(url, method).apply {
|
||||
outputStream.write(body)
|
||||
ret = inputStream.readBytes()
|
||||
if (responseCode >= 400)
|
||||
throw IllegalArgumentException("HTTP${responseCode} " +
|
||||
parseErrorResponse(errorStream.bufferedReader().use(BufferedReader::readText))
|
||||
)
|
||||
ret = inputStream.use(InputStream::readBytes)
|
||||
disconnect()
|
||||
coding = getHeaderField("Content-Encoding")?:""
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ open class AutoDownloadHandler(
|
||||
var cnt = 0
|
||||
while (cnt++ <= 3) {
|
||||
try {
|
||||
val data = Config.myHostApiUrl.get(url())
|
||||
val data = Config.api.get(url())
|
||||
if(exit) return@withContext
|
||||
val pass = setGsonItem(Gson().fromJson(data, jsonClass))
|
||||
if (pass && loadFromCache) {
|
||||
|
||||
@@ -13,7 +13,7 @@ class PausableDownloader(private val url: String, private val waitMilliseconds:
|
||||
var c = 0
|
||||
while (!exit && c++ < 3) {
|
||||
try {
|
||||
val data = Config.myHostApiUrl.get(url)
|
||||
val data = Config.api.get(url)
|
||||
whenFinish?.let { it(data.encodeToByteArray()) }
|
||||
return@withContext true
|
||||
} catch (e: Exception) {
|
||||
|
||||
290
app/src/main/java/top/fumiama/copymanga/storage/DataLoader.kt
Normal file
290
app/src/main/java/top/fumiama/copymanga/storage/DataLoader.kt
Normal file
@@ -0,0 +1,290 @@
|
||||
package top.fumiama.copymanga.storage
|
||||
|
||||
import top.fumiama.copymanga.api.Config
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.zip.CRC32
|
||||
|
||||
class DataLoader {
|
||||
data class Settings(
|
||||
var appVer: String,
|
||||
var platform: String,
|
||||
var generalEnableTransparentSystemBar: Boolean,
|
||||
var generalDisableKanbanAnimation: Boolean,
|
||||
var generalCardPerRow: Int,
|
||||
|
||||
var mangaDlMaxBatch: Int,
|
||||
var mangaDlShow0mManga: Boolean,
|
||||
|
||||
var reverseProxyUrl: String,
|
||||
var networkApiUrl: String,
|
||||
var proxyKey: String,
|
||||
|
||||
var netUseGzip: Boolean,
|
||||
var netUseJson: Boolean,
|
||||
var netPlatform: Boolean,
|
||||
var netReferer: Boolean,
|
||||
var netVersion: Boolean,
|
||||
var netRegion: Boolean,
|
||||
var netNoWebp: Boolean,
|
||||
var netUseComandy: Boolean,
|
||||
var netUseForeign: Boolean,
|
||||
var netUseImgProxy: Boolean,
|
||||
var netUseApiProxy: Boolean,
|
||||
|
||||
var netImgResolution: String,
|
||||
var netUmstring: String,
|
||||
var netSource: String,
|
||||
var netUa: String,
|
||||
|
||||
var viewMangaInverseChapters: Boolean,
|
||||
var viewMangaAlwaysDarkBg: Boolean,
|
||||
var viewMangaVerticalMax: Int,
|
||||
var viewMangaQuality: Int,
|
||||
var viewMangaVolTurn: Boolean,
|
||||
var viewMangaUseCellar: Boolean,
|
||||
var viewMangaHideInfo: Boolean,
|
||||
) {
|
||||
fun export() {
|
||||
Config.app_ver.value = appVer
|
||||
Config.platform.value = platform
|
||||
Config.general_enable_transparent_system_bar.value = generalEnableTransparentSystemBar
|
||||
Config.general_disable_kanban_animation.value = generalDisableKanbanAnimation
|
||||
Config.general_card_per_row.value = generalCardPerRow
|
||||
|
||||
Config.manga_dl_max_batch.value = mangaDlMaxBatch
|
||||
Config.manga_dl_show_0m_manga.value = mangaDlShow0mManga
|
||||
|
||||
Config.reverseProxyUrl.value = reverseProxyUrl
|
||||
Config.networkApiUrl.value = networkApiUrl
|
||||
Config.proxy_key.value = proxyKey
|
||||
|
||||
Config.net_use_gzip.value = netUseGzip
|
||||
Config.net_use_json.value = netUseJson
|
||||
Config.net_platform.value = netPlatform
|
||||
Config.net_referer.value = netReferer
|
||||
Config.net_version.value = netVersion
|
||||
Config.net_region.value = netRegion
|
||||
Config.net_no_webp.value = netNoWebp
|
||||
Config.net_use_comandy.value = netUseComandy
|
||||
Config.net_use_foreign.value = netUseForeign
|
||||
Config.net_use_img_proxy.value = netUseImgProxy
|
||||
Config.net_use_api_proxy.value = netUseApiProxy
|
||||
|
||||
Config.net_img_resolution.value = netImgResolution
|
||||
Config.net_umstring.value = netUmstring
|
||||
Config.net_source.value = netSource
|
||||
Config.net_ua.value = netUa
|
||||
|
||||
Config.view_manga_inverse_chapters.value = viewMangaInverseChapters
|
||||
Config.view_manga_always_dark_bg.value = viewMangaAlwaysDarkBg
|
||||
Config.view_manga_vertical_max.value = viewMangaVerticalMax
|
||||
Config.view_manga_quality.value = viewMangaQuality
|
||||
Config.view_manga_vol_turn.value = viewMangaVolTurn
|
||||
Config.view_manga_use_cellar.value = viewMangaUseCellar
|
||||
Config.view_manga_hide_info.value = viewMangaHideInfo
|
||||
}
|
||||
}
|
||||
|
||||
val settings: Settings
|
||||
|
||||
constructor(): this(Settings(
|
||||
appVer = Config.app_ver.value,
|
||||
platform = Config.platform.value,
|
||||
generalEnableTransparentSystemBar = Config.general_enable_transparent_system_bar.value,
|
||||
generalDisableKanbanAnimation = Config.general_disable_kanban_animation.value,
|
||||
generalCardPerRow = Config.general_card_per_row.value,
|
||||
|
||||
mangaDlMaxBatch = Config.manga_dl_max_batch.value,
|
||||
mangaDlShow0mManga = Config.manga_dl_show_0m_manga.value,
|
||||
|
||||
reverseProxyUrl = Config.reverseProxyUrl.value,
|
||||
networkApiUrl = Config.networkApiUrl.value,
|
||||
proxyKey = Config.proxy_key.value,
|
||||
|
||||
netUseGzip = Config.net_use_gzip.value,
|
||||
netUseJson = Config.net_use_json.value,
|
||||
netPlatform = Config.net_platform.value,
|
||||
netReferer = Config.net_referer.value,
|
||||
netVersion = Config.net_version.value,
|
||||
netRegion = Config.net_region.value,
|
||||
netNoWebp = Config.net_no_webp.value,
|
||||
netUseComandy = Config.net_use_comandy.value,
|
||||
netUseForeign = Config.net_use_foreign.value,
|
||||
netUseImgProxy = Config.net_use_img_proxy.value,
|
||||
netUseApiProxy = Config.net_use_api_proxy.value,
|
||||
|
||||
netImgResolution = Config.net_img_resolution.value,
|
||||
netUmstring = Config.net_umstring.value,
|
||||
netSource = Config.net_source.value,
|
||||
netUa = Config.net_ua.value,
|
||||
|
||||
viewMangaInverseChapters = Config.view_manga_inverse_chapters.value,
|
||||
viewMangaAlwaysDarkBg = Config.view_manga_always_dark_bg.value,
|
||||
viewMangaVerticalMax = Config.view_manga_vertical_max.value,
|
||||
viewMangaQuality = Config.view_manga_quality.value,
|
||||
viewMangaVolTurn = Config.view_manga_vol_turn.value,
|
||||
viewMangaUseCellar = Config.view_manga_use_cellar.value,
|
||||
viewMangaHideInfo = Config.view_manga_hide_info.value,
|
||||
))
|
||||
|
||||
constructor(settings: Settings) {
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
constructor(data: ByteArray) {
|
||||
if (data.size < 4) throw IllegalArgumentException("Data too short for CRC")
|
||||
val payload = data.copyOfRange(0, data.size - 4)
|
||||
val expectedCrc = ByteBuffer.wrap(data, data.size - 4, 4).int
|
||||
val actualCrc = CRC32().apply { update(payload) }.value.toInt()
|
||||
if (expectedCrc != actualCrc) throw IllegalArgumentException("CRC32 check failed")
|
||||
|
||||
val buffer = ByteBuffer.wrap(payload)
|
||||
|
||||
val netFlags = buffer.short.toInt() and 0xFFFF
|
||||
val miscFlags = buffer.get().toInt() and 0xFF
|
||||
fun bit(value: Int, pos: Int) = ((value shr pos) and 1) != 0
|
||||
|
||||
val generalCardPerRow = buffer.int
|
||||
val mangaDlMaxBatch = buffer.int
|
||||
val viewMangaVerticalMax = buffer.int
|
||||
val viewMangaQuality = buffer.int
|
||||
|
||||
fun readString(): String {
|
||||
val len = buffer.short.toInt()
|
||||
val bytes = ByteArray(len)
|
||||
buffer.get(bytes)
|
||||
return String(bytes, Charsets.UTF_8)
|
||||
}
|
||||
|
||||
settings = Settings(
|
||||
appVer = readString(),
|
||||
platform = readString(),
|
||||
generalEnableTransparentSystemBar = bit(miscFlags, 7),
|
||||
generalDisableKanbanAnimation = bit(miscFlags, 6),
|
||||
generalCardPerRow = generalCardPerRow,
|
||||
|
||||
mangaDlMaxBatch = mangaDlMaxBatch,
|
||||
mangaDlShow0mManga = bit(miscFlags, 5),
|
||||
|
||||
reverseProxyUrl = readString(),
|
||||
networkApiUrl = readString(),
|
||||
proxyKey = readString(),
|
||||
|
||||
netUseGzip = bit(netFlags, 15),
|
||||
netUseJson = bit(netFlags, 14),
|
||||
netPlatform = bit(netFlags, 13),
|
||||
netReferer = bit(netFlags, 12),
|
||||
netVersion = bit(netFlags, 11),
|
||||
netRegion = bit(netFlags, 10),
|
||||
netNoWebp = bit(netFlags, 9),
|
||||
netUseComandy = bit(netFlags, 8),
|
||||
netUseForeign = bit(netFlags, 7),
|
||||
netUseImgProxy = bit(netFlags, 6),
|
||||
netUseApiProxy = bit(netFlags, 5),
|
||||
|
||||
netImgResolution = readString(),
|
||||
netUmstring = readString(),
|
||||
netSource = readString(),
|
||||
netUa = readString(),
|
||||
|
||||
viewMangaInverseChapters = bit(miscFlags, 4),
|
||||
viewMangaAlwaysDarkBg = bit(miscFlags, 3),
|
||||
viewMangaVerticalMax = viewMangaVerticalMax,
|
||||
viewMangaQuality = viewMangaQuality,
|
||||
viewMangaVolTurn = bit(miscFlags, 2),
|
||||
viewMangaUseCellar = bit(miscFlags, 1),
|
||||
viewMangaHideInfo = bit(miscFlags, 0),
|
||||
)
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray {
|
||||
val strFields = listOf(
|
||||
settings.appVer, settings.platform,
|
||||
settings.reverseProxyUrl, settings.networkApiUrl, settings.proxyKey,
|
||||
settings.netImgResolution, settings.netUmstring, settings.netSource, settings.netUa
|
||||
)
|
||||
val strBytes = strFields.map { it.toByteArray(Charsets.UTF_8) }
|
||||
|
||||
val netFlags = wrapBooleans(
|
||||
settings.netUseGzip,
|
||||
settings.netUseJson,
|
||||
settings.netPlatform,
|
||||
settings.netReferer,
|
||||
settings.netVersion,
|
||||
settings.netRegion,
|
||||
settings.netNoWebp,
|
||||
settings.netUseComandy,
|
||||
settings.netUseForeign,
|
||||
settings.netUseImgProxy,
|
||||
settings.netUseApiProxy,
|
||||
false, false, false, false, false
|
||||
)
|
||||
val miscFlags = wrapBooleans(
|
||||
settings.generalEnableTransparentSystemBar,
|
||||
settings.generalDisableKanbanAnimation,
|
||||
settings.mangaDlShow0mManga,
|
||||
settings.viewMangaInverseChapters,
|
||||
settings.viewMangaAlwaysDarkBg,
|
||||
settings.viewMangaVolTurn,
|
||||
settings.viewMangaUseCellar,
|
||||
settings.viewMangaHideInfo
|
||||
)
|
||||
|
||||
val strTotal = strBytes.sumOf { 2 + it.size }
|
||||
val buffer = ByteBuffer.allocate(
|
||||
2 + // netFlags: UShort (16 bits)
|
||||
1 + // miscFlags: UByte (8 bits)
|
||||
4 * 4 + // Ints: 4 fields x 4 bytes
|
||||
strTotal + // all string fields with 2-byte length prefix
|
||||
4 // CRC32
|
||||
)
|
||||
buffer.putShort(netFlags.toShort())
|
||||
buffer.put(miscFlags.toByte())
|
||||
buffer.putInt(settings.generalCardPerRow)
|
||||
buffer.putInt(settings.mangaDlMaxBatch)
|
||||
buffer.putInt(settings.viewMangaVerticalMax)
|
||||
buffer.putInt(settings.viewMangaQuality)
|
||||
for (b in strBytes) {
|
||||
buffer.putShort(b.size.toShort())
|
||||
buffer.put(b)
|
||||
}
|
||||
|
||||
val payload = buffer.array().copyOfRange(0, buffer.position())
|
||||
val crc = CRC32().apply { update(payload) }.value
|
||||
val finalBuffer = ByteBuffer.allocate(payload.size + 4)
|
||||
finalBuffer.put(payload)
|
||||
finalBuffer.putInt(crc.toInt())
|
||||
|
||||
return finalBuffer.array()
|
||||
}
|
||||
|
||||
private fun wrapBooleans(
|
||||
b0: Boolean, b1: Boolean, b2: Boolean, b3: Boolean,
|
||||
b4: Boolean, b5: Boolean, b6: Boolean, b7: Boolean
|
||||
): UByte {
|
||||
var result: UByte = 0u
|
||||
val flags = listOf(b0, b1, b2, b3, b4, b5, b6, b7)
|
||||
for ((i, flag) in flags.withIndex()) {
|
||||
if (flag) result = result or (1u shl (7 - i)).toUByte()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun wrapBooleans(
|
||||
b0: Boolean, b1: Boolean, b2: Boolean, b3: Boolean,
|
||||
b4: Boolean, b5: Boolean, b6: Boolean, b7: Boolean,
|
||||
b8: Boolean, b9: Boolean, b10: Boolean, b11: Boolean,
|
||||
b12: Boolean, b13: Boolean, b14: Boolean, b15: Boolean
|
||||
): UShort {
|
||||
var result: UShort = 0u
|
||||
val flags = listOf(
|
||||
b0, b1, b2, b3, b4, b5, b6, b7,
|
||||
b8, b9, b10, b11, b12, b13, b14, b15
|
||||
)
|
||||
for ((i, flag) in flags.withIndex()) {
|
||||
if (flag) result = result or (1u shl (15 - i)).toUShort()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,30 @@
|
||||
package top.fumiama.copymanga.storage
|
||||
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
|
||||
data class PreferenceBoolean(private val key: String, private var default: Boolean) {
|
||||
val value: Boolean
|
||||
var value: Boolean = default
|
||||
get() {
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
getBoolean(key, default).let { v ->
|
||||
getBoolean(key, field).let { v ->
|
||||
Log.d("MyPB", "get key $key value $v")
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return default
|
||||
return field
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
Log.d("MyUPI", "set key $key value $value")
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
edit(commit = true) { putBoolean(key, value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
package top.fumiama.copymanga.storage
|
||||
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
|
||||
data class PreferenceInt(private val key: String, private var default: Int) {
|
||||
val value: Int
|
||||
var value: Int = default
|
||||
get() {
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
getInt(key, default).let { v ->
|
||||
getInt(key, field).let { v ->
|
||||
Log.d("MyPI", "get key $key value $v")
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return default
|
||||
return field
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
Log.d("MyUPI", "set key $key value $value")
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
edit(commit = true) { putInt(key, value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package top.fumiama.copymanga.storage
|
||||
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
|
||||
@@ -20,7 +21,7 @@ data class PreferenceString(private val key: String, private var default: String
|
||||
}
|
||||
return default?:""
|
||||
}
|
||||
val value: String
|
||||
var value: String = defaultField
|
||||
get() {
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
@@ -32,7 +33,16 @@ data class PreferenceString(private val key: String, private var default: String
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d("MyPS", "get default key $key value $defaultField")
|
||||
return defaultField
|
||||
Log.d("MyPS", "get default key $key value $field")
|
||||
return field
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
Log.d("MyUPI", "set key $key value $value")
|
||||
MainActivity.mainWeakReference?.get()?.let {
|
||||
PreferenceManager.getDefaultSharedPreferences(it).apply {
|
||||
edit(commit = true) { putString(key, value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package top.fumiama.copymanga.storage
|
||||
//PropertiesTools.kt
|
||||
//created by fumiama 20200724
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
|
||||
class PropertiesTools(private val f: File):Properties() {
|
||||
private var cache = hashMapOf<String, String>()
|
||||
|
||||
init {
|
||||
if(!f.exists()) {
|
||||
if(f.parentFile?.exists() != true) f.parentFile?.mkdirs()
|
||||
if(f.parentFile?.canWrite() != true) f.parentFile?.setWritable(true)
|
||||
createNew(f)
|
||||
} else if(f.isDirectory) {
|
||||
if(f.parentFile?.canWrite() != true) f.parentFile?.setWritable(true)
|
||||
f.delete()
|
||||
createNew(f)
|
||||
}
|
||||
if(f.parentFile?.canWrite() != true) f.parentFile?.setWritable(true)
|
||||
if(f.parentFile?.canRead() != true) f.parentFile?.setReadable(true)
|
||||
}
|
||||
|
||||
private fun createNew(f: File) {
|
||||
f.createNewFile()
|
||||
f.outputStream().use { o ->
|
||||
this.storeToXML(o, "store")
|
||||
}
|
||||
Log.d("MyPT", "Generate new prop.")
|
||||
}
|
||||
|
||||
private fun loadFromXml(`in`: InputStream?): PropertiesTools {
|
||||
this.loadFromXML(`in`)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun setProp(key: String?, value: String?): PropertiesTools {
|
||||
this.setProperty(key, value)
|
||||
return this
|
||||
}
|
||||
|
||||
operator fun get(key: String): String{
|
||||
return if(cache.containsKey(key)) cache[key]?:"null"
|
||||
else {
|
||||
f.inputStream().use { i ->
|
||||
val re = this.loadFromXml(i).getProperty(key)?:"null"
|
||||
Log.d("MyPT", "Read $key = $re")
|
||||
cache[key] = re
|
||||
re
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun set(key: String, value: String) {
|
||||
cache[key] = value
|
||||
f.outputStream().use { o ->
|
||||
this.setProp(key, value).storeToXML(o, "store")
|
||||
}
|
||||
Log.d("MyPT", "Set $key = $value")
|
||||
}
|
||||
}
|
||||
73
app/src/main/java/top/fumiama/copymanga/strings/Base16384.kt
Normal file
73
app/src/main/java/top/fumiama/copymanga/strings/Base16384.kt
Normal file
@@ -0,0 +1,73 @@
|
||||
package top.fumiama.copymanga.strings
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
|
||||
object Base16384 {
|
||||
private const val BASE = 16384
|
||||
private const val FIRST_CHAR = 0x4E00
|
||||
private const val PAD_START = 0x3D00
|
||||
|
||||
fun encode(input: ByteArray): String {
|
||||
val sb = StringBuilder()
|
||||
var buffer = 0
|
||||
var bitCount = 0
|
||||
|
||||
for (b in input) {
|
||||
buffer = (buffer shl 8) or (b.toInt() and 0xFF)
|
||||
bitCount += 8
|
||||
while (bitCount >= 14) {
|
||||
bitCount -= 14
|
||||
val index = (buffer shr bitCount) and (BASE - 1)
|
||||
sb.append(Char(FIRST_CHAR + index))
|
||||
buffer = buffer and ((1 shl bitCount) - 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (bitCount in 1..6) {
|
||||
buffer = (buffer shl (14 - bitCount))
|
||||
val index = buffer and (BASE - 1)
|
||||
sb.append(Char(FIRST_CHAR + index))
|
||||
val padIndex = PAD_START + bitCount
|
||||
sb.append(Char(padIndex))
|
||||
}
|
||||
|
||||
Log.d("MyB14", "encode bitCount $bitCount, len ${input.size}, data: ${Base64.encode(input, Base64.DEFAULT).decodeToString()}")
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
fun decode(input: String): ByteArray {
|
||||
val bits = mutableListOf<Boolean>()
|
||||
var tailBits = 0
|
||||
for (c in input) {
|
||||
when (val code = c.code) {
|
||||
in FIRST_CHAR until FIRST_CHAR + BASE -> {
|
||||
val idx = code - FIRST_CHAR
|
||||
for (i in 13 downTo 0) {
|
||||
bits.add(((idx shr i) and 1) == 1)
|
||||
}
|
||||
}
|
||||
in (PAD_START + 1)..(PAD_START + 6) -> {
|
||||
tailBits = code - PAD_START
|
||||
break
|
||||
}
|
||||
else -> throw IllegalArgumentException("Invalid base16384 char: $c")
|
||||
}
|
||||
}
|
||||
|
||||
val totalBits = bits.size - (14 - tailBits)
|
||||
val byteCount = totalBits / 8
|
||||
val out = ByteArray(byteCount)
|
||||
for (i in 0 until byteCount) {
|
||||
var byteVal = 0
|
||||
for (j in 0 until 8) {
|
||||
if (bits[i * 8 + j]) byteVal = byteVal or (1 shl (7 - j))
|
||||
}
|
||||
out[i] = byteVal.toByte()
|
||||
}
|
||||
|
||||
Log.d("MyB14", "decode totalBits $totalBits, tailBits $tailBits, len ${out.size}, data: ${Base64.encode(out, Base64.DEFAULT).decodeToString()}")
|
||||
return out
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,8 @@ object Chinese {
|
||||
/**
|
||||
* 简单检测字符串是否包含常见 CJK(中日韩)汉字。
|
||||
*/
|
||||
fun containsChinese(text: String): Boolean {
|
||||
private fun containsChinese(text: String): Boolean {
|
||||
val regex = Regex("[\u4E00-\u9FFF]")
|
||||
return regex.containsMatchIn(text)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.app_bar_main.*
|
||||
import kotlinx.android.synthetic.main.card_book.*
|
||||
import kotlinx.android.synthetic.main.fragment_book.*
|
||||
@@ -57,8 +58,9 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
if(mBookHandler?.exit != false) return@launch
|
||||
Toast.makeText(context, R.string.null_book, Toast.LENGTH_SHORT).show()
|
||||
findNavController().popBackStack()
|
||||
Snackbar.make(view, "${e::class.simpleName} ${e.message}", Snackbar.LENGTH_LONG).setTextMaxLines(10).setDuration(10000).show()
|
||||
// Toast.makeText(context, R.string.null_book, Toast.LENGTH_SHORT).show()
|
||||
// findNavController().popBackStack()
|
||||
return@launch
|
||||
}
|
||||
Log.d("MyBF", "read path: ${book?.path}")
|
||||
@@ -196,9 +198,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
activity?.runOnUiThread {
|
||||
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,7 +258,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageButton
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.Gson
|
||||
import com.lapism.search.internal.SearchLayout
|
||||
import kotlinx.android.synthetic.main.card_book_plain.view.*
|
||||
@@ -29,12 +29,12 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import top.fumiama.copymanga.MainActivity
|
||||
import top.fumiama.copymanga.MainActivity.Companion.ime
|
||||
import top.fumiama.copymanga.json.BookListStructure
|
||||
import top.fumiama.copymanga.view.template.NoBackRefreshFragment
|
||||
import top.fumiama.copymanga.net.template.PausableDownloader
|
||||
import top.fumiama.copymanga.api.Config
|
||||
import top.fumiama.copymanga.view.operation.GlideHideLottieViewListener
|
||||
import top.fumiama.copymanga.json.BookListStructure
|
||||
import top.fumiama.copymanga.net.template.PausableDownloader
|
||||
import top.fumiama.copymanga.view.interaction.Navigate
|
||||
import top.fumiama.copymanga.view.operation.GlideHideLottieViewListener
|
||||
import top.fumiama.copymanga.view.template.NoBackRefreshFragment
|
||||
import top.fumiama.dmzj.copymanga.R
|
||||
import java.lang.ref.WeakReference
|
||||
import java.net.URLEncoder
|
||||
@@ -51,18 +51,15 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
|
||||
val netInfo = tb.netInfo
|
||||
if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError)
|
||||
MainActivity.member?.apply { lifecycleScope.launch {
|
||||
Config.myHostApiUrl.init()
|
||||
try {
|
||||
info().let { l ->
|
||||
if (l.code != 200 && l.code != 449) {
|
||||
Toast.makeText(context, l.message, Toast.LENGTH_SHORT).show()
|
||||
logout()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "${e::class.simpleName} ${e.message}", Toast.LENGTH_SHORT).show()
|
||||
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()
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
||||
@@ -148,9 +148,7 @@ class VMHandler(activity: ViewMangaActivity, private val chapterUrl: String, pri
|
||||
override suspend fun onError() {
|
||||
super.onError()
|
||||
if(exit) return
|
||||
withContext(Dispatchers.Main) {
|
||||
wv.get()?.toolsBox?.toastError(R.string.download_chapter_info_failed)
|
||||
}
|
||||
wv.get()?.toolsBox?.toastErrorAndFinish(R.string.download_chapter_info_failed)
|
||||
}
|
||||
|
||||
override suspend fun doWhenFinishDownload() {
|
||||
@@ -187,7 +185,7 @@ class VMHandler(activity: ViewMangaActivity, private val chapterUrl: String, pri
|
||||
true
|
||||
} catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
wv.get()?.toolsBox?.toastError(R.string.load_local_chapter_info_failed)
|
||||
wv.get()?.toolsBox?.toastErrorAndFinish(R.string.load_local_chapter_info_failed)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,6 @@ class ViewMangaActivity : TitleActivityTemplate() {
|
||||
var clicked = 0
|
||||
private var isInSeek = false
|
||||
private var isInScroll = true
|
||||
//private var progressLog: PropertiesTools? = null
|
||||
var scrollImages = arrayOf<ScaleImageView>()
|
||||
var scrollButtons = arrayOf<Button>()
|
||||
var scrollPositions = arrayOf<Int>()
|
||||
@@ -192,7 +191,7 @@ class ViewMangaActivity : TitleActivityTemplate() {
|
||||
} else prepareImgFromWeb()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
toolsBox.toastError(R.string.load_manga_error)
|
||||
toolsBox.toastErrorAndFinish(R.string.load_manga_error)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
startPostponedEnterTransition()
|
||||
@@ -350,7 +349,7 @@ class ViewMangaActivity : TitleActivityTemplate() {
|
||||
Log.d("MyVM", "[$i] 分析: ${it.name}, cut: $useCut")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) { toolsBox.toastError(R.string.count_zip_entries_error) }
|
||||
toolsBox.toastErrorAndFinish(R.string.count_zip_entries_error)
|
||||
}
|
||||
Log.d("MyVM", "开始加载控件")
|
||||
doWhenFinish(count)
|
||||
@@ -377,9 +376,8 @@ class ViewMangaActivity : TitleActivityTemplate() {
|
||||
loadOneImg()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
withContext(Dispatchers.Main) {
|
||||
toolsBox.toastError(getString(R.string.load_page_number_error).format(currentItem))
|
||||
}
|
||||
toolsBox.toastError(getString(R.string.load_page_number_error).format(currentItem))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -637,7 +635,7 @@ class ViewMangaActivity : TitleActivityTemplate() {
|
||||
}*/
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
toolsBox.toastError(R.string.load_chapter_error)
|
||||
toolsBox.toastErrorAndFinish(R.string.load_chapter_error)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,11 +564,11 @@ class ScaleImageView : ImageView {
|
||||
} else {
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
}catch (e:Exception){
|
||||
} catch (e:Exception){
|
||||
e.printStackTrace()
|
||||
ViewMangaActivity.va?.get()?.apply {
|
||||
lifecycleScope.launch {
|
||||
toolsBox.toastError(R.string.show_image_error_try_lower_resolution, false)
|
||||
toolsBox.toastErrorAndFinish(R.string.show_image_error_try_lower_resolution, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,10 @@ class UITools(that: Context?, w: WeakReference<Activity>? = null) {
|
||||
}
|
||||
} ?: transportStringError
|
||||
}
|
||||
suspend fun toastError(s: String, willFinish: Boolean = true) = withContext(Dispatchers.Main) {
|
||||
suspend fun toastError(s: String) = withContext(Dispatchers.Main) {
|
||||
Toast.makeText(zis, s, Toast.LENGTH_SHORT).show()
|
||||
if (willFinish) weak?.get()?.finish()
|
||||
}
|
||||
suspend fun toastError(s: Int, willFinish: Boolean = true) = withContext(Dispatchers.Main) {
|
||||
suspend fun toastErrorAndFinish(s: Int, willFinish: Boolean = true) = withContext(Dispatchers.Main) {
|
||||
Toast.makeText(zis, s, Toast.LENGTH_SHORT).show()
|
||||
if (willFinish) weak?.get()?.finish()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/appbar_coord"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="top.fumiama.copymanga.MainActivity">
|
||||
@@ -31,6 +32,6 @@
|
||||
layout="@layout/content_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -67,7 +67,7 @@
|
||||
<string name="analyze_img_size_error">读取图片大小失败</string>
|
||||
|
||||
<string name="networkApiUrl">/api/v3/system/network2?platform=%1$s</string>
|
||||
<string name="mainPageApiUrl">/api/v3/h5/homeIndex?platform=%1$s</string>
|
||||
<string name="mainPageApiUrl">/api/v3/h5/homeIndex2?platform=%1$s</string>
|
||||
<string name="hostUrl">&hosturl;</string>
|
||||
<string name="proxyUrl">&proxyurl;</string>
|
||||
<string name="rankApiUrl">/api/v3/ranks?limit=21&offset=%1$d&date_type=%2$s&audience_type=%3$s&platform=%4$s</string>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = "$cm_kotlin_version"
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.google.com' }
|
||||
|
||||
Reference in New Issue
Block a user