1
0
mirror of https://github.com/fumiama/copymanga.git synced 2026-06-04 23:10:23 +08:00
新增
1. 导入/导出设置
优化
1. 错误显示
This commit is contained in:
源文雨
2025-06-11 17:29:05 +09:00
parent cd68a5056d
commit 74c0b165ed
27 changed files with 597 additions and 225 deletions

View File

@@ -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"

View File

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

View File

@@ -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) { _, _ -> }

View File

@@ -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")

View File

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

View File

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

View File

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

View File

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

View File

@@ -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")?:""
}

View File

@@ -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) {

View File

@@ -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) {

View 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
}
}

View File

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

View File

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

View File

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

View File

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

View 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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>

View File

@@ -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&amp;offset=%1$d&amp;date_type=%2$s&amp;audience_type=%3$s&amp;platform=%4$s</string>

View File

@@ -2,6 +2,7 @@
buildscript {
ext.kotlin_version = "$cm_kotlin_version"
repositories {
gradlePluginPortal()
google()
mavenCentral()
maven { url 'https://maven.google.com' }