mirror of
https://github.com/fumiama/copymanga.git
synced 2026-07-17 10:56:24 +08:00
optimize: improve chapter reader streaming and download reliability (#200)
This commit is contained in:
@@ -12,44 +12,142 @@ if (typeof (loaded) == "undefined") {
|
|||||||
return chapterArr;
|
return chapterArr;
|
||||||
}
|
}
|
||||||
function smoothLoadChapter(speed, interval) {
|
function smoothLoadChapter(speed, interval) {
|
||||||
let prevHeight = document.body.scrollHeight;
|
|
||||||
let lastTime = 0;
|
let lastTime = 0;
|
||||||
let ticking = false;
|
let ticking = false;
|
||||||
|
let lastHeight = 0;
|
||||||
|
let lastImageCount = 0;
|
||||||
|
let stableCount = 0;
|
||||||
|
let finished = false;
|
||||||
|
let declaredCount = 0;
|
||||||
|
let lastProgressNotify = 0;
|
||||||
|
let lastPushNotify = 0;
|
||||||
|
const pushed = Object.create(null);
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
function getTextByClass(name) {
|
||||||
|
const item = document.getElementsByClassName(name)[0];
|
||||||
|
return item ? item.innerText : "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImages() {
|
||||||
|
const content = document.getElementsByClassName("container-fluid comicContent")[0];
|
||||||
|
return content ? content.getElementsByTagName("li") : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeHref(className, index) {
|
||||||
|
try {
|
||||||
|
const items = document.getElementsByClassName(className);
|
||||||
|
const links = items[index].getElementsByTagName("a");
|
||||||
|
return links[0].href;
|
||||||
|
} catch(e) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChapterHeader() {
|
||||||
|
var nextChapter = safeHref("comicContent-next", 0);
|
||||||
|
var prevChapter = safeHref("comicContent-prev", 1);
|
||||||
|
if(nextChapter == location.href) nextChapter = "null";
|
||||||
|
if(prevChapter == location.href) prevChapter = "null";
|
||||||
|
return document.title.split(" - ")[1] + " " + location.href.substring(location.href.lastIndexOf("/")+1) + "\n" + nextChapter + "\n" + prevChapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyMeta() {
|
||||||
|
try { GM.setChapterMeta(getChapterHeader()); } catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageSrcOf(li) {
|
||||||
|
const img = li.getElementsByTagName("img")[0];
|
||||||
|
if (!img) return "";
|
||||||
|
return img.dataset.src || img.getAttribute("data-src") || img.src || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyCount(count) {
|
||||||
|
if (count > 0 && count !== declaredCount) {
|
||||||
|
declaredCount = count;
|
||||||
|
try { GM.setChapterCount(count.toString()); } catch(e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushNewImages(force) {
|
||||||
|
const images = getImages();
|
||||||
|
const batch = [];
|
||||||
|
for(var i = 0; i < images.length; i++) {
|
||||||
|
const src = imageSrcOf(images[i]);
|
||||||
|
if (src && !pushed[src]) {
|
||||||
|
pushed[src] = true;
|
||||||
|
batch.push(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (batch.length > 0) {
|
||||||
|
try { GM.appendChapterImages(batch.join("\n")); } catch(e) {}
|
||||||
|
} else if (force) {
|
||||||
|
try { GM.appendChapterImages(""); } catch(e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish() {
|
||||||
|
if (finished) return;
|
||||||
|
finished = true;
|
||||||
|
notifyMeta();
|
||||||
|
pushNewImages(true);
|
||||||
|
var images = getImages();
|
||||||
|
var result = getChapterHeader();
|
||||||
|
for(var i = 0; i < images.length; i++) {
|
||||||
|
var src = imageSrcOf(images[i]);
|
||||||
|
if (src) result += "\n" + src;
|
||||||
|
}
|
||||||
|
try { GM.finishStreamingChapter(); } catch(e) {}
|
||||||
|
GM.setLoadingDialog(false);
|
||||||
|
GM.loadChapter(result);
|
||||||
|
}
|
||||||
|
|
||||||
function requestTick() {
|
function requestTick() {
|
||||||
if (!ticking) {
|
if (!ticking && !finished) {
|
||||||
ticking = true;
|
ticking = true;
|
||||||
requestAnimationFrame(step);
|
requestAnimationFrame(step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function step(timestamp) {
|
function step(timestamp) {
|
||||||
if (!lastTime) lastTime = timestamp;
|
if (!lastTime) lastTime = timestamp;
|
||||||
const elapsed = timestamp - lastTime;
|
const elapsed = timestamp - lastTime;
|
||||||
if (elapsed >= interval) {
|
if (elapsed >= interval) {
|
||||||
const index = document.getElementsByClassName("comicIndex")[0].innerText;
|
const index = parseInt(getTextByClass("comicIndex")) || 0;
|
||||||
const count = document.getElementsByClassName("comicCount")[0].innerText;
|
const count = parseInt(getTextByClass("comicCount")) || 0;
|
||||||
GM.setLoadingDialogProgress(index, count);
|
const images = getImages();
|
||||||
|
notifyCount(count);
|
||||||
|
if (timestamp - lastProgressNotify >= 200) {
|
||||||
|
lastProgressNotify = timestamp;
|
||||||
|
try { GM.setLoadingDialogProgress(index.toString(), count.toString()); } catch(e) {}
|
||||||
|
}
|
||||||
|
if (timestamp - lastPushNotify >= 80) {
|
||||||
|
lastPushNotify = timestamp;
|
||||||
|
pushNewImages(false);
|
||||||
|
}
|
||||||
window.scrollBy(0, speed);
|
window.scrollBy(0, speed);
|
||||||
lastTime = timestamp;
|
lastTime = timestamp;
|
||||||
const currentHeight = document.body.scrollHeight;
|
|
||||||
if (Math.round(window.innerHeight+window.scrollY+0.5) >= currentHeight) { /*避免小数不符无法触发*/
|
const height = document.body.scrollHeight;
|
||||||
if (currentHeight === prevHeight) {
|
const imageCount = images.length;
|
||||||
var images = document.getElementsByClassName("container-fluid comicContent")[0].getElementsByTagName("li");
|
const atBottom = Math.round(window.innerHeight + window.scrollY + 1) >= height;
|
||||||
var nextChapter = document.getElementsByClassName("comicContent-next")[0].getElementsByTagName("a")[0].href;
|
if (height === lastHeight && imageCount === lastImageCount) stableCount++;
|
||||||
var prevChapter = document.getElementsByClassName("comicContent-prev")[1].getElementsByTagName("a")[0].href;
|
else stableCount = 0;
|
||||||
if(nextChapter == location.href) nextChapter = "null";
|
lastHeight = height;
|
||||||
if(prevChapter == location.href) prevChapter = "null";
|
lastImageCount = imageCount;
|
||||||
var result = document.title.split(" - ")[1] + " " + location.href.substring(location.href.lastIndexOf("/")+1) + "\n" + nextChapter + "\n" + prevChapter;
|
|
||||||
for(var i = 0; i < images.length; i++) result += "\n" + images[i].getElementsByTagName("img")[0].dataset.src;
|
if ((count > 0 && imageCount >= count && (index >= count || atBottom) && stableCount >= 2) ||
|
||||||
GM.setLoadingDialog(false);
|
(atBottom && stableCount >= 10) ||
|
||||||
GM.loadChapter(result);
|
(Date.now() - startTime > 180000)) {
|
||||||
return;
|
finish();
|
||||||
}
|
return;
|
||||||
prevHeight = currentHeight;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ticking = false;
|
ticking = false;
|
||||||
requestTick();
|
requestTick();
|
||||||
}
|
}
|
||||||
|
notifyMeta();
|
||||||
|
pushNewImages(false);
|
||||||
requestTick();
|
requestTick();
|
||||||
}
|
}
|
||||||
function modify() {
|
function modify() {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import top.fumiama.copymangaweb.activity.template.ToolsBoxActivity
|
|||||||
import top.fumiama.copymangaweb.data.ComicStructure
|
import top.fumiama.copymangaweb.data.ComicStructure
|
||||||
import top.fumiama.copymangaweb.databinding.ActivityDlBinding
|
import top.fumiama.copymangaweb.databinding.ActivityDlBinding
|
||||||
import top.fumiama.copymangaweb.handler.DlHandler
|
import top.fumiama.copymangaweb.handler.DlHandler
|
||||||
|
import top.fumiama.copymangaweb.tool.InsetsTools
|
||||||
import top.fumiama.copymangaweb.tool.MangaDlTools
|
import top.fumiama.copymangaweb.tool.MangaDlTools
|
||||||
import top.fumiama.copymangaweb.tool.MangaDlTools.Companion.wmdlt
|
import top.fumiama.copymangaweb.tool.MangaDlTools.Companion.wmdlt
|
||||||
import top.fumiama.copymangaweb.view.ChapterToggleButton
|
import top.fumiama.copymangaweb.view.ChapterToggleButton
|
||||||
@@ -51,13 +52,14 @@ class DlActivity : ToolsBoxActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
mBinding = ActivityDlBinding.inflate(layoutInflater)
|
mBinding = ActivityDlBinding.inflate(layoutInflater)
|
||||||
setContentView(mBinding.root)
|
setContentView(mBinding.root)
|
||||||
|
InsetsTools.applySafeContentInsets(this, mBinding.root)
|
||||||
wm?.get()?.saveUrlsOnly = true
|
wm?.get()?.saveUrlsOnly = true
|
||||||
mangaDlTools = MangaDlTools(this)
|
mangaDlTools = MangaDlTools(this)
|
||||||
mBinding.dwh.apply { post {
|
mBinding.dwh.apply { post {
|
||||||
settings.userAgentString = getString(R.string.pc_ua)
|
settings.userAgentString = getString(R.string.pc_ua)
|
||||||
webChromeClient = WebChromeClient()
|
webChromeClient = WebChromeClient()
|
||||||
setWebViewClient("h.js")
|
setWebViewClient("h.js")
|
||||||
loadJSInterface(JSHidden())
|
loadJSInterface(JSHidden(onLoadChapter = { onHiddenChapterLoaded(it) }))
|
||||||
} }
|
} }
|
||||||
handler.sendEmptyMessage(-2) //setLayouts
|
handler.sendEmptyMessage(-2) //setLayouts
|
||||||
}
|
}
|
||||||
@@ -91,11 +93,16 @@ class DlActivity : ToolsBoxActivity() {
|
|||||||
).start()
|
).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillChapters() {
|
private fun fillChapters(): Boolean {
|
||||||
mangaDlTools.allocateChapterUrls(checkedChapter)
|
mangaDlTools.allocateChapterUrls(checkedChapter)
|
||||||
|
var ok = true
|
||||||
for (i in tbtnlist) {
|
for (i in tbtnlist) {
|
||||||
if (i.isChecked) mangaDlTools.dlChapterUrl(i.url.toString())
|
if (i.isChecked && !mangaDlTools.dlChapterUrl(i.url.toString())) {
|
||||||
|
ok = false
|
||||||
|
handler.obtainMessage(-1, i.index, 0).sendToTarget()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return ok && mangaDlTools.waitChapterUrlsReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dlThread(dlMethod: (i: ChapterToggleButton) -> Unit) {
|
private fun dlThread(dlMethod: (i: ChapterToggleButton) -> Unit) {
|
||||||
@@ -146,8 +153,13 @@ class DlActivity : ToolsBoxActivity() {
|
|||||||
handler.sendEmptyMessage(9) //set dl card color to red
|
handler.sendEmptyMessage(9) //set dl card color to red
|
||||||
Toast.makeText(this@DlActivity, "请耐心等待加载...", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@DlActivity, "请耐心等待加载...", Toast.LENGTH_SHORT).show()
|
||||||
Thread {
|
Thread {
|
||||||
fillChapters()
|
if (fillChapters()) {
|
||||||
dlThread { downloadChapterPages(it) }
|
dlThread { downloadChapterPages(it) }
|
||||||
|
} else {
|
||||||
|
canDl = false
|
||||||
|
haveDlStarted = false
|
||||||
|
handler.sendEmptyMessage(8)
|
||||||
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,6 +212,14 @@ class DlActivity : ToolsBoxActivity() {
|
|||||||
if(!(jsonFile.exists() && intent.getBooleanExtra("callFromDlList", false))) json?.let { jsonFile.writeText(it) }
|
if(!(jsonFile.exists() && intent.getBooleanExtra("callFromDlList", false))) json?.let { jsonFile.writeText(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onHiddenChapterLoaded(content: String) {
|
||||||
|
val listChapter = content.split('\n')
|
||||||
|
val hash = listChapter.getOrNull(0)?.substringAfterLast(' ')
|
||||||
|
if (hash.isNullOrBlank()) return
|
||||||
|
val imgs = listChapter.drop(3).filter { it.isNotBlank() }.toTypedArray()
|
||||||
|
mangaDlTools.setChapterImages(hash, imgs)
|
||||||
|
}
|
||||||
|
|
||||||
private fun downloadChapterPages(i: ChapterToggleButton) {
|
private fun downloadChapterPages(i: ChapterToggleButton) {
|
||||||
mangaDlTools.onDownloadedListener =
|
mangaDlTools.onDownloadedListener =
|
||||||
object : MangaDlTools.OnDownloadedListener {
|
object : MangaDlTools.OnDownloadedListener {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.widget.Toast
|
|||||||
import top.fumiama.copymangaweb.R
|
import top.fumiama.copymangaweb.R
|
||||||
import top.fumiama.copymangaweb.databinding.ActivityDlistBinding
|
import top.fumiama.copymangaweb.databinding.ActivityDlistBinding
|
||||||
import top.fumiama.copymangaweb.handler.DlLHandler
|
import top.fumiama.copymangaweb.handler.DlLHandler
|
||||||
|
import top.fumiama.copymangaweb.tool.InsetsTools
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
@@ -23,6 +24,7 @@ class DlListActivity: Activity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
mBinding = ActivityDlistBinding.inflate(layoutInflater)
|
mBinding = ActivityDlistBinding.inflate(layoutInflater)
|
||||||
setContentView(mBinding.root)
|
setContentView(mBinding.root)
|
||||||
|
InsetsTools.applySafeContentInsets(this, mBinding.root)
|
||||||
mBinding.myt.ttitle.text = intent.getStringExtra("title")
|
mBinding.myt.ttitle.text = intent.getStringExtra("title")
|
||||||
handler = DlLHandler(Looper.myLooper()!!, this)
|
handler = DlLHandler(Looper.myLooper()!!, this)
|
||||||
handler?.obtainMessage(3, currentDir)?.sendToTarget() //call scanFile
|
handler?.obtainMessage(3, currentDir)?.sendToTarget() //call scanFile
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import top.fumiama.copymangaweb.activity.template.ToolsBoxActivity
|
|||||||
import top.fumiama.copymangaweb.activity.viewmodel.MainViewModel
|
import top.fumiama.copymangaweb.activity.viewmodel.MainViewModel
|
||||||
import top.fumiama.copymangaweb.databinding.ActivityMainBinding
|
import top.fumiama.copymangaweb.databinding.ActivityMainBinding
|
||||||
import top.fumiama.copymangaweb.handler.MainHandler
|
import top.fumiama.copymangaweb.handler.MainHandler
|
||||||
|
import top.fumiama.copymangaweb.tool.InsetsTools
|
||||||
import top.fumiama.copymangaweb.tool.MangaDlTools.Companion.wmdlt
|
import top.fumiama.copymangaweb.tool.MangaDlTools.Companion.wmdlt
|
||||||
import top.fumiama.copymangaweb.tool.SetDraggable
|
import top.fumiama.copymangaweb.tool.SetDraggable
|
||||||
import top.fumiama.copymangaweb.tool.Updater
|
import top.fumiama.copymangaweb.tool.Updater
|
||||||
@@ -41,6 +42,7 @@ class MainActivity: ToolsBoxActivity() {
|
|||||||
mBinding.mainViewModel = mViewModel
|
mBinding.mainViewModel = mViewModel
|
||||||
mBinding.lifecycleOwner = this
|
mBinding.lifecycleOwner = this
|
||||||
setContentView(mBinding.root)
|
setContentView(mBinding.root)
|
||||||
|
InsetsTools.applySafeContentInsets(this, mBinding.root)
|
||||||
|
|
||||||
wm = WeakReference(this)
|
wm = WeakReference(this)
|
||||||
mh = MainHandler(Looper.myLooper()!!)
|
mh = MainHandler(Looper.myLooper()!!)
|
||||||
@@ -125,6 +127,16 @@ class MainActivity: ToolsBoxActivity() {
|
|||||||
lifecycleScope.launch { mViewModel.updateLoadProgress(p) }
|
lifecycleScope.launch { mViewModel.updateLoadProgress(p) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun openStreamingManga(url: String) {
|
||||||
|
ViewMangaActivity.streamChapterUrl = url
|
||||||
|
ViewMangaActivity.titleText = "加载中..."
|
||||||
|
ViewMangaActivity.nextChapterUrl = null
|
||||||
|
ViewMangaActivity.previousChapterUrl = null
|
||||||
|
ViewMangaActivity.imgUrls = arrayOf()
|
||||||
|
ViewMangaActivity.zipFile = null
|
||||||
|
startActivity(Intent(this, ViewMangaActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
fun setFab(content: String) {
|
fun setFab(content: String) {
|
||||||
json = content
|
json = content
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
|||||||
@@ -28,10 +28,14 @@ import top.fumiama.copymangaweb.handler.TimeThread
|
|||||||
import top.fumiama.copymangaweb.tool.PropertiesTools
|
import top.fumiama.copymangaweb.tool.PropertiesTools
|
||||||
import top.fumiama.copymangaweb.tool.ToolsBox
|
import top.fumiama.copymangaweb.tool.ToolsBox
|
||||||
import top.fumiama.copymangaweb.view.ScaleImageView
|
import top.fumiama.copymangaweb.view.ScaleImageView
|
||||||
|
import top.fumiama.copymangaweb.web.JSHidden
|
||||||
|
import top.fumiama.copymangaweb.web.WebChromeClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
@@ -52,6 +56,12 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
private var notUseVP = true
|
private var notUseVP = true
|
||||||
private var mangaZip = zipFile
|
private var mangaZip = zipFile
|
||||||
val dlZip2View = mangaZip != null
|
val dlZip2View = mangaZip != null
|
||||||
|
private var streamUrl = streamChapterUrl
|
||||||
|
private var readerPrepared = false
|
||||||
|
private var streamFinished = false
|
||||||
|
private var streamDeclaredCount = 0
|
||||||
|
private val streamSeenUrls = LinkedHashSet<String>()
|
||||||
|
private var vpAdapter: RecyclerView.Adapter<ViewData>? = null
|
||||||
private val volTurnPage get() = p["volturn"] == "true"
|
private val volTurnPage get() = p["volturn"] == "true"
|
||||||
var pageNum = 1
|
var pageNum = 1
|
||||||
get() {
|
get() {
|
||||||
@@ -91,8 +101,9 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
mBinding.oneinfo.inftitle.ttitle.apply { post { text = titleText } }
|
mBinding.oneinfo.inftitle.ttitle.apply { post { text = titleText } }
|
||||||
Log.d("MyVM", "dlZip2View: $dlZip2View, mangaZip: $mangaZip")
|
Log.d("MyVM", "dlZip2View: $dlZip2View, mangaZip: $mangaZip, streamUrl: $streamUrl")
|
||||||
if(dlZip2View && mangaZip?.exists() != true) toolsBox.toastError("已经到头了~")
|
if(dlZip2View && mangaZip?.exists() != true) toolsBox.toastError("已经到头了~")
|
||||||
|
else if(!dlZip2View && !streamUrl.isNullOrBlank()) startStreamingCollector(streamUrl!!)
|
||||||
else Thread {
|
else Thread {
|
||||||
try {
|
try {
|
||||||
count = if (dlZip2View) countZipItems() else imgUrls.size
|
count = if (dlZip2View) countZipItems() else imgUrls.size
|
||||||
@@ -100,25 +111,172 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
runOnUiThread { toolsBox.toastError("分析图片url错误") }
|
runOnUiThread { toolsBox.toastError("分析图片url错误") }
|
||||||
}
|
}
|
||||||
runOnUiThread {
|
runOnUiThread { prepareReaderIfNeeded() }
|
||||||
try {
|
}.start()
|
||||||
prepareItems()
|
}
|
||||||
if(pn > 0) {
|
|
||||||
pageNum = pn
|
|
||||||
pn = -1
|
private fun startStreamingCollector(url: String) {
|
||||||
} else if(pn == -2){
|
Log.d("CopymangaDL", "reader collector load url=$url")
|
||||||
pageNum = count
|
mBinding.wcollector.apply { post {
|
||||||
pn = -1
|
settings.userAgentString = getString(R.string.pc_ua)
|
||||||
}
|
webChromeClient = WebChromeClient()
|
||||||
} catch (e: Exception) {
|
setWebViewClient("h.js")
|
||||||
e.printStackTrace()
|
loadJSInterface(
|
||||||
toolsBox.toastError("准备控件错误")
|
JSHidden(
|
||||||
} finally {
|
onLoadChapter = { onStreamingChapterFinished(it) },
|
||||||
dialog?.dismiss()
|
onChapterMeta = { onStreamingMeta(it) },
|
||||||
dialog = null
|
onAppendImages = { appendStreamingImages(it) },
|
||||||
|
onFinishStreaming = { onStreamingFinished() },
|
||||||
|
onChapterCount = { onStreamingCount(it) },
|
||||||
|
enableLoadingDialog = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
loadUrl(url)
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStreamingMeta(headerString: String) {
|
||||||
|
val lines = headerString.split('\n')
|
||||||
|
if (lines.size < 3) return
|
||||||
|
titleText = lines[0].substringBeforeLast(' ')
|
||||||
|
nextChapterUrl = lines[1].let { if(it == "null") null else it }
|
||||||
|
previousChapterUrl = lines[2].let { if(it == "null") null else it }
|
||||||
|
runOnUiThread { mBinding.oneinfo.inftitle.ttitle.text = titleText }
|
||||||
|
}
|
||||||
|
private fun setEarlyTapLayerEnabled(enabled: Boolean) {
|
||||||
|
mBinding.onec.apply { post {
|
||||||
|
isClickable = enabled
|
||||||
|
isFocusable = false
|
||||||
|
setOnClickListener(if (enabled) View.OnClickListener {
|
||||||
|
if (clicked) hideSettings() else showSettings()
|
||||||
|
} else null)
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seekProgressToPage(progress: Int): Int {
|
||||||
|
if (count <= 1) return 1
|
||||||
|
return ((progress.coerceIn(0, 100) * (count - 1) + 50) / 100 + 1).coerceIn(1, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStreamingCount(total: Int) {
|
||||||
|
if (total <= 0) 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)
|
||||||
|
}
|
||||||
|
syncSeekBarOnly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStreamingChapterFinished(content: String) {
|
||||||
|
val listChapter = content.split('\n')
|
||||||
|
if (listChapter.size >= 3) onStreamingMeta(listChapter.take(3).joinToString("\n"))
|
||||||
|
appendStreamingImages(listChapter.drop(3).filter { it.isNotBlank() }.toTypedArray())
|
||||||
|
onStreamingFinished()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onStreamingFinished() {
|
||||||
|
streamFinished = true
|
||||||
|
Log.d("CopymangaDL", "reader stream finished pages=$count")
|
||||||
|
runOnUiThread {
|
||||||
|
dialog?.dismiss()
|
||||||
|
dialog = null
|
||||||
|
syncSeekBarOnly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendStreamingImages(urls: Array<String>) {
|
||||||
|
runOnUiThread {
|
||||||
|
val oldCount = count
|
||||||
|
val oldImageCount = imgUrls.size
|
||||||
|
var added = 0
|
||||||
|
for (url in urls) {
|
||||||
|
if (streamSeenUrls.add(url)) {
|
||||||
|
imgUrls += url
|
||||||
|
added++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
if (added <= 0) return@runOnUiThread
|
||||||
|
count = max(max(streamDeclaredCount, imgUrls.size), count)
|
||||||
|
Log.d("CopymangaDL", "reader appended images added=$added total=$count images=${imgUrls.size}")
|
||||||
|
if (!readerPrepared) prepareReaderIfNeeded()
|
||||||
|
else {
|
||||||
|
if (notUseVP) {
|
||||||
|
if (currentItem in oldImageCount until imgUrls.size) loadOneImg(hidePanel = false)
|
||||||
|
else syncSeekBarOnly()
|
||||||
|
} else {
|
||||||
|
if (count > oldCount) vpAdapter?.notifyItemRangeInserted(oldCount, count - oldCount)
|
||||||
|
notifyVisiblePagesIfArrived(oldImageCount, imgUrls.size)
|
||||||
|
syncSeekBarOnly()
|
||||||
|
}
|
||||||
|
preloadAround(getLogicalCurrentItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prepareReaderIfNeeded() {
|
||||||
|
try {
|
||||||
|
if (count <= 0) return
|
||||||
|
prepareItems()
|
||||||
|
if(pn > 0) {
|
||||||
|
pageNum = pn
|
||||||
|
pn = -1
|
||||||
|
} else if(pn == -2){
|
||||||
|
pageNum = count
|
||||||
|
pn = -1
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
toolsBox.toastError("准备控件错误")
|
||||||
|
} finally {
|
||||||
|
if (streamUrl == null || count > 0) {
|
||||||
|
dialog?.dismiss()
|
||||||
|
dialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun preloadAround(position: Int) {
|
||||||
|
if (dlZip2View || position < 0 || imgUrls.isEmpty()) return
|
||||||
|
val end = min(imgUrls.size - 1, position + 4)
|
||||||
|
for (i in position + 1..end) {
|
||||||
|
imgUrls.getOrNull(i)?.let { url ->
|
||||||
|
Glide.with(this@ViewMangaActivity)
|
||||||
|
.load(toolsBox.resolution.wrap(url))
|
||||||
|
.preload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyVisiblePagesIfArrived(oldImageCount: Int, newImageCount: Int) {
|
||||||
|
if (notUseVP || 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
@@ -155,20 +313,47 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
|
|
||||||
private fun getImgBitmap(position: Int): Bitmap? {
|
private fun getImgBitmap(position: Int): Bitmap? {
|
||||||
if (position >= count || position < 0) return null
|
if (position >= count || position < 0) return null
|
||||||
else {
|
val zipPath = mangaZip ?: return null
|
||||||
val zip = ZipFile(mangaZip)
|
return try {
|
||||||
return BitmapFactory.decodeStream(zip.getInputStream(zip.getEntry("${position}.webp")))
|
ZipFile(zipPath).use { zip ->
|
||||||
|
// Older downloaded zips use 0.webp, 1.webp...
|
||||||
|
// The large-chapter fix may use 000.webp, 001.webp... for stable sorting.
|
||||||
|
// Support both formats and fall back to the sorted entry list.
|
||||||
|
val entry = zip.getEntry("${position}.webp")
|
||||||
|
?: zip.getEntry("%03d.webp".format(position))
|
||||||
|
?: zip.entries().asSequence()
|
||||||
|
.filter { !it.isDirectory }
|
||||||
|
.sortedBy { it.name }
|
||||||
|
.elementAtOrNull(position)
|
||||||
|
?: run {
|
||||||
|
Log.e("CopymangaDL", "zip entry missing position=$position file=$zipPath")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
zip.getInputStream(entry).use { input ->
|
||||||
|
BitmapFactory.decodeStream(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("CopymangaDL", "decode zip image failed position=$position file=$zipPath", e)
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadOneImg() {
|
private fun loadOneImg(hidePanel: Boolean = true) {
|
||||||
if(dlZip2View) mBinding.vone.onei.apply { post { setImageBitmap(getImgBitmap(currentItem)) } }
|
if(dlZip2View) mBinding.vone.onei.apply { post { setImageBitmap(getImgBitmap(currentItem)) } }
|
||||||
else Glide.with(this@ViewMangaActivity)
|
else {
|
||||||
.load(toolsBox.resolution.wrap(imgUrls[currentItem]))
|
val url = imgUrls.getOrNull(currentItem)
|
||||||
.placeholder(R.drawable.ic_dl)
|
if (url.isNullOrBlank()) {
|
||||||
.dontAnimate()
|
mBinding.vone.onei.apply { post { setImageResource(R.drawable.ic_dl) } }
|
||||||
.into(mBinding.vone.onei)
|
} else {
|
||||||
updateSeekBar()
|
Glide.with(this@ViewMangaActivity)
|
||||||
|
.load(toolsBox.resolution.wrap(url))
|
||||||
|
.placeholder(R.drawable.ic_dl)
|
||||||
|
.dontAnimate()
|
||||||
|
.into(mBinding.vone.onei)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateSeekBar(hidePanel)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setIdPosition(position: Int) {
|
private fun setIdPosition(position: Int) {
|
||||||
@@ -178,6 +363,7 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun prepareItems() {
|
private fun prepareItems() {
|
||||||
|
if (count <= 0) return
|
||||||
prepareVP()
|
prepareVP()
|
||||||
prepareInfoBar(count)
|
prepareInfoBar(count)
|
||||||
if (notUseVP) loadOneImg() else prepareIdBtVH()
|
if (notUseVP) loadOneImg() else prepareIdBtVH()
|
||||||
@@ -185,6 +371,7 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
prepareIdBtVolTurn()
|
prepareIdBtVolTurn()
|
||||||
prepareIdBtVP()
|
prepareIdBtVP()
|
||||||
prepareIdBtLR()
|
prepareIdBtLR()
|
||||||
|
readerPrepared = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareIdBtLR() {
|
private fun prepareIdBtLR() {
|
||||||
@@ -216,9 +403,12 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
} else {
|
} else {
|
||||||
mBinding.vp.apply { post {
|
mBinding.vp.apply { post {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
adapter = ViewData(this).RecyclerViewAdapter()
|
vpAdapter = ViewData(this).RecyclerViewAdapter()
|
||||||
|
adapter = vpAdapter
|
||||||
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
|
val pos = if (r2l) count - position - 1 else position
|
||||||
|
preloadAround(pos)
|
||||||
updateSeekBar()
|
updateSeekBar()
|
||||||
super.onPageSelected(position)
|
super.onPageSelected(position)
|
||||||
}
|
}
|
||||||
@@ -229,8 +419,12 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSeekBar() {
|
private fun updateSeekBar(hidePanel: Boolean = true) {
|
||||||
if (!isInSeek) hideSettings()
|
if (hidePanel && !isInSeek) hideSettings()
|
||||||
|
syncSeekBarOnly()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun syncSeekBarOnly() {
|
||||||
updateSeekText()
|
updateSeekText()
|
||||||
updateSeekProgress()
|
updateSeekProgress()
|
||||||
}
|
}
|
||||||
@@ -242,9 +436,10 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
visibility = View.INVISIBLE
|
visibility = View.INVISIBLE
|
||||||
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
override fun onProgressChanged(p0: SeekBar?, p1: Int, isHuman: Boolean) {
|
override fun onProgressChanged(p0: SeekBar?, p1: Int, isHuman: Boolean) {
|
||||||
if (isHuman) {
|
if (isHuman && count > 0) {
|
||||||
if (p1 >= (pageNum + 1) * 100 / size) scrollForward()
|
val targetPage = seekProgressToPage(p1)
|
||||||
else if (p1 < (pageNum - 1) * 100 / size) scrollBack()
|
if (targetPage != pageNum) pageNum = targetPage
|
||||||
|
updateSeekText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +448,12 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopTrackingTouch(p0: SeekBar?) {
|
override fun onStopTrackingTouch(p0: SeekBar?) {
|
||||||
|
p0?.let {
|
||||||
|
val targetPage = seekProgressToPage(it.progress)
|
||||||
|
if (targetPage != pageNum) pageNum = targetPage
|
||||||
|
}
|
||||||
isInSeek = false
|
isInSeek = false
|
||||||
|
updateSeekBar()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} }
|
} }
|
||||||
@@ -312,11 +512,12 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun scrollBack() {
|
fun scrollBack() {
|
||||||
pageNum--
|
if (pageNum > 1) pageNum--
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollForward() {
|
fun scrollForward() {
|
||||||
pageNum++
|
if (pageNum < count) pageNum++
|
||||||
|
else if (!streamFinished && streamUrl != null) Toast.makeText(this, "后续页面仍在后台加载", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
@@ -325,7 +526,10 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSeekProgress() {
|
private fun updateSeekProgress() {
|
||||||
mBinding.oneinfo.infseek.apply { post { progress = pageNum * 100 / count } }
|
if (isInSeek || count <= 0) return
|
||||||
|
mBinding.oneinfo.infseek.apply { post {
|
||||||
|
progress = if (count <= 1) 0 else ((pageNum - 1) * 100 / (count - 1)).coerceIn(0, 100)
|
||||||
|
} }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
@@ -338,6 +542,7 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
tt.canDo = false
|
tt.canDo = false
|
||||||
handler.removeCallbacksAndMessages(null)
|
handler.removeCallbacksAndMessages(null)
|
||||||
|
if (streamUrl != null) streamChapterUrl = null
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,10 +564,13 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
//Glide.with(this@ViewMangaActivity).load(it).placeholder(R.drawable.bg_comment).into(holder.itemView.onei)
|
//Glide.with(this@ViewMangaActivity).load(it).placeholder(R.drawable.bg_comment).into(holder.itemView.onei)
|
||||||
oneImage.setImageBitmap(it)
|
oneImage.setImageBitmap(it)
|
||||||
}
|
}
|
||||||
else Glide.with(this@ViewMangaActivity)
|
else imgUrls.getOrNull(pos)?.let { url ->
|
||||||
.load(toolsBox.resolution.wrap(imgUrls[pos])).placeholder(R.drawable.ic_dl)
|
Glide.with(this@ViewMangaActivity)
|
||||||
.dontAnimate().timeout(10000)
|
.load(toolsBox.resolution.wrap(url)).placeholder(R.drawable.ic_dl)
|
||||||
.into(oneImage)
|
.dontAnimate().timeout(10000)
|
||||||
|
.into(oneImage)
|
||||||
|
preloadAround(pos)
|
||||||
|
} ?: oneImage.setImageResource(R.drawable.ic_dl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,5 +674,6 @@ class ViewMangaActivity : ToolsBoxActivity() {
|
|||||||
var zipList: Array<String>? = null
|
var zipList: Array<String>? = null
|
||||||
var cd: File? = null
|
var cd: File? = null
|
||||||
var pn = -1
|
var pn = -1
|
||||||
|
var streamChapterUrl: String? = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package top.fumiama.copymangaweb.tool
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
object InsetsTools {
|
||||||
|
fun applySafeContentInsets(activity: Activity, root: View) {
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(activity.window, false)
|
||||||
|
|
||||||
|
val startLeft = root.paddingLeft
|
||||||
|
val startTop = root.paddingTop
|
||||||
|
val startRight = root.paddingRight
|
||||||
|
val startBottom = root.paddingBottom
|
||||||
|
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
|
||||||
|
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
val displayCutout = insets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||||
|
val captionBar = insets.getInsets(WindowInsetsCompat.Type.captionBar())
|
||||||
|
|
||||||
|
view.updatePadding(
|
||||||
|
left = startLeft + max(systemBars.left, displayCutout.left),
|
||||||
|
top = startTop + maxOf(systemBars.top, displayCutout.top, captionBar.top),
|
||||||
|
right = startRight + max(systemBars.right, displayCutout.right),
|
||||||
|
bottom = startBottom + max(systemBars.bottom, displayCutout.bottom)
|
||||||
|
)
|
||||||
|
insets
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ViewCompat.isAttachedToWindow(root)) {
|
||||||
|
ViewCompat.requestApplyInsets(root)
|
||||||
|
} else {
|
||||||
|
root.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
||||||
|
override fun onViewAttachedToWindow(v: View) {
|
||||||
|
v.removeOnAttachStateChangeListener(this)
|
||||||
|
ViewCompat.requestApplyInsets(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(v: View) = Unit
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package top.fumiama.copymangaweb.tool
|
package top.fumiama.copymangaweb.tool
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import top.fumiama.copymangaweb.R
|
import top.fumiama.copymangaweb.R
|
||||||
import top.fumiama.copymangaweb.activity.DlActivity
|
import top.fumiama.copymangaweb.activity.DlActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.Thread.sleep
|
import java.lang.Thread.sleep
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.concurrent.Semaphore
|
import java.util.concurrent.Semaphore
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
import java.util.zip.CheckedOutputStream
|
import java.util.zip.CheckedOutputStream
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
@@ -25,7 +27,7 @@ class MangaDlTools(activity: DlActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getImgsCountByHash(hash: String): Int?{
|
fun getImgsCountByHash(hash: String): Int?{
|
||||||
return imgUrlsList?.get(p[hash].toInt())?.size
|
return p[hash].toIntOrNull()?.let { imgUrlsList?.getOrNull(it)?.size }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allocateChapterUrls(count: Int){
|
fun allocateChapterUrls(count: Int){
|
||||||
@@ -33,50 +35,118 @@ class MangaDlTools(activity: DlActivity) {
|
|||||||
chaptersCount = 0
|
chaptersCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dlChapterUrl(url: String){
|
fun dlChapterUrl(url: String): Boolean {
|
||||||
sem.acquire()
|
Log.d("CopymangaDL", "waiting chapter semaphore url=$url")
|
||||||
|
if (!sem.tryAcquire(3, TimeUnit.MINUTES)) {
|
||||||
|
Log.e("CopymangaDL", "timeout waiting previous chapter url collection")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var started = false
|
||||||
da.get()?.apply {
|
da.get()?.apply {
|
||||||
p[url.substringAfterLast("/")] = (chaptersCount++).toString()
|
p[url.substringAfterLast("/")] = (chaptersCount++).toString()
|
||||||
|
Log.d("CopymangaDL", "load chapter url=$url hash=${url.substringAfterLast("/")}")
|
||||||
runOnUiThread { mBinding.dwh.apply { post { loadUrl(url) } } }
|
runOnUiThread { mBinding.dwh.apply { post { loadUrl(url) } } }
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
if (!started) sem.release()
|
||||||
|
return started
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun waitChapterUrlsReady(): Boolean {
|
||||||
|
Log.d("CopymangaDL", "waiting final chapter url collection")
|
||||||
|
return if (sem.tryAcquire(5, TimeUnit.MINUTES)) {
|
||||||
|
sem.release()
|
||||||
|
Log.d("CopymangaDL", "all chapter urls collected")
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
Log.e("CopymangaDL", "timeout waiting final chapter url collection")
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setChapterImages(hash: String, imgUrls: Array<String>){
|
fun setChapterImages(hash: String, imgUrls: Array<String>){
|
||||||
imgUrlsList?.set(p[hash].toInt(), imgUrls)
|
val index = p[hash].toIntOrNull()
|
||||||
|
if (index == null) {
|
||||||
|
Log.e("CopymangaDL", "missing chapter hash=$hash, images=${imgUrls.size}")
|
||||||
|
sem.release()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log.d("CopymangaDL", "chapter images collected hash=$hash index=$index images=${imgUrls.size}")
|
||||||
|
imgUrlsList?.set(index, imgUrls)
|
||||||
sem.release()
|
sem.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dlChapterAndPackIntoZip(zipf: File, hash: String){
|
fun dlChapterAndPackIntoZip(zipf: File, hash: String){
|
||||||
imgUrlsList?.get(p[hash].toInt())?.let { images ->
|
val idx = p[hash].toIntOrNull()
|
||||||
val dl = DownloadTools()
|
val images = idx?.let { imgUrlsList?.getOrNull(it) }
|
||||||
zipf.parentFile?.let { if (!it.exists()) it.mkdirs() }
|
if (images.isNullOrEmpty()) {
|
||||||
if (zipf.exists()) zipf.delete()
|
Log.e("CopymangaDL", "no images to zip hash=$hash idx=$idx")
|
||||||
zipf.createNewFile()
|
onDownloadedListener?.handleMessage(false)
|
||||||
val zip = ZipOutputStream(CheckedOutputStream(zipf.outputStream(), CRC32()))
|
return
|
||||||
zip.setLevel(9)
|
}
|
||||||
var succeed = true
|
|
||||||
for (i in images.indices) {
|
val dl = DownloadTools()
|
||||||
zip.putNextEntry(ZipEntry("$i.webp"))
|
zipf.parentFile?.let { if (!it.exists()) it.mkdirs() }
|
||||||
var tryTimes = 3
|
val tmpZip = File(zipf.absolutePath + ".tmp")
|
||||||
var s = false
|
if (tmpZip.exists()) tmpZip.delete()
|
||||||
while (!s && tryTimes-- > 0){
|
|
||||||
s = d?.toolsBox?.resolution?.wrap(images[i])?.let { u ->
|
Log.d("CopymangaDL", "start zip file=${zipf.absolutePath} pages=${images.size}")
|
||||||
dl.getHttpContent(u, d?.getString(R.string.web_home_www),
|
var succeed = true
|
||||||
d?.getString(R.string.pc_ua)
|
try {
|
||||||
)?.let { zip.write(it); true } ?: false
|
ZipOutputStream(CheckedOutputStream(tmpZip.outputStream().buffered(), CRC32())).use { zip ->
|
||||||
} ?: false
|
zip.setLevel(9)
|
||||||
if (!s) {
|
for (i in images.indices) {
|
||||||
onDownloadedListener?.handleMessage(i + 1)
|
if (i % 10 == 0) Log.d("CopymangaDL", "zipping page ${i + 1}/${images.size}")
|
||||||
sleep(2000)
|
var tryTimes = 3
|
||||||
|
var data: ByteArray? = null
|
||||||
|
while (data == null && tryTimes-- > 0) {
|
||||||
|
data = d?.toolsBox?.resolution?.wrap(images[i])?.let { u ->
|
||||||
|
dl.getHttpContent(
|
||||||
|
u,
|
||||||
|
d?.getString(R.string.web_home_www),
|
||||||
|
d?.getString(R.string.pc_ua)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (data == null) {
|
||||||
|
Log.w("CopymangaDL", "retry page=${i + 1} left=$tryTimes url=${images[i]}")
|
||||||
|
onDownloadedListener?.handleMessage(i + 1)
|
||||||
|
sleep(2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
succeed = false
|
||||||
|
Log.e("CopymangaDL", "download failed page=${i + 1} url=${images[i]}")
|
||||||
|
onDownloadedListener?.handleMessage(false, i + 1)
|
||||||
|
} else {
|
||||||
|
zip.putNextEntry(ZipEntry("%03d.webp".format(i)))
|
||||||
|
zip.write(data)
|
||||||
|
zip.closeEntry()
|
||||||
|
onDownloadedListener?.handleMessage(true, i + 1)
|
||||||
|
}
|
||||||
|
zip.flush()
|
||||||
|
if (exit) {
|
||||||
|
succeed = false
|
||||||
|
Log.w("CopymangaDL", "zip canceled")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!s && tryTimes <= 0) succeed = false
|
|
||||||
onDownloadedListener?.handleMessage(s, i + 1)
|
|
||||||
zip.flush()
|
|
||||||
if (exit) break
|
|
||||||
}
|
}
|
||||||
zip.close()
|
|
||||||
|
if (succeed) {
|
||||||
|
if (zipf.exists()) zipf.delete()
|
||||||
|
if (!tmpZip.renameTo(zipf)) {
|
||||||
|
tmpZip.copyTo(zipf, overwrite = true)
|
||||||
|
tmpZip.delete()
|
||||||
|
}
|
||||||
|
} else tmpZip.delete()
|
||||||
|
Log.d("CopymangaDL", "zip finished succeed=$succeed file=${zipf.absolutePath}")
|
||||||
onDownloadedListener?.handleMessage(succeed)
|
onDownloadedListener?.handleMessage(succeed)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("CopymangaDL", "zip exception", e)
|
||||||
|
tmpZip.delete()
|
||||||
|
onDownloadedListener?.handleMessage(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class JS {
|
|||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
Log.d("MyJS", "Load comic: $u")
|
Log.d("MyJS", "Load comic: $u")
|
||||||
wm?.get()?.loadHiddenUrl(u)
|
if (u.contains("/chapter/")) wm?.get()?.openStreamingManga(u)
|
||||||
|
else wm?.get()?.loadHiddenUrl(u)
|
||||||
}
|
}
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun hideFab() {
|
fun hideFab() {
|
||||||
|
|||||||
@@ -7,11 +7,58 @@ import top.fumiama.copymangaweb.activity.MainActivity.Companion.mh
|
|||||||
import top.fumiama.copymangaweb.activity.MainActivity.Companion.wm
|
import top.fumiama.copymangaweb.activity.MainActivity.Companion.wm
|
||||||
import top.fumiama.copymangaweb.handler.MainHandler
|
import top.fumiama.copymangaweb.handler.MainHandler
|
||||||
|
|
||||||
class JSHidden {
|
class JSHidden(
|
||||||
|
private val onLoadChapter: ((String) -> Unit)? = null,
|
||||||
|
private val onChapterMeta: ((String) -> Unit)? = null,
|
||||||
|
private val onAppendImages: ((Array<String>) -> Unit)? = null,
|
||||||
|
private val onFinishStreaming: (() -> Unit)? = null,
|
||||||
|
private val onChapterCount: ((Int) -> Unit)? = null,
|
||||||
|
private val enableLoadingDialog: Boolean = true,
|
||||||
|
) {
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun loadChapter(listString: String){
|
fun loadChapter(listString: String){
|
||||||
wm?.get()?.callViewManga(listString)
|
Log.d("CopymangaDL", "JS loadChapter called, length=${listString.length}, lines=${listString.count { it == '\n' } + 1}")
|
||||||
|
val callback = onLoadChapter
|
||||||
|
if (callback != null) callback(listString)
|
||||||
|
else {
|
||||||
|
val main = wm?.get()
|
||||||
|
if (main == null) Log.e("CopymangaDL", "MainActivity reference lost, dropped chapter result")
|
||||||
|
else main.callViewManga(listString)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun setChapterMeta(headerString: String) {
|
||||||
|
Log.d("CopymangaDL", "JS setChapterMeta called, length=${headerString.length}")
|
||||||
|
onChapterMeta?.invoke(headerString)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun appendChapterImages(urlsString: String) {
|
||||||
|
val urls = urlsString
|
||||||
|
.split('\n')
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.toTypedArray()
|
||||||
|
Log.d("CopymangaDL", "JS appendChapterImages called, images=${urls.size}")
|
||||||
|
if (urls.isNotEmpty()) onAppendImages?.invoke(urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun setChapterCount(countString: String) {
|
||||||
|
val c = countString.toIntOrNull() ?: return
|
||||||
|
if (c > 0) {
|
||||||
|
Log.d("CopymangaDL", "JS setChapterCount called, count=$c")
|
||||||
|
onChapterCount?.invoke(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
fun finishStreamingChapter() {
|
||||||
|
Log.d("CopymangaDL", "JS finishStreamingChapter called")
|
||||||
|
onFinishStreaming?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun setTitle(title:String){
|
fun setTitle(title:String){
|
||||||
Log.d("MyJSH", "Set title: $title")
|
Log.d("MyJSH", "Set title: $title")
|
||||||
@@ -23,10 +70,10 @@ class JSHidden {
|
|||||||
}
|
}
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun setLoadingDialog(display: Boolean) {
|
fun setLoadingDialog(display: Boolean) {
|
||||||
mh?.sendEmptyMessage(if (display) MainHandler.SHOW_LOADING_DIALOG else MainHandler.HIDE_LOADING_DIALOG)
|
if (enableLoadingDialog) mh?.sendEmptyMessage(if (display) MainHandler.SHOW_LOADING_DIALOG else MainHandler.HIDE_LOADING_DIALOG)
|
||||||
}
|
}
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun setLoadingDialogProgress(index: String, count: String) {
|
fun setLoadingDialogProgress(index: String, count: String) {
|
||||||
mh?.obtainMessage(MainHandler.SET_LOADING_DIALOG_TEXT, "$index/$count")?.sendToTarget()
|
if (enableLoadingDialog) mh?.obtainMessage(MainHandler.SET_LOADING_DIALOG_TEXT, "$index/$count")?.sendToTarget()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,19 +2,15 @@ package top.fumiama.copymangaweb.web
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.webkit.WebResourceRequest
|
import android.webkit.WebResourceRequest
|
||||||
import android.webkit.WebResourceResponse
|
import android.webkit.WebResourceResponse
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import top.fumiama.copymangaweb.R
|
import top.fumiama.copymangaweb.R
|
||||||
import top.fumiama.copymangaweb.activity.MainActivity.Companion.wm
|
|
||||||
|
|
||||||
class WebViewClient(private val context: Context, jsFileName: String):WebViewClient() {
|
class WebViewClient(private val context: Context, jsFileName: String):WebViewClient() {
|
||||||
private val js = context.assets.open(jsFileName).readBytes().decodeToString()
|
private val js = context.assets.open(jsFileName).readBytes().decodeToString()
|
||||||
@@ -30,16 +26,11 @@ class WebViewClient(private val context: Context, jsFileName: String):WebViewCli
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
wm?.get()?.lifecycleScope?.launch {
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
withContext(Dispatchers.IO) {
|
view?.loadUrl(js)
|
||||||
delay(500)
|
Log.d("MyWC", "Inject JS into: $url")
|
||||||
withContext(Dispatchers.Main) {
|
}, 500)
|
||||||
view?.loadUrl(js)
|
super.onPageFinished(view, url)
|
||||||
Log.d("MyWC", "Inject JS into: $url")
|
|
||||||
super.onPageFinished(view, url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldInterceptRequest(
|
override fun shouldInterceptRequest(
|
||||||
|
|||||||
@@ -6,6 +6,14 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<top.fumiama.copymangaweb.view.JSWebView
|
||||||
|
android:id="@+id/wcollector"
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:alpha="0.01"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/vone"
|
android:id="@+id/vone"
|
||||||
layout="@layout/page_imgview"
|
layout="@layout/page_imgview"
|
||||||
|
|||||||
Reference in New Issue
Block a user