1
0
mirror of https://github.com/fumiama/copymanga.git synced 2026-06-07 16:20:24 +08:00
新增
1. 导出已下载漫画为zip压缩包
2. 浅色和深色模式切换 (fix #136)
3. 支持自建反向API代理 (不支持图片)
4. 我的下载显示本地阅读进度 (fix #46)
5. 倒序排列每一章节
This commit is contained in:
源文雨
2025-05-24 21:40:50 +09:00
parent ecd2f9d491
commit 98b2d1a4ae
37 changed files with 509 additions and 168 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId 'top.fumiama.copymanga'
minSdkVersion 23
targetSdkVersion 34
versionCode 70
versionName '2.4.3'
versionCode 71
versionName '2.5.0'
resourceConfigurations += ['zh', 'zh-rCN']
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -23,6 +23,7 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
@@ -81,6 +82,19 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(null)
val darkModePrefKey = getString(R.string.darkModeKeyID)
PreferenceManager.getDefaultSharedPreferences(this)?.apply {
if (contains(darkModePrefKey)) getString(darkModePrefKey, "0")?.toInt()?.let {
if (it > 0) {
AppCompatDelegate.setDefaultNightMode(listOf(
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
AppCompatDelegate.MODE_NIGHT_NO,
AppCompatDelegate.MODE_NIGHT_YES,
)[it])
}
}
}
// must init before setContentView because HomeF need them to init
mainWeakReference = WeakReference(this)
toolsBox = UITools(this)
@@ -172,13 +186,13 @@ class MainActivity : AppCompatActivity() {
)[it])
}
}
if (Config.general_enable_transparent_system_bar.value) {
WindowCompat.setDecorFitsSystemWindows(window, false)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
window.statusBarColor = 0
window.navigationBarColor = 0
}
}
if (Config.general_enable_transparent_system_bar.value) {
WindowCompat.setDecorFitsSystemWindows(window, false)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
window.statusBarColor = 0
window.navigationBarColor = 0
}
}
@@ -197,7 +211,11 @@ class MainActivity : AppCompatActivity() {
true
}
R.id.action_download -> {
bookHandler.get()?.sendEmptyMessage(BookHandler.NAVIGATE_TO_DOWNLOAD)
if (NewDownloadFragment.wn != null) {
//TODO: fill it
} else {
bookHandler.get()?.sendEmptyMessage(BookHandler.NAVIGATE_TO_DOWNLOAD)
}
true
}
R.id.action_sort -> {
@@ -412,7 +430,7 @@ class MainActivity : AppCompatActivity() {
dl.setMessage("${getString(R.string.app_description)}\n" +
"\n$comandy\n" +
"$comancry\n\n"+ File("/proc/self/cmdline").readText() + "\n" +
"安装位置: ${applicationInfo.sourceDir}")
"当前API: ${Config.myHostApiUrl.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

@@ -1,14 +1,21 @@
package top.fumiama.copymanga.api
import android.util.Log
import com.bumptech.glide.load.model.LazyHeaders
import com.google.gson.Gson
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.json.NetworkStructure
import top.fumiama.copymanga.net.DownloadTools
import top.fumiama.copymanga.net.Proxy
import top.fumiama.copymanga.net.Resolution
import top.fumiama.copymanga.storage.PreferenceBoolean
import top.fumiama.copymanga.storage.PreferenceInt
import top.fumiama.copymanga.storage.PreferenceString
import top.fumiama.copymanga.storage.UserPreferenceInt
import top.fumiama.copymanga.storage.UserPreferenceString
import top.fumiama.copymanga.net.Proxy
import top.fumiama.copymanga.net.Resolution
import top.fumiama.dmzj.copymanga.R
import java.io.File
@@ -53,9 +60,52 @@ object Config {
return field
}
val myHostApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl)
val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!!
private val reverseProxyUrl = PreferenceString(R.string.reverseProxyKeyID)
private val networkApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl)
private var mHostApiUrls: Array<String> = arrayOf()
private var mHostApiUrlsMutex = Mutex()
val myHostApiUrl: Array<String>
get() {
if (mHostApiUrls.isNotEmpty()) return mHostApiUrls
if (reverseProxyUrl.value.isNotEmpty() && reverseProxyUrl.value != proxyUrl) {
mHostApiUrls = arrayOf(reverseProxyUrl.value)
Log.d("MyC", "myHostApiUrl set reverse proxy to ${mHostApiUrls[0]}")
return mHostApiUrls
}
MainActivity.mainWeakReference?.get()?.apply {
runBlocking {
mHostApiUrlsMutex.withLock {
if (mHostApiUrls.isNotEmpty()) return@runBlocking
try {
val u = getString(R.string.networkApiUrl).format(networkApiUrl.value)
val r = Gson().fromJson((apiProxy?.comancry(u) {
DownloadTools.getHttpContent(it, referer, pc_ua)
}?:DownloadTools.getHttpContent(u, referer, pc_ua)).decodeToString(), NetworkStructure::class.java)
if (r != null) {
Log.d("MyC", "myHostApiUrl get code ${r.code} msg ${r.message}")
if (r.code == 200 && r.results != null) {
// need header umstring
// r.results.api.forEach { it.forEach { api -> if (!api.isNullOrEmpty() && api !in field) field += api } }
r.results.share.forEach { api -> if (!api.isNullOrEmpty() && api !in mHostApiUrls) mHostApiUrls += api }
}
}
} catch (e: Exception) {
e.printStackTrace()
mHostApiUrls = arrayOf(networkApiUrl.value)
}
if (mHostApiUrls.isEmpty()) {
mHostApiUrls = arrayOf(networkApiUrl.value)
Log.d("MyC", "myHostApiUrl set default ${mHostApiUrls[0]}")
}
}
}
}
Log.d("MyC", "myHostApiUrl get hosts ${mHostApiUrls.joinToString(", ")}")
return mHostApiUrls
}
val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo)
val proxy_key = PreferenceString(R.string.imgProxyKeyID)
val proxy_key = PreferenceString(R.string.imgProxyCodeKeyID)
val app_ver = PreferenceString("settings_cat_general_et_app_version", R.string.app_ver)
val token = UserPreferenceString("token", "", null)
val pc_ua get() = MainActivity.mainWeakReference?.get()?.getString(R.string.pc_ua)?.format(app_ver.value)?:""
@@ -80,6 +130,7 @@ object Config {
val net_use_api_proxy = PreferenceBoolean("settings_cat_net_sw_use_api_proxy", false)
val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID)
val view_manga_inverse_chapters = PreferenceBoolean("settings_cat_vm_sw_inverse_chapters", false)
val view_manga_always_dark_bg = PreferenceBoolean("settings_cat_vm_sw_always_dark_bg", false)
val view_manga_vertical_max = PreferenceInt("settings_cat_vm_sb_vertical_max", 20)
val view_manga_quality = PreferenceInt("settings_cat_vm_sb_quality", 100)
@@ -92,5 +143,5 @@ object Config {
fun getChapterInfoApiUrl(path: String?, uuid: String?, version: Int) =
MainActivity.mainWeakReference?.get()?.getString(R.string.chapterInfoApiUrl)
?.format(myHostApiUrl.value, path, if (version >= 2) "$version" else "" , uuid)
?.format(myHostApiUrl.random(), path, if (version >= 2) "$version" else "" , uuid)
}

View File

@@ -15,7 +15,7 @@ import top.fumiama.dmzj.copymanga.R
import java.io.File
class Book(val path: String, private val getString: (Int) -> String, private val exDir: File, private val loadCache: Boolean = false, private val mPassName: String? = null) {
private val mBookApiUrl = getString(R.string.bookInfoApiUrl).format(Config.myHostApiUrl.value, path)
private val mBookApiUrl = getString(R.string.bookInfoApiUrl).format(Config.myHostApiUrl.random(), path)
private val mUserAgent = getString(R.string.pc_ua).format(Config.app_ver.value)
private var mBook: BookInfoStructure? = null
private var mGroupPathWords = arrayOf<String>()
@@ -37,12 +37,6 @@ class Book(val path: String, private val getString: (Int) -> String, private val
val author: Array<ThemeStructure>? get() = mBook?.results?.comic?.author
val theme: Array<ThemeStructure>? get() = mBook?.results?.comic?.theme
val keys get() = mKeys
val imageType: String
get() = when(mBook?.results?.comic?.img_type) {
1 -> "条漫"
2 -> "普通"
else -> "未知类型${mBook?.results?.comic?.img_type}"
}
val popular get() = mBook?.results?.comic?.popular?:0
val status get() = mBook?.results?.comic?.status?.display?:"未知"
val updateTime get() = mBook?.results?.comic?.datetime_updated?:"未知"

View File

@@ -1,12 +1,16 @@
package top.fumiama.copymanga.api.manga
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.Intent
import android.util.Log
import androidx.core.content.edit
import androidx.fragment.app.FragmentActivity
import com.google.gson.Gson
import kotlinx.android.synthetic.main.button_tbutton.view.*
import kotlinx.coroutines.delay
import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference
import top.fumiama.copymanga.api.Config
import top.fumiama.copymanga.json.VolumeStructure
import top.fumiama.copymanga.ui.vm.ViewMangaActivity
import java.io.File
@@ -81,4 +85,37 @@ object Reader {
}
return "N/A:null_gson"
}
fun getComicChapterNamesInFolder(file: File): Array<String> {
if(!file.exists()) {
return arrayOf()
}
val jsonFile = File(file, "info.json")
if(!jsonFile.exists()) {
return arrayOf()
}
Gson().fromJson(jsonFile.readText(), Array<VolumeStructure>::class.java)?.let { volumes ->
if(volumes.isEmpty()) {
return arrayOf()
}
var arr = arrayOf<String>()
volumes.forEach { v ->
v.results?.list?.forEach {
arr += it.name?:""
}
}
return arr
}
return arrayOf()
}
fun getLocalReadingProgress(activity: FragmentActivity, name: String, chapterNames: Array<String>): String {
var chapter = "未读"
activity.apply {
getPreferences(MODE_PRIVATE).getInt(name, -1).let { p ->
if(p >= 0) chapterNames.let {
chapter = it[if (p >= it.size) it.size-1 else p]
}
}
}
return chapter
}
}

View File

@@ -10,7 +10,7 @@ import top.fumiama.copymanga.net.DownloadTools
import top.fumiama.dmzj.copymanga.R
class Shelf(private val getString: (Int) -> String) {
private val apiUrl: String get() = getString(R.string.shelfOperateApiUrl).format(Config.myHostApiUrl.value)
private val apiUrl: String get() = getString(R.string.shelfOperateApiUrl).format(Config.myHostApiUrl.random())
private val queryApiUrlTemplate = getString(R.string.bookUserQueryApiUrl)
private val addApiUrl get() = "$apiUrl?platform=3"
private val delApiUrl get() = "${apiUrl}s?platform=3"
@@ -68,7 +68,7 @@ class Shelf(private val getString: (Int) -> String) {
suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) {
try {
val queryUrl = queryApiUrlTemplate.format(Config.myHostApiUrl.value, pathWord)
val queryUrl = queryApiUrlTemplate.format(Config.myHostApiUrl.random(), pathWord)
(Config.apiProxy?.comancry(queryUrl) { url ->
DownloadTools.getHttpContent(url, Config.referer)
}?:DownloadTools.getHttpContent(queryUrl, Config.referer)).let {

View File

@@ -39,7 +39,7 @@ class Volume(private val path: String, private val groupPathWord: String, getStr
return@withContext mVolume
}
private fun getApiUrl(offset: Int) = mGroupInfoApiUrlTemplate.format(Config.myHostApiUrl.value, path, groupPathWord, offset)
private fun getApiUrl(offset: Int) = mGroupInfoApiUrlTemplate.format(Config.myHostApiUrl.random(), path, groupPathWord, offset)
private suspend fun download(re: Array<VolumeStructure?>, offset: Int, c: Int) = withContext(Dispatchers.IO) {
Log.d("MyV", "下载偏移: $offset")
getApiUrl(offset).let {

View File

@@ -49,7 +49,7 @@ class Member(private val getString: (Int) -> String) {
}
try {
val u = getString(R.string.memberInfoApiUrl)
.format(Config.myHostApiUrl.value)
.format(Config.myHostApiUrl.random())
val data = (Config.apiProxy?.comancry(u) {
DownloadTools.getHttpContent(it)
}?:DownloadTools.getHttpContent(u)).decodeToString()
@@ -97,7 +97,7 @@ class Member(private val getString: (Int) -> String) {
}
private suspend fun postLogin(username: String, pwd: String, salt: Int): ByteArray? =
getString(R.string.loginApiUrl).format(Config.myHostApiUrl.value).let { u ->
getString(R.string.loginApiUrl).format(Config.myHostApiUrl.random()).let { u ->
val use: suspend (String) -> ByteArray? = { it: String ->
DownloadTools.getApiConnection(it, "POST").let { c ->
c.doOutput = true
@@ -129,7 +129,7 @@ class Member(private val getString: (Int) -> String) {
private suspend fun postComandyLogin(username: String, pwd: String, salt: Int) =
getString(R.string.loginApiUrl).format(Config.myHostApiUrl.value).let { u ->
getString(R.string.loginApiUrl).format(Config.myHostApiUrl.random()).let { u ->
val use: suspend (String) -> ByteArray? = { it: String ->
DownloadTools.getComandyApiConnection(it, "POST", null, Config.pc_ua).apply {
headers["content-type"] = "application/x-www-form-urlencoded;charset=utf-8"

View File

@@ -0,0 +1,9 @@
package top.fumiama.copymanga.json;
public class NetworkStructure extends ReturnBase {
public Results results;
public static class Results {
public String[] share;
public String[][] api;
}
}

View File

@@ -16,7 +16,7 @@ class Comandy: LazyLibrary<ComandyMethods>(
Log.d("MyComandy", "$name block enabled for isInInit")
return false
}
if (mEnabled != true && DownloadTools.failTimes.get() >= 3) {
if (mEnabled != true && DownloadTools.failTimes.get() >= 16) {
mEnabled = true
return true
}

View File

@@ -2,20 +2,18 @@ package top.fumiama.copymanga.lib.template
import android.os.Build
import android.util.Log
import androidx.lifecycle.lifecycleScope
import com.google.gson.Gson
import com.sun.jna.Library
import com.sun.jna.Native
import kotlinx.android.synthetic.main.dialog_progress.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.json.ComandyVersion
import top.fumiama.copymanga.net.Client
import top.fumiama.copymanga.net.DownloadTools
import top.fumiama.copymanga.storage.PreferenceBoolean
import top.fumiama.copymanga.storage.UserPreferenceInt
import top.fumiama.copymanga.net.DownloadTools
import top.fumiama.copymanga.net.Client
import top.fumiama.dmzj.copymanga.R
import java.io.ByteArrayInputStream
import java.io.File
@@ -54,68 +52,66 @@ open class LazyLibrary<T: Library>(
}?:return null
Log.d("MyLazyLibrary", "$name arch: $prefix")
MainActivity.mainWeakReference?.get()?.let { ma ->
ma.lifecycleScope.launch {
withContext(Dispatchers.IO) {
Log.d("MyLazyLibrary", "$name launched")
var f = File(ma.filesDir, "libs")
if (!f.exists()) f.mkdirs()
f = File(f, name)
var remoteVersion = 0
if (f.exists()) {
DownloadTools.getHttpContent(ma.getString(R.string.comandy_version_url).format(repoName), -1)?.let dataLet@{
withContext(Dispatchers.IO) {
Log.d("MyLazyLibrary", "$name launched")
var f = File(ma.filesDir, "libs")
if (!f.exists()) f.mkdirs()
f = File(f, name)
var remoteVersion = 0
if (f.exists()) {
DownloadTools.getHttpContent(ma.getString(R.string.comandy_version_url).format(repoName), -1)?.let dataLet@{
try {
val body = Gson().fromJson(it.decodeToString(), ComandyVersion::class.java)?.body
if (body?.startsWith("Version: ") == true) {
remoteVersion = body.substring(9).toInt()
}
} catch (e: Exception) {
e.printStackTrace()
}
val myVersion = version.value?:0
if (myVersion >= remoteVersion) {
Log.d("MyLazyLibrary", "$name version $myVersion is latest")
isInInit.set(false)
mLibraryFile = f
return@withContext
}
Log.d("MyLazyLibrary", "$name version $myVersion <= latest $remoteVersion, update...")
}
}
withContext(Dispatchers.Main) {
val progressBar = ma.layoutInflater.inflate(R.layout.dialog_progress, null, false)
val progressHandler = object : Client.Progress{
override fun notify(progressPercentage: Int) {
Log.d("MyLazyLibrary", "Set dl $name progress: $progressPercentage")
progressBar.dpp.progress = progressPercentage
}
}
val info = ma.toolsBox.buildAlertWithView("加载${functionName}组件", progressBar, "隐藏")
withContext(Dispatchers.IO) {
DownloadTools.getHttpContent(ma.getString(R.string.comandy_download_url).format(repoName, prefix, name), -1, progressHandler)?.let {
if(f.exists()) f.delete()
try {
val body = Gson().fromJson(it.decodeToString(), ComandyVersion::class.java)?.body
if (body?.startsWith("Version: ") == true) {
remoteVersion = body.substring(9).toInt()
GZIPInputStream(ByteArrayInputStream(it)).use { dataIn ->
f.outputStream().use { dataOut ->
dataIn.copyTo(dataOut)
}
}
if (remoteVersion > 0) version.value = remoteVersion
Log.d("MyLazyLibrary", "update success")
isInInit.set(false)
withContext(Dispatchers.Main) {
info.dismiss()
}
} catch (e: Exception) {
e.printStackTrace()
}
val myVersion = version.value?:0
if (myVersion >= remoteVersion) {
Log.d("MyLazyLibrary", "$name version $myVersion is latest")
isInInit.set(false)
mLibraryFile = f
return@withContext
}
Log.d("MyLazyLibrary", "$name version $myVersion <= latest $remoteVersion, update...")
}
}
withContext(Dispatchers.Main) {
val progressBar = ma.layoutInflater.inflate(R.layout.dialog_progress, null, false)
val progressHandler = object : Client.Progress{
override fun notify(progressPercentage: Int) {
Log.d("MyLazyLibrary", "Set dl $name progress: $progressPercentage")
progressBar.dpp.progress = progressPercentage
}
}
val info = ma.toolsBox.buildAlertWithView("加载${functionName}组件", progressBar, "隐藏")
withContext(Dispatchers.IO) {
DownloadTools.getHttpContent(ma.getString(R.string.comandy_download_url).format(repoName, prefix, name), -1, progressHandler)?.let {
if(f.exists()) f.delete()
try {
GZIPInputStream(ByteArrayInputStream(it)).use { dataIn ->
f.outputStream().use { dataOut ->
dataIn.copyTo(dataOut)
}
}
if (remoteVersion > 0) version.value = remoteVersion
Log.d("MyLazyLibrary", "update success")
isInInit.set(false)
withContext(Dispatchers.Main) {
info.dismiss()
}
} catch (e: Exception) {
e.printStackTrace()
if(f.exists()) f.delete()
}
}
}
}
mLibraryFile = if(f.exists()) f else null
return@withContext
}
}.join()
mLibraryFile = if(f.exists()) f else null
return@withContext
}
}
return mLibraryFile
}

View File

@@ -17,6 +17,7 @@ import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.signature.ObjectKey
import com.google.gson.Gson
import kotlinx.coroutines.runBlocking
import top.fumiama.copymanga.api.Config.proxyUrl
import top.fumiama.copymanga.json.ComandyCapsule
import top.fumiama.copymanga.lib.Comandy
import java.nio.ByteBuffer
@@ -86,7 +87,7 @@ class ComandyGlideModule: AppGlideModule() {
override fun handles(model: GlideUrl): Boolean {
return Comandy.instance.enabled && model.toURL().let {
it.protocol == "https" && it.host != "copymanga.azurewebsites.net"
it.protocol == "https" && it.host != proxyUrl
}
}
@@ -105,7 +106,7 @@ class ComandyGlideModule: AppGlideModule() {
override fun handles(model: String): Boolean {
return Comandy.instance.enabled &&
model.startsWith("https://") &&
!model.startsWith("https://copymanga.azurewebsites.net")
!model.startsWith("https://$proxyUrl")
}
}

View File

@@ -8,6 +8,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity
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.ByteArrayOutputStream
@@ -16,34 +17,40 @@ import java.io.OutputStream
import java.lang.Thread.sleep
import java.net.HttpURLConnection
import java.net.URL
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.FutureTask
import java.util.concurrent.atomic.AtomicInteger
object DownloadTools {
val failTimes = AtomicInteger(0)
fun getApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null, timeout: Int = 20000) =
url.let {
val connection = URL(url).openConnection() as HttpURLConnection
connection.requestMethod = method
connection.connectTimeout = timeout
connection.readTimeout = timeout
connection.apply {
setRequestProperty("host", url.substringAfter("://").substringBefore("/"))
ua?.let { setRequestProperty("user-agent", it) }
refer?.let { setRequestProperty("referer", it) }
setRequestProperty("source", "copyApp")
setRequestProperty("webp", "1")
setRequestProperty("region", if(!Config.net_use_foreign.value) "1" else "0")
setRequestProperty("version", Config.app_ver.value)
Config.token.value?.let { tk ->
setRequestProperty("authorization", "Token $tk")
}
setRequestProperty("platform", "3")
fun getApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null, timeout: Int = 20000): HttpURLConnection {
val connection = URL(url).openConnection() as HttpURLConnection
connection.requestMethod = method
connection.connectTimeout = timeout
connection.readTimeout = timeout
connection.apply {
/*setRequestProperty("host", if (url.startsWith("https://copymanga.azurewebsites.net")) {
Uri.parse(url).getQueryParameter("url")?.substringAfter("://")?.substringBefore("/")?:""
} else {
url.substringAfter("://").substringBefore("/")
})*/
ua?.let { setRequestProperty("user-agent", it) }
refer?.let { setRequestProperty("referer", it) }
setRequestProperty("source", "copyApp")
setRequestProperty("webp", "1")
setRequestProperty("region", if(!Config.net_use_foreign.value) "1" else "0")
setRequestProperty("version", Config.app_ver.value)
setRequestProperty("dt", SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()).format(Calendar.getInstance().time))
Config.token.value?.let { tk ->
setRequestProperty("authorization", "Token $tk")
}
Log.d("MyDT", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
connection
setRequestProperty("platform", "3")
}
Log.d("MyDT", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
return connection
}
fun getComandyApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null) =
run {
@@ -51,7 +58,11 @@ object DownloadTools {
capsule.url = url
capsule.method = method
capsule.headers = hashMapOf()
capsule.headers["host"] = url.substringAfter("://").substringBefore("/")
/*capsule.headers["host"] = if (url.startsWith("https://copymanga.azurewebsites.net")) {
Uri.parse(url).getQueryParameter("url")?.substringAfter("://")?.substringBefore("/")?:""
} else {
url.substringAfter("://").substringBefore("/")
}*/
ua?.let { capsule.headers["user-agent"] = it }
refer?.let { capsule.headers["referer"] = it }
capsule.headers["source"] = "copyApp"
@@ -64,6 +75,7 @@ object DownloadTools {
}
}
capsule.headers["platform"] = "3"
capsule.headers["dt"] = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()).format(Calendar.getInstance().time)
Log.d("MyDT", "getComandyConnection: $url\n${capsule.headers.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
capsule
}
@@ -119,7 +131,7 @@ object DownloadTools {
suspend fun getHttpContent(u: String, refer: String? = null, ua: String? = Config.pc_ua, p: Client.Progress? = null): ByteArray =
withContext(Dispatchers.IO) {
if (!u.startsWith("https://copymanga.azurewebsites.net") && Comandy.instance.enabled) {
if (!u.startsWith("https://$proxyUrl") && Comandy.instance.enabled) {
getComandyApiConnection(u, "GET", refer, ua).let { capsule ->
val para = Gson().toJson(capsule)
//Log.d("MyDT", "comandy request: $para")
@@ -188,7 +200,7 @@ object DownloadTools {
// prepare p only take effect when readSize is -1
fun prepare(u: String, readSize: Int = -1, p: Client.Progress? = null) = run {
Log.d("MyDT", "prepareHttp: $u")
FutureTask(if (!u.startsWith("https://copymanga.azurewebsites.net") && Comandy.instance.enabled) Callable{
FutureTask(if (!u.startsWith("https://$proxyUrl") && Comandy.instance.enabled) Callable{
try {
runBlocking { Comandy.instance.getInstance() }?.let { ins ->
runBlocking {
@@ -261,7 +273,7 @@ object DownloadTools {
fun requestWithBody(url: String, method: String, body: ByteArray, refer: String? = Config.referer, ua: String? = Config.pc_ua, contentType: String? = "application/x-www-form-urlencoded;charset=utf-8"): ByteArray? {
Log.d("MyDT", "$method Http: $url")
var ret: ByteArray? = null
val task = FutureTask(if(!url.startsWith("https://copymanga.azurewebsites.net") && Comandy.instance.enabled) Callable{
val task = FutureTask(if(!url.startsWith("https://$proxyUrl") && Comandy.instance.enabled) Callable{
try {
val capsule = getComandyApiConnection(url, method, refer, ua)
contentType?.let { capsule.headers["content-type"] = it }

View File

@@ -44,6 +44,7 @@ class Proxy(id: Int, private val apiRegex: Regex) {
?:u
Log.d("MyP", "[M] comancry: $wu, sd: $sourceDir")
return use(wu)?.let { data ->
Log.d("MyP", "[M] comancry: decrypt ${data.size} bytes data")
Comancry.instance.decrypt(sourceDir, data)?.encodeToByteArray()
}
}

View File

@@ -19,7 +19,7 @@ import java.io.File
import java.security.MessageDigest
open class AutoDownloadHandler(
private val url: String, private val jsonClass: Class<*>,
private val url: () -> String, private val jsonClass: Class<*>,
private val context: LifecycleOwner?,
private val loadFromCache: Boolean = false,
private val customCacheFile: File? = null): Handler(Looper.myLooper()!!) {
@@ -57,7 +57,7 @@ open class AutoDownloadHandler(
toString()
}
private suspend fun downloadCoroutine() = withContext(Dispatchers.IO) {
val cacheName = toHexStr(MessageDigest.getInstance("MD5").digest(url.encodeToByteArray()))
val cacheName = toHexStr(MessageDigest.getInstance("MD5").digest(url().encodeToByteArray()))
val cacheFile = customCacheFile?:(mainWeakReference?.get()?.externalCacheDir?.let { File(it, cacheName) })
if(loadFromCache) {
cacheFile?.let {
@@ -77,9 +77,9 @@ open class AutoDownloadHandler(
var cnt = 0
while (cnt++ <= 3) {
try {
val data = Config.apiProxy?.comancry(url) {
val data = Config.apiProxy?.comancry(url()) {
DownloadTools.getHttpContent(it)
}?:DownloadTools.getHttpContent(url)
}?:DownloadTools.getHttpContent(url())
if(exit) return@withContext
val fi = data.inputStream()
val pass = setGsonItem(Gson().fromJson(fi.reader(), jsonClass))

View File

@@ -1,6 +1,15 @@
package top.fumiama.copymanga.storage
import android.content.Context
import android.net.Uri
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
object FileUtils {
fun recursiveRemove(f: File) {
@@ -19,4 +28,52 @@ object FileUtils {
}
return size
}
fun registerZipExportLauncher(
fragment: Fragment,
callback: (Uri?) -> Unit
): ActivityResultLauncher<String> {
return fragment.registerForActivityResult(ActivityResultContracts.CreateDocument("application/zip"), callback)
}
fun compressToUserFile(
context: Context,
sourceDir: File,
targetUri: Uri,
setProgress: (Int) -> Unit = {}
) {
val allFiles = collectAllFiles(sourceDir)
val totalFiles = allFiles.size
var filesDone = 0
setProgress(0) // 确保初始进度为0%
context.contentResolver.openOutputStream(targetUri)?.use { outputStream ->
ZipOutputStream(BufferedOutputStream(outputStream)).use { zipOut ->
for (file in allFiles) {
val relativePath = sourceDir.toURI().relativize(file.toURI()).path
val entry = ZipEntry(relativePath)
zipOut.putNextEntry(entry)
FileInputStream(file).use { input ->
input.copyTo(zipOut)
}
zipOut.closeEntry()
filesDone++
val progress = (filesDone * 100 / totalFiles).coerceIn(0, 100)
setProgress(progress)
}
}
}
setProgress(100) // 确保最终进度为100%
}
private fun collectAllFiles(root: File): List<File> {
val files = mutableListOf<File>()
root.walkTopDown().forEach {
if (it.isFile) files.add(it)
}
return files
}
}

View File

@@ -140,17 +140,11 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
fun setReadTo() {
var chapter = "未读"
if(!mBookHandler?.chapterNames.isNullOrEmpty()) {
activity?.apply {
chapter = "读至 ${activity?.let { a ->
book?.name?.let { name ->
getPreferences(MODE_PRIVATE).getInt(name, -1).let { p ->
if(p >= 0) mBookHandler!!.chapterNames.let {
chapter = it[if (p >= it.size) it.size-1 else p]
}
}
}
}
Reader.getLocalReadingProgress(a, name, mBookHandler!!.chapterNames)
} }}"
}
chapter = "读至 $chapter"
this@BookFragment.bttag.text = chapter
}

View File

@@ -218,13 +218,16 @@ class BookHandler(private val th: WeakReference<BookFragment>): Handler(Looper.m
}
var last = i-1
val comicName = name?:return@withContext
val init = i
Log.d("MyBH", "i = $i, last=$last, add comic $comicName, volume $p")
volumes[p].let { v ->
if(exit) return@withContext
var line: View? = null
last += v.results.list.size
v.results.list.forEach {
val inv = Config.view_manga_inverse_chapters.value
(if(!inv) v.results.list.toList() else v.results.list.reversed()).forEach {
val f = Config.getZipFile(context?.getExternalFilesDir(""), comicName, keys[p], it.name)
//Log.d("MyBH", "i = $i, last=$last, add chapter ${it.name}, line is null: ${line == null}")
Log.d("MyBH", "i = $i, last=$last, add chapter ${it.name}, line is null: ${line == null}")
that?.isOnPause?.let { isOnPause ->
while (isOnPause && !exit) delay(500)
if (exit) return@withContext
@@ -236,7 +239,7 @@ class BookHandler(private val th: WeakReference<BookFragment>): Handler(Looper.m
lct.text = it.name
if (f.exists()) lci.setBackgroundResource(R.drawable.ic_success)
Log.d("MyBH", "add last single chapter ${it.name}")
val index = i
val index = if (!inv) i else init
setOnClickListener { Reader.start2viewManga(comicName, index, urlArray, uuidArray) }
}
line?.let { l -> addVolumesView(fbl, l) }
@@ -245,14 +248,14 @@ class BookHandler(private val th: WeakReference<BookFragment>): Handler(Looper.m
line?.l2cl?.apply {
lct.text = it.name
if (f.exists()) lci.setBackgroundResource(R.drawable.ic_success)
val index = i
val index = if (!inv) i else (init+last-i)
setOnClickListener { Reader.start2viewManga(comicName, index, urlArray, uuidArray) }
}
}
} else line?.l2cr?.apply {
lct.text = it.name
if (f.exists()) lci.setBackgroundResource(R.drawable.ic_success)
val index = i
val index = if (!inv) i else (init+last-i)
setOnClickListener { Reader.start2viewManga(comicName, index, urlArray, uuidArray) }
line?.let { l -> addVolumesView(fbl, l) }
line = null

View File

@@ -11,7 +11,7 @@ import top.fumiama.dmzj.copymanga.R
@OptIn(ExperimentalStdlibApi::class)
class HistoryFragment : InfoCardLoader(R.layout.fragment_history, R.id.action_nav_history_to_nav_book, isHistoryBook = true) {
override fun getApiUrl() =
getString(R.string.historyApiUrl).format(Config.myHostApiUrl.value, page * 21)
getString(R.string.historyApiUrl).format(Config.myHostApiUrl.random(), page * 21)
override fun onCreate(savedInstanceState: Bundle?) {
if (MainActivity.member?.hasLogin != true) {

View File

@@ -7,5 +7,5 @@ import top.fumiama.dmzj.copymanga.R
@ExperimentalStdlibApi
class NewestFragment : InfoCardLoader(R.layout.fragment_newest, R.id.action_nav_newest_to_nav_book, true) {
override fun getApiUrl() =
getString(R.string.newestApiUrl).format(Config.myHostApiUrl.value, page * 21)
getString(R.string.newestApiUrl).format(Config.myHostApiUrl.random(), page * 21)
}

View File

@@ -48,7 +48,7 @@ class RankFragment : InfoCardLoader(R.layout.fragment_rank, R.id.action_nav_rank
override fun getApiUrl() =
getString(R.string.rankApiUrl).format(
Config.myHostApiUrl.value,
Config.myHostApiUrl.random(),
page * 21,
sortWay[sortValue],
audienceWay[audience]

View File

@@ -7,5 +7,5 @@ import top.fumiama.dmzj.copymanga.R
@ExperimentalStdlibApi
class RecFragment : InfoCardLoader(R.layout.fragment_recommend, R.id.action_nav_recommend_to_nav_book, true) {
override fun getApiUrl() =
getString(R.string.recommendApiUrl).format(Config.myHostApiUrl.value, page * 21)
getString(R.string.recommendApiUrl).format(Config.myHostApiUrl.random(), page * 21)
}

View File

@@ -13,7 +13,7 @@ class SearchFragment : InfoCardLoader(R.layout.fragment_search, R.id.action_nav_
private var query: String? = null
private var type: String? = null
override fun getApiUrl() =
getString(R.string.searchApiUrl).format(Config.myHostApiUrl.value, page * 21, query, type)
getString(R.string.searchApiUrl).format(Config.myHostApiUrl.random(), page * 21, query, type)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@@ -26,7 +26,7 @@ class ShelfFragment : InfoCardLoader(R.layout.fragment_shelf, R.id.action_nav_su
override fun getApiUrl() =
getString(R.string.shelfApiUrl).format(
Config.myHostApiUrl.value,
Config.myHostApiUrl.random(),
page * 21,
sortWay[sortValue]
)

View File

@@ -25,7 +25,7 @@ class SortFragment : StatusCardFlow(0, R.id.action_nav_sort_to_nav_book, R.layou
override fun getApiUrl() =
getString(R.string.sortApiUrl).format(
Config.myHostApiUrl.value,
Config.myHostApiUrl.random(),
page * 21,
sortWay[sortValue],
if(theme >= 0 && theme < (filter?.results?.theme?.size ?: 0)) (filter?.results?.theme?.get(theme)?.path_word ?: "") else "",
@@ -42,7 +42,7 @@ class SortFragment : StatusCardFlow(0, R.id.action_nav_sort_to_nav_book, R.layou
super.setListeners()
lifecycleScope.launch {
setProgress(5)
PausableDownloader(getString(R.string.filterApiUrl).format(Config.myHostApiUrl.value)) {
PausableDownloader(getString(R.string.filterApiUrl).format(Config.myHostApiUrl.random())) {
if(ad?.exit == true) return@PausableDownloader
it.let {
it.inputStream().use { i ->

View File

@@ -18,13 +18,13 @@ import top.fumiama.dmzj.copymanga.R
class TopicFragment : InfoCardLoader(R.layout.fragment_topic, R.id.action_nav_topic_to_nav_book) {
private var type = 1
override fun getApiUrl() =
getString(R.string.topicContentApiUrl).format(Config.myHostApiUrl.value, arguments?.getString("path"), type, offset)
getString(R.string.topicContentApiUrl).format(Config.myHostApiUrl.random(), arguments?.getString("path"), type, offset)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
setProgress(5)
PausableDownloader(getString(R.string.topicApiUrl).format(Config.myHostApiUrl.value, arguments?.getString("path"))) { data ->
PausableDownloader(getString(R.string.topicApiUrl).format(Config.myHostApiUrl.random(), arguments?.getString("path"))) { data ->
setProgress(10)
withContext(Dispatchers.IO) {
if(ad?.exit == true) return@withContext

View File

@@ -1,25 +1,33 @@
package top.fumiama.copymanga.ui.download
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.dialog_progress.view.*
import kotlinx.android.synthetic.main.line_header.view.*
import kotlinx.android.synthetic.main.line_lazybooklines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference
import top.fumiama.copymanga.MainActivity
import top.fumiama.copymanga.api.Config.manga_dl_show_0m_manga
import top.fumiama.copymanga.api.manga.Downloader
import top.fumiama.copymanga.api.manga.Reader
import top.fumiama.copymanga.view.template.MangaPagesFragmentTemplate
import top.fumiama.copymanga.view.template.CardList
import top.fumiama.copymanga.net.Client
import top.fumiama.copymanga.storage.FileUtils
import top.fumiama.copymanga.storage.FileUtils.compressToUserFile
import top.fumiama.copymanga.view.interaction.Navigate
import top.fumiama.copymanga.view.interaction.UITools
import top.fumiama.copymanga.view.template.CardList
import top.fumiama.copymanga.view.template.MangaPagesFragmentTemplate
import top.fumiama.dmzj.copymanga.R
import java.io.File
import java.lang.ref.WeakReference
@@ -27,25 +35,48 @@ import java.lang.ref.WeakReference
@OptIn(ExperimentalStdlibApi::class)
class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownload, forceLoad = true) {
private var sortedBookList: List<File>? = null
private val oldDlCardName = mainWeakReference?.get()?.getString(R.string.old_download_card_name)!!
private val extDir = mainWeakReference?.get()?.getExternalFilesDir("")
private val oldDlCardName by lazy { getString(R.string.old_download_card_name) }
private val extDir by lazy { activity?.getExternalFilesDir("") }
private var isReverse = false
private var isContentChanged = false
private var exit = false
private val showAll get() = manga_dl_show_0m_manga.value
private var compressIntoFile: ActivityResultLauncher<String>? = null
private var compressName = ""
private var compressFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
wn = WeakReference(this)
compressIntoFile = FileUtils.registerZipExportLauncher(this@NewDownloadFragment) { uri ->
val progressBar = layoutInflater.inflate(R.layout.dialog_progress, null, false)
val progressHandler = object : Client.Progress{
override fun notify(progressPercentage: Int) {
progressBar?.dpp?.progress = progressPercentage
}
}
val info = (activity as MainActivity).toolsBox.buildAlertWithView("压缩${compressName}.zip", progressBar!!, "隐藏")
val f = compressFile!!
Thread{
uri?.let { context?.let { ctx -> compressToUserFile(ctx, f, it) {
progressHandler.notify(it)
if (it >= 100) activity?.runOnUiThread { info.dismiss() }
} } }
}.start()
}
}
override fun onPause() {
super.onPause()
exit = true
wn = null
}
override fun onResume() {
super.onResume()
exit = false
wn = WeakReference(this)
}
override fun onDestroy() {
@@ -54,6 +85,7 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl
exit = true
}
@SuppressLint("SetTextI18n")
override suspend fun addPage(): Unit = withContext(Dispatchers.IO) {
super.addPage()
if(isRefresh) {
@@ -105,8 +137,18 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl
cnt = 1
}
val size = sortedBookList?.size?:0
sortedBookList?.let {
for(i in it.listIterator(page)) {
sortedBookList?.let { lst ->
withContext(Dispatchers.Main) {
mysp?.footerView?.lht?.text = "$page+"
activity?.findViewById<Toolbar>(R.id.toolbar)?.let { appbar ->
appbar.title.let { title ->
if (!title.endsWith(")")) {
appbar.title = "$title (${lst.size})"
}
}
}
}
for(i in lst.listIterator(page)) {
if(cardList?.exitCardList != false) return@withContext
page++ // page is actually count
val chosenJson = File(i, "info.bin")
@@ -120,14 +162,16 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl
chosenJson.exists() -> continue // unsupported old folder
newJson.exists() -> {
if(cardList?.exitCardList != false) return@withContext
cardList?.addCard(i.name, bookSize)
cardList?.addCard(i.name, "\n本地读至 ${activity?.let { a ->
Reader.getLocalReadingProgress(a, i.name, Reader.getComicChapterNamesInFolder(i))
}}$bookSize")
cnt++
}
}
setProgress(80+20*(cnt-1)/size)
if (cnt >= 21) break
}
if(page >= it.size) {
if(page >= lst.size) {
isEnd = true
}
}
@@ -155,7 +199,7 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl
AlertDialog.Builder(context)
.setIcon(R.drawable.ic_launcher_foreground)
.setTitle(R.string.new_download_card_option_hint)
.setItems(arrayOf("删除数据文件夹", "直接前往下载页")) { d, p ->
.setItems(arrayOf("删除数据文件夹", "直接前往下载页", "导出压缩包")) { d, p ->
d.cancel()
when (p) {
0 -> {
@@ -174,10 +218,14 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl
.show()
}
1 -> callDownloadFragment(name)
2 -> {
compressName = name
compressFile = chosenFile
compressIntoFile?.launch("${name}.zip")
}
}
}
.show()
true
}
}
@@ -220,6 +268,10 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl
)
}
fun importMangaFromZip() {
}
companion object {
var wn: WeakReference<NewDownloadFragment>? = null
}

View File

@@ -298,7 +298,7 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) {
query = q.toString()
activity?.apply {
PausableDownloader(getString(R.string.searchApiUrl).format(Config.myHostApiUrl.value, 0,
PausableDownloader(getString(R.string.searchApiUrl).format(Config.myHostApiUrl.random(), 0,
URLEncoder.encode(q.toString(), Charset.defaultCharset().name()), type)) {
results = Gson().fromJson(it.decodeToString(), BookListStructure::class.java)
count = results?.results?.total?:0

View File

@@ -39,8 +39,9 @@ import top.fumiama.dmzj.copymanga.R
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicInteger
class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadHandler(
that.get()?.getString(R.string.mainPageApiUrl)!!.format(Config.myHostApiUrl.value),
class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadHandler({
that.get()?.getString(R.string.mainPageApiUrl)!!.format(Config.myHostApiUrl.random())
},
IndexStructure::class.java,
that.get()
) {

View File

@@ -3,14 +3,19 @@ package top.fumiama.copymanga.ui.settings
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.lifecycleScope
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.SwitchPreferenceCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import top.fumiama.copymanga.api.Config.proxyUrl
import top.fumiama.copymanga.view.interaction.UITools
import top.fumiama.copymanga.view.AutoHideEditTextPreferenceDialogFragmentCompat
import top.fumiama.dmzj.copymanga.R
@@ -22,6 +27,21 @@ class SettingsFragment: PreferenceFragmentCompat() {
delay(300)
withContext(Dispatchers.Main) {
setPreferencesFromResource(R.xml.pref_setting, rootKey)
findPreference<ListPreference>(getString(R.string.darkModeKeyID))?.setOnPreferenceChangeListener { _, newValue ->
when ((newValue as String).toInt()) {
0 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
1 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
2 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
true // 允许保存新值
}
context?.let { PreferenceManager.getDefaultSharedPreferences(it) }?.let {
if (it.getString(getString(R.string.reverseProxyKeyID), "") != proxyUrl) {
findPreference<SwitchPreferenceCompat>(getString(R.string.apiProxyKeyID))?.isVisible = false
findPreference<SwitchPreferenceCompat>(getString(R.string.imgProxyKeyID))?.isVisible = false
findPreference<EditTextPreference>(getString(R.string.imgProxyCodeKeyID))?.isVisible = false
}
}
}
}
}

View File

@@ -30,7 +30,7 @@ import java.text.SimpleDateFormat
import java.util.*
class VMHandler(activity: ViewMangaActivity, private val chapterUrl: String, private val weeks: Array<String>) : AutoDownloadHandler(
chapterUrl, Chapter2Return::class.java, activity
{ chapterUrl }, Chapter2Return::class.java, activity
) {
var manga: Chapter2Return? = null
private val wv = WeakReference(activity)

View File

@@ -1,12 +1,16 @@
package top.fumiama.copymanga.view.template
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.google.gson.Gson
import kotlinx.android.synthetic.main.line_header.view.*
import kotlinx.android.synthetic.main.line_lazybooklines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -18,6 +22,7 @@ import top.fumiama.copymanga.json.TypeBookListStructure
import top.fumiama.copymanga.net.template.PausableDownloader
import top.fumiama.copymanga.strings.Chinese
import top.fumiama.copymanga.view.interaction.Navigate
import top.fumiama.dmzj.copymanga.R
import java.lang.ref.WeakReference
@ExperimentalStdlibApi
@@ -26,6 +31,7 @@ open class InfoCardLoader(inflateRes:Int, private val navId:Int, private val isT
private val subUrl get() = getApiUrl()
var ad: PausableDownloader? = null
@SuppressLint("SetTextI18n")
override suspend fun addPage(): Unit = withContext(Dispatchers.IO) {
super.addPage()
setProgress(20)
@@ -38,6 +44,16 @@ open class InfoCardLoader(inflateRes:Int, private val navId:Int, private val isT
val bookList = Gson().fromJson(data.decodeToString(), TypeBookListStructure::class.java)
bookList?.apply {
Log.d("MyICL", "offset:${results.offset}, total:${results.total}")
withContext(Dispatchers.Main) {
mysp?.footerView?.lht?.text = "${results.offset}+"
activity?.findViewById<Toolbar>(R.id.toolbar)?.let { appbar ->
appbar.title.let {
if (!it.endsWith(")")) {
appbar.title = "$it (${results.total})"
}
}
}
}
if(results.offset < results.total) {
if(code == 200) {
val size = results?.list?.size?:0
@@ -60,6 +76,16 @@ open class InfoCardLoader(inflateRes:Int, private val navId:Int, private val isT
val bookList = Gson().fromJson(data.decodeToString(), HistoryBookListStructure::class.java)
bookList?.apply {
Log.d("MyICL", "offset:${results?.offset}, total:${results?.total}")
withContext(Dispatchers.Main) {
mysp?.footerView?.lht?.text = "${results.offset}+"
activity?.findViewById<Toolbar>(R.id.toolbar)?.let { appbar ->
appbar.title.let {
if (!it.endsWith(")")) {
appbar.title = "$it (${results.total})"
}
}
}
}
if(results.offset < results.total) {
if(code == 200) {
val size = results?.list?.size?:0
@@ -82,6 +108,16 @@ open class InfoCardLoader(inflateRes:Int, private val navId:Int, private val isT
val bookList = Gson().fromJson(data.decodeToString(), ShelfStructure::class.java)
bookList?.apply {
Log.d("MyICL", "offset:${results?.offset}, total:${results?.total}")
withContext(Dispatchers.Main) {
mysp?.footerView?.lht?.text = "${results.offset}+"
activity?.findViewById<Toolbar>(R.id.toolbar)?.let { appbar ->
appbar.title.let {
if (!it.endsWith(")")) {
appbar.title = "$it (${results.total})"
}
}
}
}
if(results.offset < results.total) {
if(code == 200) {
val size = results?.list?.size?:0
@@ -105,6 +141,16 @@ open class InfoCardLoader(inflateRes:Int, private val navId:Int, private val isT
val bookList = Gson().fromJson(data.decodeToString(), BookListStructure::class.java)
bookList?.apply {
Log.d("MyICL", "offset:${results?.offset}, total:${results?.total}")
withContext(Dispatchers.Main) {
mysp?.footerView?.lht?.text = "${results.offset}+"
activity?.findViewById<Toolbar>(R.id.toolbar)?.let { appbar ->
appbar.title.let {
if (!it.endsWith(")")) {
appbar.title = "$it (${results.total})"
}
}
}
}
if(results.offset < results.total) {
if(code == 200) {
val size = results?.list?.size?:0

View File

@@ -18,7 +18,7 @@ open class StatusCardFlow(private val api: Int, nav: Int, inflateRes: Int,
override fun getApiUrl() =
getString(api).format(
Config.myHostApiUrl.value,
Config.myHostApiUrl.random(),
page * 21,
sortWay[sortValue]
)

View File

@@ -12,7 +12,7 @@ open class ThemeCardFlow(private val api: Int, nav: Int) : StatusCardFlow(0, nav
private var theme = ""
override fun getApiUrl() =
getString(api).format(
Config.myHostApiUrl.value,
Config.myHostApiUrl.random(),
page * 21,
sortWay[sortValue],
theme

View File

@@ -38,4 +38,14 @@
<item>5</item>
<item>6</item>
</string-array>
<string-array name="themes">
<item>跟随系统</item>
<item>浅色</item>
<item>深色</item>
</string-array>
<string-array name="themes_ids">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
</resources>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
<!ENTITY hosturl "www.copy20.com">
<!ENTITY appver "2.2.9">
<!ENTITY hosturl "api.copy-manga.com">
<!ENTITY appver "2.3.0">
<!ENTITY proxyurl "copymanga.azurewebsites.net">
]>
<resources>
<string name="app_name">拷贝漫画</string>
@@ -66,6 +67,7 @@
<string name="mainPageApiUrl">https://%1$s/api/v3/h5/homeIndex?platform=3</string>
<string name="referUrl">https://%1$s</string>
<string name="hostUrl">&hosturl;</string>
<string name="proxyUrl">&proxyurl;</string>
<string name="rankApiUrl">https://%1$s/api/v3/ranks?limit=21&amp;offset=%2$d&amp;date_type=%3$s&amp;audience_type=%4$s&amp;platform=3</string>
<string name="searchApiUrl">https://%1$s/api/v3/search/comic?limit=21&amp;offset=%2$d&amp;q=%3$s&amp;q_type=%4$s&amp;platform=3</string>
<string name="filterApiUrl">https://%1$s/api/v3/h5/filter/comic/tags?platform=3</string>
@@ -87,11 +89,16 @@
<string name="shelfApiUrl">https://%1$s/api/v3/member/collect/comics?limit=21&amp;offset=%2$d&amp;free_type=1&amp;ordering=%3$s&amp;platform=3</string>
<string name="shelfOperateApiUrl">https://%1$s/api/v3/member/collect/comic</string>
<string name="imgProxyApiUrl">https://copymanga.azurewebsites.net/api/img?code=%1$s&amp;url=%2$s</string>
<string name="imgProxyKeyID">settings_cat_net_et_img_proxy_code</string>
<string name="apiProxyApiUrl">https://copymanga.azurewebsites.net/api/api?code=%1$s&amp;url=%2$s</string>
<string name="imgProxyApiUrl">https://&proxyurl;/api/img?code=%1$s&amp;url=%2$s</string>
<string name="imgProxyCodeKeyID">settings_cat_net_et_img_proxy_code</string>
<string name="apiProxyApiUrl">https://&proxyurl;/api/api?code=%1$s&amp;url=%2$s</string>
<string name="imgResolutionKeyID">settings_cat_net_sb_image_resolution</string>
<string name="darkModeKeyID">settings_cat_general_lp_dark_mode</string>
<string name="reverseProxyKeyID">settings_cat_net_et_reverse_proxy</string>
<string name="apiProxyKeyID">settings_cat_net_sw_use_api_proxy</string>
<string name="imgProxyKeyID">settings_cat_net_sw_use_img_proxy</string>
<string name="comandy_version_url">https://gitea.seku.su/api/v1/repos/fumiama/%1$s/releases/tags/default</string>
<string name="comandy_download_url">"https://gitea.seku.su/fumiama/%1$s/releases/download/default/%2$s_%3$s.gz"</string>
@@ -141,6 +148,8 @@
<string name="caption">标签</string>
<string name="settings_cat_general">通用</string>
<string name="settings_cat_general_lp_title_dark_mode">主题</string>
<string name="settings_cat_general_lp_summary_dark_mode">默认跟随系统</string>
<string name="settings_cat_general_sb_title_startup_menu">启动时显示</string>
<string name="settings_cat_general_sb_summary_startup_menu">默认主页</string>
<string name="settings_cat_general_et_title_app_version">拷贝版本号</string>
@@ -163,14 +172,18 @@
<string name="settings_cat_net_sm_use_comandy">使用经过优化的请求方法访问服务器</string>
<string name="settings_cat_net_et_title_api_url">请求API网址</string>
<string name="settings_cat_net_et_summary_api_url">一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl;</string>
<string name="settings_cat_net_et_title_reverse_proxy">反向代理</string>
<string name="settings_cat_net_et_summary_reverse_proxy">您可以自建反向代理并填写在此处以解决网络不畅</string>
<string name="settings_cat_net_sw_use_api_proxy">使用API代理需要密钥</string>
<string name="settings_cat_net_sm_use_api_proxy">作者自建的API代理可缓解国内图书详情加载问题但不保证100%解决,也不保证一直可用</string>
<string name="settings_cat_net_sw_use_img_proxy">使用图床代理(需要密钥)</string>
<string name="settings_cat_net_sm_use_img_proxy">作者自建的图床代理可缓解国内图片无法加载问题但不保证100%解决,也不保证一直可用</string>
<string name="settings_cat_net_et_title_img_proxy">代理密钥</string>
<string name="settings_cat_net_et_summary_img_proxy">为避免滥用,该密钥需加群559748702获得随时可能会刷新</string>
<string name="settings_cat_net_et_summary_img_proxy">为避免滥用,该密钥随时可能会刷新</string>
<string name="settings_cat_vm">漫画浏览</string>
<string name="settings_cat_vm_sw_inverse_chapters">倒序排列章节</string>
<string name="settings_cat_vm_sm_inverse_chapters">打开后详情页将从后往前排列每一话</string>
<string name="settings_cat_vm_sw_always_dark_bg">深色阅读背景</string>
<string name="settings_cat_vm_sm_always_dark_bg">打开后阅览漫画的背景色永远为黑色</string>
<string name="settings_cat_vm_sw_hide_info">隐藏底部时间栏</string>

View File

@@ -4,6 +4,16 @@
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/settings_cat_general">
<ListPreference
android:max="2"
app:iconSpaceReserved="false"
app:key="@string/darkModeKeyID"
app:selectable="true"
app:summary="@string/settings_cat_general_lp_summary_dark_mode"
app:title="@string/settings_cat_general_lp_title_dark_mode"
app:entries="@array/themes"
app:entryValues="@array/themes_ids"
android:defaultValue="0"/>
<ListPreference
android:max="6"
app:iconSpaceReserved="false"
@@ -59,7 +69,7 @@
<ListPreference
android:max="1500"
app:iconSpaceReserved="false"
app:key="settings_cat_net_sb_image_resolution"
app:key="@string/imgResolutionKeyID"
app:selectable="true"
app:summary="@string/settings_cat_net_sb_summary_image_resolution"
app:title="@string/settings_cat_net_sb_title_image_resolution"
@@ -89,15 +99,25 @@
app:enableCopying="true"
app:iconSpaceReserved="false"
app:key="settings_cat_net_et_api_url" />
<EditTextPreference
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:selectAllOnFocus="false"
android:singleLine="true"
android:title="@string/settings_cat_net_et_title_reverse_proxy"
android:summary="@string/settings_cat_net_et_summary_reverse_proxy"
app:enableCopying="true"
app:iconSpaceReserved="false"
app:key="@string/reverseProxyKeyID" />
<SwitchPreferenceCompat
app:iconSpaceReserved="false"
app:key="settings_cat_net_sw_use_api_proxy"
app:key="@string/apiProxyKeyID"
app:selectable="true"
app:summary="@string/settings_cat_net_sm_use_api_proxy"
app:title="@string/settings_cat_net_sw_use_api_proxy" />
<SwitchPreferenceCompat
app:iconSpaceReserved="false"
app:key="settings_cat_net_sw_use_img_proxy"
app:key="@string/imgProxyKeyID"
app:selectable="true"
app:summary="@string/settings_cat_net_sm_use_img_proxy"
app:title="@string/settings_cat_net_sw_use_img_proxy" />
@@ -110,11 +130,17 @@
android:summary="@string/settings_cat_net_et_summary_img_proxy"
app:enableCopying="true"
app:iconSpaceReserved="false"
app:key="settings_cat_net_et_img_proxy_code" />
app:key="@string/imgProxyCodeKeyID" />
</PreferenceCategory>
<PreferenceCategory
app:iconSpaceReserved="false"
app:title="@string/settings_cat_vm">
<SwitchPreferenceCompat
app:iconSpaceReserved="false"
app:key="settings_cat_vm_sw_inverse_chapters"
app:selectable="true"
app:summary="@string/settings_cat_vm_sm_inverse_chapters"
app:title="@string/settings_cat_vm_sw_inverse_chapters" />
<SwitchPreferenceCompat
app:iconSpaceReserved="false"
app:key="settings_cat_vm_sw_always_dark_bg"