Merge remote-tracking branch 'upstream/master'
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
{"arm64-v8a":"11ded68691faa98fb1ab552210c8d4a8","armeabi-v7a":"4a986b523de502fc85ba6ea51f6f7184","x86_64":"80a642112fe079f3d689a6420a27e926","x86":"e72bf103350222d212bcb0b641733d43","version":"105.0.5195.77"}
|
{"arm64-v8a":"27f6651b39339e6734e7ca63a9c9022b","armeabi-v7a":"abbf91c8b8f3dd02a4bda8872e3b9b98","x86_64":"d6923e011add8b18a824ef5a7c85772e","x86":"ba27795b663a8420790da40d40b99f96","version":"105.0.5195.79"}
|
||||||
@@ -11,10 +11,11 @@
|
|||||||
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
|
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
|
||||||
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
|
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
|
||||||
|
|
||||||
**2022/09/03**
|
**2022/09/07**
|
||||||
|
|
||||||
* 更新cronet: 105.0.5195.77
|
* 更新cronet: 105.0.5195.79
|
||||||
* 更新web端书源编辑 by jgckM
|
* 更新web端书源编辑 by jgckM
|
||||||
|
* 修复仿真翻页点击时翻页动画异常,化仿真翻页点击翻页效果 by 821938089
|
||||||
|
|
||||||
**2022/08/31**
|
**2022/08/31**
|
||||||
|
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ object BookController {
|
|||||||
}
|
}
|
||||||
returnData.setData(content)
|
returnData.setData(content)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
returnData.setErrorMsg(e.msg)
|
returnData.setErrorMsg(e.stackTraceStr)
|
||||||
}
|
}
|
||||||
return returnData
|
return returnData
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import io.legado.app.data.entities.ReplaceRule
|
|||||||
import io.legado.app.exception.RegexTimeoutException
|
import io.legado.app.exception.RegexTimeoutException
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.utils.msg
|
|
||||||
import io.legado.app.utils.replace
|
import io.legado.app.utils.replace
|
||||||
|
import io.legado.app.utils.stackTraceStr
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
@@ -149,7 +149,7 @@ class ContentProcessor private constructor(
|
|||||||
} catch (e: RegexTimeoutException) {
|
} catch (e: RegexTimeoutException) {
|
||||||
item.isEnabled = false
|
item.isEnabled = false
|
||||||
appDb.replaceRuleDao.update(item)
|
appDb.replaceRuleDao.update(item)
|
||||||
return item.name + e.msg
|
return item.name + e.stackTraceStr
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
return mContent
|
return mContent
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import io.legado.app.model.ReadAloud
|
|||||||
import io.legado.app.utils.FileUtils
|
import io.legado.app.utils.FileUtils
|
||||||
import io.legado.app.utils.getFile
|
import io.legado.app.utils.getFile
|
||||||
import io.legado.app.utils.longToastOnUi
|
import io.legado.app.utils.longToastOnUi
|
||||||
import io.legado.app.utils.msg
|
import io.legado.app.utils.stackTraceStr
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.io.StringWriter
|
import java.io.StringWriter
|
||||||
@@ -48,7 +48,7 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
|
|||||||
if (ex == null) return
|
if (ex == null) return
|
||||||
//保存日志文件
|
//保存日志文件
|
||||||
saveCrashInfo2File(ex)
|
saveCrashInfo2File(ex)
|
||||||
context.longToastOnUi(ex.msg)
|
context.longToastOnUi(ex.stackTraceStr)
|
||||||
Thread.sleep(3000)
|
Thread.sleep(3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import splitties.init.appCtx
|
|||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@@ -56,9 +57,8 @@ interface JsExtensions {
|
|||||||
analyzeUrl.getStrResponseAwait().body
|
analyzeUrl.getStrResponseAwait().body
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
AppLog.put("ajax(${urlStr}) error\n${it.localizedMessage}", it)
|
AppLog.put("ajax(${urlStr}) error\n${it.localizedMessage}", it)
|
||||||
it.printOnDebug()
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.msg
|
it.stackTraceStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,10 +91,9 @@ interface JsExtensions {
|
|||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
analyzeUrl.getStrResponseAwait()
|
analyzeUrl.getStrResponseAwait()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
log("connect(${urlStr}) error\n${it.stackTraceToString()}")
|
AppLog.put("connect(${urlStr}) error\n${it.localizedMessage}", it)
|
||||||
it.printOnDebug()
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
StrResponse(analyzeUrl.url, it.localizedMessage)
|
StrResponse(analyzeUrl.url, it.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,10 +105,9 @@ interface JsExtensions {
|
|||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
analyzeUrl.getStrResponseAwait()
|
analyzeUrl.getStrResponseAwait()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
log("ajax($urlStr,$header) error\n${it.stackTraceToString()}")
|
AppLog.put("ajax($urlStr,$header) error\n${it.localizedMessage}", it)
|
||||||
it.printOnDebug()
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
StrResponse(analyzeUrl.url, it.localizedMessage)
|
StrResponse(analyzeUrl.url, it.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +144,10 @@ interface JsExtensions {
|
|||||||
* 使用内置浏览器打开链接,并等待网页结果
|
* 使用内置浏览器打开链接,并等待网页结果
|
||||||
*/
|
*/
|
||||||
fun startBrowserAwait(url: String, title: String): StrResponse {
|
fun startBrowserAwait(url: String, title: String): StrResponse {
|
||||||
return StrResponse(url, SourceVerificationHelp.getVerificationResult(getSource(), url, title, true))
|
return StrResponse(
|
||||||
|
url,
|
||||||
|
SourceVerificationHelp.getVerificationResult(getSource(), url, title, true)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,6 +207,29 @@ interface JsExtensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
* @param url 下载地址:可带参数type,文件后缀,不带默认zip
|
||||||
|
* @return 下载的文件相对路径
|
||||||
|
*/
|
||||||
|
fun downloadFile(url: String): String {
|
||||||
|
val analyzeUrl = AnalyzeUrl(url, source = getSource())
|
||||||
|
val type = analyzeUrl.type ?: "zip"
|
||||||
|
val path = FileUtils.getPath(
|
||||||
|
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
|
||||||
|
"${MD5Utils.md5Encode16(url)}.${type}"
|
||||||
|
)
|
||||||
|
FileUtils.delete(path)
|
||||||
|
analyzeUrl.getInputStream().use { iStream ->
|
||||||
|
val file = FileUtils.createFileIfNotExist(path)
|
||||||
|
FileOutputStream(file).use { oStream ->
|
||||||
|
iStream.copyTo(oStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path.substring(FileUtils.getCachePath().length)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实现16进制字符串转文件
|
* 实现16进制字符串转文件
|
||||||
* @param content 需要转成文件的16进制字符串
|
* @param content 需要转成文件的16进制字符串
|
||||||
@@ -214,18 +238,18 @@ interface JsExtensions {
|
|||||||
*/
|
*/
|
||||||
fun downloadFile(content: String, url: String): String {
|
fun downloadFile(content: String, url: String): String {
|
||||||
val type = AnalyzeUrl(url, source = getSource()).type ?: return ""
|
val type = AnalyzeUrl(url, source = getSource()).type ?: return ""
|
||||||
val zipPath = FileUtils.getPath(
|
val path = FileUtils.getPath(
|
||||||
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
|
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
|
||||||
"${MD5Utils.md5Encode16(url)}.${type}"
|
"${MD5Utils.md5Encode16(url)}.${type}"
|
||||||
)
|
)
|
||||||
FileUtils.delete(zipPath)
|
FileUtils.delete(path)
|
||||||
val zipFile = FileUtils.createFileIfNotExist(zipPath)
|
val file = FileUtils.createFileIfNotExist(path)
|
||||||
StringUtils.hexStringToByte(content).let {
|
StringUtils.hexStringToByte(content).let {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
zipFile.writeBytes(it)
|
file.writeBytes(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return zipPath.substring(FileUtils.getCachePath().length)
|
return path.substring(FileUtils.getCachePath().length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -436,13 +460,15 @@ interface JsExtensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* js实现文件夹内所有文件读取
|
* js实现文件夹内所有文本文件读取
|
||||||
|
* @param path 文件夹相对路径
|
||||||
|
* @return 所有文件字符串换行连接
|
||||||
*/
|
*/
|
||||||
fun getTxtInFolder(unzipPath: String): String {
|
fun getTxtInFolder(path: String): String {
|
||||||
if (unzipPath.isEmpty()) return ""
|
if (path.isEmpty()) return ""
|
||||||
val unzipFolder = getFile(unzipPath)
|
val folder = getFile(path)
|
||||||
val contents = StringBuilder()
|
val contents = StringBuilder()
|
||||||
unzipFolder.listFiles().let {
|
folder.listFiles().let {
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
for (f in it) {
|
for (f in it) {
|
||||||
val charsetName = EncodingDetect.getEncode(f)
|
val charsetName = EncodingDetect.getEncode(f)
|
||||||
@@ -452,7 +478,7 @@ interface JsExtensions {
|
|||||||
contents.deleteCharAt(contents.length - 1)
|
contents.deleteCharAt(contents.length - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FileUtils.delete(unzipFolder.absolutePath)
|
FileUtils.delete(folder.absolutePath)
|
||||||
return contents.toString()
|
return contents.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1031,7 +1057,10 @@ interface JsExtensions {
|
|||||||
algorithm: String,
|
algorithm: String,
|
||||||
key: String
|
key: String
|
||||||
): String {
|
): String {
|
||||||
return Base64.encodeToString(HMac(algorithm, key.toByteArray()).digest(data), Base64.NO_WRAP)
|
return Base64.encodeToString(
|
||||||
|
HMac(algorithm, key.toByteArray()).digest(data),
|
||||||
|
Base64.NO_WRAP
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun md5Encode(str: String): String {
|
fun md5Encode(str: String): String {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import io.legado.app.model.rss.Rss
|
|||||||
import io.legado.app.model.webBook.WebBook
|
import io.legado.app.model.webBook.WebBook
|
||||||
import io.legado.app.utils.HtmlFormatter
|
import io.legado.app.utils.HtmlFormatter
|
||||||
import io.legado.app.utils.isAbsUrl
|
import io.legado.app.utils.isAbsUrl
|
||||||
import io.legado.app.utils.msg
|
import io.legado.app.utils.stackTraceStr
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -128,7 +128,7 @@ object Debug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onError {
|
.onError {
|
||||||
log(debugSource, it.msg, state = -1)
|
log(debugSource, it.stackTraceStr, state = -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ object Debug {
|
|||||||
log(debugSource, "︽内容页解析完成", state = 1000)
|
log(debugSource, "︽内容页解析完成", state = 1000)
|
||||||
}
|
}
|
||||||
.onError {
|
.onError {
|
||||||
log(debugSource, it.msg, state = -1)
|
log(debugSource, it.stackTraceStr, state = -1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ object Debug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onError {
|
.onError {
|
||||||
log(debugSource, it.msg, state = -1)
|
log(debugSource, it.stackTraceStr, state = -1)
|
||||||
}
|
}
|
||||||
tasks.add(explore)
|
tasks.add(explore)
|
||||||
}
|
}
|
||||||
@@ -222,7 +222,7 @@ object Debug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onError {
|
.onError {
|
||||||
log(debugSource, it.msg, state = -1)
|
log(debugSource, it.stackTraceStr, state = -1)
|
||||||
}
|
}
|
||||||
tasks.add(search)
|
tasks.add(search)
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ object Debug {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onError {
|
.onError {
|
||||||
log(debugSource, it.msg, state = -1)
|
log(debugSource, it.stackTraceStr, state = -1)
|
||||||
}
|
}
|
||||||
tasks.add(info)
|
tasks.add(info)
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ object Debug {
|
|||||||
contentDebug(scope, bookSource, book, it.first(), nextChapterUrl)
|
contentDebug(scope, bookSource, book, it.first(), nextChapterUrl)
|
||||||
}
|
}
|
||||||
.onError {
|
.onError {
|
||||||
log(debugSource, it.msg, state = -1)
|
log(debugSource, it.stackTraceStr, state = -1)
|
||||||
}
|
}
|
||||||
tasks.add(chapterList)
|
tasks.add(chapterList)
|
||||||
}
|
}
|
||||||
@@ -284,7 +284,7 @@ object Debug {
|
|||||||
).onSuccess {
|
).onSuccess {
|
||||||
log(debugSource, "︽正文页解析完成", state = 1000)
|
log(debugSource, "︽正文页解析完成", state = 1000)
|
||||||
}.onError {
|
}.onError {
|
||||||
log(debugSource, it.msg, state = -1)
|
log(debugSource, it.stackTraceStr, state = -1)
|
||||||
}
|
}
|
||||||
tasks.add(content)
|
tasks.add(content)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import io.legado.app.model.webBook.WebBook
|
|||||||
import io.legado.app.service.BaseReadAloudService
|
import io.legado.app.service.BaseReadAloudService
|
||||||
import io.legado.app.ui.book.read.page.entities.TextChapter
|
import io.legado.app.ui.book.read.page.entities.TextChapter
|
||||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||||
import io.legado.app.utils.msg
|
import io.legado.app.utils.stackTraceStr
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
@@ -387,7 +387,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("ChapterProvider ERROR", it)
|
AppLog.put("ChapterProvider ERROR", it)
|
||||||
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.msg}")
|
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}")
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
success?.invoke()
|
success?.invoke()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -667,7 +667,7 @@ class AnalyzeRule(
|
|||||||
log("ajax(${urlStr}) error\n${it.stackTraceToString()}")
|
log("ajax(${urlStr}) error\n${it.stackTraceToString()}")
|
||||||
it.printOnDebug()
|
it.printOnDebug()
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.msg
|
it.stackTraceStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import io.legado.app.data.appDb
|
|||||||
import io.legado.app.data.entities.BookSource
|
import io.legado.app.data.entities.BookSource
|
||||||
import io.legado.app.data.entities.SearchBook
|
import io.legado.app.data.entities.SearchBook
|
||||||
import io.legado.app.model.webBook.WebBook
|
import io.legado.app.model.webBook.WebBook
|
||||||
import io.legado.app.utils.msg
|
|
||||||
import io.legado.app.utils.printOnDebug
|
import io.legado.app.utils.printOnDebug
|
||||||
|
import io.legado.app.utils.stackTraceStr
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application
|
|||||||
page++
|
page++
|
||||||
}.onError {
|
}.onError {
|
||||||
it.printOnDebug()
|
it.printOnDebug()
|
||||||
errorLiveData.postValue(it.msg)
|
errorLiveData.postValue(it.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,11 +254,12 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
return@execute
|
return@execute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw NoStackTraceException("自动换源失败")
|
throw NoStackTraceException("没有搜索到 ${name}(${author})")
|
||||||
}.onStart {
|
}.onStart {
|
||||||
ReadBook.upMsg(context.getString(R.string.source_auto_changing))
|
ReadBook.upMsg(context.getString(R.string.source_auto_changing))
|
||||||
}.onError {
|
}.onError {
|
||||||
context.toastOnUi(it.msg)
|
AppLog.put("自动换源失败\n${it.localizedMessage}", it)
|
||||||
|
context.toastOnUi("自动换源失败\n${it.localizedMessage}")
|
||||||
}.onFinally {
|
}.onFinally {
|
||||||
ReadBook.upMsg(null)
|
ReadBook.upMsg(null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
|
|||||||
importConfig(it)
|
importConfig(it)
|
||||||
}
|
}
|
||||||
}.onError {
|
}.onError {
|
||||||
longToast(it.msg)
|
longToast(it.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import io.legado.app.help.config.AppConfig
|
|||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.lib.theme.accentColor
|
import io.legado.app.lib.theme.accentColor
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.ui.book.read.page.entities.TextChar
|
import io.legado.app.ui.book.read.page.entities.TextColumn
|
||||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
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.TextPage
|
||||||
import io.legado.app.ui.book.read.page.entities.TextPos
|
import io.legado.app.ui.book.read.page.entities.TextPos
|
||||||
@@ -30,7 +30,7 @@ import io.legado.app.utils.toastOnUi
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阅读内容界面
|
* 阅读内容视图
|
||||||
*/
|
*/
|
||||||
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
|
||||||
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true)
|
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble, true)
|
||||||
@@ -51,7 +51,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
private var cacheIncreased = false
|
private var cacheIncreased = false
|
||||||
private val increaseSize = 8 * 1024 * 1024
|
private val increaseSize = 8 * 1024 * 1024
|
||||||
private val maxCacheSize = 256 * 1024 * 1024
|
private val maxCacheSize = 256 * 1024 * 1024
|
||||||
private val reviewButtonArea: ArrayList<FloatArray> = arrayListOf()
|
|
||||||
|
|
||||||
//滚动参数
|
//滚动参数
|
||||||
private val pageFactory: TextPageFactory get() = callBack.pageFactory
|
private val pageFactory: TextPageFactory get() = callBack.pageFactory
|
||||||
@@ -68,12 +67,18 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
callBack = activity as CallBack
|
callBack = activity as CallBack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置内容
|
||||||
|
*/
|
||||||
fun setContent(textPage: TextPage) {
|
fun setContent(textPage: TextPage) {
|
||||||
this.textPage = textPage
|
this.textPage = textPage
|
||||||
imagePaint.isAntiAlias = AppConfig.useAntiAlias
|
imagePaint.isAntiAlias = AppConfig.useAntiAlias
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新绘制区域
|
||||||
|
*/
|
||||||
fun upVisibleRect() {
|
fun upVisibleRect() {
|
||||||
visibleRect.set(
|
visibleRect.set(
|
||||||
ChapterProvider.paddingLeft.toFloat(),
|
ChapterProvider.paddingLeft.toFloat(),
|
||||||
@@ -102,7 +107,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
* 绘制页面
|
* 绘制页面
|
||||||
*/
|
*/
|
||||||
private fun drawPage(canvas: Canvas) {
|
private fun drawPage(canvas: Canvas) {
|
||||||
reviewButtonArea.clear()
|
|
||||||
var relativeOffset = relativeOffset(0)
|
var relativeOffset = relativeOffset(0)
|
||||||
textPage.textLines.forEach { textLine ->
|
textPage.textLines.forEach { textLine ->
|
||||||
draw(canvas, textPage, textLine, relativeOffset)
|
draw(canvas, textPage, textLine, relativeOffset)
|
||||||
@@ -125,6 +129,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制页面
|
||||||
|
*/
|
||||||
private fun draw(
|
private fun draw(
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
textPage: TextPage,
|
textPage: TextPage,
|
||||||
@@ -161,23 +168,17 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
reviewCountPaint.textSize = textPaint.textSize * 0.6F
|
reviewCountPaint.textSize = textPaint.textSize * 0.6F
|
||||||
reviewCountPaint.color = textColor
|
reviewCountPaint.color = textColor
|
||||||
textLine.textChars.forEach {
|
textLine.textChars.forEach {
|
||||||
if (it.isImage) {
|
when (it.style) {
|
||||||
drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
|
0 -> {
|
||||||
} else {
|
|
||||||
textPaint.color = textColor
|
textPaint.color = textColor
|
||||||
if (it.isSearchResult) {
|
if (it.isSearchResult) {
|
||||||
textPaint.color = context.accentColor
|
textPaint.color = context.accentColor
|
||||||
}
|
}
|
||||||
if (it.charData == "\uD83D\uDCAC" && it.isLineEnd) {
|
canvas.drawText(it.charData, it.start, lineBase, textPaint)
|
||||||
|
}
|
||||||
|
1 -> drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
|
||||||
|
2 -> {
|
||||||
if (textLine.reviewCount <= 0) return@forEach
|
if (textLine.reviewCount <= 0) return@forEach
|
||||||
reviewButtonArea.add(
|
|
||||||
floatArrayOf(
|
|
||||||
it.start - textPaint.textSize * 0.4F,
|
|
||||||
lineBase + textPaint.textSize * 0.2F,
|
|
||||||
it.start + textPaint.textSize * 2F,
|
|
||||||
lineBase - textPaint.textSize * 1F,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
canvas.drawLine(
|
canvas.drawLine(
|
||||||
it.start,
|
it.start,
|
||||||
lineBase - textPaint.textSize * 2 / 5,
|
lineBase - textPaint.textSize * 2 / 5,
|
||||||
@@ -243,7 +244,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
lineBase - textPaint.textSize / 6,
|
lineBase - textPaint.textSize / 6,
|
||||||
reviewCountPaint
|
reviewCountPaint
|
||||||
)
|
)
|
||||||
} else canvas.drawText(it.charData, it.start, lineBase, textPaint)
|
}
|
||||||
}
|
}
|
||||||
if (it.selected) {
|
if (it.selected) {
|
||||||
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
|
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
|
||||||
@@ -259,7 +260,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
textPage: TextPage,
|
textPage: TextPage,
|
||||||
textLine: TextLine,
|
textLine: TextLine,
|
||||||
textChar: TextChar,
|
textChar: TextColumn,
|
||||||
lineTop: Float,
|
lineTop: Float,
|
||||||
lineBottom: Float
|
lineBottom: Float
|
||||||
) {
|
) {
|
||||||
@@ -344,56 +345,62 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置滚动位置
|
||||||
|
*/
|
||||||
fun resetPageOffset() {
|
fun resetPageOffset() {
|
||||||
pageOffset = 0
|
pageOffset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击评论按钮
|
|
||||||
*/
|
|
||||||
fun pressReviewButton(x: Float, y: Float): Boolean {
|
|
||||||
reviewButtonArea.forEach {
|
|
||||||
if (x in it[0]..it[2] && y in it[3]..it[1]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 长按
|
* 长按
|
||||||
*/
|
*/
|
||||||
fun longPress(
|
fun longPress(
|
||||||
x: Float,
|
x: Float,
|
||||||
y: Float,
|
y: Float,
|
||||||
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
select: (textPos: TextPos) -> Unit,
|
||||||
) {
|
) {
|
||||||
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar ->
|
touch(x, y) { _, textPos, _, _, textColumn ->
|
||||||
if (textChar.isImage) {
|
if (textColumn.style == 2) return@touch
|
||||||
callBack.onImageLongPress(x, y, textChar.charData)
|
if (textColumn.style == 1) {
|
||||||
|
callBack.onImageLongPress(x, y, textColumn.charData)
|
||||||
} else {
|
} else {
|
||||||
if (!selectAble) return@touch
|
if (!selectAble) return@touch
|
||||||
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) return@touch
|
textColumn.selected = true
|
||||||
textChar.selected = true
|
|
||||||
invalidate()
|
invalidate()
|
||||||
select(relativePos, lineIndex, charIndex)
|
select(textPos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单击
|
||||||
|
* @return true:已处理, false:未处理
|
||||||
|
*/
|
||||||
|
fun click(x: Float, y: Float): Boolean {
|
||||||
|
var handled = false
|
||||||
|
touch(x, y) { _, textPos, textPage, textLine, textColumn ->
|
||||||
|
if (textColumn.style == 2) {
|
||||||
|
context.toastOnUi("Button Pressed!")
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handled
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选择文字
|
* 选择文字
|
||||||
*/
|
*/
|
||||||
fun selectText(
|
fun selectText(
|
||||||
x: Float,
|
x: Float,
|
||||||
y: Float,
|
y: Float,
|
||||||
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
select: (textPos: TextPos) -> Unit,
|
||||||
) {
|
) {
|
||||||
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar ->
|
touch(x, y) { _, textPos, _, _, textColumn ->
|
||||||
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) return@touch
|
if (textColumn.style == 2) return@touch
|
||||||
textChar.selected = true
|
textColumn.selected = true
|
||||||
invalidate()
|
invalidate()
|
||||||
select(relativePos, lineIndex, charIndex)
|
select(textPos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,11 +408,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
* 开始选择符移动
|
* 开始选择符移动
|
||||||
*/
|
*/
|
||||||
fun selectStartMove(x: Float, y: Float) {
|
fun selectStartMove(x: Float, y: Float) {
|
||||||
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar ->
|
touch(x, y) { relativeOffset, textPos, _, textLine, textChar ->
|
||||||
val pos = TextPos(relativePos, lineIndex, charIndex)
|
if (selectStart.compare(textPos) != 0) {
|
||||||
if (selectStart.compare(pos) != 0) {
|
if (textPos.compare(selectEnd) <= 0) {
|
||||||
if (pos.compare(selectEnd) <= 0) {
|
selectStart.upData(pos = textPos)
|
||||||
selectStart.upData(pos = pos)
|
|
||||||
upSelectedStart(
|
upSelectedStart(
|
||||||
textChar.start,
|
textChar.start,
|
||||||
textLine.lineBottom + relativeOffset,
|
textLine.lineBottom + relativeOffset,
|
||||||
@@ -421,11 +427,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
* 结束选择符移动
|
* 结束选择符移动
|
||||||
*/
|
*/
|
||||||
fun selectEndMove(x: Float, y: Float) {
|
fun selectEndMove(x: Float, y: Float) {
|
||||||
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar ->
|
touch(x, y) { relativeOffset, textPos, _, textLine, textChar ->
|
||||||
val pos = TextPos(relativePos, lineIndex, charIndex)
|
if (textPos.compare(selectEnd) != 0) {
|
||||||
if (pos.compare(selectEnd) != 0) {
|
if (textPos.compare(selectStart) >= 0) {
|
||||||
if (pos.compare(selectStart) >= 0) {
|
selectEnd.upData(textPos)
|
||||||
selectEnd.upData(pos)
|
|
||||||
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset)
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset)
|
||||||
upSelectChars()
|
upSelectChars()
|
||||||
}
|
}
|
||||||
@@ -433,17 +438,19 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触碰位置信息
|
||||||
|
* @param touched 回调
|
||||||
|
*/
|
||||||
private fun touch(
|
private fun touch(
|
||||||
x: Float,
|
x: Float,
|
||||||
y: Float,
|
y: Float,
|
||||||
touched: (
|
touched: (
|
||||||
relativeOffset: Float,
|
relativeOffset: Float,
|
||||||
relativePos: Int,
|
textPos: TextPos,
|
||||||
textPage: TextPage,
|
textPage: TextPage,
|
||||||
lineIndex: Int,
|
|
||||||
textLine: TextLine,
|
textLine: TextLine,
|
||||||
charIndex: Int,
|
textColumn: TextColumn
|
||||||
textChar: TextChar
|
|
||||||
) -> Unit
|
) -> Unit
|
||||||
) {
|
) {
|
||||||
if (!visibleRect.contains(x, y)) return
|
if (!visibleRect.contains(x, y)) return
|
||||||
@@ -462,9 +469,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
if (textChar.isTouch(x)) {
|
if (textChar.isTouch(x)) {
|
||||||
touched.invoke(
|
touched.invoke(
|
||||||
relativeOffset,
|
relativeOffset,
|
||||||
relativePos, textPage,
|
TextPos(relativePos, lineIndex, charIndex),
|
||||||
lineIndex, textLine,
|
textPage, textLine, textChar
|
||||||
charIndex, textChar
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -514,7 +520,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
textPos.lineIndex = lineIndex
|
textPos.lineIndex = lineIndex
|
||||||
for ((charIndex, textChar) in textLine.textChars.withIndex()) {
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) {
|
||||||
textPos.charIndex = charIndex
|
textPos.charIndex = charIndex
|
||||||
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) continue
|
if (textChar.style == 2) continue
|
||||||
textChar.selected =
|
textChar.selected =
|
||||||
textPos.compare(selectStart) >= 0 && textPos.compare(selectEnd) <= 0
|
textPos.compare(selectStart) >= 0 && textPos.compare(selectEnd) <= 0
|
||||||
textChar.isSearchResult = textChar.selected && callBack.isSelectingSearchResult
|
textChar.isSearchResult = textChar.selected && callBack.isSelectingSearchResult
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import io.legado.app.help.config.ReadTipConfig
|
|||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.ui.book.read.ReadBookActivity
|
import io.legado.app.ui.book.read.ReadBookActivity
|
||||||
import io.legado.app.ui.book.read.page.entities.TextPage
|
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||||
|
import io.legado.app.ui.book.read.page.entities.TextPos
|
||||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||||
import io.legado.app.ui.widget.BatteryView
|
import io.legado.app.ui.widget.BatteryView
|
||||||
import io.legado.app.utils.activity
|
import io.legado.app.utils.activity
|
||||||
@@ -24,7 +25,7 @@ import splitties.views.backgroundColor
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阅读界面
|
* 页面视图
|
||||||
*/
|
*/
|
||||||
class PageView(context: Context) : FrameLayout(context) {
|
class PageView(context: Context) : FrameLayout(context) {
|
||||||
|
|
||||||
@@ -104,6 +105,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
isGone = ReadBookConfig.hideStatusBar || readBookActivity?.isInMultiWindow == true
|
isGone = ReadBookConfig.hideStatusBar || readBookActivity?.isInMultiWindow == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新阅读信息
|
||||||
|
*/
|
||||||
private fun upTipStyle() = binding.run {
|
private fun upTipStyle() = binding.run {
|
||||||
tvHeaderLeft.tag = null
|
tvHeaderLeft.tag = null
|
||||||
tvHeaderMiddle.tag = null
|
tvHeaderMiddle.tag = null
|
||||||
@@ -189,6 +193,10 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取信息视图
|
||||||
|
* @param tip 信息类型
|
||||||
|
*/
|
||||||
private fun getTipView(tip: Int): BatteryView? = binding.run {
|
private fun getTipView(tip: Int): BatteryView? = binding.run {
|
||||||
return when (tip) {
|
return when (tip) {
|
||||||
ReadTipConfig.tipHeaderLeft -> tvHeaderLeft
|
ReadTipConfig.tipHeaderLeft -> tvHeaderLeft
|
||||||
@@ -201,6 +209,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新背景
|
||||||
|
*/
|
||||||
fun upBg() {
|
fun upBg() {
|
||||||
if (ReadBookConfig.bgAlpha < 100) {
|
if (ReadBookConfig.bgAlpha < 100) {
|
||||||
binding.vwRoot.backgroundColor = ReadBookConfig.bgMeanColor
|
binding.vwRoot.backgroundColor = ReadBookConfig.bgMeanColor
|
||||||
@@ -211,15 +222,24 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
upBgAlpha()
|
upBgAlpha()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新背景透明度
|
||||||
|
*/
|
||||||
fun upBgAlpha() {
|
fun upBgAlpha() {
|
||||||
binding.vwBg.alpha = ReadBookConfig.bgAlpha / 100f
|
binding.vwBg.alpha = ReadBookConfig.bgAlpha / 100f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间信息
|
||||||
|
*/
|
||||||
fun upTime() {
|
fun upTime() {
|
||||||
tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))
|
tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))
|
||||||
upTimeBattery()
|
upTimeBattery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新电池信息
|
||||||
|
*/
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun upBattery(battery: Int) {
|
fun upBattery(battery: Int) {
|
||||||
this.battery = battery
|
this.battery = battery
|
||||||
@@ -228,6 +248,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
upTimeBattery()
|
upTimeBattery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新电池信息
|
||||||
|
*/
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun upTimeBattery() {
|
private fun upTimeBattery() {
|
||||||
val time = timeFormat.format(Date(System.currentTimeMillis()))
|
val time = timeFormat.format(Date(System.currentTimeMillis()))
|
||||||
@@ -235,6 +258,9 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
tvTimeBatteryP?.text = "$time $battery%"
|
tvTimeBatteryP?.text = "$time $battery%"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置内容
|
||||||
|
*/
|
||||||
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
|
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
|
||||||
setProgress(textPage)
|
setProgress(textPage)
|
||||||
if (resetPageOffset) {
|
if (resetPageOffset) {
|
||||||
@@ -243,14 +269,23 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
binding.contentTextView.setContent(textPage)
|
binding.contentTextView.setContent(textPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置无障碍文本
|
||||||
|
*/
|
||||||
fun setContentDescription(content: String) {
|
fun setContentDescription(content: String) {
|
||||||
binding.contentTextView.contentDescription = content
|
binding.contentTextView.contentDescription = content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置滚动位置
|
||||||
|
*/
|
||||||
fun resetPageOffset() {
|
fun resetPageOffset() {
|
||||||
binding.contentTextView.resetPageOffset()
|
binding.contentTextView.resetPageOffset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置进度
|
||||||
|
*/
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun setProgress(textPage: TextPage) = textPage.apply {
|
fun setProgress(textPage: TextPage) = textPage.apply {
|
||||||
tvBookName?.text = ReadBook.book?.name
|
tvBookName?.text = ReadBook.book?.name
|
||||||
@@ -260,28 +295,44 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
|
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动事件
|
||||||
|
*/
|
||||||
fun scroll(offset: Int) {
|
fun scroll(offset: Int) {
|
||||||
binding.contentTextView.scroll(offset)
|
binding.contentTextView.scroll(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新是否开启选择功能
|
||||||
|
*/
|
||||||
fun upSelectAble(selectAble: Boolean) {
|
fun upSelectAble(selectAble: Boolean) {
|
||||||
binding.contentTextView.selectAble = selectAble
|
binding.contentTextView.selectAble = selectAble
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pressReviewButton(x: Float, y: Float): Boolean {
|
/**
|
||||||
return binding.contentTextView.pressReviewButton(x, y - headerHeight)
|
* 优先处理页面内单击
|
||||||
|
* @return true:已处理, false:未处理
|
||||||
|
*/
|
||||||
|
fun onClick(x: Float, y: Float): Boolean {
|
||||||
|
return binding.contentTextView.click(x, y - headerHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 长按事件
|
||||||
|
*/
|
||||||
fun longPress(
|
fun longPress(
|
||||||
x: Float, y: Float,
|
x: Float, y: Float,
|
||||||
select: (relativePagePos: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
select: (textPos: TextPos) -> Unit,
|
||||||
) {
|
) {
|
||||||
return binding.contentTextView.longPress(x, y - headerHeight, select)
|
return binding.contentTextView.longPress(x, y - headerHeight, select)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择文本
|
||||||
|
*/
|
||||||
fun selectText(
|
fun selectText(
|
||||||
x: Float, y: Float,
|
x: Float, y: Float,
|
||||||
select: (relativePagePos: Int, lineIndex: Int, charIndex: Int) -> Unit,
|
select: (textPos: TextPos) -> Unit,
|
||||||
) {
|
) {
|
||||||
return binding.contentTextView.selectText(x, y - headerHeight, select)
|
return binding.contentTextView.selectText(x, y - headerHeight, select)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ import java.text.BreakIterator
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阅读视图
|
||||||
|
*/
|
||||||
class ReadView(context: Context, attrs: AttributeSet) :
|
class ReadView(context: Context, attrs: AttributeSet) :
|
||||||
FrameLayout(context, attrs),
|
FrameLayout(context, attrs),
|
||||||
DataSource {
|
DataSource {
|
||||||
@@ -223,7 +225,9 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
pressDown = false
|
pressDown = false
|
||||||
if (!isPageMove) {
|
if (!isPageMove) {
|
||||||
if (!longPressed && !pressOnTextSelected) {
|
if (!longPressed && !pressOnTextSelected) {
|
||||||
|
if (!curPage.onClick(startX, startY)) {
|
||||||
onSingleTapUp()
|
onSingleTapUp()
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,18 +297,18 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
*/
|
*/
|
||||||
private fun onLongPress() {
|
private fun onLongPress() {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
curPage.longPress(startX, startY) { relativePos, lineIndex, charIndex ->
|
curPage.longPress(startX, startY) { textPos: TextPos ->
|
||||||
isTextSelected = true
|
isTextSelected = true
|
||||||
pressOnTextSelected = true
|
pressOnTextSelected = true
|
||||||
initialTextPos.upData(relativePos, lineIndex, charIndex)
|
initialTextPos.upData(textPos)
|
||||||
val startPos = TextPos(relativePos, lineIndex, charIndex)
|
val startPos = textPos.copy()
|
||||||
val endPos = TextPos(relativePos, lineIndex, charIndex)
|
val endPos = textPos.copy()
|
||||||
val page = curPage.relativePage(relativePos)
|
val page = curPage.relativePage(textPos.relativePagePos)
|
||||||
val stringBuilder = StringBuilder()
|
val stringBuilder = StringBuilder()
|
||||||
var cIndex = charIndex
|
var cIndex = textPos.charIndex
|
||||||
var lineStart = lineIndex
|
var lineStart = textPos.lineIndex
|
||||||
var lineEnd = lineIndex
|
var lineEnd = textPos.lineIndex
|
||||||
for (index in lineIndex - 1 downTo 0) {
|
for (index in textPos.lineIndex - 1 downTo 0) {
|
||||||
val textLine = page.getLine(index)
|
val textLine = page.getLine(index)
|
||||||
if (textLine.isParagraphEnd) {
|
if (textLine.isParagraphEnd) {
|
||||||
break
|
break
|
||||||
@@ -314,7 +318,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
cIndex += textLine.charSize
|
cIndex += textLine.charSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (index in lineIndex until page.lineSize) {
|
for (index in textPos.lineIndex until page.lineSize) {
|
||||||
val textLine = page.getLine(index)
|
val textLine = page.getLine(index)
|
||||||
stringBuilder.append(textLine.text)
|
stringBuilder.append(textLine.text)
|
||||||
lineEnd += 1
|
lineEnd += 1
|
||||||
@@ -370,9 +374,6 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
*/
|
*/
|
||||||
private fun onSingleTapUp() {
|
private fun onSingleTapUp() {
|
||||||
when {
|
when {
|
||||||
curPage.pressReviewButton(startX, startY) -> {
|
|
||||||
context.toastOnUi("Button Pressed!")
|
|
||||||
}
|
|
||||||
isTextSelected -> Unit
|
isTextSelected -> Unit
|
||||||
mcRect.contains(startX, startY) -> if (!isAbortAnim) {
|
mcRect.contains(startX, startY) -> if (!isAbortAnim) {
|
||||||
click(AppConfig.clickActionMC)
|
click(AppConfig.clickActionMC)
|
||||||
@@ -423,11 +424,15 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
* 选择文本
|
* 选择文本
|
||||||
*/
|
*/
|
||||||
private fun selectText(x: Float, y: Float) {
|
private fun selectText(x: Float, y: Float) {
|
||||||
curPage.selectText(x, y) { relativePagePos, lineIndex, charIndex ->
|
curPage.selectText(x, y) { textPos ->
|
||||||
val compare = initialTextPos.compare(relativePagePos, lineIndex, charIndex)
|
val compare = initialTextPos.compare(textPos)
|
||||||
when {
|
when {
|
||||||
compare >= 0 -> {
|
compare >= 0 -> {
|
||||||
curPage.selectStartMoveIndex(relativePagePos, lineIndex, charIndex)
|
curPage.selectStartMoveIndex(
|
||||||
|
textPos.relativePagePos,
|
||||||
|
textPos.lineIndex,
|
||||||
|
textPos.charIndex
|
||||||
|
)
|
||||||
curPage.selectEndMoveIndex(
|
curPage.selectEndMoveIndex(
|
||||||
initialTextPos.relativePagePos,
|
initialTextPos.relativePagePos,
|
||||||
initialTextPos.lineIndex,
|
initialTextPos.lineIndex,
|
||||||
@@ -440,7 +445,11 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
initialTextPos.lineIndex,
|
initialTextPos.lineIndex,
|
||||||
initialTextPos.charIndex
|
initialTextPos.charIndex
|
||||||
)
|
)
|
||||||
curPage.selectEndMoveIndex(relativePagePos, lineIndex, charIndex)
|
curPage.selectEndMoveIndex(
|
||||||
|
textPos.relativePagePos,
|
||||||
|
textPos.lineIndex,
|
||||||
|
textPos.charIndex
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -119,7 +119,11 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie
|
|||||||
abortAnim()
|
abortAnim()
|
||||||
if (!hasNext()) return
|
if (!hasNext()) return
|
||||||
setDirection(PageDirection.NEXT)
|
setDirection(PageDirection.NEXT)
|
||||||
readView.setStartPoint(viewWidth.toFloat(), 1f, false)
|
val y = when {
|
||||||
|
viewHeight / 2 < startY -> viewHeight.toFloat() * 0.9f
|
||||||
|
else -> 1f
|
||||||
|
}
|
||||||
|
readView.setStartPoint(viewWidth.toFloat() * 0.9f, y, false)
|
||||||
onAnimStart(animationSpeed)
|
onAnimStart(animationSpeed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package io.legado.app.ui.book.read.page.entities
|
|||||||
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 章节信息
|
||||||
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
data class TextChapter(
|
data class TextChapter(
|
||||||
val position: Int,
|
val position: Int,
|
||||||
|
|||||||
+6
-4
@@ -1,13 +1,15 @@
|
|||||||
package io.legado.app.ui.book.read.page.entities
|
package io.legado.app.ui.book.read.page.entities
|
||||||
|
|
||||||
data class TextChar(
|
/**
|
||||||
|
* 字符信息
|
||||||
|
*/
|
||||||
|
data class TextColumn(
|
||||||
val charData: String,
|
val charData: String,
|
||||||
var start: Float,
|
var start: Float,
|
||||||
var end: Float,
|
var end: Float,
|
||||||
|
val style: Int = 0, //0:文字,1:图片,2:按钮
|
||||||
var selected: Boolean = false,
|
var selected: Boolean = false,
|
||||||
var isImage: Boolean = false,
|
var isSearchResult: Boolean = false
|
||||||
var isSearchResult: Boolean = false,
|
|
||||||
var isLineEnd: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun isTouch(x: Float): Boolean {
|
fun isTouch(x: Float): Boolean {
|
||||||
@@ -4,10 +4,13 @@ import android.text.TextPaint
|
|||||||
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
import io.legado.app.ui.book.read.page.provider.ChapterProvider
|
||||||
import io.legado.app.utils.textHeight
|
import io.legado.app.utils.textHeight
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行信息
|
||||||
|
*/
|
||||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||||
data class TextLine(
|
data class TextLine(
|
||||||
var text: String = "",
|
var text: String = "",
|
||||||
val textChars: ArrayList<TextChar> = arrayListOf(),
|
val textChars: ArrayList<TextColumn> = arrayListOf(),
|
||||||
val reviewCount: Int = 1,
|
val reviewCount: Int = 1,
|
||||||
var lineTop: Float = 0f,
|
var lineTop: Float = 0f,
|
||||||
var lineBase: Float = 0f,
|
var lineBase: Float = 0f,
|
||||||
@@ -28,13 +31,13 @@ data class TextLine(
|
|||||||
lineBase = lineBottom - textPaint.fontMetrics.descent
|
lineBase = lineBottom - textPaint.fontMetrics.descent
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTextChar(index: Int): TextChar {
|
fun getTextChar(index: Int): TextColumn {
|
||||||
return textChars.getOrElse(index) {
|
return textChars.getOrElse(index) {
|
||||||
textChars.last()
|
textChars.last()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTextCharReverseAt(index: Int): TextChar {
|
fun getTextCharReverseAt(index: Int): TextColumn {
|
||||||
return textChars[textChars.lastIndex - index]
|
return textChars[textChars.lastIndex - index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ import splitties.init.appCtx
|
|||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面信息
|
||||||
|
*/
|
||||||
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
@Suppress("unused", "MemberVisibilityCanBePrivate")
|
||||||
data class TextPage(
|
data class TextPage(
|
||||||
var index: Int = 0,
|
var index: Int = 0,
|
||||||
@@ -111,11 +114,11 @@ data class TextPage(
|
|||||||
val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint)
|
val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint)
|
||||||
val x1 = x + cw
|
val x1 = x + cw
|
||||||
textLine.textChars.add(
|
textLine.textChars.add(
|
||||||
TextChar(
|
TextColumn(
|
||||||
char,
|
char,
|
||||||
start = x,
|
start = x,
|
||||||
end = x1,
|
end = x1,
|
||||||
isLineEnd = textLine.text.length - 1 == i
|
style = if (textLine.text.length - 1 == index && char == "\uD83D\uDCAC") 2 else 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
x = x1
|
x = x1
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package io.legado.app.ui.book.read.page.entities
|
package io.legado.app.ui.book.read.page.entities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 位置信息
|
||||||
|
*/
|
||||||
data class TextPos(
|
data class TextPos(
|
||||||
var relativePagePos: Int,
|
var relativePagePos: Int,
|
||||||
var lineIndex: Int,
|
var lineIndex: Int,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import io.legado.app.help.config.AppConfig
|
|||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.model.ReadBook
|
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.TextChapter
|
||||||
import io.legado.app.ui.book.read.page.entities.TextChar
|
import io.legado.app.ui.book.read.page.entities.TextColumn
|
||||||
import io.legado.app.ui.book.read.page.entities.TextLine
|
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.TextPage
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.*
|
||||||
@@ -264,7 +264,7 @@ object ChapterProvider {
|
|||||||
Pair(0f, width.toFloat())
|
Pair(0f, width.toFloat())
|
||||||
}
|
}
|
||||||
textLine.textChars.add(
|
textLine.textChars.add(
|
||||||
TextChar(charData = src, start = x + start, end = x + end, isImage = true)
|
TextColumn(charData = src, start = x + start, end = x + end, style = 1)
|
||||||
)
|
)
|
||||||
textPages.last().textLines.add(textLine)
|
textPages.last().textLines.add(textLine)
|
||||||
}
|
}
|
||||||
@@ -409,14 +409,13 @@ object ChapterProvider {
|
|||||||
}
|
}
|
||||||
val bodyIndent = ReadBookConfig.paragraphIndent
|
val bodyIndent = ReadBookConfig.paragraphIndent
|
||||||
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
|
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
|
||||||
for ((index, char) in bodyIndent.toStringArray().withIndex()) {
|
for (char in bodyIndent.toStringArray()) {
|
||||||
val x1 = x + icw
|
val x1 = x + icw
|
||||||
textLine.textChars.add(
|
textLine.textChars.add(
|
||||||
TextChar(
|
TextColumn(
|
||||||
charData = char,
|
charData = char,
|
||||||
start = absStartX + x,
|
start = absStartX + x,
|
||||||
end = absStartX + x1,
|
end = absStartX + x1
|
||||||
isLineEnd = bodyIndent.length - 1 == index
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
x = x1
|
x = x1
|
||||||
@@ -452,7 +451,7 @@ object ChapterProvider {
|
|||||||
words.forEachIndexed { index, char ->
|
words.forEachIndexed { index, char ->
|
||||||
val cw = StaticLayout.getDesiredWidth(char, textPaint)
|
val cw = StaticLayout.getDesiredWidth(char, textPaint)
|
||||||
val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)
|
val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)
|
||||||
addCharToLine(book, absStartX, textLine, char, x, x1, srcList)
|
addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)
|
||||||
x = x1
|
x = x1
|
||||||
}
|
}
|
||||||
exceed(absStartX, textLine, words)
|
exceed(absStartX, textLine, words)
|
||||||
@@ -471,10 +470,10 @@ object ChapterProvider {
|
|||||||
srcList: LinkedList<String>?
|
srcList: LinkedList<String>?
|
||||||
) {
|
) {
|
||||||
var x = startX
|
var x = startX
|
||||||
words.forEach { char ->
|
words.forEachIndexed { index, char ->
|
||||||
val cw = StaticLayout.getDesiredWidth(char, textPaint)
|
val cw = StaticLayout.getDesiredWidth(char, textPaint)
|
||||||
val x1 = x + cw
|
val x1 = x + cw
|
||||||
addCharToLine(book, absStartX, textLine, char, x, x1, srcList)
|
addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)
|
||||||
x = x1
|
x = x1
|
||||||
}
|
}
|
||||||
exceed(absStartX, textLine, words)
|
exceed(absStartX, textLine, words)
|
||||||
@@ -490,26 +489,27 @@ object ChapterProvider {
|
|||||||
char: String,
|
char: String,
|
||||||
xStart: Float,
|
xStart: Float,
|
||||||
xEnd: Float,
|
xEnd: Float,
|
||||||
|
isLineEnd: Boolean,
|
||||||
srcList: LinkedList<String>?
|
srcList: LinkedList<String>?
|
||||||
) {
|
) {
|
||||||
if (srcList != null && char == srcReplaceChar) {
|
if (srcList != null && char == srcReplaceChar) {
|
||||||
val src = srcList.removeFirst()
|
val src = srcList.removeFirst()
|
||||||
ImageProvider.cacheImage(book, src, ReadBook.bookSource)
|
ImageProvider.cacheImage(book, src, ReadBook.bookSource)
|
||||||
textLine.textChars.add(
|
textLine.textChars.add(
|
||||||
TextChar(
|
TextColumn(
|
||||||
charData = src,
|
charData = src,
|
||||||
start = absStartX + xStart,
|
start = absStartX + xStart,
|
||||||
end = absStartX + xEnd,
|
end = absStartX + xEnd,
|
||||||
isImage = true
|
style = 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
textLine.textChars.add(
|
textLine.textChars.add(
|
||||||
TextChar(
|
TextColumn(
|
||||||
charData = char,
|
charData = char,
|
||||||
start = absStartX + xStart,
|
start = absStartX + xStart,
|
||||||
end = absStartX + xEnd,
|
end = absStartX + xEnd,
|
||||||
isLineEnd = true
|
style = if (isLineEnd && char == "\uD83D\uDCAC") 2 else 0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
|
|||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
success.invoke(it)
|
success.invoke(it)
|
||||||
}.onError {
|
}.onError {
|
||||||
context.toastOnUi(it.msg)
|
context.toastOnUi(it.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import io.legado.app.data.entities.RssSource
|
|||||||
import io.legado.app.help.RuleComplete
|
import io.legado.app.help.RuleComplete
|
||||||
import io.legado.app.help.http.CookieStore
|
import io.legado.app.help.http.CookieStore
|
||||||
import io.legado.app.utils.getClipText
|
import io.legado.app.utils.getClipText
|
||||||
import io.legado.app.utils.msg
|
|
||||||
import io.legado.app.utils.printOnDebug
|
import io.legado.app.utils.printOnDebug
|
||||||
|
import io.legado.app.utils.stackTraceStr
|
||||||
|
|
||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -74,7 +74,7 @@ class RssSourceEditViewModel(application: Application) : BaseViewModel(applicati
|
|||||||
finally.invoke(it)
|
finally.invoke(it)
|
||||||
}
|
}
|
||||||
}.onError {
|
}.onError {
|
||||||
context.toastOnUi(it.msg)
|
context.toastOnUi(it.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
|
|||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
success.invoke(it)
|
success.invoke(it)
|
||||||
}.onError {
|
}.onError {
|
||||||
context.toastOnUi(it.msg)
|
context.toastOnUi(it.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ fun Context.openFileUri(uri: Uri, type: String? = null) {
|
|||||||
try {
|
try {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
toastOnUi(e.msg)
|
toastOnUi(e.stackTraceStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package io.legado.app.utils
|
|||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
val Throwable.msg: String
|
val Throwable.stackTraceStr: String
|
||||||
get() {
|
get() {
|
||||||
val stackTrace = stackTraceToString()
|
val stackTrace = stackTraceToString()
|
||||||
val lMsg = this.localizedMessage ?: "noErrorMsg"
|
val lMsg = this.localizedMessage ?: "noErrorMsg"
|
||||||
|
|||||||
+1
-1
@@ -26,4 +26,4 @@ android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
|||||||
# and none from the library's dependencies, thereby reducing the size of the R class for that library.
|
# and none from the library's dependencies, thereby reducing the size of the R class for that library.
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
#https://chromiumdash.appspot.com/releases?platform=Android
|
#https://chromiumdash.appspot.com/releases?platform=Android
|
||||||
CronetVersion=105.0.5195.77
|
CronetVersion=105.0.5195.79
|
||||||
|
|||||||
Reference in New Issue
Block a user