1
0
mirror of https://github.com/fumiama/copymanga.git synced 2026-07-17 02:40:34 +08:00
新增
1. 竖向翻页支持瀑布流
优化
1. 阅读器启动、章节图片流式收集与大章节下载稳定性 (#200) by @xuanli20
2. 同步到最新拷贝网址
3. 代码结构
修复
1. 高版本安卓下按返回键直接退出程序而非回退到上个页面
2. 音量键翻页无法切换到下一章节
This commit is contained in:
源文雨
2026-07-15 20:41:35 +08:00
parent f0b0189c9e
commit 85fb6b3834
23 changed files with 857 additions and 503 deletions

6
.idea/kotlinc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.1.0" />
</component>
</project>

View File

@@ -10,8 +10,8 @@ android {
applicationId "top.fumiama.copymangaweb"
minSdkVersion 23
targetSdkVersion 36
versionCode 16
versionName '1.5.3'
versionCode 17
versionName '1.6.0'
resConfigs "zh", "zh-rCN"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application
android:allowBackup="true"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"

View File

@@ -2,15 +2,61 @@ javascript:
if (typeof (loaded) == "undefined") {
var loaded = true;
function scanChapters(chapter) {
var chapterList = chapter.getElementsByClassName("tab-pane fade show active")[0].getElementsByTagName("ul")[0].getElementsByTagName("a");
var activeTab = chapter.getElementsByClassName("tab-pane fade show active")[0];
var list = activeTab && activeTab.getElementsByTagName("ul")[0];
var chapterList = list ? list.getElementsByTagName("a") : [];
var chapterArr = Array();
for (var i = 0; i < chapterList.length; i++) {
chapterArr.push(JSON.constructor());
chapterArr[i]["name"] = chapterList[i].title;
chapterArr[i]["url"] = chapterList[i].href;
var name = chapterList[i].title || chapterList[i].innerText.trim();
var url = chapterList[i].href;
if (name && url) chapterArr.push({"name": name, "url": url});
}
return chapterArr;
}
var detailScanVersion = 0;
function collectChapterGroups() {
var container = document.getElementsByClassName("upLoop")[0];
if (!container || container.children.length < 2 || container.children.length % 2) return null;
var groups = Array();
for (var i = 0; i < container.children.length; i += 2) {
var name = container.children[i].innerText.trim();
var chapters = scanChapters(container.children[i + 1]);
if (!name || !chapters.length) return null;
groups.push({"name": name, "chapters": chapters});
}
return groups.length ? groups : null;
}
function waitForChapterGroups() {
var version = ++detailScanVersion;
var sourceUrl = location.href;
var lastChapterCount = -1;
var stableChecks = 0;
var startedAt = Date.now();
function scan() {
if (version !== detailScanVersion || location.href !== sourceUrl) return;
var groups = collectChapterGroups();
if (groups) {
var chapterCount = groups.reduce(function (count, group) {
return count + group.chapters.length;
}, 0);
stableChecks = chapterCount === lastChapterCount ? stableChecks + 1 : 0;
lastChapterCount = chapterCount;
if (stableChecks >= 4) {
var titleElement = document.getElementsByTagName("h6")[0];
var comicTitle = titleElement && titleElement.title
? titleElement.title
: document.title;
GM.setFab(JSON.stringify(groups), sourceUrl, comicTitle);
return;
}
} else {
stableChecks = 0;
lastChapterCount = -1;
}
if (Date.now() - startedAt < 120000) setTimeout(scan, 250);
}
scan();
}
function smoothLoadChapter(speed, interval) {
let lastTime = 0;
let ticking = false;
@@ -156,22 +202,7 @@ if (typeof (loaded) == "undefined") {
GM.setLoadingDialog(true);
smoothLoadChapter(320, 16);
} else {
var json = Array();
var chapters = document.getElementsByClassName("upLoop")[0].children;
var newObj = null;
for(var i = 0; i < chapters.length; i++) {
if(i % 2) {
newObj["chapters"] = scanChapters(chapters[i]);
json.push(newObj);
newObj = null;
}
else {
newObj = JSON.constructor();
newObj["name"] = chapters[i].innerText;
}
}
GM.setTitle(document.getElementsByTagName("h6")[0].title);
GM.setFab(JSON.stringify(json));
waitForChapterGroups();
}
}
modify();

View File

@@ -61,13 +61,14 @@ class DlActivity : ToolsBoxActivity() {
setWebViewClient("h.js")
loadJSInterface(JSHidden(onLoadChapter = { onHiddenChapterLoaded(it) }))
} }
handler.sendEmptyMessage(-2) //setLayouts
handler.sendEmptyMessage(DlHandler.INITIALIZE_LAYOUTS)
}
override fun onDestroy() {
wm?.get()?.saveUrlsOnly = false
wmdlt?.get()?.exit = true
handler.removeCallbacksAndMessages(null)
mBinding.dwh.destroy()
super.onDestroy()
}
@@ -99,7 +100,7 @@ class DlActivity : ToolsBoxActivity() {
for (i in tbtnlist) {
if (i.isChecked && !mangaDlTools.dlChapterUrl(i.url.toString())) {
ok = false
handler.obtainMessage(-1, i.index, 0).sendToTarget()
handler.obtainMessage(DlHandler.CHAPTER_DOWNLOAD_FAILED, i.index, 0).sendToTarget()
}
}
return ok && mangaDlTools.waitChapterUrlsReady()
@@ -119,7 +120,7 @@ class DlActivity : ToolsBoxActivity() {
haveDlStarted = false
canDl = false
}
handler.sendEmptyMessage(8) //set dl card color to blue
handler.sendEmptyMessage(DlHandler.SET_DOWNLOAD_CARD_BLUE)
}
@SuppressLint("SetTextI18n")
@@ -150,7 +151,7 @@ class DlActivity : ToolsBoxActivity() {
else {
haveDlStarted = true
canDl = true
handler.sendEmptyMessage(9) //set dl card color to red
handler.sendEmptyMessage(DlHandler.SET_DOWNLOAD_CARD_RED)
Toast.makeText(this@DlActivity, "请耐心等待加载...", Toast.LENGTH_SHORT).show()
Thread {
if (fillChapters()) {
@@ -158,14 +159,14 @@ class DlActivity : ToolsBoxActivity() {
} else {
canDl = false
haveDlStarted = false
handler.sendEmptyMessage(8)
handler.sendEmptyMessage(DlHandler.SET_DOWNLOAD_CARD_BLUE)
}
}.start()
}
}
}
it.setOnLongClickListener {
handler.sendEmptyMessage(4)
handler.sendEmptyMessage(DlHandler.TOGGLE_SELECT_ALL)
return@setOnLongClickListener true
}
} }
@@ -181,7 +182,7 @@ class DlActivity : ToolsBoxActivity() {
}
private fun analyzeStructure() {
ViewMangaActivity.zipList = arrayOf()
ViewMangaActivity.zipList = emptyArray()
Gson().fromJson(json?.reader(), Array<ComicStructure>::class.java)?.let {
for (group in it) {
val tc = layoutInflater.inflate(R.layout.line_caption, mBinding.ldwn, false)
@@ -224,12 +225,17 @@ class DlActivity : ToolsBoxActivity() {
mangaDlTools.onDownloadedListener =
object : MangaDlTools.OnDownloadedListener {
override fun handleMessage(succeed: Boolean) {
handler.obtainMessage(if (succeed) 1 else -1, i.index, 0)
handler.obtainMessage(
if (succeed) DlHandler.CHAPTER_DOWNLOAD_SUCCEEDED
else DlHandler.CHAPTER_DOWNLOAD_FAILED,
i.index,
0
)
.sendToTarget()
}
override fun handleMessage(succeed: Boolean, pageNow: Int) {
handler.obtainMessage(
5,
DlHandler.PAGE_DOWNLOAD_FINISHED,
i.index,
pageNow,
succeed
@@ -237,7 +243,7 @@ class DlActivity : ToolsBoxActivity() {
}
override fun handleMessage(pageNow: Int){
handler.obtainMessage(
10,
DlHandler.PAGE_DOWNLOAD_RETRYING,
i.index,
pageNow
).sendToTarget()
@@ -268,14 +274,19 @@ class DlActivity : ToolsBoxActivity() {
tbtnlist += tbvTbtn
tbvTbtn.url = url
tbtncnt++
val zipPosition = ViewMangaActivity.zipList?.size
ViewMangaActivity.zipList = ViewMangaActivity.zipList?.plus("$title.zip")
tbvTbtn.textOff = title
tbvTbtn.textOn = title
tbvTbtn.text = title
tbvTbtn.hint = caption
tbvTbtn.layoutParams.width = btnw
val zipFile = File("${getExternalFilesDir("")}/$comicName/$caption/$title.zip")
val zipPosition = if (zipFile.exists()) {
ViewMangaActivity.zipList.orEmpty().size.also {
ViewMangaActivity.zipList = (
ViewMangaActivity.zipList?.toList().orEmpty() + zipFile
).toTypedArray()
}
} else null
if (zipFile.exists()) {
tbvTbtn.setBackgroundResource(R.drawable.rndbg_checked)
tbvTbtn.isChecked = false
@@ -301,9 +312,7 @@ class DlActivity : ToolsBoxActivity() {
} else if(tbtn.isChecked) {
tbtn.apply { post {
isChecked = false
zipPosition?.let { Thread {
callVM(title, zipFile, it)
}.start() }
zipPosition?.let { callVM(title, zipFile, it) }
} }
}
}
@@ -321,7 +330,7 @@ class DlActivity : ToolsBoxActivity() {
text = "$dldChapter/${++checkedChapter}"
} }
}
handler.sendEmptyMessage(7)
handler.sendEmptyMessage(DlHandler.DELETE_SELECTED_CHAPTERS)
})
}
true
@@ -338,15 +347,16 @@ class DlActivity : ToolsBoxActivity() {
}
}
}
handler.sendEmptyMessage(6)
handler.sendEmptyMessage(DlHandler.UPDATE_CHAPTER_PROGRESS)
}
private fun callVM(titleText: String, zipFile: File, zipPosition:Int) {
ViewMangaActivity.titleText = titleText
ViewMangaActivity.zipFile = zipFile
//ViewMangaActivity.zipList = zipArrayList
ViewMangaActivity.zipPosition = zipPosition
ViewMangaActivity.cd = zipFile.parentFile
ViewMangaActivity.nextChapterUrl = null
ViewMangaActivity.previousChapterUrl = null
startActivity(Intent(this@DlActivity, ViewMangaActivity::class.java))
}

View File

@@ -4,21 +4,21 @@ import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.Toast
import top.fumiama.copymangaweb.R
import top.fumiama.copymangaweb.databinding.ActivityDlistBinding
import top.fumiama.copymangaweb.handler.DlLHandler
import top.fumiama.copymangaweb.tool.InsetsTools
import java.io.File
import java.util.concurrent.Executors
import java.util.regex.Pattern
import java.util.zip.ZipInputStream
class DlListActivity: Activity() {
class DlListActivity : Activity() {
private lateinit var mBinding: ActivityDlistBinding
private var nullZipDirStr = emptyArray<String>()
private var handler: DlLHandler? = null
private val fileExecutor = Executors.newSingleThreadExecutor()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -26,79 +26,97 @@ class DlListActivity: Activity() {
setContentView(mBinding.root)
InsetsTools.applySafeContentInsets(this, mBinding.root)
mBinding.myt.ttitle.text = intent.getStringExtra("title")
handler = DlLHandler(Looper.myLooper()!!, this)
handler?.obtainMessage(3, currentDir)?.sendToTarget() //call scanFile
loadDirectory(currentDir)
}
override fun onDestroy() {
fileExecutor.shutdownNow()
super.onDestroy()
handler?.removeCallbacksAndMessages(null)
handler = null
}
fun scanFile(cd: File?){
val isRoot = cd == getExternalFilesDir("")
val jsonFile = File(cd, "info.bin")
if(isRoot || !jsonFile.exists()) cd?.list()?.sortedArrayWith { o1, o2 ->
if(o1.endsWith(".zip") && o2.endsWith(".zip")) (10000*getFloat(o1) - 10000*getFloat(o2) + 0.5).toInt()
else o1[0] - o2[0]
}?.let {
mBinding.mylv.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, it)
mBinding.mylv.setOnItemClickListener { _, _, position, _ ->
val chosenFile = File(cd, it[position])
val chosenJson = File(chosenFile, "info.bin")
//Toast.makeText(this, "进入$chosenFile", Toast.LENGTH_SHORT).show()
when {
chosenJson.exists() -> callDownloadActivity(chosenJson)
chosenFile.isDirectory -> {
currentDir = chosenFile
startActivity(
Intent(
this,
DlListActivity::class.java
).putExtra("title", it[position])
)
}
chosenFile.name.endsWith(".zip") -> {
Toast.makeText(this, "加载中...", Toast.LENGTH_SHORT).show()
ViewMangaActivity.zipFile = chosenFile
ViewMangaActivity.titleText = it[position]
ViewMangaActivity.zipPosition = position
ViewMangaActivity.zipList = it.toList().toTypedArray()
ViewMangaActivity.cd = cd
startActivity(Intent(this, ViewMangaActivity::class.java))
}
private fun loadDirectory(directory: File?) {
fileExecutor.execute {
val isRoot = directory == getExternalFilesDir("")
val jsonFile = File(directory, "info.bin")
val entries = if (isRoot || !jsonFile.exists()) {
directory?.list()?.sortedWith(::compareFileNames)
} else null
runOnUiThread {
if (!isFinishing && !isDestroyed && entries != null) {
showDirectory(directory, entries)
}
}
mBinding.mylv.setOnItemLongClickListener { _, _, position, _ ->
val chosenFile = File(cd, it[position])
AlertDialog.Builder(this)
.setIcon(R.drawable.ic_launcher_foreground).setMessage("在此执行删除/查错?")
.setTitle("提示").setPositiveButton("删除"){ _, _ ->
if(chosenFile.exists()) handler?.obtainMessage(2, chosenFile)?.sendToTarget() //call rmrf
handler?.obtainMessage(3, cd)?.sendToTarget() //call scanFile
}.setNegativeButton(android.R.string.cancel){_, _ ->}
.setNeutralButton("查错"){_, _ -> handler?.obtainMessage(1, chosenFile)?.sendToTarget()} //call checkDir
.show()
true
}
}
}
fun rmrf(f: File) {
private fun showDirectory(directory: File?, entries: List<String>) {
mBinding.mylv.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, entries)
mBinding.mylv.setOnItemClickListener { _, _, position, _ ->
val chosenFile = File(directory, entries[position])
val chosenJson = File(chosenFile, "info.bin")
when {
chosenJson.exists() -> callDownloadActivity(chosenJson)
chosenFile.isDirectory -> {
currentDir = chosenFile
startActivity(
Intent(this, DlListActivity::class.java)
.putExtra("title", entries[position])
)
}
chosenFile.name.endsWith(".zip", ignoreCase = true) -> {
Toast.makeText(this, "加载中...", Toast.LENGTH_SHORT).show()
val zipFiles = entries
.map { File(directory, it) }
.filter { it.isFile && it.extension.equals("zip", ignoreCase = true) }
ViewMangaActivity.zipFile = chosenFile
ViewMangaActivity.titleText = entries[position]
ViewMangaActivity.zipPosition = zipFiles.indexOf(chosenFile)
ViewMangaActivity.zipList = zipFiles.toTypedArray()
ViewMangaActivity.cd = directory
ViewMangaActivity.nextChapterUrl = null
ViewMangaActivity.previousChapterUrl = null
startActivity(Intent(this, ViewMangaActivity::class.java))
}
}
}
mBinding.mylv.setOnItemLongClickListener { _, _, position, _ ->
val chosenFile = File(directory, entries[position])
AlertDialog.Builder(this)
.setIcon(R.drawable.ic_launcher_foreground)
.setMessage("在此执行删除/查错?")
.setTitle("提示")
.setPositiveButton("删除") { _, _ ->
fileExecutor.execute {
if (chosenFile.exists()) deleteRecursively(chosenFile)
loadDirectory(directory)
}
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton("查错") { _, _ -> checkDirectory(chosenFile) }
.show()
true
}
}
private fun deleteRecursively(f: File) {
if (f.isDirectory) f.listFiles()?.let {
for (i in it)
if (i.isDirectory) rmrf(i)
if (i.isDirectory) deleteRecursively(i)
else i.delete()
}
f.delete()
}
fun checkDir(f: File){
nullZipDirStr = emptyArray()
findNullWebpZipFileInDir(f)
if(nullZipDirStr.isNotEmpty()) showErrorZip(nullZipDirStr.joinToString("\n"))
else Toast.makeText(this, "未发现错误", Toast.LENGTH_SHORT).show()
private fun checkDirectory(directory: File) {
fileExecutor.execute {
val invalidFiles = findInvalidZipFiles(directory)
runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
nullZipDirStr = invalidFiles.toTypedArray()
if (invalidFiles.isNotEmpty()) showErrorZip(invalidFiles.joinToString("\n"))
else Toast.makeText(this, "未发现错误", Toast.LENGTH_SHORT).show()
}
}
}
private fun callDownloadActivity(jsonFile: File){
@@ -110,54 +128,64 @@ class DlListActivity: Activity() {
)
}
private fun findNullWebpZipFileInDir(f: File){
if (f.isDirectory) f.listFiles()?.let {
for (i in it)
if (i.isDirectory) findNullWebpZipFileInDir(i)
else if(!checkZip(i)) nullZipDirStr += i.path.substringAfterLast(getExternalFilesDir("").toString())
private fun findInvalidZipFiles(file: File): List<String> {
val invalidFiles = mutableListOf<String>()
if (file.isDirectory) file.listFiles()?.forEach { child ->
if (child.isDirectory) invalidFiles += findInvalidZipFiles(child)
else if (child.extension.equals("zip", ignoreCase = true) && !checkZip(child)) {
invalidFiles += child.path.substringAfterLast(getExternalFilesDir("").toString())
}
}
return invalidFiles
}
private fun checkZip(f: File): Boolean{
private fun checkZip(f: File): Boolean {
return try {
val exist = f.exists()
if (!exist) true
else {
var re = true
val zip = ZipInputStream(f.inputStream().buffered())
var entry = zip.nextEntry
while (entry != null) {
if (!entry.isDirectory){
if(zip.read() == -1 && entry.size == 0L){
ZipInputStream(f.inputStream().buffered()).use { zip ->
var entry = zip.nextEntry
while (entry != null) {
if (!entry.isDirectory && zip.read() == -1 && entry.size == 0L) {
re = false
break
}
zip.closeEntry()
entry = zip.nextEntry
}
entry = zip.nextEntry
}
zip.closeEntry()
zip.close()
re
}
} catch (e: Exception) {
Toast.makeText(this, "读取${f.name}错误!", Toast.LENGTH_SHORT).show()
true
Log.e("DlListActivity", "读取 ${f.name} 失败", e)
false
}
}
private fun compareFileNames(first: String, second: String): Int {
return if (first.endsWith(".zip") && second.endsWith(".zip")) {
(10000 * getFloat(first) - 10000 * getFloat(second) + 0.5).toInt()
} else first.compareTo(second)
}
private fun showErrorZip(msg: CharSequence) = AlertDialog.Builder(this)
.setIcon(R.drawable.ic_launcher_foreground)
.setTitle("找到以下错误文件,是否删除?")
.setMessage(msg)
.setPositiveButton(android.R.string.ok){_, _ -> deleteErrorZip()}
.setNegativeButton(android.R.string.cancel){_, _ ->}
.setPositiveButton(android.R.string.ok) { _, _ -> deleteErrorZip() }
.setNegativeButton(android.R.string.cancel, null)
.show()
private fun deleteErrorZip(){
private fun deleteErrorZip() {
val exf = getExternalFilesDir("")
for(i in nullZipDirStr){
val f = File(exf, i)
if(f.exists()) f.delete()
fileExecutor.execute {
for (path in nullZipDirStr) {
val file = File(exf, path)
if (file.exists()) file.delete()
}
loadDirectory(currentDir)
}
}
@@ -165,16 +193,14 @@ class DlListActivity: Activity() {
val newString = StringBuffer()
var matcher = Pattern.compile("\\d+.+\\d+").matcher(oldString)
while (matcher.find()) newString.append(matcher.group())
//Log.d("MyDLL1", newString.toString())
if(newString.isEmpty()){
if (newString.isEmpty()) {
matcher = Pattern.compile("\\d").matcher(oldString)
while (matcher.find()) newString.append(matcher.group())
}
//Log.d("MyDLL2", newString.toString().toFloat().toString())
return if(newString.isEmpty()) 0f else newString.toString().toFloat()
return newString.toString().toFloatOrNull() ?: 0f
}
companion object{
companion object {
var currentDir: File? = null
}
}

View File

@@ -3,12 +3,14 @@ package top.fumiama.copymangaweb.activity
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Looper
import android.view.View
import android.view.WindowManager
import android.webkit.ValueCallback
import android.webkit.WebView
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -34,6 +36,9 @@ class MainActivity: ToolsBoxActivity() {
var saveUrlsOnly = false
lateinit var mBinding: ActivityMainBinding
private val mViewModel = MainViewModel()
private var backInvokedCallback: OnBackInvokedCallback? = null
@Volatile
private var requestedDetailsUrl: String? = null
@SuppressLint("JavascriptInterface")
override fun onCreate(savedInstanceState: Bundle?) {
@@ -43,6 +48,7 @@ class MainActivity: ToolsBoxActivity() {
mBinding.lifecycleOwner = this
setContentView(mBinding.root)
InsetsTools.applySafeContentInsets(this, mBinding.root)
registerBackCallback()
wm = WeakReference(this)
mh = MainHandler(Looper.myLooper()!!)
@@ -58,7 +64,7 @@ class MainActivity: ToolsBoxActivity() {
}
}
WebView.setWebContentsDebuggingEnabled(true)
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
mBinding.w.apply { post {
setWebViewClient("i.js")
webChromeClient = WebChromeClient()
@@ -78,36 +84,38 @@ class MainActivity: ToolsBoxActivity() {
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
if(mBinding.w.canGoBack()) mBinding.w.goBack()
else super.onBackPressed()
navigateBack()
}
private fun registerBackCallback() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
backInvokedCallback = OnBackInvokedCallback(::navigateBack).also {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
it
)
}
}
private fun navigateBack() {
if (mBinding.w.canGoBack()) mBinding.w.goBack()
else finishAfterTransition()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == FILE_CHOOSER_RESULT_CODE) { //处理返回的图片,并进行上传
if (uploadMessageAboveL == null || resultCode != RESULT_OK) return
data?.let {
onActivityResultAboveL(requestCode, resultCode, it)
}
}
if (requestCode != FILE_CHOOSER_RESULT_CODE) return
val callback = uploadMessageAboveL ?: return
uploadMessageAboveL = null
callback.onReceiveValue(if (resultCode == RESULT_OK) data?.selectedUris() else null)
}
private fun onActivityResultAboveL(requestCode: Int, resultCode: Int, intent: Intent) {
if (requestCode != FILE_CHOOSER_RESULT_CODE ||
uploadMessageAboveL == null ||
resultCode != RESULT_OK
) return
intent.clipData?.let { clipData ->
var results = arrayOf<Uri>()
for (i in 0..clipData.itemCount) {
val item = clipData.getItemAt(i)
results += item.uri
}
if (intent.dataString != null) {
uploadMessageAboveL?.onReceiveValue(results)
uploadMessageAboveL = null
}
}
private fun Intent.selectedUris(): Array<Uri>? {
val uris = clipData?.let { clips ->
Array(clips.itemCount) { index -> clips.getItemAt(index).uri }
} ?: data?.let { arrayOf(it) }
return uris?.takeIf { it.isNotEmpty() }
}
private suspend fun goCheckUpdate(ignoreSkip: Boolean) {
@@ -120,6 +128,7 @@ class MainActivity: ToolsBoxActivity() {
}
fun loadHiddenUrl(u: String) {
requestedDetailsUrl = u
mBinding.wh.apply { post { loadUrl(u) } }
}
@@ -137,7 +146,9 @@ class MainActivity: ToolsBoxActivity() {
startActivity(Intent(this, ViewMangaActivity::class.java))
}
fun setFab(content: String) {
fun setFab(content: String, sourceUrl: String, comicTitle: String) {
if (content.isBlank() || content == "[]" || !isRequestedDetailsPage(sourceUrl)) return
DlActivity.comicName = comicTitle
json = content
lifecycleScope.launch {
withContext(Dispatchers.Main) {
@@ -148,6 +159,7 @@ class MainActivity: ToolsBoxActivity() {
}
fun setFab2DlList() {
requestedDetailsUrl = null
lifecycleScope.launch {
withContext(Dispatchers.Main) {
mViewModel.showDlList.value = true
@@ -157,9 +169,20 @@ class MainActivity: ToolsBoxActivity() {
}
fun hideFab() {
requestedDetailsUrl = null
lifecycleScope.launch { mViewModel.setFabVisibility(false) }
}
private fun isRequestedDetailsPage(sourceUrl: String): Boolean {
val requestedPath = requestedDetailsUrl
?.let(Uri::parse)
?.path
?.trimEnd('/')
?: return false
val sourcePath = Uri.parse(sourceUrl).path?.trimEnd('/') ?: return false
return requestedPath == sourcePath
}
fun onFabClicked(v: View) {
DlListActivity.currentDir = getExternalFilesDir("")
startActivity(
@@ -168,13 +191,16 @@ class MainActivity: ToolsBoxActivity() {
)
}
fun openImageChooserActivity() {
// 调用自己的图库
fun openImageChooserActivity(callback: ValueCallback<Array<Uri>>) {
uploadMessageAboveL?.onReceiveValue(null)
uploadMessageAboveL = callback
startActivityForResult(
Intent.createChooser(
Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("image/*"), "Image Chooser"
.setType("image/*")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false),
"Image Chooser"
), FILE_CHOOSER_RESULT_CODE
)
}
@@ -182,25 +208,40 @@ class MainActivity: ToolsBoxActivity() {
fun callViewManga(content: String) {
lifecycleScope.launch { withContext(Dispatchers.IO) {
val listChapter = content.split('\n')
if (listChapter.size < CHAPTER_METADATA_LINE_COUNT) return@withContext
val images = listChapter.drop(CHAPTER_METADATA_LINE_COUNT).toTypedArray()
if(!saveUrlsOnly) {
ViewMangaActivity.titleText = listChapter[0].substringBeforeLast(' ')
ViewMangaActivity.nextChapterUrl = listChapter[1].let { if(it == "null") null else it }
ViewMangaActivity.previousChapterUrl = listChapter[2].let { if(it == "null") null else it }
ViewMangaActivity.imgUrls = arrayOf()
for(i in 3 until listChapter.size) ViewMangaActivity.imgUrls += listChapter[i]
ViewMangaActivity.imgUrls = images
withContext(Dispatchers.Main) {
startActivity(Intent(this@MainActivity, ViewMangaActivity::class.java))
}
} else {
var imgs = arrayOf<String>()
for(i in 3 until listChapter.size) imgs += listChapter[i]
wmdlt?.get()?.setChapterImages(listChapter[0].substringAfterLast(' '), imgs)
wmdlt?.get()?.setChapterImages(listChapter[0].substringAfterLast(' '), images)
}
} }
}
override fun onDestroy() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
backInvokedCallback?.let(onBackInvokedDispatcher::unregisterOnBackInvokedCallback)
}
backInvokedCallback = null
uploadMessageAboveL?.onReceiveValue(null)
uploadMessageAboveL = null
mBinding.w.destroy()
mBinding.wh.destroy()
mh?.dispose()
mh = null
if (wm?.get() === this) wm = null
super.onDestroy()
}
companion object {
const val FILE_CHOOSER_RESULT_CODE = 1
private const val FILE_CHOOSER_RESULT_CODE = 1
private const val CHAPTER_METADATA_LINE_COUNT = 3
var wm: WeakReference<MainActivity>? = null
var mh: MainHandler? = null
}

View File

@@ -7,41 +7,37 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import android.widget.Toast
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import top.fumiama.copymangaweb.R
import top.fumiama.copymangaweb.activity.reader.ContinuousMangaAdapter
import top.fumiama.copymangaweb.activity.reader.PagedMangaAdapter
import top.fumiama.copymangaweb.activity.reader.ReaderOverlayController
import top.fumiama.copymangaweb.activity.MainActivity.Companion.wm
import top.fumiama.copymangaweb.activity.template.ToolsBoxActivity
import top.fumiama.copymangaweb.databinding.ActivityViewmangaBinding
import top.fumiama.copymangaweb.handler.TimeThread
import top.fumiama.copymangaweb.tool.PropertiesTools
import top.fumiama.copymangaweb.tool.ToolsBox
import top.fumiama.copymangaweb.tool.PagesManager
import top.fumiama.copymangaweb.view.ScaleImageView
import top.fumiama.copymangaweb.web.JSHidden
import top.fumiama.copymangaweb.web.WebChromeClient
import java.io.File
import java.lang.ref.WeakReference
import java.text.SimpleDateFormat
import java.util.Date
import java.util.concurrent.Executors
import kotlin.math.max
import kotlin.math.min
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
class ViewMangaActivity : ToolsBoxActivity() {
lateinit var handler: Handler
lateinit var tt: TimeThread
lateinit var mBinding: ActivityViewmangaBinding
var count = 0
@@ -54,32 +50,40 @@ class ViewMangaActivity : ToolsBoxActivity() {
private var isInSeek = false
private var currentItem = 0
private var notUseVP = true
private var verticalReading = false
private var mangaZip = zipFile
val dlZip2View = mangaZip != null
private var streamUrl = streamChapterUrl
private var readerPrepared = false
private var streamFinished = false
private var streamDeclaredCount = 0
private var userRequestedExit = false
private var backInvokedCallback: OnBackInvokedCallback? = null
private val streamSeenUrls = LinkedHashSet<String>()
private var vpAdapter: RecyclerView.Adapter<ViewData>? = null
private var pagedAdapter: PagedMangaAdapter? = null
private var continuousAdapter: ContinuousMangaAdapter? = null
private val imageExecutor = Executors.newFixedThreadPool(2)
private lateinit var overlayController: ReaderOverlayController
private val pagesManager by lazy { PagesManager(WeakReference(this)) }
private val volTurnPage get() = p["volturn"] == "true"
var pageNum = 1
get() {
field = getPageNumber()
return field
private val readerMode: ReaderMode
get() = when {
verticalReading -> ReaderMode.CONTINUOUS
notUseVP -> ReaderMode.SINGLE_PAGE
else -> ReaderMode.PAGED
}
var pageNum: Int
get() = getPageNumber()
set(value) {
setPageNumber(value)
if (notUseVP) {
//currentItem += delta
setPageNumber(value, smoothScroll = true)
if (readerMode == ReaderMode.SINGLE_PAGE) {
try {
loadOneImg()
} catch (e: java.lang.Exception) {
e.printStackTrace()
toolsBox.toastError("页数${currentItem}不合法")
}
}// else vp.currentItem += delta
field = getPageNumber()
}
}
@SuppressLint("SetTextI18n")
@@ -87,14 +91,18 @@ class ViewMangaActivity : ToolsBoxActivity() {
super.onCreate(savedInstanceState)
mBinding = ActivityViewmangaBinding.inflate(layoutInflater)
setContentView(mBinding.root)
registerBackCallback()
va = WeakReference(this)
p = PropertiesTools(File("$filesDir/settings.properties"))
r2l = p["r2l"] == "true"
notUseVP = p["noAnimation"] == "true"
handler = MyHandler(toolsBox)
tt = TimeThread(handler, 22)
tt.canDo = true
tt.start()
verticalReading = p["vertical"] == "true"
overlayController = ReaderOverlayController(
activity = this,
binding = mBinding,
toolsBox = toolsBox,
drawerOffset = { infoDrawerDelta }
).also { it.start() }
dialog = Dialog(this)
dialog?.apply {
setContentView(R.layout.dialog_unzipping)
@@ -105,13 +113,18 @@ class ViewMangaActivity : ToolsBoxActivity() {
if(dlZip2View && mangaZip?.exists() != true) toolsBox.toastError("已经到头了~")
else if(!dlZip2View && !streamUrl.isNullOrBlank()) startStreamingCollector(streamUrl!!)
else Thread {
try {
count = if (dlZip2View) countZipItems() else imgUrls.size
val initialCount = try {
if (dlZip2View) countZipItems() else imgUrls.size
} catch (e: Exception) {
e.printStackTrace()
runOnUiThread { toolsBox.toastError("分析图片url错误") }
0
}
runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
count = initialCount
if (initialCount == 0) toolsBox.toastError("分析图片url错误")
prepareReaderIfNeeded()
}
runOnUiThread { prepareReaderIfNeeded() }
}.start()
}
@@ -162,14 +175,15 @@ class ViewMangaActivity : ToolsBoxActivity() {
private fun onStreamingCount(total: Int) {
if (total <= 0) return
runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
val oldCount = count
streamDeclaredCount = max(streamDeclaredCount, total)
count = max(count, streamDeclaredCount)
if (!readerPrepared) {
prepareReaderIfNeeded()
} else {
if (!notUseVP && count > oldCount) {
vpAdapter?.notifyItemRangeInserted(oldCount, count - oldCount)
if (readerMode != ReaderMode.SINGLE_PAGE && count > oldCount) {
notifyPagesInserted(oldCount, count - oldCount)
}
syncSeekBarOnly()
}
@@ -184,17 +198,25 @@ class ViewMangaActivity : ToolsBoxActivity() {
}
private fun onStreamingFinished() {
streamFinished = true
Log.d("CopymangaDL", "reader stream finished pages=$count")
runOnUiThread {
dialog?.dismiss()
dialog = null
syncSeekBarOnly()
if (isFinishing || isDestroyed) return@runOnUiThread
streamFinished = true
Log.d(
"CopymangaDL",
"reader stream finished declared=$streamDeclaredCount received=${imgUrls.size}"
)
prepareReaderIfNeeded()
if (readerPrepared) {
dialog?.dismiss()
dialog = null
syncSeekBarOnly()
}
}
}
private fun appendStreamingImages(urls: Array<String>) {
runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
val oldCount = count
val oldImageCount = imgUrls.size
var added = 0
@@ -209,12 +231,16 @@ class ViewMangaActivity : ToolsBoxActivity() {
Log.d("CopymangaDL", "reader appended images added=$added total=$count images=${imgUrls.size}")
if (!readerPrepared) prepareReaderIfNeeded()
else {
if (notUseVP) {
if (readerMode == ReaderMode.SINGLE_PAGE) {
if (currentItem in oldImageCount until imgUrls.size) loadOneImg(hidePanel = false)
else syncSeekBarOnly()
} else {
if (count > oldCount) vpAdapter?.notifyItemRangeInserted(oldCount, count - oldCount)
if (count > oldCount) notifyPagesInserted(oldCount, count - oldCount)
notifyVisiblePagesIfArrived(oldImageCount, imgUrls.size)
continuousAdapter?.notifyItemRangeChanged(
oldImageCount,
imgUrls.size - oldImageCount
)
syncSeekBarOnly()
}
preloadAround(getLogicalCurrentItem())
@@ -223,17 +249,21 @@ class ViewMangaActivity : ToolsBoxActivity() {
}
private fun prepareReaderIfNeeded() {
if (
readerPrepared ||
count <= 0 ||
isFinishing ||
isDestroyed ||
shouldWaitForLastStreamingPage()
) return
readerPrepared = true
try {
if (count <= 0) return
prepareItems()
if(pn > 0) {
pageNum = pn
pn = -1
} else if(pn == -2){
pageNum = count
pn = -1
}
setPageNumber(consumeRequestedPage(), smoothScroll = false)
if (readerMode == ReaderMode.SINGLE_PAGE) loadOneImg()
else syncSeekBarOnly()
} catch (e: Exception) {
readerPrepared = false
e.printStackTrace()
toolsBox.toastError("准备控件错误")
} finally {
@@ -244,6 +274,23 @@ class ViewMangaActivity : ToolsBoxActivity() {
}
}
private fun shouldWaitForLastStreamingPage(): Boolean {
if (streamUrl == null || pn != LAST_PAGE) return false
val allDeclaredPagesReceived =
streamDeclaredCount <= 0 || imgUrls.size >= streamDeclaredCount
return !streamFinished || !allDeclaredPagesReceived
}
private fun consumeRequestedPage(): Int {
val requestedPage = when {
pn == LAST_PAGE -> count
pn > 0 -> pn.coerceAtMost(count)
else -> 1
}
pn = FIRST_PAGE
return requestedPage
}
private fun preloadAround(position: Int) {
if (dlZip2View || position < 0 || imgUrls.isEmpty()) return
val end = min(imgUrls.size - 1, position + 4)
@@ -257,24 +304,20 @@ class ViewMangaActivity : ToolsBoxActivity() {
}
private fun getLogicalCurrentItem(): Int {
return if (notUseVP) currentItem
else if (r2l) count - mBinding.vp.currentItem - 1
else mBinding.vp.currentItem
}
private fun logicalToPagerPosition(logicalPosition: Int): Int {
return if (r2l) count - logicalPosition - 1 else logicalPosition
return when (readerMode) {
ReaderMode.SINGLE_PAGE, ReaderMode.CONTINUOUS -> currentItem
ReaderMode.PAGED -> mBinding.vp.currentItem
}
}
private fun notifyVisiblePagesIfArrived(oldImageCount: Int, newImageCount: Int) {
if (notUseVP || oldImageCount >= newImageCount || count <= 0) return
if (readerMode != ReaderMode.PAGED || oldImageCount >= newImageCount || count <= 0) return
val center = getLogicalCurrentItem().coerceIn(0, count - 1)
val from = max(0, center - 1)
val to = min(newImageCount - 1, center + 1)
for (logicalPosition in from..to) {
if (logicalPosition >= oldImageCount) {
val pagerPosition = logicalToPagerPosition(logicalPosition)
if (pagerPosition in 0 until count) vpAdapter?.notifyItemChanged(pagerPosition)
pagedAdapter?.notifyItemChanged(logicalPosition)
}
}
}
@@ -287,28 +330,57 @@ class ViewMangaActivity : ToolsBoxActivity() {
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
var flag = false
if(volTurnPage) when(keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
scrollBack()
flag = true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
scrollForward()
flag = true
if (volTurnPage) {
when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> pagesManager.goBackward()
KeyEvent.KEYCODE_VOLUME_DOWN -> pagesManager.goForward()
else -> return super.onKeyDown(keyCode, event)
}
return true
}
return if(flag) true else super.onKeyDown(keyCode, event)
return super.onKeyDown(keyCode, event)
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
exitByUserRequest()
}
private fun registerBackCallback() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
backInvokedCallback = OnBackInvokedCallback(::exitByUserRequest).also { callback ->
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
callback
)
}
}
private fun exitByUserRequest() {
userRequestedExit = true
finishAfterTransition()
}
private fun getPageNumber(): Int {
return if (r2l && !notUseVP) count - mBinding.vp.currentItem
else (if (notUseVP) currentItem else mBinding.vp.currentItem) + 1
return when (readerMode) {
ReaderMode.SINGLE_PAGE, ReaderMode.CONTINUOUS -> currentItem + 1
ReaderMode.PAGED -> mBinding.vp.currentItem + 1
}
}
private fun setPageNumber(num: Int) {
if (r2l && !notUseVP) mBinding.vp.apply { post { currentItem = count - num } }
else if (notUseVP) currentItem = num - 1 else mBinding.vp.currentItem = num - 1
private fun setPageNumber(num: Int, smoothScroll: Boolean) {
val target = (num - 1).coerceIn(0, (count - 1).coerceAtLeast(0))
when (readerMode) {
ReaderMode.SINGLE_PAGE -> currentItem = target
ReaderMode.CONTINUOUS -> {
currentItem = target
mBinding.continuousPages.scrollToPosition(target)
}
ReaderMode.PAGED -> mBinding.vp.setCurrentItem(
target,
smoothScroll
)
}
}
private fun getImgBitmap(position: Int): Bitmap? {
@@ -340,6 +412,7 @@ class ViewMangaActivity : ToolsBoxActivity() {
}
private fun loadOneImg(hidePanel: Boolean = true) {
mBinding.vone.onei.resetImageTransform()
if(dlZip2View) mBinding.vone.onei.apply { post { setImageBitmap(getImgBitmap(currentItem)) } }
else {
val url = imgUrls.getOrNull(currentItem)
@@ -364,14 +437,14 @@ class ViewMangaActivity : ToolsBoxActivity() {
@SuppressLint("SetTextI18n")
private fun prepareItems() {
if (count <= 0) return
mBinding.vone.onei.setOnTapRegionListener(::onImageTapped)
prepareVP()
prepareInfoBar(count)
if (notUseVP) loadOneImg() else prepareIdBtVH()
toolsBox.dp2px(67)?.let { setIdPosition(it) }
prepareIdBtVolTurn()
prepareIdBtVH()
prepareIdBtVP()
prepareIdBtLR()
readerPrepared = true
}
private fun prepareIdBtLR() {
@@ -388,6 +461,7 @@ class ViewMangaActivity : ToolsBoxActivity() {
private fun prepareIdBtVP() {
mBinding.infcard.idtbvp.apply { post {
isChecked = notUseVP
isEnabled = !verticalReading
setOnClickListener {
if (mBinding.infcard.idtbvp.isChecked) p["noAnimation"] = "true"
else p["noAnimation"] = "false"
@@ -397,25 +471,107 @@ class ViewMangaActivity : ToolsBoxActivity() {
}
private fun prepareVP() {
if (notUseVP) {
mBinding.vp.apply { post { visibility = View.INVISIBLE } }
mBinding.vone.root.apply { post { visibility = View.VISIBLE } }
} else {
mBinding.vp.apply { post {
visibility = View.VISIBLE
vpAdapter = ViewData(this).RecyclerViewAdapter()
adapter = vpAdapter
mBinding.vone.root.visibility = if (readerMode == ReaderMode.SINGLE_PAGE) View.VISIBLE else View.GONE
mBinding.vp.visibility = if (readerMode == ReaderMode.PAGED) View.VISIBLE else View.GONE
mBinding.continuousPages.visibility = if (readerMode == ReaderMode.CONTINUOUS) View.VISIBLE else View.GONE
when (readerMode) {
ReaderMode.SINGLE_PAGE -> Unit
ReaderMode.PAGED -> mBinding.vp.apply {
layoutDirection = if (r2l) {
View.LAYOUT_DIRECTION_RTL
} else {
View.LAYOUT_DIRECTION_LTR
}
pagedAdapter = PagedMangaAdapter(
itemCountProvider = { count },
bindImage = ::bindPageImage
)
adapter = pagedAdapter
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
val pos = if (r2l) count - position - 1 else position
preloadAround(pos)
preloadAround(position)
updateSeekBar()
super.onPageSelected(position)
}
})
if (r2l) currentItem = count - 1
} }
mBinding.vone.root.apply { post { visibility = View.INVISIBLE } }
}
ReaderMode.CONTINUOUS -> prepareContinuousReader()
}
}
private fun prepareContinuousReader() {
continuousAdapter = ContinuousMangaAdapter(
itemCountProvider = { count },
bindImage = ::bindPageImage
)
mBinding.continuousPages.apply {
adapter = continuousAdapter
itemAnimator = null
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val manager = recyclerView.layoutManager as? LinearLayoutManager ?: return
val firstVisible = manager.findFirstVisibleItemPosition()
if (firstVisible != RecyclerView.NO_POSITION) {
currentItem = firstVisible
syncSeekBarOnly()
preloadAround(firstVisible)
}
}
})
}
}
private fun notifyPagesInserted(positionStart: Int, itemCount: Int) {
if (itemCount <= 0) return
pagedAdapter?.notifyItemRangeInserted(positionStart, itemCount)
continuousAdapter?.notifyItemRangeInserted(positionStart, itemCount)
}
private fun bindPageImage(imageView: ScaleImageView, position: Int) {
imageView.tag = position
imageView.setOnTapRegionListener(::onImageTapped)
if (dlZip2View) loadZipImage(imageView, position)
else loadNetworkImage(imageView, position)
}
private fun onImageTapped(region: ScaleImageView.TapRegion) {
when (region) {
ScaleImageView.TapRegion.PREVIOUS -> {
if (readerMode == ReaderMode.CONTINUOUS) pagesManager.goBackward()
else pagesManager.toPreviousPage()
}
ScaleImageView.TapRegion.CENTER -> pagesManager.manageInfo()
ScaleImageView.TapRegion.NEXT -> {
if (readerMode == ReaderMode.CONTINUOUS) pagesManager.goForward()
else pagesManager.toNextPage()
}
}
}
private fun loadNetworkImage(imageView: ScaleImageView, position: Int) {
val url = imgUrls.getOrNull(position)
if (url.isNullOrBlank()) {
imageView.setImageResource(R.drawable.ic_dl)
return
}
Glide.with(this)
.load(toolsBox.resolution.wrap(url))
.placeholder(R.drawable.ic_dl)
.dontAnimate()
.into(imageView)
preloadAround(position)
}
private fun loadZipImage(imageView: ScaleImageView, position: Int) {
imageView.setImageResource(R.drawable.ic_dl)
imageExecutor.execute {
val bitmap = getImgBitmap(position)
imageView.post {
if (!isFinishing && !isDestroyed && imageView.tag == position) {
imageView.setImageBitmap(bitmap)
}
}
}
}
@@ -459,28 +615,18 @@ class ViewMangaActivity : ToolsBoxActivity() {
} }
mBinding.oneinfo.inftitle.isearch.apply { post {
visibility = View.INVISIBLE
setOnClickListener {
this@ViewMangaActivity.handler.sendEmptyMessage(3)
}
setOnClickListener { overlayController.toggleDrawer() }
} }
mBinding.oneinfo.inftxtprogress.apply { post { text = "$pageNum/$size" } }
}
private fun prepareIdBtVH() {
mBinding.infcard.idtbvh.apply { post {
isChecked = p["vertical"] == "true"
isChecked = verticalReading
setOnClickListener {
if (mBinding.infcard.idtbvh.isChecked) {
mBinding.vp.apply { post { orientation = ViewPager2.ORIENTATION_VERTICAL } }
p["vertical"] = "true"
} else {
mBinding.vp.apply { post { orientation = ViewPager2.ORIENTATION_HORIZONTAL } }
p["vertical"] = "false"
}
p["vertical"] = isChecked.toString()
Toast.makeText(this@ViewMangaActivity, "下次浏览生效", Toast.LENGTH_SHORT).show()
}
if (isChecked) mBinding.vp.apply { post {
orientation = ViewPager2.ORIENTATION_VERTICAL
} }
} }
}
@@ -532,52 +678,24 @@ class ViewMangaActivity : ToolsBoxActivity() {
} }
}
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
tt.canDo = false
wm?.get()?.mBinding?.w?.goBack()
super.onBackPressed()
}
override fun onDestroy() {
tt.canDo = false
handler.removeCallbacksAndMessages(null)
if (streamUrl != null) streamChapterUrl = null
super.onDestroy()
}
inner class ViewData(itemView: View) : RecyclerView.ViewHolder(itemView) {
inner class RecyclerViewAdapter :
RecyclerView.Adapter<ViewData>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewData {
return ViewData(
LayoutInflater.from(parent.context)
.inflate(R.layout.page_imgview, parent, false)
)
}
@SuppressLint("ClickableViewAccessibility", "SetTextI18n")
override fun onBindViewHolder(holder: ViewData, position: Int) {
val pos = if (r2l) count - position - 1 else position
holder.itemView.findViewById<ScaleImageView>(R.id.onei)?.let { oneImage ->
if(dlZip2View) getImgBitmap(pos)?.let {
//Glide.with(this@ViewMangaActivity).load(it).placeholder(R.drawable.bg_comment).into(holder.itemView.onei)
oneImage.setImageBitmap(it)
}
else imgUrls.getOrNull(pos)?.let { url ->
Glide.with(this@ViewMangaActivity)
.load(toolsBox.resolution.wrap(url)).placeholder(R.drawable.ic_dl)
.dontAnimate().timeout(10000)
.into(oneImage)
preloadAround(pos)
} ?: oneImage.setImageResource(R.drawable.ic_dl)
}
}
override fun getItemCount(): Int {
return count
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
backInvokedCallback?.let(onBackInvokedDispatcher::unregisterOnBackInvokedCallback)
}
backInvokedCallback = null
overlayController.close()
imageExecutor.shutdownNow()
pagedAdapter = null
continuousAdapter = null
mBinding.vp.adapter = null
mBinding.continuousPages.adapter = null
dialog?.dismiss()
dialog = null
mBinding.wcollector.destroy()
if (userRequestedExit && !dlZip2View) wm?.get()?.mBinding?.w?.goBack()
if (streamUrl != null) streamChapterUrl = null
if (va?.get() === this) va = null
super.onDestroy()
}
fun showSettings() {
@@ -603,62 +721,17 @@ class ViewMangaActivity : ToolsBoxActivity() {
).setDuration(233).start()
clicked = false
mBinding.oneinfo.infseek.postDelayed({
if (isFinishing || isDestroyed) return@postDelayed
mBinding.oneinfo.infseek.visibility = View.INVISIBLE
mBinding.oneinfo.inftitle.isearch.visibility = View.INVISIBLE
}, 300)
handler.sendEmptyMessage(1)
}
class MyHandler(
private val toolsBox: ToolsBox
) : Handler(Looper.myLooper()!!) {
private var infoShown = false
private var delta = -1f
get() {
if (field < 0) field = va?.get()?.infoDrawerDelta ?: 0f
return field
}
@SuppressLint("SimpleDateFormat", "SetTextI18n")
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
1 -> if (infoShown) {
hideInfCard(); infoShown = false
}
2 -> if (!infoShown) {
showInfCard(); infoShown = true
}
3 -> infoShown = if (infoShown) {
hideInfCard(); false
} else {
showInfCard(); true
}
22 -> (toolsBox.zis as? ViewMangaActivity)?.mBinding?.infcard?.idtime?.apply { post {
text = SimpleDateFormat("HH:mm")
.format(Date()) + toolsBox.week + toolsBox.netInfo
} }
}
}
private fun showInfCard() {
Log.d("MyVM", "showInfCard delta $delta")
va?.get()?.mBinding?.infcard?.apply {
ObjectAnimator.ofFloat(idc, "alpha", 0.3F, 0.8F).setDuration(233).start()
ObjectAnimator.ofFloat(root, "translationY", delta, 0F).setDuration(233).start()
}
}
private fun hideInfCard() {
Log.d("MyVM", "hideInfCard delta $delta")
va?.get()?.mBinding?.infcard?.apply {
ObjectAnimator.ofFloat(idc, "alpha", 0.8F, 0.3F).setDuration(233).start()
ObjectAnimator.ofFloat(root, "translationY", 0F, delta).setDuration(233).start()
}
}
overlayController.hideDrawer()
}
companion object {
const val FIRST_PAGE = -1
const val LAST_PAGE = -2
var va: WeakReference<ViewMangaActivity>? = null
var imgUrls = arrayOf<String>()
var zipFile: File? = null
@@ -671,9 +744,15 @@ class ViewMangaActivity : ToolsBoxActivity() {
var nextChapterUrl: String? = null
var previousChapterUrl: String? = null
var zipPosition = 0
var zipList: Array<String>? = null
var zipList: Array<File>? = null
var cd: File? = null
var pn = -1
var pn = FIRST_PAGE
var streamChapterUrl: String? = null
}
private enum class ReaderMode {
SINGLE_PAGE,
PAGED,
CONTINUOUS,
}
}

View File

@@ -0,0 +1,66 @@
package top.fumiama.copymangaweb.activity.reader
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import top.fumiama.copymangaweb.R
import top.fumiama.copymangaweb.view.ScaleImageView
class PagedMangaAdapter(
private val itemCountProvider: () -> Int,
private val bindImage: (ScaleImageView, Int) -> Unit,
) : RecyclerView.Adapter<PagedMangaAdapter.PageViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.page_imgview, parent, false)
return PageViewHolder(view)
}
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
holder.image.resetImageTransform()
bindImage(holder.image, position)
}
override fun onViewRecycled(holder: PageViewHolder) {
holder.image.setOnTapRegionListener(null)
holder.image.setImageDrawable(null)
super.onViewRecycled(holder)
}
override fun getItemCount(): Int = itemCountProvider()
class PageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val image: ScaleImageView = itemView.findViewById(R.id.onei)
}
}
class ContinuousMangaAdapter(
private val itemCountProvider: () -> Int,
private val bindImage: (ScaleImageView, Int) -> Unit,
) : RecyclerView.Adapter<ContinuousMangaAdapter.PageViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.page_img_continuous, parent, false)
return PageViewHolder(view)
}
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
holder.image.resetImageTransform()
bindImage(holder.image, position)
}
override fun onViewRecycled(holder: PageViewHolder) {
holder.image.setOnTapRegionListener(null)
holder.image.setImageDrawable(null)
super.onViewRecycled(holder)
}
override fun getItemCount(): Int = itemCountProvider()
class PageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val image: ScaleImageView = itemView.findViewById(R.id.pageImage)
}
}

View File

@@ -0,0 +1,70 @@
package top.fumiama.copymangaweb.activity.reader
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import android.text.format.DateFormat
import android.util.Log
import top.fumiama.copymangaweb.activity.ViewMangaActivity
import top.fumiama.copymangaweb.databinding.ActivityViewmangaBinding
import top.fumiama.copymangaweb.tool.ToolsBox
import java.util.Date
class ReaderOverlayController(
private val activity: ViewMangaActivity,
private val binding: ActivityViewmangaBinding,
private val toolsBox: ToolsBox,
private val drawerOffset: () -> Float,
) {
private val handler = Handler(Looper.getMainLooper())
private val clockFormat = DateFormat.getTimeFormat(activity)
private var drawerVisible = false
private val updateStatus = object : Runnable {
@SuppressLint("SetTextI18n")
override fun run() {
if (activity.isFinishing || activity.isDestroyed) return
binding.infcard.idtime.text = "${clockFormat.format(Date())}${toolsBox.week}${toolsBox.netInfo}"
handler.postDelayed(this, STATUS_UPDATE_INTERVAL_MS)
}
}
fun start() {
handler.post(updateStatus)
}
fun toggleDrawer() {
if (drawerVisible) hideDrawer() else showDrawer()
}
fun hideDrawer() {
if (!drawerVisible) return
val offset = drawerOffset()
Log.d("ReaderOverlay", "hide drawer offset=$offset")
binding.infcard.apply {
ObjectAnimator.ofFloat(idc, "alpha", idc.alpha, 0.3f).setDuration(ANIMATION_DURATION_MS).start()
ObjectAnimator.ofFloat(root, "translationY", root.translationY, offset).setDuration(ANIMATION_DURATION_MS).start()
}
drawerVisible = false
}
private fun showDrawer() {
val offset = drawerOffset()
Log.d("ReaderOverlay", "show drawer offset=$offset")
binding.infcard.apply {
ObjectAnimator.ofFloat(idc, "alpha", idc.alpha, 0.8f).setDuration(ANIMATION_DURATION_MS).start()
ObjectAnimator.ofFloat(root, "translationY", root.translationY, 0f).setDuration(ANIMATION_DURATION_MS).start()
}
drawerVisible = true
}
fun close() {
handler.removeCallbacksAndMessages(null)
}
companion object {
private const val STATUS_UPDATE_INTERVAL_MS = 3_000L
private const val ANIMATION_DURATION_MS = 233L
}
}

View File

@@ -16,7 +16,6 @@ open class ToolsBoxActivity: Activity(), LifecycleOwner {
super.onCreate(savedInstanceState)
lifecycleRegistry = LifecycleRegistry(this)
lifecycleRegistry.currentState = Lifecycle.State.CREATED
lifecycleRegistry.currentState = Lifecycle.State.STARTED
toolsBox = ToolsBox(WeakReference(this))
}

View File

@@ -20,10 +20,12 @@ class DlHandler(activity: DlActivity, looper: Looper) : Handler(looper) {
@SuppressLint("SetTextI18n")
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val chapterIndex = msg.arg1
val pageNumber = msg.arg2
when (msg.what) {
-2 -> d?.setLayouts()
1 -> {
d?.tbtnlist?.get(msg.arg1)?.apply { post {
INITIALIZE_LAYOUTS -> d?.setLayouts()
CHAPTER_DOWNLOAD_SUCCEEDED -> {
d?.tbtnlist?.get(chapterIndex)?.apply { post {
setBackgroundResource(R.drawable.rndbg_checked)
isChecked = false
d?.updateProgressBar()
@@ -39,15 +41,15 @@ class DlHandler(activity: DlActivity, looper: Looper) : Handler(looper) {
}
} }
}
-1 -> {
d?.tbtnlist?.get(msg.arg1)?.apply { post {
CHAPTER_DOWNLOAD_FAILED -> {
d?.tbtnlist?.get(chapterIndex)?.apply { post {
setBackgroundResource(R.drawable.rndbg_error)
d!!.dldChapter--
Toast.makeText(d, "下载${d?.tbtnlist?.get(msg.arg1)?.textOn}失败", Toast.LENGTH_SHORT).show()
Toast.makeText(d, "下载${d?.tbtnlist?.get(chapterIndex)?.textOn}失败", Toast.LENGTH_SHORT).show()
d?.updateProgressBar()
} }
}
4 -> {
TOGGLE_SELECT_ALL -> {
d?.mBinding?.dldlbar?.pdwn?.apply { post { progress = 0 } }
val selectDownloaded = d?.multiSelect?:false
if (d?.haveSElectAll == true) {
@@ -81,27 +83,27 @@ class DlHandler(activity: DlActivity, looper: Looper) : Handler(looper) {
text = "${d?.dldChapter}/${d?.checkedChapter}"
} }
}
5 -> {
setSize(msg.arg2, msg.arg1)
d?.updateProgressBar(msg.arg2, size)
if (!(msg.obj as Boolean)) {
Toast.makeText(d, "下载${d?.tbtnlist?.get(msg.arg1)?.textOn}的第${msg.arg2}页失败", Toast.LENGTH_SHORT).show()
PAGE_DOWNLOAD_FINISHED -> {
setSize(pageNumber, chapterIndex)
d?.updateProgressBar(pageNumber, size)
if (msg.obj != true) {
Toast.makeText(d, "下载${d?.tbtnlist?.get(chapterIndex)?.textOn}的第${pageNumber}页失败", Toast.LENGTH_SHORT).show()
}else{
val progressTxt = d?.mBinding?.dldlbar?.tdwn?.text.toString()
d?.mBinding?.dldlbar?.tdwn?.apply { post {
text = "${progressTxt.substringBefore(' ')}${msg.arg2}/${size}"
text = "${progressTxt.substringBefore(' ')}${pageNumber}/${size}"
} }
}
}
6 -> d?.mBinding?.dldlbar?.tdwn?.apply { post { text = "${d?.dldChapter}/${d?.checkedChapter}" } }
7 -> d?.deleteChapters()
8 -> d?.resources?.getColor(R.color.colorBlue)?.let { d?.mBinding?.dldlbar?.cdwn?.apply { post {
UPDATE_CHAPTER_PROGRESS -> d?.mBinding?.dldlbar?.tdwn?.apply { post { text = "${d?.dldChapter}/${d?.checkedChapter}" } }
DELETE_SELECTED_CHAPTERS -> d?.deleteChapters()
SET_DOWNLOAD_CARD_BLUE -> d?.resources?.getColor(R.color.colorBlue)?.let { d?.mBinding?.dldlbar?.cdwn?.apply { post {
setCardBackgroundColor(it)
} } }
9 -> d?.resources?.getColor(R.color.colorRed)?.let { d?.mBinding?.dldlbar?.cdwn?.apply { post {
SET_DOWNLOAD_CARD_RED -> d?.resources?.getColor(R.color.colorRed)?.let { d?.mBinding?.dldlbar?.cdwn?.apply { post {
setCardBackgroundColor(it)
} } }
10 -> Toast.makeText(d, "下载${d?.tbtnlist?.get(msg.arg1)?.textOn}的第${msg.arg2}页失败,尝试重新下载...", Toast.LENGTH_SHORT).show()
PAGE_DOWNLOAD_RETRYING -> Toast.makeText(d, "下载${d?.tbtnlist?.get(chapterIndex)?.textOn}的第${pageNumber}页失败,尝试重新下载...", Toast.LENGTH_SHORT).show()
}
}
private fun setSize(pageNow: Int, tbtnNo: Int){
@@ -110,4 +112,17 @@ class DlHandler(activity: DlActivity, looper: Looper) : Handler(looper) {
refreshSize = false
}else if(pageNow == size) refreshSize = true
}
companion object {
const val INITIALIZE_LAYOUTS = -2
const val CHAPTER_DOWNLOAD_FAILED = -1
const val CHAPTER_DOWNLOAD_SUCCEEDED = 1
const val TOGGLE_SELECT_ALL = 4
const val PAGE_DOWNLOAD_FINISHED = 5
const val UPDATE_CHAPTER_PROGRESS = 6
const val DELETE_SELECTED_CHAPTERS = 7
const val SET_DOWNLOAD_CARD_BLUE = 8
const val SET_DOWNLOAD_CARD_RED = 9
const val PAGE_DOWNLOAD_RETRYING = 10
}
}

View File

@@ -1,21 +0,0 @@
package top.fumiama.copymangaweb.handler
import android.os.Handler
import android.os.Looper
import android.os.Message
import top.fumiama.copymangaweb.activity.DlListActivity
import java.io.File
import java.lang.ref.WeakReference
class DlLHandler(looper: Looper, activity: DlListActivity): Handler(looper) {
private val dll = WeakReference(activity)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when(msg.what){
1 -> dll.get()?.checkDir(msg.obj as File)
2 -> dll.get()?.rmrf(msg.obj as File)
3 -> dll.get()?.scanFile(msg.obj as File)
}
}
}

View File

@@ -28,12 +28,17 @@ class MainHandler(looper: Looper): Handler(looper) {
}
SET_LOADING_DIALOG_TEXT -> {
val t = msg.obj as? String?:return
dialog?.findViewById<TextView>(R.id.tunz)?.apply { post {
text = t
} }
dialog?.findViewById<TextView>(R.id.tunz)?.text = t
}
}
}
fun dispose() {
removeCallbacksAndMessages(null)
dialog?.dismiss()
dialog = null
}
companion object {
const val SHOW_LOADING_DIALOG = 7
const val HIDE_LOADING_DIALOG = 8

View File

@@ -1,17 +0,0 @@
package top.fumiama.copymangaweb.handler
import android.os.Handler
class TimeThread(private val handler: Handler, private val msg: Int) : Thread() {
var canDo = false
override fun run() {
while (canDo) {
try {
handler.sendEmptyMessage(msg)
sleep(3000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
}

View File

@@ -3,35 +3,31 @@ package top.fumiama.copymangaweb.tool
import android.util.Log
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.Callable
import java.util.concurrent.FutureTask
class DownloadTools {
fun getHttpContent(u: String, refer: String? = null, ua: String? = null): ByteArray? {
Log.d("Mydl", "getHttp: $u")
var ret: ByteArray? = null
val task = FutureTask(Callable {
try {
val connection = URL(u).openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.connectTimeout = 10000
connection.readTimeout = 10000
refer?.let { connection.setRequestProperty("referer", it) }
ua?.let { connection.setRequestProperty("User-agent", it) }
val connection = try {
URL(u).openConnection() as HttpURLConnection
} catch (e: Exception) {
Log.e("Mydl", "Unable to open connection: $u", e)
return null
}
ret = connection.inputStream.readBytes()
connection.disconnect()
} catch (ex: Exception) {
ex.printStackTrace()
}
return@Callable ret
})
Thread(task).start()
return try {
task.get()
} catch (ex: Exception) {
ex.printStackTrace()
connection.run {
requestMethod = "GET"
connectTimeout = 10000
readTimeout = 10000
refer?.let { setRequestProperty("referer", it) }
ua?.let { setRequestProperty("User-agent", it) }
inputStream.use { it.readBytes() }
}
} catch (e: Exception) {
Log.e("Mydl", "Download failed: $u", e)
null
} finally {
connection.disconnect()
}
}
}

View File

@@ -4,63 +4,86 @@ import android.content.Intent
import android.widget.Toast
import top.fumiama.copymangaweb.activity.MainActivity.Companion.wm
import top.fumiama.copymangaweb.activity.ViewMangaActivity
import java.io.File
import java.lang.ref.WeakReference
class PagesManager(w: WeakReference<ViewMangaActivity>) {
val v = w.get()
private val activity = w
private val v get() = activity.get()
private var isEndL = false
private var isEndR = false
fun toPreviousPage(){ toPage(v?.r2l==true) }
fun toNextPage(){ toPage(v?.r2l!=true) }
fun goBackward() = toPage(false)
fun goForward() = toPage(true)
private fun judgePrevious() = (v?.pageNum ?: 0) > 1
private fun judgeNext() = (v?.pageNum ?: 0) < (v?.count ?: 0)
private fun toPage(goNext:Boolean){
if (v?.clicked == false) {
if (if(goNext)judgeNext() else judgePrevious()) {
if(goNext) {
v.scrollForward()
v?.scrollForward()
isEndR = false
} else {
v.scrollBack()
v?.scrollBack()
isEndL = false
}
} else {
val chapterUrl = if(goNext) ViewMangaActivity.nextChapterUrl else ViewMangaActivity.previousChapterUrl
if (chapterUrl != null) {
if (v?.dlZip2View == true) {
switchZipChapter(goNext)
} else {
val chapterUrl = if(goNext) ViewMangaActivity.nextChapterUrl else ViewMangaActivity.previousChapterUrl
if (chapterUrl == null) {
showReachedEnd()
return
}
if (if(goNext)isEndR else isEndL) {
if(!goNext) ViewMangaActivity.pn = -2
setChapterStartPage(goNext)
wm?.get()?.mBinding?.w?.apply { post {
loadUrl("javascript:invoke.clickClass(\"comicControlBottomTopClick\",${if(goNext)1 else 0});")
} }
v.tt.canDo = false
v.finish()
v?.finish()
} else doubleTapToast(goNext)
} else {
val newZipPosition = ViewMangaActivity.zipPosition + (if(goNext) 1 else -1)
if(v.dlZip2View && newZipPosition >= 0 && newZipPosition <
(ViewMangaActivity.zipList?.size ?: 0)){
if (if(goNext)isEndR else isEndL){
if(!goNext) ViewMangaActivity.pn = -2
ViewMangaActivity.zipPosition = newZipPosition
ViewMangaActivity.titleText = ViewMangaActivity.zipList?.get(newZipPosition) ?: "null"
ViewMangaActivity.zipFile = File(ViewMangaActivity.cd, ViewMangaActivity.titleText)
v.startActivity(Intent(v, ViewMangaActivity::class.java))
v.tt.canDo = false
v.finish()
}else doubleTapToast(goNext)
}
else Toast.makeText(
v.applicationContext,
"已经到头了~",
Toast.LENGTH_SHORT
).show()
}
}
} else v?.hideSettings()
}
private fun switchZipChapter(goNext: Boolean) {
val chapters = ViewMangaActivity.zipList.orEmpty()
val newPosition = ViewMangaActivity.zipPosition + if (goNext) 1 else -1
val chapter = chapters.getOrNull(newPosition)
if (chapter == null) {
showReachedEnd()
return
}
if (!(if (goNext) isEndR else isEndL)) {
doubleTapToast(goNext)
return
}
val reader = v ?: return
setChapterStartPage(goNext)
ViewMangaActivity.zipPosition = newPosition
ViewMangaActivity.titleText = chapter.nameWithoutExtension
ViewMangaActivity.zipFile = chapter
reader.startActivity(Intent(reader, ViewMangaActivity::class.java))
reader.finish()
}
private fun showReachedEnd() {
Toast.makeText(v?.applicationContext, "已经到头了~", Toast.LENGTH_SHORT).show()
}
private fun setChapterStartPage(goNext: Boolean) {
ViewMangaActivity.pn = if (goNext) {
ViewMangaActivity.FIRST_PAGE
} else {
ViewMangaActivity.LAST_PAGE
}
}
fun manageInfo(){
if (v?.clicked == false) v.showSettings() else v?.hideSettings()
if (v?.clicked == false) v?.showSettings() else v?.hideSettings()
}
private fun doubleTapToast(goNext: Boolean){
val hint = if(goNext) "" else ""

View File

@@ -15,8 +15,6 @@ import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.widget.ImageView
import top.fumiama.copymangaweb.activity.ViewMangaActivity
import top.fumiama.copymangaweb.tool.PagesManager
import java.lang.ref.WeakReference
import java.util.*
import kotlin.math.sqrt
@@ -26,6 +24,28 @@ import kotlin.math.sqrt
* @author clifford
*/
class ScaleImageView : ImageView {
enum class TapRegion {
PREVIOUS,
CENTER,
NEXT,
}
private var onTapRegionListener: ((TapRegion) -> Unit)? = null
fun setOnTapRegionListener(listener: ((TapRegion) -> Unit)?) {
onTapRegionListener = listener
}
fun resetImageTransform() {
cancelAllAnimator()
mOuterMatrix.reset()
pinchMode = PINCH_MODE_FREE
mLastMovePoint.set(0f, 0f)
mScaleCenter.set(0f, 0f)
mScaleBase = 0f
invalidate()
}
////////////////////////////////监听器////////////////////////////////
/**
* 外界点击事件
@@ -672,24 +692,14 @@ class ScaleImageView : ImageView {
return true
}
var v :WeakReference<ViewMangaActivity>? = null
var pm:PagesManager? = null
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
if(v == null) {
v = ViewMangaActivity.va
v?.let { pm = PagesManager(it) }
}
//触发点击
if (mOnClickListener != null) {
mOnClickListener!!.onClick(this@ScaleImageView)
}
(event.x / width).let {
when {
it <= 1.0 / 3.0 -> pm?.toPreviousPage()
it <= 2.0 / 3.0 -> pm?.manageInfo()
else -> pm?.toNextPage()
}
mOnClickListener?.onClick(this@ScaleImageView)
val region = when {
width <= 0 || event.x <= width / 3f -> TapRegion.PREVIOUS
event.x <= width * 2f / 3f -> TapRegion.CENTER
else -> TapRegion.NEXT
}
onTapRegionListener?.invoke(region)
return true
}
})
@@ -739,6 +749,7 @@ class ScaleImageView : ImageView {
} else if (action == MotionEvent.ACTION_POINTER_DOWN) {
//停止所有动画
cancelAllAnimator()
parent.requestDisallowInterceptTouchEvent(true)
//切换到缩放模式
pinchMode = PINCH_MODE_SCALE
//保存缩放的两个手指

View File

@@ -2,7 +2,6 @@ package top.fumiama.copymangaweb.web
import android.util.Log
import android.webkit.JavascriptInterface
import top.fumiama.copymangaweb.activity.DlActivity
import top.fumiama.copymangaweb.activity.MainActivity.Companion.mh
import top.fumiama.copymangaweb.activity.MainActivity.Companion.wm
import top.fumiama.copymangaweb.handler.MainHandler
@@ -60,13 +59,8 @@ class JSHidden(
}
@JavascriptInterface
fun setTitle(title:String){
Log.d("MyJSH", "Set title: $title")
DlActivity.comicName = title
}
@JavascriptInterface
fun setFab(content: String){
wm?.get()?.setFab(content)
fun setFab(content: String, sourceUrl: String, comicTitle: String){
wm?.get()?.setFab(content, sourceUrl, comicTitle)
}
@JavascriptInterface
fun setLoadingDialog(display: Boolean) {

View File

@@ -51,10 +51,9 @@ class WebChromeClient:WebChromeClient() {
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
wm?.get()?.apply {
uploadMessageAboveL = filePathCallback
openImageChooserActivity()
}
val callback = filePathCallback ?: return false
val activity = wm?.get() ?: return false
activity.openImageChooserActivity(callback)
return true
}
}

View File

@@ -25,6 +25,19 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/continuousPages"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
android:overScrollMode="never"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp"
android:layout_width="0dp"

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<top.fumiama.copymangaweb.view.ScaleImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pageImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:contentDescription="@null" />

View File

@@ -1,8 +1,8 @@
<resources>
<string name="app_name">拷贝Lite</string>
<string name="web_home">https://www.copy20.com</string>
<string name="web_home_www">https://www.copy20.com</string>
<string name="web_comic_detail_pc">https://www.copy20.com/comic</string>
<string name="web_home">https://www.copy3000.com</string>
<string name="web_home_www">https://www.copy3000.com</string>
<string name="web_comic_detail_pc">https://www.copy3000.com/comic</string>
<string name="pc_ua">Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36 Edg/86.0.622.38</string>