[优化] 支持更多Sigma版本特性(@Luoyacheng)

This commit is contained in:
HapeLee
2026-02-02 21:09:24 +08:00
parent d3c85eccfe
commit 2d14d82251
20 changed files with 200 additions and 88 deletions
@@ -35,5 +35,6 @@ object EventBus {
const val UP_MANGA_CONFIG = "upMangaConfig"
const val PLAY_MODE_CHANGED = "playModeChanged"
const val REFRESH_BOOK_INFO = "refreshBookInfo"
const val REFRESH_BOOK_CONTENT = "refreshBookContent"
const val UP_TOC = "upToc"
}
@@ -118,7 +118,7 @@ object PreferKey {
const val enableMangaEInk = "enableMangaEInk"
const val mangaEInkThreshold = "mangaEInkThreshold"
const val enableMangaGray = "enableMangaGray"
const val mediaButtonOnExit = "mediaButtonOnExit"
const val autoRefresh = "auto_refresh"
const val defaultToRead = "defaultToRead"
const val exportCharset = "exportCharset"
@@ -9,17 +9,21 @@ import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog
import io.legado.app.data.entities.rule.RowUi
import io.legado.app.help.CacheManager
import io.legado.app.help.ConcurrentRateLimiter.Companion.updateConcurrentRate
import io.legado.app.help.JsExtensions
import io.legado.app.help.config.AppConfig
import io.legado.app.help.crypto.SymmetricCryptoAndroid
import io.legado.app.help.http.CookieStore
import io.legado.app.help.source.clearExploreKindsCache
import io.legado.app.help.source.getShareScope
import io.legado.app.model.SharedJsScope.remove
import io.legado.app.utils.GSON
import io.legado.app.utils.GSONStrict
import io.legado.app.utils.fromJsonArray
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.has
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.isMainThread
import kotlinx.coroutines.runBlocking
import org.intellij.lang.annotations.Language
/**
@@ -75,6 +79,9 @@ interface BaseSource : JsExtensions {
}
}
/**
* 调用login函数 实现登录请求
*/
@JavascriptInterface
fun login() {
val loginJs = getLoginJs()
@@ -91,30 +98,6 @@ interface BaseSource : JsExtensions {
}
}
fun loginUi(): List<RowUi>? {
val json = loginUi?.let {
val loginJS = getLoginJs() ?: ""
try {
when {
it.startsWith("@js:") -> evalJS(loginJS + it.substring(4)){
put("result", getLoginInfoMap())
}.toString()
it.startsWith("<js>") -> evalJS(loginJS + it.substring(4,it.lastIndexOf("<"))){
put("result", getLoginInfoMap())
}.toString()
else -> it
}
} catch (e: Throwable) {
log(e)
e.printOnDebug()
null
}
}
return GSON.fromJsonArray<RowUi>(json).onFailure {
it.printOnDebug()
}.getOrNull()
}
/**
* 解析header规则
*/
@@ -152,6 +135,7 @@ interface BaseSource : JsExtensions {
/**
* 获取用于登录的头部信息
*/
@JavascriptInterface
fun getLoginHeader(): String? {
return CacheManager.get("loginHeader_${getKey()}")
}
@@ -182,6 +166,7 @@ interface BaseSource : JsExtensions {
* 获取用户信息,可以用来登录
* 用户信息采用aes加密存储
*/
@JavascriptInterface
fun getLoginInfo(): String? {
try {
val key = AppConst.androidId.encodeToByteArray(0, 16)
@@ -232,6 +217,7 @@ interface BaseSource : JsExtensions {
/**
* 保存用户信息,aes加密
*/
@JavascriptInterface
fun putLoginInfo(info: String): Boolean {
return try {
val key = (AppConst.androidId).encodeToByteArray(0, 16)
@@ -244,6 +230,7 @@ interface BaseSource : JsExtensions {
}
}
@JavascriptInterface
fun removeLoginInfo() {
CacheManager.delete("userInfo_${getKey()}")
}
@@ -260,9 +247,23 @@ interface BaseSource : JsExtensions {
}
}
/**
* 设置自定义变量
* 新,统一为put名称存变量
*/
@JavascriptInterface
fun putVariable(variable: String?) {
if (variable != null) {
CacheManager.put("sourceVariable_${getKey()}", variable)
} else {
CacheManager.delete("sourceVariable_${getKey()}")
}
}
/**
* 获取自定义变量
*/
@JavascriptInterface
fun getVariable(): String {
return CacheManager.get("sourceVariable_${getKey()}") ?: ""
}
@@ -270,6 +271,7 @@ interface BaseSource : JsExtensions {
/**
* 保存数据
*/
@JavascriptInterface
fun put(key: String, value: String): String {
CacheManager.put("v_${getKey()}_${key}", value)
return value
@@ -278,10 +280,44 @@ interface BaseSource : JsExtensions {
/**
* 获取保存的数据
*/
@JavascriptInterface
fun get(key: String): String {
return CacheManager.get("v_${getKey()}_${key}") ?: ""
}
/**
* 刷新发现
*/
fun refreshExplore() {
if (isMainThread) {
error("refreshExplore must be called on a background thread")
}
runBlocking {
if (this@BaseSource is BookSource) {
this@BaseSource.clearExploreKindsCache()
}
}
}
/**
* 刷新JSLib
*/
fun refreshJSLib() {
if (isMainThread) {
error("refreshJSLib must be called on a background thread")
}
runBlocking {
remove(jsLib)
}
}
/**
* 设置并发率
*/
fun putConcurrent(value: String) {
updateConcurrentRate(getKey(), value)
}
/**
* 执行JS
*/
@@ -16,6 +16,7 @@ import io.legado.app.data.entities.BaseSource
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.config.ThemeConfig
import io.legado.app.help.http.BackstageWebView
import io.legado.app.help.http.CookieManager.cookieJarHeader
import io.legado.app.help.http.CookieStore
@@ -321,17 +322,6 @@ interface JsExtensions : JsEncodeUtils {
}
}
/**
* 打开内置视频播放器
* @param url 视频播放链接
* @param title 视频的标题
* @param float 是否悬浮窗打开
*/
@JavascriptInterface
fun openVideoPlayer(url: String, title: String, float: Boolean) {
//SourceHelp.openVideoPlayer(getSource(), url, title, float)
}
/**
* 使用内置浏览器打开链接,手动验证网站防爬
* @param url 要打开的链接
@@ -1196,9 +1186,9 @@ interface JsExtensions : JsEncodeUtils {
return GSON.toJson(ReadBookConfig.durConfig)
}
//fun getReadBookConfigMap(): Map<String, Any> {
// return ReadBookConfig.durConfig.toMap()
//}
fun getReadBookConfigMap(): Map<String, Any> {
return ReadBookConfig.durConfig.toMap()
}
/**
* 获取主题模式
@@ -1211,14 +1201,14 @@ interface JsExtensions : JsEncodeUtils {
/**
* 获取主题配置
*/
// @JavascriptInterface
//fun getThemeConfig(): String {
// val themeConfig = ThemeConfig.getDurConfig(appCtx)
// return GSON.toJson(themeConfig)
//}
@JavascriptInterface
fun getThemeConfig(): String {
val themeConfig = ThemeConfig.getDurConfig(appCtx)
return GSON.toJson(themeConfig)
}
//fun getThemeConfigMap(): Map<String, Any?> {
// return ThemeConfig.getDurConfig(appCtx).toMap()
//}
fun getThemeConfigMap(): Map<String, Any?> {
return ThemeConfig.getDurConfig(appCtx).toMap()
}
}
@@ -31,6 +31,7 @@ import io.legado.app.utils.onEachParallel
import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
@@ -47,7 +48,6 @@ import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import java.util.regex.Pattern
import java.util.zip.ZipFile
import kotlin.coroutines.coroutineContext
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@@ -234,7 +234,7 @@ object BookHelp {
return
}
val analyzeUrl = AnalyzeUrl(
src, source = bookSource, coroutineContext = coroutineContext
src, source = bookSource, coroutineContext = currentCoroutineContext()
)
val bytes = analyzeUrl.getByteArrayAwait()
//某些图片被加密,需要进一步解密
@@ -252,7 +252,7 @@ object BookHelp {
writeImage(book, src, it)
}
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
val msg = "${book.name} ${chapter?.title} 图片 $src 下载失败\n${e.localizedMessage}"
AppLog.put(msg, e)
} finally {
@@ -578,7 +578,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val syncBookProgressPlus get() = appCtx.getPrefBoolean(PreferKey.syncBookProgressPlus, false)
val mediaButtonOnExit get() = appCtx.getPrefBoolean("mediaButtonOnExit", true)
val mediaButtonOnExit get() = appCtx.getPrefBoolean(PreferKey.mediaButtonOnExit, true)
val readAloudByMediaButton
get() = appCtx.getPrefBoolean(PreferKey.readAloudByMediaButton, false)
@@ -788,6 +788,69 @@ object ReadBookConfig {
@Transient
private var initColorInt = false
fun toMap() = mapOf(
"name" to name,
"bgStr" to bgStr,
"bgStrNight" to bgStrNight,
"bgStrEInk" to bgStrEInk,
"bgAlpha" to bgAlpha,
"bgType" to bgType,
"bgTypeNight" to bgTypeNight,
"bgTypeEInk" to bgTypeEInk,
"darkStatusIcon" to darkStatusIcon,
"darkStatusIconNight" to darkStatusIconNight,
"darkStatusIconEInk" to darkStatusIconEInk,
"textColor" to textColor,
"textColorNight" to textColorNight,
"textColorEInk" to textColorEInk,
"textColorInt" to textColorInt,
"textColorIntNight" to textColorIntNight,
"textColorIntEInk" to textColorIntEInk,
"textAccentColor" to textAccentColor,
"textAccentColorNight" to textAccentColorNight,
"textAccentColorEInk" to textAccentColorEInk,
"textAccentColorInt" to textAccentColorInt,
"textAccentColorIntNight" to textAccentColorIntNight,
"textAccentColorIntEInk" to textAccentColorIntEInk,
"pageAnim" to pageAnim,
"pageAnimEInk" to pageAnimEInk,
"textFont" to textFont,
"textBold" to textBold,
"textSize" to textSize,
"letterSpacing" to letterSpacing,
"lineSpacingExtra" to lineSpacingExtra,
"paragraphSpacing" to paragraphSpacing,
"titleMode" to titleMode,
"titleSize" to titleSize,
"titleTopSpacing" to titleTopSpacing,
"titleBottomSpacing" to titleBottomSpacing,
"paragraphIndent" to paragraphIndent,
"paddingBottom" to paddingBottom,
"paddingLeft" to paddingLeft,
"paddingRight" to paddingRight,
"paddingTop" to paddingTop,
"headerPaddingBottom" to headerPaddingBottom,
"headerPaddingLeft" to headerPaddingLeft,
"headerPaddingRight" to headerPaddingRight,
"headerPaddingTop" to headerPaddingTop,
"footerPaddingBottom" to footerPaddingBottom,
"footerPaddingLeft" to footerPaddingLeft,
"footerPaddingRight" to footerPaddingRight,
"footerPaddingTop" to footerPaddingTop,
"showHeaderLine" to showHeaderLine,
"showFooterLine" to showFooterLine,
"tipHeaderLeft" to tipHeaderLeft,
"tipHeaderMiddle" to tipHeaderMiddle,
"tipHeaderRight" to tipHeaderRight,
"tipFooterLeft" to tipFooterLeft,
"tipFooterMiddle" to tipFooterMiddle,
"tipFooterRight" to tipFooterRight,
"tipColor" to tipColor,
"tipDividerColor" to tipDividerColor,
"headerMode" to headerMode,
"footerMode" to footerMode
)
fun getBgPath(bgIndex: Int): String? {
val bgType = when (bgIndex) {
0 -> bgType
@@ -218,7 +218,6 @@ class WebJsExtensions(
val letters = "abcdefghijklmnopqrstuvwxyz"
return letters.random()
}
val uuid by lazy {
UUID.randomUUID().toString().replace('-', getRandomLetter()).chunked(6)
}
@@ -41,7 +41,7 @@ object ImageProvider {
private const val M = 1024 * 1024
val cacheSize: Int
get() {
if (AppConfig.bitmapCacheSize <= 0 || AppConfig.bitmapCacheSize >= 2048) {
if (AppConfig.bitmapCacheSize !in 1..<2048) {
AppConfig.bitmapCacheSize = 50
}
return AppConfig.bitmapCacheSize * M
@@ -183,6 +183,12 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
} else {
appDb.bookSourceDao.getBookSource(book.origin)?.let {
bookSource = it
SourceCallBack.callBackBook(
SourceCallBack.START_READ,
it,
book,
curTextChapter?.chapter
)
if (book.getImageStyle().isNullOrBlank()) {
var imageStyle = it.getContentRule().imageStyle
if (imageStyle.isNullOrBlank() && (book.isImage || book.isPdf)) {
@@ -978,9 +984,9 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
}
fun saveRead(pageChanged: Boolean = false) {
val book = book ?: return
executor.execute {
kotlin.runCatching {
val book = book ?: return@execute
book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis()
val chapterChanged = book.durChapterIndex != durChapterIndex
@@ -992,9 +998,10 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),
book.getUseReplaceRule()
)
SourceCallBack.callBackBook(SourceCallBack.SAVE_READ, bookSource, book, it)
}
}
appDb.bookDao.update(book)
book.update()
}.onFailure {
AppLog.put("保存书籍阅读进度信息出错\n$it", it)
}
@@ -1034,17 +1041,6 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
}
}
private fun clearExpiredChapterLoadingJob(clearAll: Boolean = false) {
val iterator = chapterLoadingJobs.iterator()
while (iterator.hasNext()) {
val (index, job) = iterator.next()
if (clearAll || index !in durChapterIndex - 1..durChapterIndex + 1) {
job.cancel()
iterator.remove()
}
}
}
fun cancelPreDownloadTask() {
if (contentLoadFinish) {
preDownloadTask?.cancel()
@@ -1068,6 +1064,17 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
}
}
private fun clearExpiredChapterLoadingJob(clearAll: Boolean = false) {
val iterator = chapterLoadingJobs.iterator()
while (iterator.hasNext()) {
val (index, job) = iterator.next()
if (clearAll || index !in durChapterIndex - 1..durChapterIndex + 1) {
job.cancel()
iterator.remove()
}
}
}
/**
* 注册回调
*/
@@ -1124,4 +1131,4 @@ object ReadBook : CoroutineScope by MainScope(), KoinComponent {
fun cancelSelect()
}
}
}
@@ -969,4 +969,3 @@ class AnalyzeUrl(
)
}
@@ -1918,6 +1918,13 @@ class ReadBookActivity : BaseReadBookActivity(),
observeEvent<Boolean>(EventBus.UP_SEEK_BAR) {
readMenu.upSeekBar()
}
observeEvent<Boolean>(EventBus.REFRESH_BOOK_CONTENT) {
ReadBook.book?.let {
ReadBook.curTextChapter = null
binding.readView.upContent()
viewModel.refreshContentDur(it)
}
}
}
private fun upScreenTimeOut() {
@@ -97,7 +97,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
if (!isMainView) return
ChapterProvider.upViewSize(w, h)
textPage.format()
//TODO: 有关测量相关问题
}
override fun onDraw(canvas: Canvas) {
@@ -267,7 +266,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
is ImageColumn -> when (AppConfig.clickImgWay) {
"1" -> { //预览图片
activity?.showDialogFragment(PhotoDialog(column.src))
activity?.showDialogFragment(PhotoDialog(column.src, isBook = true))
handled = true
}
"2" -> { //兼容处理
@@ -7,12 +7,10 @@ import android.graphics.Paint.FontMetrics
import android.graphics.RectF
import android.graphics.Typeface
import android.os.Build
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import androidx.core.net.toUri
import androidx.core.os.postDelayed
import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
@@ -21,28 +19,18 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.config.ReadBookConfig.dottedBase
import io.legado.app.help.config.ReadBookConfig.dottedRatio
import io.legado.app.model.ImageProvider
import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextLine
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.ui.book.read.page.entities.column.ImageColumn
import io.legado.app.ui.book.read.page.entities.column.ReviewColumn
import io.legado.app.ui.book.read.page.entities.column.TextColumn
import io.legado.app.utils.buildMainHandler
import io.legado.app.utils.dpToPx
import io.legado.app.utils.fastSum
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.isPad
import io.legado.app.utils.postEvent
import io.legado.app.utils.spToPx
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.textHeight
import kotlinx.coroutines.CoroutineScope
import splitties.init.appCtx
import java.io.File
import java.util.LinkedList
import java.util.Locale
/**
* 解析内容生成章节和页面
@@ -180,6 +168,7 @@ object ChapterProvider {
upStyle()
}
/*
/**
* 获取拆分完的章节数据
*/
@@ -337,6 +326,7 @@ object ChapterProvider {
bookContent.effectiveReplaceRules
)
}
*/
fun getTextChapterAsync(
scope: CoroutineScope,
@@ -362,6 +352,7 @@ object ChapterProvider {
return textChapter
}
/*
/**
* 排版图片
*/
@@ -869,6 +860,7 @@ object ChapterProvider {
}
return stringList to widths
}
*/
/**
* 更新样式
@@ -47,6 +47,10 @@ class SourceLoginJsExtensions(
postEvent(EventBus.REFRESH_BOOK_INFO, true)
}
fun refreshContent() {
postEvent(EventBus.REFRESH_BOOK_CONTENT, true)
}
fun copyText(text: String) {
activityRef.get()?.sendToClip(text)
}
@@ -28,10 +28,11 @@ import java.io.ByteArrayOutputStream
*/
class PhotoDialog() : BaseBottomSheetDialogFragment(R.layout.dialog_photo_view) {
constructor(src: String, sourceOrigin: String? = null) : this() {
constructor(src: String, sourceOrigin: String? = null, isBook: Boolean = false) : this() {
arguments = Bundle().apply {
putString("src", src)
putString("sourceOrigin", sourceOrigin)
putBoolean("isBook", isBook)
}
}
+12 -1
View File
@@ -6,7 +6,13 @@
android:id="@+id/menu_ok"
android:icon="@drawable/ic_check"
android:title="@string/ok"
app:showAsAction="always" />
app:showAsAction="ifRoom" />
<item
android:id="@+id/menu_web_refresh"
android:icon="@drawable/ic_refresh"
android:title="@string/refresh"
app:showAsAction="ifRoom" />
<item
android:id="@+id/menu_open_in_browser"
@@ -23,6 +29,11 @@
android:title="@string/full_screen"
app:showAsAction="never" />
<item
android:id="@+id/menu_show_web_log"
android:checkable="true"
android:title="@string/show_web_log" />
<item
android:id="@+id/menu_disable_source"
android:title="@string/disable_source"
+1
View File
@@ -57,6 +57,7 @@
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
<string-array name="double_page_value">
+4 -3
View File
@@ -117,10 +117,11 @@
</string-array>
<string-array name="click_image_way_title">
<item>新包名阅读行为</item>
<item>新包名行为</item>
<item>预览图片</item>
<item>经典阅读行为</item>
<item>不响应</item>
<item>经典行为</item>
<item>关闭点击</item>
<item>双击生效</item>
</string-array>
<string-array name="double_page_title">
+1
View File
@@ -1387,4 +1387,5 @@
<string name="audio_cache_clean_time_summary">当前%d, 输入 0 代表退出即刻清理。</string>
<string name="show_swipe_animation">启用滑动时动画</string>
<string name="click_image_way">点击图片行为</string>
<string name="show_web_log">输出日志</string>
</resources>