Merge remote-tracking branch 'upstream/master'

pull/2250/head
Seidko 2 years ago
commit e2af0a43ba
  1. BIN
      app/cronetlib/cronet_api.jar
  2. BIN
      app/cronetlib/cronet_impl_common_java.jar
  3. 2
      app/src/main/assets/cronet.json
  4. 5
      app/src/main/assets/updateLog.md
  5. 2
      app/src/main/java/io/legado/app/api/controller/BookController.kt
  6. 4
      app/src/main/java/io/legado/app/help/ContentProcessor.kt
  7. 4
      app/src/main/java/io/legado/app/help/CrashHandler.kt
  8. 81
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  9. 16
      app/src/main/java/io/legado/app/model/Debug.kt
  10. 4
      app/src/main/java/io/legado/app/model/ReadBook.kt
  11. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  12. 4
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
  13. 5
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  14. 2
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  15. 132
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  16. 61
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  17. 47
      app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
  18. 6
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  19. 3
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt
  20. 10
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextColumn.kt
  21. 9
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
  22. 7
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
  23. 3
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPos.kt
  24. 26
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  25. 2
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  26. 4
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt
  27. 2
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  28. 2
      app/src/main/java/io/legado/app/utils/ContextExtensions.kt
  29. 2
      app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt
  30. 2
      gradle.properties

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
* 修复仿真翻页点击时翻页动画异常,化仿真翻页点击翻页效果 by 821938089
**2022/08/31**

@ -188,7 +188,7 @@ object BookController {
}
returnData.setData(content)
} catch (e: Exception) {
returnData.setErrorMsg(e.msg)
returnData.setErrorMsg(e.stackTraceStr)
}
return returnData
}

@ -9,8 +9,8 @@ import io.legado.app.data.entities.ReplaceRule
import io.legado.app.exception.RegexTimeoutException
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.utils.msg
import io.legado.app.utils.replace
import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CancellationException
import splitties.init.appCtx
@ -149,7 +149,7 @@ class ContentProcessor private constructor(
} catch (e: RegexTimeoutException) {
item.isEnabled = false
appDb.replaceRuleDao.update(item)
return item.name + e.msg
return item.name + e.stackTraceStr
} catch (e: CancellationException) {
return mContent
} catch (e: Exception) {

@ -8,7 +8,7 @@ import io.legado.app.model.ReadAloud
import io.legado.app.utils.FileUtils
import io.legado.app.utils.getFile
import io.legado.app.utils.longToastOnUi
import io.legado.app.utils.msg
import io.legado.app.utils.stackTraceStr
import splitties.init.appCtx
import java.io.PrintWriter
import java.io.StringWriter
@ -48,7 +48,7 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
if (ex == null) return
//保存日志文件
saveCrashInfo2File(ex)
context.longToastOnUi(ex.msg)
context.longToastOnUi(ex.stackTraceStr)
Thread.sleep(3000)
}

@ -27,6 +27,7 @@ import splitties.init.appCtx
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.net.URLEncoder
import java.nio.charset.Charset
import java.text.SimpleDateFormat
@ -56,9 +57,8 @@ interface JsExtensions {
analyzeUrl.getStrResponseAwait().body
}.onFailure {
AppLog.put("ajax(${urlStr}) error\n${it.localizedMessage}", it)
it.printOnDebug()
}.getOrElse {
it.msg
it.stackTraceStr
}
}
}
@ -91,10 +91,9 @@ interface JsExtensions {
kotlin.runCatching {
analyzeUrl.getStrResponseAwait()
}.onFailure {
log("connect(${urlStr}) error\n${it.stackTraceToString()}")
it.printOnDebug()
AppLog.put("connect(${urlStr}) error\n${it.localizedMessage}", it)
}.getOrElse {
StrResponse(analyzeUrl.url, it.localizedMessage)
StrResponse(analyzeUrl.url, it.stackTraceStr)
}
}
}
@ -106,10 +105,9 @@ interface JsExtensions {
kotlin.runCatching {
analyzeUrl.getStrResponseAwait()
}.onFailure {
log("ajax($urlStr,$header) error\n${it.stackTraceToString()}")
it.printOnDebug()
AppLog.put("ajax($urlStr,$header) error\n${it.localizedMessage}", it)
}.getOrElse {
StrResponse(analyzeUrl.url, it.localizedMessage)
StrResponse(analyzeUrl.url, it.stackTraceStr)
}
}
}
@ -128,7 +126,7 @@ interface JsExtensions {
html = html,
javaScript = js,
headerMap = getSource()?.getHeaderMap(true),
tag = getSource()?.getKey()
tag = getSource()?.getKey()
).getStrResponse().body
}
}
@ -146,15 +144,18 @@ interface JsExtensions {
* 使用内置浏览器打开链接并等待网页结果
*/
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)
)
}
/**
* 打开图片验证码对话框等待返回验证结果
*/
fun getVerificationCode(imageUrl: String): String {
return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false)
}
fun getVerificationCode(imageUrl: String): String {
return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false)
}
/**
* 可从网络本地文件(阅读私有缓存目录和书籍保存位置支持相对路径)导入JavaScript脚本
@ -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进制字符串转文件
* @param content 需要转成文件的16进制字符串
@ -214,18 +238,18 @@ interface JsExtensions {
*/
fun downloadFile(content: String, url: String): String {
val type = AnalyzeUrl(url, source = getSource()).type ?: return ""
val zipPath = FileUtils.getPath(
val path = FileUtils.getPath(
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
"${MD5Utils.md5Encode16(url)}.${type}"
)
FileUtils.delete(zipPath)
val zipFile = FileUtils.createFileIfNotExist(zipPath)
FileUtils.delete(path)
val file = FileUtils.createFileIfNotExist(path)
StringUtils.hexStringToByte(content).let {
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 {
if (unzipPath.isEmpty()) return ""
val unzipFolder = getFile(unzipPath)
fun getTxtInFolder(path: String): String {
if (path.isEmpty()) return ""
val folder = getFile(path)
val contents = StringBuilder()
unzipFolder.listFiles().let {
folder.listFiles().let {
if (it != null) {
for (f in it) {
val charsetName = EncodingDetect.getEncode(f)
@ -452,7 +478,7 @@ interface JsExtensions {
contents.deleteCharAt(contents.length - 1)
}
}
FileUtils.delete(unzipFolder.absolutePath)
FileUtils.delete(folder.absolutePath)
return contents.toString()
}
@ -836,7 +862,7 @@ interface JsExtensions {
}
fun desBase64DecodeToString(
data: String, key: String, transformation: String, iv: String
data: String, key: String, transformation: String, iv: String
): String? {
return EncoderUtils.decryptBase64DES(
data.encodeToByteArray(),
@ -1031,7 +1057,10 @@ interface JsExtensions {
algorithm: String,
key: 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 {

@ -9,7 +9,7 @@ import io.legado.app.model.rss.Rss
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.msg
import io.legado.app.utils.stackTraceStr
import kotlinx.coroutines.CoroutineScope
import java.text.SimpleDateFormat
import java.util.*
@ -128,7 +128,7 @@ object Debug {
}
}
.onError {
log(debugSource, it.msg, state = -1)
log(debugSource, it.stackTraceStr, state = -1)
}
}
@ -145,7 +145,7 @@ object Debug {
log(debugSource, "︽内容页解析完成", state = 1000)
}
.onError {
log(debugSource, it.msg, state = -1)
log(debugSource, it.stackTraceStr, state = -1)
}
}
@ -204,7 +204,7 @@ object Debug {
}
}
.onError {
log(debugSource, it.msg, state = -1)
log(debugSource, it.stackTraceStr, state = -1)
}
tasks.add(explore)
}
@ -222,7 +222,7 @@ object Debug {
}
}
.onError {
log(debugSource, it.msg, state = -1)
log(debugSource, it.stackTraceStr, state = -1)
}
tasks.add(search)
}
@ -246,7 +246,7 @@ object Debug {
}
}
.onError {
log(debugSource, it.msg, state = -1)
log(debugSource, it.stackTraceStr, state = -1)
}
tasks.add(info)
}
@ -261,7 +261,7 @@ object Debug {
contentDebug(scope, bookSource, book, it.first(), nextChapterUrl)
}
.onError {
log(debugSource, it.msg, state = -1)
log(debugSource, it.stackTraceStr, state = -1)
}
tasks.add(chapterList)
}
@ -284,7 +284,7 @@ object Debug {
).onSuccess {
log(debugSource, "︽正文页解析完成", state = 1000)
}.onError {
log(debugSource, it.msg, state = -1)
log(debugSource, it.stackTraceStr, state = -1)
}
tasks.add(content)
}

@ -14,7 +14,7 @@ import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.book.read.page.entities.TextChapter
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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
@ -387,7 +387,7 @@ object ReadBook : CoroutineScope by MainScope() {
}
}.onError {
AppLog.put("ChapterProvider ERROR", it)
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.msg}")
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}")
}.onSuccess {
success?.invoke()
}

@ -667,7 +667,7 @@ class AnalyzeRule(
log("ajax(${urlStr}) error\n${it.stackTraceToString()}")
it.printOnDebug()
}.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.SearchBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.msg
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.stackTraceStr
import kotlinx.coroutines.Dispatchers.IO
@ -46,7 +46,7 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application
page++
}.onError {
it.printOnDebug()
errorLiveData.postValue(it.msg)
errorLiveData.postValue(it.stackTraceStr)
}
}
}

@ -254,11 +254,12 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
return@execute
}
}
throw NoStackTraceException("自动换源失败")
throw NoStackTraceException("没有搜索到 ${name}(${author})")
}.onStart {
ReadBook.upMsg(context.getString(R.string.source_auto_changing))
}.onError {
context.toastOnUi(it.msg)
AppLog.put("自动换源失败\n${it.localizedMessage}", it)
context.toastOnUi("自动换源失败\n${it.localizedMessage}")
}.onFinally {
ReadBook.upMsg(null)
}

@ -322,7 +322,7 @@ class BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {
importConfig(it)
}
}.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.lib.theme.accentColor
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.TextPage
import io.legado.app.ui.book.read.page.entities.TextPos
@ -30,7 +30,7 @@ import io.legado.app.utils.toastOnUi
import kotlin.math.min
/**
* 阅读内容界面
* 阅读内容视图
*/
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
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 val increaseSize = 8 * 1024 * 1024
private val maxCacheSize = 256 * 1024 * 1024
private val reviewButtonArea: ArrayList<FloatArray> = arrayListOf()
//滚动参数
private val pageFactory: TextPageFactory get() = callBack.pageFactory
@ -68,12 +67,18 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
callBack = activity as CallBack
}
/**
* 设置内容
*/
fun setContent(textPage: TextPage) {
this.textPage = textPage
imagePaint.isAntiAlias = AppConfig.useAntiAlias
invalidate()
}
/**
* 更新绘制区域
*/
fun upVisibleRect() {
visibleRect.set(
ChapterProvider.paddingLeft.toFloat(),
@ -102,7 +107,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
* 绘制页面
*/
private fun drawPage(canvas: Canvas) {
reviewButtonArea.clear()
var relativeOffset = relativeOffset(0)
textPage.textLines.forEach { textLine ->
draw(canvas, textPage, textLine, relativeOffset)
@ -125,6 +129,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
}
}
/**
* 绘制页面
*/
private fun draw(
canvas: Canvas,
textPage: TextPage,
@ -161,23 +168,17 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
reviewCountPaint.textSize = textPaint.textSize * 0.6F
reviewCountPaint.color = textColor
textLine.textChars.forEach {
if (it.isImage) {
drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
} else {
textPaint.color = textColor
if (it.isSearchResult) {
textPaint.color = context.accentColor
when (it.style) {
0 -> {
textPaint.color = textColor
if (it.isSearchResult) {
textPaint.color = context.accentColor
}
canvas.drawText(it.charData, it.start, lineBase, textPaint)
}
if (it.charData == "\uD83D\uDCAC" && it.isLineEnd) {
1 -> drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
2 -> {
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(
it.start,
lineBase - textPaint.textSize * 2 / 5,
@ -243,7 +244,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
lineBase - textPaint.textSize / 6,
reviewCountPaint
)
} else canvas.drawText(it.charData, it.start, lineBase, textPaint)
}
}
if (it.selected) {
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
@ -259,7 +260,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
canvas: Canvas,
textPage: TextPage,
textLine: TextLine,
textChar: TextChar,
textChar: TextColumn,
lineTop: Float,
lineBottom: Float
) {
@ -344,20 +345,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
invalidate()
}
fun resetPageOffset() {
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 resetPageOffset() {
pageOffset = 0
}
/**
@ -366,34 +358,49 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
fun longPress(
x: Float,
y: Float,
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
select: (textPos: TextPos) -> Unit,
) {
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar ->
if (textChar.isImage) {
callBack.onImageLongPress(x, y, textChar.charData)
touch(x, y) { _, textPos, _, _, textColumn ->
if (textColumn.style == 2) return@touch
if (textColumn.style == 1) {
callBack.onImageLongPress(x, y, textColumn.charData)
} else {
if (!selectAble) return@touch
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) return@touch
textChar.selected = true
textColumn.selected = true
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(
x: Float,
y: Float,
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit,
select: (textPos: TextPos) -> Unit,
) {
touch(x, y) { _, relativePos, _, lineIndex, _, charIndex, textChar ->
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) return@touch
textChar.selected = true
touch(x, y) { _, textPos, _, _, textColumn ->
if (textColumn.style == 2) return@touch
textColumn.selected = true
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) {
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar ->
val pos = TextPos(relativePos, lineIndex, charIndex)
if (selectStart.compare(pos) != 0) {
if (pos.compare(selectEnd) <= 0) {
selectStart.upData(pos = pos)
touch(x, y) { relativeOffset, textPos, _, textLine, textChar ->
if (selectStart.compare(textPos) != 0) {
if (textPos.compare(selectEnd) <= 0) {
selectStart.upData(pos = textPos)
upSelectedStart(
textChar.start,
textLine.lineBottom + relativeOffset,
@ -421,11 +427,10 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
* 结束选择符移动
*/
fun selectEndMove(x: Float, y: Float) {
touch(x, y) { relativeOffset, relativePos, _, lineIndex, textLine, charIndex, textChar ->
val pos = TextPos(relativePos, lineIndex, charIndex)
if (pos.compare(selectEnd) != 0) {
if (pos.compare(selectStart) >= 0) {
selectEnd.upData(pos)
touch(x, y) { relativeOffset, textPos, _, textLine, textChar ->
if (textPos.compare(selectEnd) != 0) {
if (textPos.compare(selectStart) >= 0) {
selectEnd.upData(textPos)
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset)
upSelectChars()
}
@ -433,17 +438,19 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
}
}
/**
* 触碰位置信息
* @param touched 回调
*/
private fun touch(
x: Float,
y: Float,
touched: (
relativeOffset: Float,
relativePos: Int,
textPos: TextPos,
textPage: TextPage,
lineIndex: Int,
textLine: TextLine,
charIndex: Int,
textChar: TextChar
textColumn: TextColumn
) -> Unit
) {
if (!visibleRect.contains(x, y)) return
@ -462,9 +469,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
if (textChar.isTouch(x)) {
touched.invoke(
relativeOffset,
relativePos, textPage,
lineIndex, textLine,
charIndex, textChar
TextPos(relativePos, lineIndex, charIndex),
textPage, textLine, textChar
)
return
}
@ -514,7 +520,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textPos.lineIndex = lineIndex
for ((charIndex, textChar) in textLine.textChars.withIndex()) {
textPos.charIndex = charIndex
if (textChar.charData == "\uD83D\uDCAC" && textChar.isLineEnd) continue
if (textChar.style == 2) continue
textChar.selected =
textPos.compare(selectStart) >= 0 && textPos.compare(selectEnd) <= 0
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.ui.book.read.ReadBookActivity
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.widget.BatteryView
import io.legado.app.utils.activity
@ -24,7 +25,7 @@ import splitties.views.backgroundColor
import java.util.*
/**
* 阅读界面
* 页面视图
*/
class PageView(context: Context) : FrameLayout(context) {
@ -104,6 +105,9 @@ class PageView(context: Context) : FrameLayout(context) {
isGone = ReadBookConfig.hideStatusBar || readBookActivity?.isInMultiWindow == true
}
/**
* 更新阅读信息
*/
private fun upTipStyle() = binding.run {
tvHeaderLeft.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 {
return when (tip) {
ReadTipConfig.tipHeaderLeft -> tvHeaderLeft
@ -201,6 +209,9 @@ class PageView(context: Context) : FrameLayout(context) {
}
}
/**
* 更新背景
*/
fun upBg() {
if (ReadBookConfig.bgAlpha < 100) {
binding.vwRoot.backgroundColor = ReadBookConfig.bgMeanColor
@ -211,15 +222,24 @@ class PageView(context: Context) : FrameLayout(context) {
upBgAlpha()
}
/**
* 更新背景透明度
*/
fun upBgAlpha() {
binding.vwBg.alpha = ReadBookConfig.bgAlpha / 100f
}
/**
* 更新时间信息
*/
fun upTime() {
tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))
upTimeBattery()
}
/**
* 更新电池信息
*/
@SuppressLint("SetTextI18n")
fun upBattery(battery: Int) {
this.battery = battery
@ -228,6 +248,9 @@ class PageView(context: Context) : FrameLayout(context) {
upTimeBattery()
}
/**
* 更新电池信息
*/
@SuppressLint("SetTextI18n")
private fun upTimeBattery() {
val time = timeFormat.format(Date(System.currentTimeMillis()))
@ -235,6 +258,9 @@ class PageView(context: Context) : FrameLayout(context) {
tvTimeBatteryP?.text = "$time $battery%"
}
/**
* 设置内容
*/
fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {
setProgress(textPage)
if (resetPageOffset) {
@ -243,14 +269,23 @@ class PageView(context: Context) : FrameLayout(context) {
binding.contentTextView.setContent(textPage)
}
/**
* 设置无障碍文本
*/
fun setContentDescription(content: String) {
binding.contentTextView.contentDescription = content
}
/**
* 重置滚动位置
*/
fun resetPageOffset() {
binding.contentTextView.resetPageOffset()
}
/**
* 设置进度
*/
@SuppressLint("SetTextI18n")
fun setProgress(textPage: TextPage) = textPage.apply {
tvBookName?.text = ReadBook.book?.name
@ -260,28 +295,44 @@ class PageView(context: Context) : FrameLayout(context) {
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"
}
/**
* 滚动事件
*/
fun scroll(offset: Int) {
binding.contentTextView.scroll(offset)
}
/**
* 更新是否开启选择功能
*/
fun upSelectAble(selectAble: Boolean) {
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(
x: Float, y: Float,
select: (relativePagePos: Int, lineIndex: Int, charIndex: Int) -> Unit,
select: (textPos: TextPos) -> Unit,
) {
return binding.contentTextView.longPress(x, y - headerHeight, select)
}
/**
* 选择文本
*/
fun selectText(
x: Float, y: Float,
select: (relativePagePos: Int, lineIndex: Int, charIndex: Int) -> Unit,
select: (textPos: TextPos) -> Unit,
) {
return binding.contentTextView.selectText(x, y - headerHeight, select)
}

@ -33,7 +33,9 @@ import java.text.BreakIterator
import java.util.*
import kotlin.math.abs
/**
* 阅读视图
*/
class ReadView(context: Context, attrs: AttributeSet) :
FrameLayout(context, attrs),
DataSource {
@ -223,7 +225,9 @@ class ReadView(context: Context, attrs: AttributeSet) :
pressDown = false
if (!isPageMove) {
if (!longPressed && !pressOnTextSelected) {
onSingleTapUp()
if (!curPage.onClick(startX, startY)) {
onSingleTapUp()
}
return true
}
}
@ -293,18 +297,18 @@ class ReadView(context: Context, attrs: AttributeSet) :
*/
private fun onLongPress() {
kotlin.runCatching {
curPage.longPress(startX, startY) { relativePos, lineIndex, charIndex ->
curPage.longPress(startX, startY) { textPos: TextPos ->
isTextSelected = true
pressOnTextSelected = true
initialTextPos.upData(relativePos, lineIndex, charIndex)
val startPos = TextPos(relativePos, lineIndex, charIndex)
val endPos = TextPos(relativePos, lineIndex, charIndex)
val page = curPage.relativePage(relativePos)
initialTextPos.upData(textPos)
val startPos = textPos.copy()
val endPos = textPos.copy()
val page = curPage.relativePage(textPos.relativePagePos)
val stringBuilder = StringBuilder()
var cIndex = charIndex
var lineStart = lineIndex
var lineEnd = lineIndex
for (index in lineIndex - 1 downTo 0) {
var cIndex = textPos.charIndex
var lineStart = textPos.lineIndex
var lineEnd = textPos.lineIndex
for (index in textPos.lineIndex - 1 downTo 0) {
val textLine = page.getLine(index)
if (textLine.isParagraphEnd) {
break
@ -314,7 +318,7 @@ class ReadView(context: Context, attrs: AttributeSet) :
cIndex += textLine.charSize
}
}
for (index in lineIndex until page.lineSize) {
for (index in textPos.lineIndex until page.lineSize) {
val textLine = page.getLine(index)
stringBuilder.append(textLine.text)
lineEnd += 1
@ -370,9 +374,6 @@ class ReadView(context: Context, attrs: AttributeSet) :
*/
private fun onSingleTapUp() {
when {
curPage.pressReviewButton(startX, startY) -> {
context.toastOnUi("Button Pressed!")
}
isTextSelected -> Unit
mcRect.contains(startX, startY) -> if (!isAbortAnim) {
click(AppConfig.clickActionMC)
@ -423,11 +424,15 @@ class ReadView(context: Context, attrs: AttributeSet) :
* 选择文本
*/
private fun selectText(x: Float, y: Float) {
curPage.selectText(x, y) { relativePagePos, lineIndex, charIndex ->
val compare = initialTextPos.compare(relativePagePos, lineIndex, charIndex)
curPage.selectText(x, y) { textPos ->
val compare = initialTextPos.compare(textPos)
when {
compare >= 0 -> {
curPage.selectStartMoveIndex(relativePagePos, lineIndex, charIndex)
curPage.selectStartMoveIndex(
textPos.relativePagePos,
textPos.lineIndex,
textPos.charIndex
)
curPage.selectEndMoveIndex(
initialTextPos.relativePagePos,
initialTextPos.lineIndex,
@ -440,7 +445,11 @@ class ReadView(context: Context, attrs: AttributeSet) :
initialTextPos.lineIndex,
initialTextPos.charIndex
)
curPage.selectEndMoveIndex(relativePagePos, lineIndex, charIndex)
curPage.selectEndMoveIndex(
textPos.relativePagePos,
textPos.lineIndex,
textPos.charIndex
)
}
}
}

@ -119,7 +119,11 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie
abortAnim()
if (!hasNext()) return
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)
}

@ -2,6 +2,9 @@ package io.legado.app.ui.book.read.page.entities
import kotlin.math.min
/**
* 章节信息
*/
@Suppress("unused")
data class TextChapter(
val position: Int,

@ -1,13 +1,15 @@
package io.legado.app.ui.book.read.page.entities
data class TextChar(
/**
* 字符信息
*/
data class TextColumn(
val charData: String,
var start: Float,
var end: Float,
val style: Int = 0, //0:文字,1:图片,2:按钮
var selected: Boolean = false,
var isImage: Boolean = false,
var isSearchResult: Boolean = false,
var isLineEnd: Boolean = false
var isSearchResult: Boolean = false
) {
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.utils.textHeight
/**
* 行信息
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
data class TextLine(
var text: String = "",
val textChars: ArrayList<TextChar> = arrayListOf(),
val textChars: ArrayList<TextColumn> = arrayListOf(),
val reviewCount: Int = 1,
var lineTop: Float = 0f,
var lineBase: Float = 0f,
@ -28,13 +31,13 @@ data class TextLine(
lineBase = lineBottom - textPaint.fontMetrics.descent
}
fun getTextChar(index: Int): TextChar {
fun getTextChar(index: Int): TextColumn {
return textChars.getOrElse(index) {
textChars.last()
}
}
fun getTextCharReverseAt(index: Int): TextChar {
fun getTextCharReverseAt(index: Int): TextColumn {
return textChars[textChars.lastIndex - index]
}

@ -11,6 +11,9 @@ import splitties.init.appCtx
import java.text.DecimalFormat
import kotlin.math.min
/**
* 页面信息
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
data class TextPage(
var index: Int = 0,
@ -111,11 +114,11 @@ data class TextPage(
val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint)
val x1 = x + cw
textLine.textChars.add(
TextChar(
TextColumn(
char,
start = x,
end = x1,
isLineEnd = textLine.text.length - 1 == i
style = if (textLine.text.length - 1 == index && char == "\uD83D\uDCAC") 2 else 0
)
)
x = x1

@ -1,5 +1,8 @@
package io.legado.app.ui.book.read.page.entities
/**
* 位置信息
*/
data class TextPos(
var relativePagePos: 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.model.ReadBook
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.TextPage
import io.legado.app.utils.*
@ -264,7 +264,7 @@ object ChapterProvider {
Pair(0f, width.toFloat())
}
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)
}
@ -409,14 +409,13 @@ object ChapterProvider {
}
val bodyIndent = ReadBookConfig.paragraphIndent
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
for ((index, char) in bodyIndent.toStringArray().withIndex()) {
for (char in bodyIndent.toStringArray()) {
val x1 = x + icw
textLine.textChars.add(
TextChar(
TextColumn(
charData = char,
start = absStartX + x,
end = absStartX + x1,
isLineEnd = bodyIndent.length - 1 == index
end = absStartX + x1
)
)
x = x1
@ -452,7 +451,7 @@ object ChapterProvider {
words.forEachIndexed { index, char ->
val cw = StaticLayout.getDesiredWidth(char, textPaint)
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
}
exceed(absStartX, textLine, words)
@ -471,10 +470,10 @@ object ChapterProvider {
srcList: LinkedList<String>?
) {
var x = startX
words.forEach { char ->
words.forEachIndexed { index, char ->
val cw = StaticLayout.getDesiredWidth(char, textPaint)
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
}
exceed(absStartX, textLine, words)
@ -490,26 +489,27 @@ object ChapterProvider {
char: String,
xStart: Float,
xEnd: Float,
isLineEnd: Boolean,
srcList: LinkedList<String>?
) {
if (srcList != null && char == srcReplaceChar) {
val src = srcList.removeFirst()
ImageProvider.cacheImage(book, src, ReadBook.bookSource)
textLine.textChars.add(
TextChar(
TextColumn(
charData = src,
start = absStartX + xStart,
end = absStartX + xEnd,
isImage = true
style = 1
)
)
} else {
textLine.textChars.add(
TextChar(
TextColumn(
charData = char,
start = absStartX + xStart,
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 {
success.invoke(it)
}.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.http.CookieStore
import io.legado.app.utils.getClipText
import io.legado.app.utils.msg
import io.legado.app.utils.printOnDebug
import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers
@ -74,7 +74,7 @@ class RssSourceEditViewModel(application: Application) : BaseViewModel(applicati
finally.invoke(it)
}
}.onError {
context.toastOnUi(it.msg)
context.toastOnUi(it.stackTraceStr)
}
}

@ -83,7 +83,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
}.onSuccess {
success.invoke(it)
}.onError {
context.toastOnUi(it.msg)
context.toastOnUi(it.stackTraceStr)
}
}

@ -325,7 +325,7 @@ fun Context.openFileUri(uri: Uri, type: String? = null) {
try {
startActivity(intent)
} catch (e: Exception) {
toastOnUi(e.msg)
toastOnUi(e.stackTraceStr)
}
}

@ -2,7 +2,7 @@ package io.legado.app.utils
import java.io.IOException
val Throwable.msg: String
val Throwable.stackTraceStr: String
get() {
val stackTrace = stackTraceToString()
val lMsg = this.localizedMessage ?: "noErrorMsg"

@ -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.
android.nonTransitiveRClass=true
#https://chromiumdash.appspot.com/releases?platform=Android
CronetVersion=105.0.5195.77
CronetVersion=105.0.5195.79

Loading…
Cancel
Save