Merge pull request #497 from gedoor/master

update
pull/500/head
Antecer 4 years ago committed by GitHub
commit 5ff58b7825
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/src/main/assets/help/appHelp.md
  2. 20
      app/src/main/assets/help/sourceHelp.md
  3. 9
      app/src/main/assets/updateLog.md
  4. 6
      app/src/main/java/io/legado/app/constant/AppConst.kt
  5. 4
      app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt
  6. 24
      app/src/main/java/io/legado/app/help/CacheManager.kt
  7. 46
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  8. 12
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  9. 33
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  10. 30
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  11. 18
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  12. 7
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  13. 11
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt
  14. 17
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  15. 17
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  16. 21
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt
  17. 11
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt
  18. 5
      app/src/main/java/io/legado/app/ui/book/read/page/entities/PageDirection.kt
  19. 11
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  20. 12
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt
  21. 437
      app/src/main/java/io/legado/app/ui/widget/text/BetterLinkMovementMethod.kt
  22. 1
      app/src/main/java/io/legado/app/ui/widget/text/InertiaScrollTextView.kt
  23. 4
      app/src/main/res/values-zh-rHK/strings.xml
  24. 4
      app/src/main/res/values-zh-rTW/strings.xml
  25. 4
      app/src/main/res/values-zh/strings.xml
  26. 3
      app/src/main/res/values/ids.xml
  27. 4
      app/src/main/res/values/strings.xml

@ -5,7 +5,7 @@
【温馨提醒】 *本帮助可以在我的-右上角帮助按钮再次打开,更新前一定要做好备份,以免数据丢失!*
1. 为什么第一次安装好之后什么东西都没有?
* 阅读只是一个转码工具,不提供内容,第一次安装app,需要自己手动导入书源,可以从公众号[开源阅读]()、QQ群、酷安评论里获取由书友制作分享的书源。
* 阅读只是一个转码工具,不提供内容,第一次安装app,需要自己手动导入书源,可以从公众号**[开源阅读]**、QQ群、酷安评论里获取由书友制作分享的书源。
2. 正文出现缺字漏字、内容缺失、排版错乱等情况,如何处理?
* 有可能是净化规则出现问题,先关闭替换净化并刷新,再观察是否正常。如果正常说明净化规则存在误杀,如果关闭后仍然出现相关问题,请点击源链接查看原文与正文是否相同,如果不同,再进行反馈。

@ -0,0 +1,20 @@
# 源规则帮助
* [书源帮助文档](https://alanskycn.gitee.io/teachme/Rule/source.html)
* [订阅源帮助文档](https://alanskycn.gitee.io/teachme/Rule/rss.html)
* 规则标志, {{......}}内使用规则必须有明显的规则标志,没有规则标志当作js执行
* @@ 默认规则,直接写时可以省略@@
* @XPath: xpath规则,直接写时以//开头可省略@XPath
* @Json: json规则,直接写时以$.开头可省略@Json
* : regex规则,不可省略,只可以用在书籍列表和目录列表
* js 变量和函数
* java 变量-当前类
* baseUrl 变量-当前url,String
* result 变量-上一步的结果
* book 变量-书籍类,方法见 io.legado.app.data.entities.Book
* cookie 变量-cookie操作类,方法见 io.legado.app.help.http.CookieStore
* cache 变量-缓存操作类,方法见 io.legado.app.help.CacheManager
* chapter 变量-当前目录类,方法见 io.legado.app.data.entities.BookChapter
* title 变量-当前标题,String

@ -1,8 +1,13 @@
# 更新日志
* 关注公众号 **[开源阅读]()** 菜单•软件下载 提前享受新版本。
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
* 关注公众号 **[开源阅读]** 菜单•软件下载 提前享受新版本。
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/11/22**
* 正文添加正确字体规则,可以通过文字轮廓对比将错误的文字替换为正确的文字
* js添加java.readFile("path")读取本地文件,返回BiteArray
* js添加java.queryTTF(font: ByteArray?),返回字体处理类,可以编码和轮廓互查,io.legado.app.model.analyzeRule.QueryTTF
**2020/11/18**
* 优化导航栏
* js添加java.log(msg: String)用于调试时输出消息

@ -36,9 +36,9 @@ object AppConst {
val keyboardToolChars: List<String> by lazy {
arrayListOf(
"", "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\",
"$", "#", "!", ".", "href", "src", "textNodes", "xpath", "json", "css",
"id", "class", "tag"
"", "@", "&", "|", "%", "/", "\\", ":", "[", "]", "{", "}", "<", ">",
"$", "#", "!", ".", "+", "-", "*", "=", "href", "src", "textNodes", "xpath", "json",
"css", "id", "class", "tag"
)
}

@ -9,8 +9,6 @@ data class ContentRule(
var nextContentUrl: String? = null,
var webJs: String? = null,
var sourceRegex: String? = null,
var replaceRegex: String? = null,
var replaceRegex: String? = null, //正文获取后处理规则
var imageStyle: String? = null, //默认大小居中,FULL最大宽度
var font: String? = null, //网页内包含的字体必须返回ByteArray
var correctFont: String? = null, //正确的字体必须返回ByteArray
) : Parcelable

@ -2,19 +2,30 @@ package io.legado.app.help
import io.legado.app.App
import io.legado.app.data.entities.Cache
import io.legado.app.model.analyzeRule.QueryTTF
import io.legado.app.utils.ACache
@Suppress("unused")
object CacheManager {
private val queryTTFMap = hashMapOf<String, Pair<Long, QueryTTF>>()
/**
* saveTime 单位为秒
*/
@JvmOverloads
fun put(key: String, value: Any, saveTime: Int = 0) {
val deadline = if (saveTime == 0) 0 else System.currentTimeMillis() + saveTime * 1000
val deadline =
if (saveTime == 0) 0 else System.currentTimeMillis() + saveTime * 1000
when (value) {
is QueryTTF -> queryTTFMap[key] = Pair(deadline, value)
is ByteArray -> ACache.get(App.INSTANCE).put(key, value, saveTime)
else -> {
val cache = Cache(key, value.toString(), deadline)
App.db.cacheDao().insert(cache)
}
}
}
fun get(key: String): String? {
return App.db.cacheDao().get(key, System.currentTimeMillis())
@ -36,4 +47,15 @@ object CacheManager {
return get(key)?.toFloatOrNull()
}
fun getByteArray(key: String): ByteArray? {
return ACache.get(App.INSTANCE).getAsBinary(key)
}
fun getQueryTTF(key: String): QueryTTF? {
val cache = queryTTFMap[key] ?: return null
if (cache.first == 0L || cache.first > System.currentTimeMillis()) {
return cache.second
}
return null
}
}

@ -1,14 +1,18 @@
package io.legado.app.help
import android.net.Uri
import android.util.Base64
import androidx.annotation.Keep
import io.legado.app.App
import io.legado.app.constant.AppConst.dateFormat
import io.legado.app.help.http.CookieStore
import io.legado.app.help.http.HttpHelper
import io.legado.app.help.http.SSLHelper
import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.analyzeRule.QueryTTF
import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.io.File
@ -230,11 +234,51 @@ interface JsExtensions {
/**
* 解析字体,返回字体解析类
*/
fun queryTTF(font: ByteArray?): QueryTTF? {
fun queryBase64TTF(base64: String?): QueryTTF? {
base64DecodeToByteArray(base64)?.let {
return QueryTTF(it)
}
return null
}
fun queryTTF(path: String): QueryTTF? {
val qTTF = CacheManager.getQueryTTF(path)
if (qTTF != null) {
return qTTF
}
val font: ByteArray? = when {
path.isAbsUrl() -> runBlocking {
var x = CacheManager.getByteArray(path)
if (x == null) {
x = HttpHelper.simpleGetBytesAsync(path)
x?.let {
CacheManager.put(path, it)
}
}
return@runBlocking x
}
path.isContentScheme() -> {
Uri.parse(path).readBytes(App.INSTANCE)
}
else -> {
File(path).readBytes()
}
}
font ?: return null
return QueryTTF(font)
}
fun replaceFont(text: String, font1: QueryTTF, font2: QueryTTF, start: Int, end: Int): String {
val contentArray = text.toCharArray()
contentArray.forEachIndexed { index, s ->
if (s > start.toChar() && s < end.toChar()) {
val code = font2.GetCodeByGlyf(font1.GetGlyfByCode(s.toInt()))
contentArray[index] = code.toChar()
}
}
return contentArray.joinToString("")
}
/**
* 输出调试日志
*/

@ -39,6 +39,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
private var objectChangedJS = false
private var objectChangedJP = false
@JvmOverloads
fun setContent(content: Any?, baseUrl: String? = null): AnalyzeRule {
if (content == null) throw AssertionError("Content cannot be null")
this.content = content
@ -297,8 +298,8 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
return ArrayList()
}
fun getByteArray(ruleStr: String): ByteArray? {
if (ruleStr.isEmpty()) return null
fun getContent(ruleStr: String, text: String?): String {
if (ruleStr.isEmpty()) return ""
val ruleList = splitSourceRule(ruleStr)
var result: Any? = null
content?.let { o ->
@ -311,7 +312,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
result.toString(),
sourceRule.rule.splitNotBlank("&&")
)
Mode.Js -> evalJS(sourceRule.rule, result)
Mode.Js -> evalJS(sourceRule.rule, result, text)
Mode.Json -> getAnalyzeByJSonPath(it).getList(sourceRule.rule)
Mode.XPath -> getAnalyzeByXPath(it).getElements(sourceRule.rule)
else -> getAnalyzeByJSoup(it).getElements(sourceRule.rule)
@ -322,7 +323,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
}
}
}
return result as? ByteArray
return result?.toString() ?: ""
}
/**
@ -648,7 +649,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
/**
* 执行JS
*/
private fun evalJS(jsStr: String, result: Any?): Any? {
private fun evalJS(jsStr: String, result: Any?, content: String? = null): Any? {
val bindings = SimpleBindings()
bindings["java"] = this
bindings["cookie"] = CookieStore
@ -658,6 +659,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
bindings["baseUrl"] = baseUrl
bindings["chapter"] = chapter
bindings["title"] = chapter?.title
bindings["content"] = content
return SCRIPT_ENGINE.eval(jsStr, bindings)
}

@ -10,10 +10,8 @@ import io.legado.app.help.BookHelp
import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.analyzeRule.QueryTTF
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.htmlFormat
import io.legado.app.utils.toStringArray
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
@ -37,21 +35,6 @@ object BookContent {
val nextUrlList = arrayListOf(baseUrl)
val contentRule = bookSource.getContentRule()
val analyzeRule = AnalyzeRule(book).setContent(body, baseUrl)
val fontRule = contentRule.font
val correctFontRule = contentRule.correctFont
var font: ByteArray? = null
var correctFont: ByteArray? = null
fontRule?.let {
//todo 获取网页嵌入字体
font = analyzeRule.getByteArray(it)
}
correctFontRule?.let {
//todo 获取正确字体
correctFont = analyzeRule.getByteArray(it)
}
if (correctFont == null && font != null) {
BookHelp.saveFont(book, bookChapter, font!!)
}
var contentData = analyzeContent(
book, baseUrl, body, contentRule, bookChapter, bookSource
)
@ -111,21 +94,7 @@ object BookContent {
var contentStr = content.toString().htmlFormat()
val replaceRegex = bookSource.ruleContent?.replaceRegex
if (!replaceRegex.isNullOrEmpty()) {
analyzeRule.setContent(contentStr).setBaseUrl(baseUrl)
analyzeRule.chapter = bookChapter
contentStr = analyzeRule.getString(replaceRegex)
}
if (correctFont != null && font != null) {
val queryTTF = QueryTTF(font!!)
val cQueryTTF = QueryTTF(correctFont!!)
val contentArray = contentStr.toCharArray()
contentArray.forEachIndexed { index, s ->
if(s> 58000.toChar()){
val code = cQueryTTF.GetCodeByGlyf(queryTTF.GetGlyfByCode(s.toInt()))
contentArray[index] = code.toChar()
}
}
contentStr = contentArray.joinToString("")
contentStr = analyzeRule.getContent(replaceRegex, contentStr)
}
Debug.log(bookSource.bookSourceUrl, "┌获取章节名称")
Debug.log(bookSource.bookSourceUrl, "${bookChapter.title}")

@ -191,12 +191,7 @@ object ReadBook {
if (book != null && textChapter != null) {
val key = IntentDataHelp.putData(textChapter)
ReadAloud.play(
App.INSTANCE,
book.name,
textChapter.title,
durPageIndex,
key,
play
App.INSTANCE, book.name, textChapter.title, durPageIndex, key, play
)
}
}
@ -275,10 +270,7 @@ object ReadBook {
CacheBook.download(webBook, book, chapter)
} else if (book != null) {
contentLoadFinish(
book,
chapter,
"没有书源",
resetPageOffset = resetPageOffset
book, chapter, "没有书源", resetPageOffset = resetPageOffset
)
removeLoading(chapter.index)
} else {
@ -383,11 +375,7 @@ object ReadBook {
durChapterIndex -> {
curTextChapter =
ChapterProvider.getTextChapter(
book,
chapter,
contents,
chapterSize,
imageStyle
book, chapter, contents, chapterSize, imageStyle
)
if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset)
callBack?.upView()
@ -397,22 +385,14 @@ object ReadBook {
durChapterIndex - 1 -> {
prevTextChapter =
ChapterProvider.getTextChapter(
book,
chapter,
contents,
chapterSize,
imageStyle
book, chapter, contents, chapterSize, imageStyle
)
if (upContent) callBack?.upContent(-1, resetPageOffset)
}
durChapterIndex + 1 -> {
nextTextChapter =
ChapterProvider.getTextChapter(
book,
chapter,
contents,
chapterSize,
imageStyle
book, chapter, contents, chapterSize, imageStyle
)
if (upContent) callBack?.upContent(1, resetPageOffset)
}

@ -37,7 +37,7 @@ import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.TEXT_COLOR
import io.legado.app.ui.book.read.page.ContentTextView
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.TextPageFactory
import io.legado.app.ui.book.read.page.delegate.PageDelegate
import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.ui.book.searchContent.SearchContentActivity
import io.legado.app.ui.book.toc.ChapterListActivity
import io.legado.app.ui.login.SourceLogin
@ -260,28 +260,28 @@ class ReadBookActivity : ReadBookBaseActivity(),
when {
isPrevKey(keyCode) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV)
page_view.pageDelegate?.keyTurnPage(PageDirection.PREV)
return true
}
}
isNextKey(keyCode) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
page_view.pageDelegate?.keyTurnPage(PageDirection.NEXT)
return true
}
}
keyCode == KeyEvent.KEYCODE_VOLUME_UP -> {
if (volumeKeyPage(PageDelegate.Direction.PREV)) {
if (volumeKeyPage(PageDirection.PREV)) {
return true
}
}
keyCode == KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (volumeKeyPage(PageDelegate.Direction.NEXT)) {
if (volumeKeyPage(PageDirection.NEXT)) {
return true
}
}
keyCode == KeyEvent.KEYCODE_SPACE -> {
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
page_view.pageDelegate?.keyTurnPage(PageDirection.NEXT)
return true
}
}
@ -307,7 +307,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (volumeKeyPage(PageDelegate.Direction.NONE)) {
if (volumeKeyPage(PageDirection.NONE)) {
return true
}
}
@ -458,7 +458,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/**
* 音量键翻页
*/
private fun volumeKeyPage(direction: PageDelegate.Direction): Boolean {
private fun volumeKeyPage(direction: PageDirection): Boolean {
if (!read_menu.isVisible) {
if (getPrefBoolean("volumeKeyPage", true)) {
if (getPrefBoolean("volumeKeyPageOnPlay")
@ -612,7 +612,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
autoPageProgress++
if (autoPageProgress >= ReadBookConfig.autoReadSpeed * 50) {
autoPageProgress = 0
page_view.fillPage(PageDelegate.Direction.NEXT)
page_view.fillPage(PageDirection.NEXT)
} else {
page_view.invalidate()
}

@ -15,6 +15,7 @@ import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.accentColor
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.delegate.*
import io.legado.app.ui.book.read.page.entities.PageDirection
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.activity
@ -339,12 +340,12 @@ class PageView(context: Context, attrs: AttributeSet) :
curPage.cancelSelect()
}
fun fillPage(direction: PageDelegate.Direction) {
fun fillPage(direction: PageDirection) {
when (direction) {
PageDelegate.Direction.PREV -> {
PageDirection.PREV -> {
pageFactory.moveToPrev(true)
}
PageDelegate.Direction.NEXT -> {
PageDirection.NEXT -> {
pageFactory.moveToNext(true)
}
else -> Unit

@ -4,6 +4,7 @@ import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.drawable.GradientDrawable
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.entities.PageDirection
class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
private val bitmapMatrix = Matrix()
@ -21,19 +22,19 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
if (!isRunning) return
val offsetX = touchX - startX
if ((mDirection == Direction.NEXT && offsetX > 0)
|| (mDirection == Direction.PREV && offsetX < 0)
if ((mDirection == PageDirection.NEXT && offsetX > 0)
|| (mDirection == PageDirection.PREV && offsetX < 0)
) {
return
}
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
if (mDirection == Direction.PREV) {
if (mDirection == PageDirection.PREV) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat())
curBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
addShadow(distanceX.toInt(), canvas)
} else if (mDirection == Direction.NEXT) {
} else if (mDirection == PageDirection.NEXT) {
bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat())
nextBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
@ -60,7 +61,7 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
override fun onAnimStart(animationSpeed: Int) {
val distanceX: Float
when (mDirection) {
Direction.NEXT -> distanceX =
PageDirection.NEXT -> distanceX =
if (isCancel) {
var dis = viewWidth - startX + touchX
if (dis > viewWidth) {

@ -3,6 +3,7 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.Bitmap
import android.view.MotionEvent
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.entities.PageDirection
import io.legado.app.utils.screenshot
abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) {
@ -11,20 +12,20 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
protected var prevBitmap: Bitmap? = null
protected var nextBitmap: Bitmap? = null
override fun setDirection(direction: Direction) {
override fun setDirection(direction: PageDirection) {
super.setDirection(direction)
setBitmap()
}
private fun setBitmap() {
when (mDirection) {
Direction.PREV -> {
PageDirection.PREV -> {
prevBitmap?.recycle()
prevBitmap = prevPage.screenshot()
curBitmap?.recycle()
curBitmap = curPage.screenshot()
}
Direction.NEXT -> {
PageDirection.NEXT -> {
nextBitmap?.recycle()
nextBitmap = nextPage.screenshot()
curBitmap?.recycle()
@ -79,19 +80,19 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
noNext = true
return
}
setDirection(Direction.PREV)
setDirection(PageDirection.PREV)
} else {
//如果不存在表示没有下一页了
if (!hasNext()) {
noNext = true
return
}
setDirection(Direction.NEXT)
setDirection(PageDirection.NEXT)
}
}
}
if (isMoved) {
isCancel = if (mDirection == Direction.NEXT) sumX > lastX else sumX < lastX
isCancel = if (mDirection == PageDirection.NEXT) sumX > lastX else sumX < lastX
isRunning = true
//设置触摸点
pageView.setTouchPoint(sumX, sumY)
@ -117,7 +118,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
override fun nextPageByAnim(animationSpeed: Int) {
abortAnim()
if (!hasNext()) return
setDirection(Direction.NEXT)
setDirection(PageDirection.NEXT)
pageView.setTouchPoint(viewWidth.toFloat(), 0f, false)
onAnimStart(animationSpeed)
}
@ -125,7 +126,7 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
override fun prevPageByAnim(animationSpeed: Int) {
abortAnim()
if (!hasPrev()) return
setDirection(Direction.PREV)
setDirection(PageDirection.PREV)
pageView.setTouchPoint(0f, 0f)
onAnimStart(animationSpeed)
}

@ -10,6 +10,7 @@ import com.google.android.material.snackbar.Snackbar
import io.legado.app.R
import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.entities.PageDirection
import kotlin.math.abs
abstract class PageDelegate(protected val pageView: PageView) {
@ -47,7 +48,7 @@ abstract class PageDelegate(protected val pageView: PageView) {
var noNext = true
//移动方向
var mDirection = Direction.NONE
var mDirection = PageDirection.NONE
var isCancel = false
var isRunning = false
var isStarted = false
@ -117,17 +118,17 @@ abstract class PageDelegate(protected val pageView: PageView) {
abstract fun prevPageByAnim(animationSpeed: Int)
open fun keyTurnPage(direction: Direction) {
open fun keyTurnPage(direction: PageDirection) {
if (isRunning) return
when (direction) {
Direction.NEXT -> nextPageByAnim(100)
Direction.PREV -> prevPageByAnim(100)
PageDirection.NEXT -> nextPageByAnim(100)
PageDirection.PREV -> prevPageByAnim(100)
else -> return
}
}
@CallSuper
open fun setDirection(direction: Direction) {
open fun setDirection(direction: PageDirection) {
mDirection = direction
}
@ -149,7 +150,7 @@ abstract class PageDelegate(protected val pageView: PageView) {
//取消
isCancel = false
//是下一章还是前一章
setDirection(Direction.NONE)
setDirection(PageDirection.NONE)
}
/**
@ -184,8 +185,4 @@ abstract class PageDelegate(protected val pageView: PageView) {
}
enum class Direction {
NONE, PREV, NEXT
}
}

@ -6,6 +6,7 @@ import android.os.Build
import android.view.MotionEvent
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.entities.PageDirection
import kotlin.math.*
@Suppress("DEPRECATION")
@ -123,13 +124,13 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
}
MotionEvent.ACTION_MOVE -> {
if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3)
|| mDirection == Direction.PREV
|| mDirection == PageDirection.PREV
) {
pageView.touchY = viewHeight.toFloat()
}
if (startY > viewHeight / 3 && startY < viewHeight / 2
&& mDirection == Direction.NEXT
&& mDirection == PageDirection.NEXT
) {
pageView.touchY = 1f
}
@ -137,17 +138,17 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
}
}
override fun setDirection(direction: Direction) {
override fun setDirection(direction: PageDirection) {
super.setDirection(direction)
when (direction) {
Direction.PREV ->
PageDirection.PREV ->
//上一页滑动不出现对角
if (startX > viewWidth / 2) {
calcCornerXY(startX, viewHeight.toFloat())
} else {
calcCornerXY(viewWidth - startX, viewHeight.toFloat())
}
Direction.NEXT ->
PageDirection.NEXT ->
if (viewWidth / 2 > startX) {
calcCornerXY(viewWidth - startX, startY)
}
@ -160,12 +161,12 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
val dy: Float
// dy 垂直方向滑动的距离,负值会使滚动向上滚动
if (isCancel) {
dx = if (mCornerX > 0 && mDirection == Direction.NEXT) {
dx = if (mCornerX > 0 && mDirection == PageDirection.NEXT) {
(viewWidth - touchX)
} else {
-touchX
}
if (mDirection != Direction.NEXT) {
if (mDirection != PageDirection.NEXT) {
dx = -(viewWidth + touchX)
}
dy = if (mCornerY > 0) {
@ -174,7 +175,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
-touchY // 防止mTouchY最终变为0
}
} else {
dx = if (mCornerX > 0 && mDirection == Direction.NEXT) {
dx = if (mCornerX > 0 && mDirection == PageDirection.NEXT) {
-(viewWidth + touchX)
} else {
(viewWidth - touchX + viewWidth)
@ -197,14 +198,14 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
override fun onDraw(canvas: Canvas) {
if (!isRunning) return
when (mDirection) {
Direction.NEXT -> {
PageDirection.NEXT -> {
calcPoints()
drawCurrentPageArea(canvas, curBitmap)
drawNextPageAreaAndShadow(canvas, nextBitmap)
drawCurrentPageShadow(canvas)
drawCurrentBackArea(canvas, curBitmap)
}
Direction.PREV -> {
PageDirection.PREV -> {
calcPoints()
drawCurrentPageArea(canvas, prevBitmap)
drawNextPageAreaAndShadow(canvas, curBitmap)

@ -3,6 +3,7 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas
import android.graphics.Matrix
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.entities.PageDirection
class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
@ -11,7 +12,7 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
override fun onAnimStart(animationSpeed: Int) {
val distanceX: Float
when (mDirection) {
Direction.NEXT -> distanceX =
PageDirection.NEXT -> distanceX =
if (isCancel) {
var dis = viewWidth - startX + touchX
if (dis > viewWidth) {
@ -34,17 +35,17 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
override fun onDraw(canvas: Canvas) {
val offsetX = touchX - startX
if ((mDirection == Direction.NEXT && offsetX > 0)
|| (mDirection == Direction.PREV && offsetX < 0)
if ((mDirection == PageDirection.NEXT && offsetX > 0)
|| (mDirection == PageDirection.PREV && offsetX < 0)
) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
if (!isRunning) return
if (mDirection == Direction.PREV) {
if (mDirection == PageDirection.PREV) {
bitmapMatrix.setTranslate(distanceX + viewWidth, 0.toFloat())
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
bitmapMatrix.setTranslate(distanceX, 0.toFloat())
prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
} else if (mDirection == Direction.NEXT) {
} else if (mDirection == PageDirection.NEXT) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat())
nextBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat())

@ -0,0 +1,5 @@
package io.legado.app.ui.book.read.page.entities
enum class PageDirection {
NONE, PREV, NEXT
}

@ -237,8 +237,6 @@ class BookSourceEditActivity :
add(EditEntity("sourceRegex", cr?.sourceRegex, R.string.rule_source_regex))
add(EditEntity("replaceRegex", cr?.replaceRegex, R.string.rule_replace_regex))
add(EditEntity("imageStyle", cr?.imageStyle, R.string.rule_image_style))
add(EditEntity("font", cr?.font, R.string.rule_font))
add(EditEntity("correctFont", cr?.correctFont, R.string.rule_correct_font))
}
//发现
val er = source?.getExploreRule()
@ -342,8 +340,6 @@ class BookSourceEditActivity :
"sourceRegex" -> contentRule.sourceRegex = it.value
"replaceRegex" -> contentRule.replaceRegex = it.value
"imageStyle" -> contentRule.imageStyle = it.value
"font" -> contentRule.font = it.value
"correctFont" -> contentRule.correctFont = it.value
}
}
source.ruleSearch = searchRule
@ -390,13 +386,18 @@ class BookSourceEditActivity :
selector(getString(R.string.help), items) { _, index ->
when (index) {
0 -> insertText(AppConst.urlOption)
1 -> openUrl("https://alanskycn.gitee.io/teachme/Rule/source.html")
1 -> showSourceHelp()
2 -> showRegexHelp()
3 -> FilePicker.selectFile(this, selectPathRequestCode)
}
}
}
private fun showSourceHelp() {
val mdText = String(assets.open("help/sourceHelp.md").readBytes())
TextDialog.show(supportFragmentManager, mdText, TextDialog.MD)
}
private fun showRegexHelp() {
val mdText = String(assets.open("help/regexHelp.md").readBytes())
TextDialog.show(supportFragmentManager, mdText, TextDialog.MD)

@ -21,7 +21,10 @@ import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.rss.source.debug.RssSourceDebugActivity
import io.legado.app.ui.widget.KeyboardToolPop
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.*
import io.legado.app.utils.GSON
import io.legado.app.utils.getViewModel
import io.legado.app.utils.sendToClip
import io.legado.app.utils.shareWithQr
import kotlinx.android.synthetic.main.activity_rss_source_edit.*
import org.jetbrains.anko.*
import kotlin.math.abs
@ -199,12 +202,17 @@ class RssSourceEditActivity :
selector(getString(R.string.help), items) { _, index ->
when (index) {
0 -> insertText(AppConst.urlOption)
1 -> openUrl("https://alanskycn.gitee.io/teachme/Rule/rss.html")
1 -> showSourceHelp()
2 -> showRegexHelp()
}
}
}
private fun showSourceHelp() {
val mdText = String(assets.open("help/sourceHelp.md").readBytes())
TextDialog.show(supportFragmentManager, mdText, TextDialog.MD)
}
private fun showRegexHelp() {
val mdText = String(assets.open("help/regexHelp.md").readBytes())
TextDialog.show(supportFragmentManager, mdText, TextDialog.MD)

@ -0,0 +1,437 @@
package io.legado.app.ui.widget.text
import android.app.Activity
import android.graphics.RectF
import android.text.Selection
import android.text.Spannable
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.BackgroundColorSpan
import android.text.style.ClickableSpan
import android.text.style.URLSpan
import android.text.util.Linkify
import android.view.*
import android.widget.TextView
import io.legado.app.R
import io.legado.app.ui.widget.text.BetterLinkMovementMethod.LongPressTimer.OnTimerReachedListener
class BetterLinkMovementMethod protected constructor() : LinkMovementMethod() {
private var onLinkClickListener: OnLinkClickListener? = null
private var onLinkLongClickListener: OnLinkLongClickListener? = null
private val touchedLineBounds = RectF()
private var isUrlHighlighted = false
private var clickableSpanUnderTouchOnActionDown: ClickableSpan? = null
private var activeTextViewHashcode = 0
private var ongoingLongPressTimer: LongPressTimer? = null
private var wasLongPressRegistered = false
interface OnLinkClickListener {
/**
* @param textView The TextView on which a click was registered.
* @param url The clicked URL.
* @return True if this click was handled. False to let Android handle the URL.
*/
fun onClick(textView: TextView?, url: String?): Boolean
}
interface OnLinkLongClickListener {
/**
* @param textView The TextView on which a long-click was registered.
* @param url The long-clicked URL.
* @return True if this long-click was handled. False to let Android handle the URL (as a short-click).
*/
fun onLongClick(textView: TextView?, url: String?): Boolean
}
/**
* Set a listener that will get called whenever any link is clicked on the TextView.
*/
fun setOnLinkClickListener(clickListener: OnLinkClickListener?): BetterLinkMovementMethod {
if (this === singleInstance) {
throw UnsupportedOperationException(
"Setting a click listener on the instance returned by getInstance() is not supported to avoid memory " +
"leaks. Please use newInstance() or any of the linkify() methods instead."
)
}
onLinkClickListener = clickListener
return this
}
/**
* Set a listener that will get called whenever any link is clicked on the TextView.
*/
fun setOnLinkLongClickListener(longClickListener: OnLinkLongClickListener?): BetterLinkMovementMethod {
if (this === singleInstance) {
throw UnsupportedOperationException(
"Setting a long-click listener on the instance returned by getInstance() is not supported to avoid " +
"memory leaks. Please use newInstance() or any of the linkify() methods instead."
)
}
onLinkLongClickListener = longClickListener
return this
}
override fun onTouchEvent(textView: TextView, text: Spannable, event: MotionEvent): Boolean {
if (activeTextViewHashcode != textView.hashCode()) {
// Bug workaround: TextView stops calling onTouchEvent() once any URL is highlighted.
// A hacky solution is to reset any "autoLink" property set in XML. But we also want
// to do this once per TextView.
activeTextViewHashcode = textView.hashCode()
textView.autoLinkMask = 0
}
val clickableSpanUnderTouch = findClickableSpanUnderTouch(textView, text, event)
if (event.action == MotionEvent.ACTION_DOWN) {
clickableSpanUnderTouchOnActionDown = clickableSpanUnderTouch
}
val touchStartedOverAClickableSpan = clickableSpanUnderTouchOnActionDown != null
return when (event.action) {
MotionEvent.ACTION_DOWN -> {
clickableSpanUnderTouch?.let { highlightUrl(textView, it, text) }
if (touchStartedOverAClickableSpan && onLinkLongClickListener != null) {
val longClickListener: OnTimerReachedListener =
object : OnTimerReachedListener {
override fun onTimerReached() {
wasLongPressRegistered = true
textView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
removeUrlHighlightColor(textView)
dispatchUrlLongClick(textView, clickableSpanUnderTouch)
}
}
startTimerForRegisteringLongClick(textView, longClickListener)
}
touchStartedOverAClickableSpan
}
MotionEvent.ACTION_UP -> {
// Register a click only if the touch started and ended on the same URL.
if (!wasLongPressRegistered && touchStartedOverAClickableSpan && clickableSpanUnderTouch === clickableSpanUnderTouchOnActionDown) {
dispatchUrlClick(textView, clickableSpanUnderTouch)
}
cleanupOnTouchUp(textView)
// Consume this event even if we could not find any spans to avoid letting Android handle this event.
// Android's TextView implementation has a bug where links get clicked even when there is no more text
// next to the link and the touch lies outside its bounds in the same direction.
touchStartedOverAClickableSpan
}
MotionEvent.ACTION_CANCEL -> {
cleanupOnTouchUp(textView)
false
}
MotionEvent.ACTION_MOVE -> {
// Stop listening for a long-press as soon as the user wanders off to unknown lands.
if (clickableSpanUnderTouch !== clickableSpanUnderTouchOnActionDown) {
removeLongPressCallback(textView)
}
if (!wasLongPressRegistered) {
// Toggle highlight.
if (clickableSpanUnderTouch != null) {
highlightUrl(textView, clickableSpanUnderTouch, text)
} else {
removeUrlHighlightColor(textView)
}
}
touchStartedOverAClickableSpan
}
else -> false
}
}
private fun cleanupOnTouchUp(textView: TextView) {
wasLongPressRegistered = false
clickableSpanUnderTouchOnActionDown = null
removeUrlHighlightColor(textView)
removeLongPressCallback(textView)
}
/**
* Determines the touched location inside the TextView's text and returns the ClickableSpan found under it (if any).
*
* @return The touched ClickableSpan or null.
*/
protected fun findClickableSpanUnderTouch(
textView: TextView,
text: Spannable,
event: MotionEvent
): ClickableSpan? {
// So we need to find the location in text where touch was made, regardless of whether the TextView
// has scrollable text. That is, not the entire text is currently visible.
var touchX = event.x.toInt()
var touchY = event.y.toInt()
// Ignore padding.
touchX -= textView.totalPaddingLeft
touchY -= textView.totalPaddingTop
// Account for scrollable text.
touchX += textView.scrollX
touchY += textView.scrollY
val layout = textView.layout
val touchedLine = layout.getLineForVertical(touchY)
val touchOffset = layout.getOffsetForHorizontal(touchedLine, touchX.toFloat())
touchedLineBounds.left = layout.getLineLeft(touchedLine)
touchedLineBounds.top = layout.getLineTop(touchedLine).toFloat()
touchedLineBounds.right = layout.getLineWidth(touchedLine) + touchedLineBounds.left
touchedLineBounds.bottom = layout.getLineBottom(touchedLine).toFloat()
return if (touchedLineBounds.contains(touchX.toFloat(), touchY.toFloat())) {
// Find a ClickableSpan that lies under the touched area.
val spans = text.getSpans(touchOffset, touchOffset, ClickableSpan::class.java)
for (span in spans) {
if (span is ClickableSpan) {
return span
}
}
// No ClickableSpan found under the touched location.
null
} else {
// Touch lies outside the line's horizontal bounds where no spans should exist.
null
}
}
/**
* Adds a background color span at <var>clickableSpan</var>'s location.
*/
protected fun highlightUrl(textView: TextView, clickableSpan: ClickableSpan?, text: Spannable) {
if (isUrlHighlighted) {
return
}
isUrlHighlighted = true
val spanStart = text.getSpanStart(clickableSpan)
val spanEnd = text.getSpanEnd(clickableSpan)
val highlightSpan = BackgroundColorSpan(textView.highlightColor)
text.setSpan(highlightSpan, spanStart, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
textView.setTag(R.id.bettermovementmethod_highlight_background_span, highlightSpan)
Selection.setSelection(text, spanStart, spanEnd)
}
/**
* Removes the highlight color under the Url.
*/
protected fun removeUrlHighlightColor(textView: TextView) {
if (!isUrlHighlighted) {
return
}
isUrlHighlighted = false
val text = textView.text as Spannable
val highlightSpan =
textView.getTag(R.id.bettermovementmethod_highlight_background_span) as BackgroundColorSpan
text.removeSpan(highlightSpan)
Selection.removeSelection(text)
}
protected fun startTimerForRegisteringLongClick(
textView: TextView,
longClickListener: OnTimerReachedListener?
) {
ongoingLongPressTimer = LongPressTimer()
ongoingLongPressTimer!!.setOnTimerReachedListener(longClickListener)
textView.postDelayed(
ongoingLongPressTimer,
ViewConfiguration.getLongPressTimeout().toLong()
)
}
/**
* Remove the long-press detection timer.
*/
protected fun removeLongPressCallback(textView: TextView) {
if (ongoingLongPressTimer != null) {
textView.removeCallbacks(ongoingLongPressTimer)
ongoingLongPressTimer = null
}
}
protected fun dispatchUrlClick(textView: TextView, clickableSpan: ClickableSpan?) {
val clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan)
val handled = onLinkClickListener != null && onLinkClickListener!!.onClick(
textView,
clickableSpanWithText.text()
)
if (!handled) {
// Let Android handle this click.
clickableSpanWithText.span()!!.onClick(textView)
}
}
protected fun dispatchUrlLongClick(textView: TextView, clickableSpan: ClickableSpan?) {
val clickableSpanWithText = ClickableSpanWithText.ofSpan(textView, clickableSpan)
val handled = onLinkLongClickListener != null && onLinkLongClickListener!!.onLongClick(
textView,
clickableSpanWithText.text()
)
if (!handled) {
// Let Android handle this long click as a short-click.
clickableSpanWithText.span()!!.onClick(textView)
}
}
protected class LongPressTimer : Runnable {
private var onTimerReachedListener: OnTimerReachedListener? = null
interface OnTimerReachedListener {
fun onTimerReached()
}
override fun run() {
onTimerReachedListener!!.onTimerReached()
}
fun setOnTimerReachedListener(listener: OnTimerReachedListener?) {
onTimerReachedListener = listener
}
}
/**
* A wrapper to support all [ClickableSpan]s that may or may not provide URLs.
*/
protected class ClickableSpanWithText protected constructor(
private val span: ClickableSpan?,
private val text: String
) {
fun span(): ClickableSpan? {
return span
}
fun text(): String {
return text
}
companion object {
fun ofSpan(textView: TextView, span: ClickableSpan?): ClickableSpanWithText {
val s = textView.text as Spanned
val text: String
text = if (span is URLSpan) {
span.url
} else {
val start = s.getSpanStart(span)
val end = s.getSpanEnd(span)
s.subSequence(start, end).toString()
}
return ClickableSpanWithText(span, text)
}
}
}
companion object {
private var singleInstance: BetterLinkMovementMethod? = null
private const val LINKIFY_NONE = -2
/**
* Return a new instance of BetterLinkMovementMethod.
*/
fun newInstance(): BetterLinkMovementMethod {
return BetterLinkMovementMethod()
}
/**
* @param linkifyMask One of [Linkify.ALL], [Linkify.PHONE_NUMBERS], [Linkify.MAP_ADDRESSES],
* [Linkify.WEB_URLS] and [Linkify.EMAIL_ADDRESSES].
* @param textViews The TextViews on which a [BetterLinkMovementMethod] should be registered.
* @return The registered [BetterLinkMovementMethod] on the TextViews.
*/
fun linkify(linkifyMask: Int, vararg textViews: TextView): BetterLinkMovementMethod {
val movementMethod = newInstance()
for (textView in textViews) {
addLinks(linkifyMask, movementMethod, textView)
}
return movementMethod
}
/**
* Like [.linkify], but can be used for TextViews with HTML links.
*
* @param textViews The TextViews on which a [BetterLinkMovementMethod] should be registered.
* @return The registered [BetterLinkMovementMethod] on the TextViews.
*/
fun linkifyHtml(vararg textViews: TextView): BetterLinkMovementMethod {
return linkify(LINKIFY_NONE, *textViews)
}
/**
* Recursively register a [BetterLinkMovementMethod] on every TextView inside a layout.
*
* @param linkifyMask One of [Linkify.ALL], [Linkify.PHONE_NUMBERS], [Linkify.MAP_ADDRESSES],
* [Linkify.WEB_URLS] and [Linkify.EMAIL_ADDRESSES].
* @return The registered [BetterLinkMovementMethod] on the TextViews.
*/
fun linkify(linkifyMask: Int, viewGroup: ViewGroup): BetterLinkMovementMethod {
val movementMethod = newInstance()
rAddLinks(linkifyMask, viewGroup, movementMethod)
return movementMethod
}
/**
* Like [.linkify], but can be used for TextViews with HTML links.
*
* @return The registered [BetterLinkMovementMethod] on the TextViews.
*/
fun linkifyHtml(viewGroup: ViewGroup): BetterLinkMovementMethod {
return linkify(LINKIFY_NONE, viewGroup)
}
/**
* Recursively register a [BetterLinkMovementMethod] on every TextView inside a layout.
*
* @param linkifyMask One of [Linkify.ALL], [Linkify.PHONE_NUMBERS], [Linkify.MAP_ADDRESSES],
* [Linkify.WEB_URLS] and [Linkify.EMAIL_ADDRESSES].
* @return The registered [BetterLinkMovementMethod] on the TextViews.
*/
fun linkify(linkifyMask: Int, activity: Activity): BetterLinkMovementMethod {
// Find the layout passed to setContentView().
val activityLayout =
(activity.findViewById<View>(Window.ID_ANDROID_CONTENT) as ViewGroup).getChildAt(0) as ViewGroup
val movementMethod = newInstance()
rAddLinks(linkifyMask, activityLayout, movementMethod)
return movementMethod
}
/**
* Like [.linkify], but can be used for TextViews with HTML links.
*
* @return The registered [BetterLinkMovementMethod] on the TextViews.
*/
fun linkifyHtml(activity: Activity): BetterLinkMovementMethod {
return linkify(LINKIFY_NONE, activity)
}
/**
* Get a static instance of BetterLinkMovementMethod. Do note that registering a click listener on the returned
* instance is not supported because it will potentially be shared on multiple TextViews.
*/
val instance: BetterLinkMovementMethod?
get() {
if (singleInstance == null) {
singleInstance = BetterLinkMovementMethod()
}
return singleInstance
}
// ======== PUBLIC APIs END ======== //
private fun rAddLinks(
linkifyMask: Int,
viewGroup: ViewGroup,
movementMethod: BetterLinkMovementMethod
) {
for (i in 0 until viewGroup.childCount) {
val child = viewGroup.getChildAt(i)
if (child is ViewGroup) {
// Recursively find child TextViews.
rAddLinks(linkifyMask, child, movementMethod)
} else if (child is TextView) {
addLinks(linkifyMask, movementMethod, child)
}
}
}
private fun addLinks(
linkifyMask: Int,
movementMethod: BetterLinkMovementMethod,
textView: TextView
) {
textView.movementMethod = movementMethod
if (linkifyMask != LINKIFY_NONE) {
Linkify.addLinks(textView, linkifyMask)
}
}
}
}

@ -48,6 +48,7 @@ open class InertiaScrollTextView @JvmOverloads constructor(
mTouchSlop = vc.scaledTouchSlop
mMinFlingVelocity = vc.scaledMinimumFlingVelocity
mMaxFlingVelocity = vc.scaledMaximumFlingVelocity
movementMethod = BetterLinkMovementMethod.instance
}
fun atTop(): Boolean {

@ -412,8 +412,6 @@
<string name="rule_next_content">正文下一頁 URL 規則 (nextContentUrl)</string>
<string name="rule_web_js">webJs</string>
<string name="rule_source_regex">資源正則 (sourceRegex)</string>
<string name="rule_font">网页内嵌字體(font)</string>
<string name="rule_correct_font">正确字体(font)</string>
<string name="source_icon">圖標 (sourceIcon)</string>
<string name="r_articles">列表規則 (ruleArticles)</string>
@ -732,7 +730,7 @@
<string name="restore_ignore_summary">恢復時忽略一些內容不恢復,方便不同手機配置不同</string>
<string name="read_config">閱讀界面設置</string>
<string name="rule_image_style">图片样式(imageStyle)</string>
<string name="rule_replace_regex">替换规则(replaceRegex)</string>
<string name="rule_replace_regex">正文获取后处理规则(replaceRegex)</string>
<string name="group_name">分組名稱</string>
<string name="note_content">備註內容</string>
<string name="replace_enable_default_t">默认启用替换净化</string>

@ -413,8 +413,6 @@
<string name="rule_next_content">正文下一頁URL規則(nextContentUrl)</string>
<string name="rule_web_js">webJs</string>
<string name="rule_source_regex">資源正則(sourceRegex)</string>
<string name="rule_font">网页内嵌字體(font)</string>
<string name="rule_correct_font">正确字体(font)</string>
<string name="source_icon">圖示(sourceIcon)</string>
<string name="r_articles">列表規則(ruleArticles)</string>
@ -732,7 +730,7 @@
<string name="restore_ignore_summary">復原時忽略一些內容不復原,方便不同手機配置不同</string>
<string name="read_config">閱讀介面設定</string>
<string name="rule_image_style">圖片樣式(imageStyle)</string>
<string name="rule_replace_regex">取代規則(replaceRegex)</string>
<string name="rule_replace_regex">正文获取后处理规则(replaceRegex)</string>
<string name="group_name">分組名稱</string>
<string name="note_content">備註內容</string>
<string name="replace_enable_default_t">預設啟用取代淨化</string>

@ -418,8 +418,6 @@
<string name="rule_next_content">正文下一页URL规则(nextContentUrl)</string>
<string name="rule_web_js">webJs</string>
<string name="rule_source_regex">资源正则(sourceRegex)</string>
<string name="rule_font">网页内嵌字体(font)</string>
<string name="rule_correct_font">正确字体(font)</string>
<string name="source_icon">图标(sourceIcon)</string>
<string name="r_articles">列表规则(ruleArticles)</string>
@ -737,7 +735,7 @@
<string name="restore_ignore_summary">恢复时忽略一些内容不恢复,方便不同手机配置不同</string>
<string name="read_config">阅读界面设置</string>
<string name="rule_image_style">图片样式(imageStyle)</string>
<string name="rule_replace_regex">替换规则(replaceRegex)</string>
<string name="rule_replace_regex">正文获取后处理规则(replaceRegex)</string>
<string name="group_name">分组名称</string>
<string name="note_content">备注内容</string>
<string name="replace_enable_default_t">默认启用替换净化</string>

@ -5,4 +5,7 @@
<item name="tag2" type="id" />
<item name="fast_scroller" type="id" />
<item name="bettermovementmethod_highlight_background_span" type="id" />
</resources>

@ -420,10 +420,8 @@
<string name="rule_next_content">正文下一页URL规则(nextContentUrl)</string>
<string name="rule_web_js">webJs</string>
<string name="rule_source_regex">资源正则(sourceRegex)</string>
<string name="rule_replace_regex">替换规则(replaceRegex)</string>
<string name="rule_replace_regex">正文获取后处理规则(replaceRegex)</string>
<string name="rule_image_style">图片样式(imageStyle)</string>
<string name="rule_font">网页内嵌字体(font)</string>
<string name="rule_correct_font">正确字体(font)</string>
<string name="source_icon">图标(sourceIcon)</string>
<string name="r_articles">列表规则(ruleArticles)</string>

Loading…
Cancel
Save