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 * 更新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)
} }
} }
} }
@ -128,7 +126,7 @@ interface JsExtensions {
html = html, html = html,
javaScript = js, javaScript = js,
headerMap = getSource()?.getHeaderMap(true), headerMap = getSource()?.getHeaderMap(true),
tag = getSource()?.getKey() tag = getSource()?.getKey()
).getStrResponse().body ).getStrResponse().body
} }
} }
@ -146,15 +144,18 @@ 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)
)
} }
/** /**
* 打开图片验证码对话框等待返回验证结果 * 打开图片验证码对话框等待返回验证结果
*/ */
fun getVerificationCode(imageUrl: String): String { fun getVerificationCode(imageUrl: String): String {
return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false) return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false)
} }
/** /**
* 可从网络本地文件(阅读私有缓存目录和书籍保存位置支持相对路径)导入JavaScript脚本 * 可从网络本地文件(阅读私有缓存目录和书籍保存位置支持相对路径)导入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进制字符串转文件 * 实现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()
} }
@ -836,7 +862,7 @@ interface JsExtensions {
} }
fun desBase64DecodeToString( fun desBase64DecodeToString(
data: String, key: String, transformation: String, iv: String data: String, key: String, transformation: String, iv: String
): String? { ): String? {
return EncoderUtils.decryptBase64DES( return EncoderUtils.decryptBase64DES(
data.encodeToByteArray(), data.encodeToByteArray(),
@ -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 }
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 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,20 +345,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
invalidate() invalidate()
} }
fun resetPageOffset() {
pageOffset = 0
}
/** /**
* 点击评论按钮 * 重置滚动位置
*/ */
fun pressReviewButton(x: Float, y: Float): Boolean { fun resetPageOffset() {
reviewButtonArea.forEach { pageOffset = 0
if (x in it[0]..it[2] && y in it[3]..it[1]) {
return true
}
}
return false
} }
/** /**
@ -366,34 +358,49 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
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) {
onSingleTapUp() if (!curPage.onClick(startX, startY)) {
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
)
} }
} }
} }

@ -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,

@ -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"

@ -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

Loading…
Cancel
Save