Merge pull request #478 from gedoor/master

update
pull/481/head
Antecer 4 years ago committed by GitHub
commit c0349b43c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/src/main/assets/updateLog.md
  2. 9
      app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt
  3. 8
      app/src/main/java/io/legado/app/data/entities/BookChapter.kt
  4. 91
      app/src/main/java/io/legado/app/help/BookHelp.kt
  5. 79
      app/src/main/java/io/legado/app/help/ContentProcessor.kt
  6. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  7. 7
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  8. 11
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  9. 96
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  10. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  11. 4
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  12. 4
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  13. 65
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  14. 40
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt
  15. 5
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt
  16. 69
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  17. 4
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
  18. 2
      app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt
  19. 2
      app/src/main/java/io/legado/app/utils/NetworkUtils.kt
  20. 3
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  21. 34
      app/src/main/res/layout/view_read_menu.xml
  22. 2
      build.gradle

@ -3,13 +3,15 @@
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/11/13**
**2020/11/15**
* 正文规则添加字体规则,返回ByteArray
* js添加方法:
```
base64DecodeToByteArray(str: String?): ByteArray?
base64DecodeToByteArray(str: String?, flags: Int): ByteArray?
```
* 导出添加替换净化
* 修复正文内容TalkBack不对的bug,优化视障使用体验
**2020/11/08**
* 优化书源,订阅源导入,添加保持原名选项

@ -32,15 +32,6 @@ interface ReplaceRuleDao {
@Query("SELECT * FROM replace_rules WHERE id in (:ids)")
fun findByIds(vararg ids: Long): List<ReplaceRule>
@Query(
"""
SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')
order by sortOrder
"""
)
fun findEnabledByScope(scope: String): List<ReplaceRule>
@Query(
"""
SELECT * FROM replace_rules WHERE isEnabled = 1

@ -5,6 +5,7 @@ import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.GSON
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.NetworkUtils
@ -61,7 +62,12 @@ data class BookChapter(
}
fun getAbsoluteURL(): String {
return NetworkUtils.getAbsoluteURL(baseUrl, url)!!
val urlArray = url.split(AnalyzeUrl.splitUrlRegex)
var absoluteUrl = NetworkUtils.getAbsoluteURL(baseUrl, urlArray[0])!!
if (urlArray.size > 1) {
absoluteUrl += urlArray[1]
}
return absoluteUrl
}
fun getFileName(): String = String.format("%05d-%s.nb", index, MD5Utils.md5Encode16(title))

@ -1,22 +1,17 @@
package io.legado.app.help
import com.hankcs.hanlp.HanLP
import io.legado.app.App
import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import net.ricecode.similarity.JaroWinklerStrategy
import net.ricecode.similarity.StringSimilarityServiceImpl
import org.jetbrains.anko.toast
import java.io.File
import java.util.concurrent.CopyOnWriteArraySet
import java.util.regex.Matcher
@ -317,90 +312,4 @@ object BookHelp {
.replace(regexOther, "")
}
private var bookName: String? = null
private var bookOrigin: String? = null
private var replaceRules: List<ReplaceRule> = arrayListOf()
@Synchronized
fun upReplaceRules() {
val o = bookOrigin
bookName?.let {
replaceRules = if (o.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(it)
} else {
App.db.replaceRuleDao().findEnabledByScope(it, o)
}
}
}
suspend fun disposeContent(
book: Book,
title: String,
content: String,
): List<String> {
var title1 = title
var content1 = content
if (book.getUseReplaceRule()) {
synchronized(this) {
if (bookName != book.name || bookOrigin != book.origin) {
bookName = book.name
bookOrigin = book.origin
replaceRules = if (bookOrigin.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(bookName!!)
} else {
App.db.replaceRuleDao().findEnabledByScope(bookName!!, bookOrigin!!)
}
}
}
replaceRules.forEach { item ->
item.pattern.let {
if (it.isNotEmpty()) {
try {
content1 = if (item.isRegex) {
content1.replace(it.toRegex(), item.replacement)
} else {
content1.replace(it, item.replacement)
}
} catch (e: Exception) {
withContext(Main) {
App.INSTANCE.toast("${item.name}替换出错")
}
}
}
}
}
}
if (book.getReSegment()) {
content1 = ContentHelp.reSegment(content1, title1)
}
try {
when (AppConfig.chineseConverterType) {
1 -> {
title1 = HanLP.convertToSimplifiedChinese(title1)
content1 = HanLP.convertToSimplifiedChinese(content1)
}
2 -> {
title1 = HanLP.convertToTraditionalChinese(title1)
content1 = HanLP.convertToTraditionalChinese(content1)
}
}
} catch (e: Exception) {
withContext(Main) {
App.INSTANCE.toast("简繁转换出错")
}
}
val contents = arrayListOf<String>()
content1.split("\n").forEach {
val str = it.replace("^[\\n\\s\\r]+".toRegex(), "")
if (contents.isEmpty()) {
contents.add(title1)
if (str != title1 && str.isNotEmpty()) {
contents.add("${ReadBookConfig.paragraphIndent}$str")
}
} else if (str.isNotEmpty()) {
contents.add("${ReadBookConfig.paragraphIndent}$str")
}
}
return contents
}
}

@ -0,0 +1,79 @@
package io.legado.app.help
import com.hankcs.hanlp.HanLP
import io.legado.app.App
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.ReplaceRule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.anko.toast
class ContentProcessor(private val bookName: String, private val bookOrigin: String) {
private var replaceRules = arrayListOf<ReplaceRule>()
init {
upReplaceRules()
}
@Synchronized
fun upReplaceRules() {
replaceRules.clear()
replaceRules.addAll(App.db.replaceRuleDao().findEnabledByScope(bookName, bookOrigin))
}
suspend fun getContent(
book: Book,
title: String, //已经经过简繁转换
content: String,
isRead: Boolean = true
): List<String> {
var content1 = content
if (book.getUseReplaceRule()) {
replaceRules.forEach { item ->
if (item.pattern.isNotEmpty()) {
try {
content1 = if (item.isRegex) {
content1.replace(item.pattern.toRegex(), item.replacement)
} else {
content1.replace(item.pattern, item.replacement)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
App.INSTANCE.toast("${item.name}替换出错")
}
}
}
}
}
if (isRead) {
if (book.getReSegment()) {
content1 = ContentHelp.reSegment(content1, title)
}
try {
when (AppConfig.chineseConverterType) {
1 -> content1 = HanLP.convertToSimplifiedChinese(content1)
2 -> content1 = HanLP.convertToTraditionalChinese(content1)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
App.INSTANCE.toast("简繁转换出错")
}
}
}
val contents = arrayListOf<String>()
content1.split("\n").forEach {
val str = it.replace("^[\\n\\s\\r]+".toRegex(), "")
if (contents.isEmpty()) {
contents.add(title)
if (str != title && str.isNotEmpty()) {
contents.add("${ReadBookConfig.paragraphIndent}$str")
}
} else if (str.isNotEmpty()) {
contents.add("${ReadBookConfig.paragraphIndent}$str")
}
}
return contents
}
}

@ -45,7 +45,7 @@ class AnalyzeUrl(
headerMapF: Map<String, String>? = null
) : JsExtensions {
companion object {
private val splitUrlRegex = Regex(",\\s*(?=\\{)")
val splitUrlRegex = Regex(",\\s*(?=\\{)")
private val pagePattern = Pattern.compile("<(.*?)>")
private val jsonType = MediaType.parse("application/json; charset=utf-8")
}

@ -144,9 +144,10 @@ class WebBook(val bookSource: BookSource) {
).getResponseAwait(bookSource.bookSourceUrl)
BookChapterList.analyzeChapterList(
this,
book, res.body,
book,
res.body,
bookSource,
res.url
book.tocUrl
)
}
@ -211,7 +212,7 @@ class WebBook(val bookSource: BookSource) {
book,
bookChapter,
bookSource,
res.url,
bookChapter.getAbsoluteURL(),
nextChapterUrl
)
}

@ -8,10 +8,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.ReadRecord
import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.IntentDataHelp
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.*
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService
@ -30,6 +27,7 @@ import org.jetbrains.anko.toast
object ReadBook {
var titleDate = MutableLiveData<String>()
var book: Book? = null
var contentProcessor: ContentProcessor? = null
var inBookshelf = false
var chapterSize = 0
var durChapterIndex = 0
@ -48,6 +46,7 @@ object ReadBook {
fun resetData(book: Book) {
this.book = book
contentProcessor = ContentProcessor(book.name, book.origin)
readRecord.bookName = book.name
readRecord.readTime = App.db.readRecordDao().getReadTime(book.name) ?: 0
durChapterIndex = book.durChapterIndex
@ -372,13 +371,14 @@ object ReadBook {
resetPageOffset: Boolean
) {
Coroutine.async {
ImageProvider.clearOut(durChapterIndex)
if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) {
chapter.title = when (AppConfig.chineseConverterType) {
1 -> HanLP.convertToSimplifiedChinese(chapter.title)
2 -> HanLP.convertToTraditionalChinese(chapter.title)
else -> chapter.title
}
val contents = BookHelp.disposeContent(book, chapter.title, content)
val contents = contentProcessor!!.getContent(book, chapter.title, content)
when (chapter.index) {
durChapterIndex -> {
curTextChapter =
@ -393,7 +393,6 @@ object ReadBook {
callBack?.upView()
curPageChanged()
callBack?.contentLoadFinish()
ImageProvider.clearOut(durChapterIndex)
}
durChapterIndex - 1 -> {
prevTextChapter =

@ -10,6 +10,7 @@ import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book
import io.legado.app.help.BookHelp
import io.legado.app.help.ContentProcessor
import io.legado.app.help.storage.WebDavHelp
import io.legado.app.utils.*
import java.io.File
@ -35,12 +36,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
}
}
private fun export(doc: DocumentFile, book: Book) {
private suspend fun export(doc: DocumentFile, book: Book) {
val filename = "${book.name} by ${book.author}.txt"
val content = getAllContents(book)
DocumentUtils.createFileIfNotExist(doc, filename)
?.writeText(context, content)
if(App.INSTANCE.getPrefBoolean(PreferKey.webDavCacheBackup,false)) {
if (App.INSTANCE.getPrefBoolean(PreferKey.webDavCacheBackup, false)) {
FileUtils.createFileIfNotExist(
File(FileUtils.getCachePath()),
filename
@ -50,74 +51,75 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
// 上传完删除cache文件
FileUtils.deleteFile("${FileUtils.getCachePath()}${File.separator}${filename}")
}
App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter ->
BookHelp.getContent(book, chapter).let { content ->
content?.split("\n")?.forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) {
var src = matcher.group(1)
src = NetworkUtils.getAbsoluteURL(chapter.url, src)
src?.let {
val vFile = BookHelp.getImage(book, src)
if (vFile.exists()) {
DocumentUtils.createFileIfNotExist(doc,
"${index}-${MD5Utils.md5Encode16(src)}.jpg",
subDirs = arrayOf("${book.name}_${book.author}",
"images",
chapter.title))
?.writeBytes(context, vFile.readBytes())
}
}
}
}
getSrcList(book).forEach {
val vFile = BookHelp.getImage(book, it.third)
if (vFile.exists()) {
DocumentUtils.createFileIfNotExist(
doc,
"${it.second}-${MD5Utils.md5Encode16(it.third)}.jpg",
subDirs = arrayOf("${book.name}_${book.author}", "images", it.first)
)?.writeBytes(context, vFile.readBytes())
}
}
}
private fun export(file: File, book: Book) {
private suspend fun export(file: File, book: Book) {
val filename = "${book.name} by ${book.author}.txt"
FileUtils.createFileIfNotExist(file, filename)
.writeText(getAllContents(book))
if(App.INSTANCE.getPrefBoolean(PreferKey.webDavCacheBackup,false)) {
if (App.INSTANCE.getPrefBoolean(PreferKey.webDavCacheBackup, false)) {
WebDavHelp.exportWebDav(file.absolutePath, filename) // 导出到webdav
}
App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter ->
BookHelp.getContent(book, chapter).let { content ->
content?.split("\n")?.forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) {
var src = matcher.group(1)
src = NetworkUtils.getAbsoluteURL(chapter.url, src)
src?.let {
val vFile = BookHelp.getImage(book, src)
if (vFile.exists()) {
FileUtils.createFileIfNotExist(file,
"${book.name}_${book.author}",
"images",
chapter.title,
"${index}-${MD5Utils.md5Encode16(src)}.jpg")
.writeBytes(vFile.readBytes())
}
}
}
}
getSrcList(book).forEach {
val vFile = BookHelp.getImage(book, it.third)
if (vFile.exists()) {
FileUtils.createFileIfNotExist(
file,
"${book.name}_${book.author}",
"images",
it.first,
"${it.second}-${MD5Utils.md5Encode16(it.third)}.jpg"
).writeBytes(vFile.readBytes())
}
}
}
private fun getAllContents(book: Book): String {
private suspend fun getAllContents(book: Book): String {
val contentProcessor = ContentProcessor(book.name, book.origin)
val stringBuilder = StringBuilder()
stringBuilder.append(book.name)
.append("\n")
.append(context.getString(R.string.author_show, book.author))
App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter ->
BookHelp.getContent(book, chapter).let {
BookHelp.getContent(book, chapter).let { content ->
val content1 = content?.let {
contentProcessor.getContent(book, chapter.title, it, false)
}
stringBuilder.append("\n\n")
.append(chapter.title)
.append("\n")
.append(it)
.append(content1)
}
}
return stringBuilder.toString()
}
private fun getSrcList(book: Book): ArrayList<Triple<String, Int, String>> {
val srcList = arrayListOf<Triple<String, Int, String>>()
App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter ->
BookHelp.getContent(book, chapter)?.let { content ->
content.split("\n").forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) {
var src = matcher.group(1)
src = NetworkUtils.getAbsoluteURL(chapter.url, src)
src?.let {
srcList.add(Triple(chapter.title, index, src))
}
}
}
}
}
return srcList
}
}

@ -276,7 +276,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
*/
fun replaceRuleChanged() {
execute {
BookHelp.upReplaceRules()
ReadBook.contentProcessor?.upReplaceRules()
ReadBook.loadContent(resetPageOffset = false)
}
}

@ -47,13 +47,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
init {
callBack = activity as CallBack
contentDescription = textPage.text
}
fun setContent(pageData: PageData) {
this.textChapter = pageData.textChapter
this.textPage = pageData.textPage
contentDescription = textPage.text
invalidate()
}
@ -193,11 +191,13 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textPage = pageFactory.curData.textPage
pageOffset -= textPage.height.toInt()
upView?.invoke(textPage)
contentDescription = textPage.text
} else if (pageOffset < -textPage.height) {
pageOffset += textPage.height.toInt()
pageFactory.moveToNext(false)
textPage = pageFactory.curData.textPage
upView?.invoke(textPage)
contentDescription = textPage.text
}
invalidate()
}

@ -226,6 +226,10 @@ class ContentView(context: Context) : FrameLayout(context) {
content_text_view.setContent(pageData)
}
fun setContentDescription(content: String) {
content_text_view.contentDescription = content
}
fun resetPageOffset() {
content_text_view.resetPageOffset()
}

@ -72,19 +72,18 @@ class PageView(context: Context, attrs: AttributeSet) :
private var firstCharIndex: Int = 0
val slopSquare by lazy { ViewConfiguration.get(context).scaledTouchSlop }
private val topLeftRectF = RectF(0F, 0F, width * 0.33f, height * 0.33f)
private val topCenterRectF = RectF(width * 0.33f, 0F, width * 0.66f, height * 0.33f)
private val topRightRectF = RectF(width * 0.36f, 0F, width.toFloat(), height * 0.33f)
private val middleLeftRectF = RectF(0F, height * 0.33f, width * 0.33f, height * 0.66f)
private val centerRectF = RectF(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
private val middleRightRectF =
RectF(width * 0.66f, height * 0.33f, width.toFloat(), height * 0.66f)
private val bottomLeftRectF = RectF(0F, height * 0.66f, width * 0.33f, height.toFloat())
private val bottomCenterRectF =
RectF(width * 0.33f, height * 0.66f, width * 0.66f, height.toFloat())
private val bottomRightRectF =
RectF(width * 0.66f, height * 0.66f, width.toFloat(), height.toFloat())
private val autoPageRect by lazy { Rect() }
private val tlRect = RectF(10F, 10F, width * 0.33f, height * 0.33f)
private val tcRect = RectF(width * 0.33f, 10F, width * 0.66f, height * 0.33f)
private val trRect = RectF(width * 0.36f, 10F, width - 10f, height * 0.33f)
private val mlRect = RectF(10F, height * 0.33f, width * 0.33f, height * 0.66f)
private val mcRect = RectF(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
private val mrRect = RectF(width * 0.66f, height * 0.33f, width - 10f, height * 0.66f)
private val blRect = RectF(10F, height * 0.66f, width * 0.33f, height - 10f)
private val bcRect = RectF(width * 0.33f, height * 0.66f, width * 0.66f, height - 10f)
private val brRect = RectF(width * 0.66f, height * 0.66f, width - 10f, height - 10f)
private val autoPageRect by lazy {
Rect()
}
private val autoPagePint by lazy {
Paint().apply {
color = context.accentColor
@ -102,15 +101,15 @@ class PageView(context: Context, attrs: AttributeSet) :
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
topLeftRectF.set(0F, 0F, width * 0.33f, height * 0.33f)
topCenterRectF.set(width * 0.33f, 0F, width * 0.66f, height * 0.33f)
topRightRectF.set(width * 0.36f, 0F, width.toFloat(), height * 0.33f)
middleLeftRectF.set(0F, height * 0.33f, width * 0.33f, height * 0.66f)
centerRectF.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
middleRightRectF.set(width * 0.66f, height * 0.33f, width.toFloat(), height * 0.66f)
bottomLeftRectF.set(0F, height * 0.66f, width * 0.33f, height.toFloat())
bottomCenterRectF.set(width * 0.33f, height * 0.66f, width * 0.66f, height.toFloat())
bottomRightRectF.set(width * 0.66f, height * 0.66f, width.toFloat(), height.toFloat())
tlRect.set(10F, 10F, width * 0.33f, height * 0.33f)
tcRect.set(width * 0.33f, 10F, width * 0.66f, height * 0.33f)
trRect.set(width * 0.36f, 10F, width - 10f, height * 0.33f)
mlRect.set(10F, height * 0.33f, width * 0.33f, height * 0.66f)
mcRect.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)
mrRect.set(width * 0.66f, height * 0.33f, width - 10f, height * 0.66f)
blRect.set(10F, height * 0.66f, width * 0.33f, height - 10f)
bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height - 10f)
brRect.set(width * 0.66f, height * 0.66f, width - 10f, height - 10f)
prevPage.x = -w.toFloat()
pageDelegate?.setViewSize(w, h)
if (oldw != 0 && oldh != 0) {
@ -258,38 +257,37 @@ class PageView(context: Context, attrs: AttributeSet) :
/**
* 单击
*/
private fun onSingleTapUp(): Boolean {
private fun onSingleTapUp() {
when {
isTextSelected -> isTextSelected = false
centerRectF.contains(startX, startY) -> if (!isAbortAnim) {
mcRect.contains(startX, startY) -> if (!isAbortAnim) {
click(AppConfig.clickActionMiddleCenter)
}
bottomCenterRectF.contains(startX, startY) -> {
bcRect.contains(startX, startY) -> {
click(AppConfig.clickActionBottomCenter)
}
bottomLeftRectF.contains(startX, startY) -> {
blRect.contains(startX, startY) -> {
click(AppConfig.clickActionBottomLeft)
}
bottomRightRectF.contains(startX, startY) -> {
brRect.contains(startX, startY) -> {
click(AppConfig.clickActionBottomRight)
}
middleLeftRectF.contains(startX, startY) -> {
mlRect.contains(startX, startY) -> {
click(AppConfig.clickActionMiddleLeft)
}
middleRightRectF.contains(startX, startY) -> {
mrRect.contains(startX, startY) -> {
click(AppConfig.clickActionMiddleRight)
}
topLeftRectF.contains(startX, startY) -> {
tlRect.contains(startX, startY) -> {
click(AppConfig.clickActionTopLeft)
}
topCenterRectF.contains(startX, startY) -> {
tcRect.contains(startX, startY) -> {
click(AppConfig.clickActionTopCenter)
}
topRightRectF.contains(startX, startY) -> {
trRect.contains(startX, startY) -> {
click(AppConfig.clickActionTopRight)
}
}
return true
}
private fun click(action: Int) {
@ -375,6 +373,7 @@ class PageView(context: Context, attrs: AttributeSet) :
}
override fun upContent(relativePosition: Int, resetPageOffset: Boolean) {
curPage.setContentDescription(pageFactory.curData.textPage.text)
if (isScroll && !callBack.isAutoPage) {
curPage.setContent(pageFactory.curData, resetPageOffset)
} else {

@ -24,7 +24,9 @@ import io.legado.app.utils.getViewModel
import io.legado.app.utils.observeEvent
import kotlinx.android.synthetic.main.activity_search_content.*
import kotlinx.android.synthetic.main.view_search.*
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.sdk27.listeners.onClick
@ -34,7 +36,6 @@ class SearchContentActivity :
override val viewModel: SearchContentViewModel
get() = getViewModel(SearchContentViewModel::class.java)
lateinit var adapter: SearchContentAdapter
private lateinit var mLayoutManager: UpLinearLayoutManager
private var searchResultCounts = 0
@ -97,14 +98,12 @@ class SearchContentActivity :
@SuppressLint("SetTextI18n")
private fun initBook() {
launch {
tv_current_search_info.text = "搜索结果:$searchResultCounts"
viewModel.book?.let {
initCacheFileNames(it)
durChapterIndex = it.durChapterIndex
intent.getStringExtra("searchWord")?.let { searchWord ->
search_view.setQuery(searchWord, true)
}
tv_current_search_info.text = "搜索结果:$searchResultCounts"
viewModel.book?.let {
initCacheFileNames(it)
durChapterIndex = it.durChapterIndex
intent.getStringExtra("searchWord")?.let { searchWord ->
search_view.setQuery(searchWord, true)
}
}
}
@ -141,14 +140,13 @@ class SearchContentActivity :
var searchResults = listOf<SearchResult>()
launch(Dispatchers.Main) {
App.db.bookChapterDao().getChapterList(viewModel.bookUrl).map { chapter ->
val job = async(Dispatchers.IO) {
withContext(Dispatchers.IO) {
if (isLocalBook
|| adapter.cacheFileNames.contains(chapter.getFileName())
) {
searchResults = searchChapter(newText, chapter)
}
}
job.await()
if (searchResults.isNotEmpty()) {
searchResultList.addAll(searchResults)
refresh_progress_bar.isAutoLoading = false
@ -164,26 +162,27 @@ class SearchContentActivity :
private suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> {
val searchResults: MutableList<SearchResult> = mutableListOf()
var positions: List<Int>
var replaceContents: List<String>? = null
var replaceContents: List<String>?
var totalContents: String
if (chapter != null) {
viewModel.book?.let { book ->
val bookContent = BookHelp.getContent(book, chapter)
if (bookContent != null) {
//搜索替换后的正文
val job = async(Dispatchers.IO) {
withContext(Dispatchers.IO) {
chapter.title = when (AppConfig.chineseConverterType) {
1 -> HanLP.convertToSimplifiedChinese(chapter.title)
2 -> HanLP.convertToTraditionalChinese(chapter.title)
else -> chapter.title
}
replaceContents = BookHelp.disposeContent(book, chapter.title, bookContent)
replaceContents =
viewModel.contentProcessor!!.getContent(
book,
chapter.title,
bookContent
)
}
job.await()
while (replaceContents == null) {
delay(100L)
}
totalContents = replaceContents!!.joinToString("")
totalContents = replaceContents?.joinToString("") ?: bookContent
positions = searchPosition(totalContents, query)
var count = 1
positions.map {
@ -241,7 +240,6 @@ class SearchContentActivity :
get() = viewModel.book?.isLocalBook() == true
override fun openSearchResult(searchResult: SearchResult) {
val searchData = Intent()
searchData.putExtra("index", searchResult.chapterIndex)
searchData.putExtra("contentPosition", searchResult.contentPosition)

@ -5,16 +5,21 @@ import android.app.Application
import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.Book
import io.legado.app.help.ContentProcessor
class SearchContentViewModel(application: Application) : BaseViewModel(application) {
var bookUrl: String = ""
var book: Book? = null
var contentProcessor: ContentProcessor? = null
var lastQuery: String = ""
fun initBook(bookUrl: String, success: () -> Unit) {
this.bookUrl = bookUrl
execute {
book = App.db.bookDao().getBook(bookUrl)
book?.let {
contentProcessor = ContentProcessor(it.name, it.origin)
}
}.onSuccess {
success.invoke()
}

@ -68,44 +68,43 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
}
}
@Synchronized
private fun updateToc() {
synchronized(this) {
bookMap.forEach { bookEntry ->
if (!updateList.contains(bookEntry.key)) {
val book = bookEntry.value
synchronized(this) {
updateList.add(book.bookUrl)
postEvent(EventBus.UP_BOOK, book.bookUrl)
}
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource ->
val webBook = WebBook(bookSource)
webBook.getChapterList(book, context = upTocPool)
.timeout(300000)
.onSuccess(IO) {
App.db.bookDao().update(book)
App.db.bookChapterDao().delByBook(book.bookUrl)
App.db.bookChapterDao().insert(*it.toTypedArray())
cacheBook(webBook, book)
}
.onError {
it.printStackTrace()
}
.onFinally {
synchronized(this) {
bookMap.remove(bookEntry.key)
updateList.remove(book.bookUrl)
postEvent(EventBus.UP_BOOK, book.bookUrl)
upNext()
}
bookMap.forEach { bookEntry ->
if (!updateList.contains(bookEntry.key)) {
val book = bookEntry.value
synchronized(this) {
updateList.add(book.bookUrl)
postEvent(EventBus.UP_BOOK, book.bookUrl)
}
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource ->
val webBook = WebBook(bookSource)
webBook.getChapterList(book, context = upTocPool)
.timeout(300000)
.onSuccess(IO) {
App.db.bookDao().update(book)
App.db.bookChapterDao().delByBook(book.bookUrl)
App.db.bookChapterDao().insert(*it.toTypedArray())
cacheBook(webBook, book)
}
.onError {
it.printStackTrace()
}
.onFinally {
synchronized(this) {
bookMap.remove(bookEntry.key)
updateList.remove(book.bookUrl)
postEvent(EventBus.UP_BOOK, book.bookUrl)
upNext()
}
} ?: synchronized(this) {
bookMap.remove(bookEntry.key)
updateList.remove(book.bookUrl)
postEvent(EventBus.UP_BOOK, book.bookUrl)
upNext()
}
return
}
} ?: synchronized(this) {
bookMap.remove(bookEntry.key)
updateList.remove(book.bookUrl)
postEvent(EventBus.UP_BOOK, book.bookUrl)
upNext()
}
return
}
}
}

@ -19,12 +19,12 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.BookHelp
import io.legado.app.help.IntentDataHelp
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.association.ImportReplaceRuleActivity
import io.legado.app.ui.filepicker.FilePicker
import io.legado.app.ui.filepicker.FilePickerDialog
@ -277,7 +277,7 @@ class ReplaceRuleActivity :
override fun onDestroy() {
super.onDestroy()
Coroutine.async { BookHelp.upReplaceRules() }
Coroutine.async { ReadBook.contentProcessor?.upReplaceRules() }
}
override fun upCountView() {

@ -139,6 +139,7 @@ class CircleImageView @JvmOverloads constructor(
DEFAULT_CIRCLE_BACKGROUND_COLOR
)
text = a.getString(R.styleable.CircleImageView_text)
contentDescription = text
if (a.hasValue(R.styleable.CircleImageView_textColor)) {
textColor = a.getColor(
R.styleable.CircleImageView_textColor,
@ -226,6 +227,7 @@ class CircleImageView @JvmOverloads constructor(
fun setText(text: String?) {
this.text = text
contentDescription = text
invalidate()
}

@ -10,6 +10,7 @@ import java.util.regex.Pattern
@Suppress("unused", "MemberVisibilityCanBePrivate")
object NetworkUtils {
fun getUrl(response: Response<*>): String {
response.raw().networkResponse()?.let {
return it.request().url().toString()
@ -79,6 +80,7 @@ object NetworkUtils {
fun getAbsoluteURL(baseURL: String?, relativePath: String?): String? {
if (baseURL.isNullOrEmpty()) return relativePath
if (relativePath.isNullOrEmpty()) return baseURL
if (relativePath.isAbsUrl()) return baseURL
var relativeUrl = relativePath
try {
val absoluteUrl = URL(baseURL.substringBefore(","))

@ -23,8 +23,7 @@ fun String.parseToUri(): Uri {
fun String?.isAbsUrl() =
this?.let {
it.startsWith("http://", true)
|| it.startsWith("https://", true)
it.startsWith("http://", true) || it.startsWith("https://", true)
} ?: false
fun String?.isJson(): Boolean =

@ -10,6 +10,7 @@
android:id="@+id/vw_menu_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/content"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="0dp" />
@ -172,19 +173,22 @@
<!--底部设置栏-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="100dp">
android:layout_height="100dp"
android:importantForAccessibility="no">
<View
android:id="@+id/vw_bg"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:importantForAccessibility="no" />
<LinearLayout
android:id="@+id/ll_bottom_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_menu"
android:orientation="vertical">
android:orientation="vertical"
android:importantForAccessibility="no">
<!--章节设置-->
<LinearLayout
android:layout_width="match_parent"
@ -193,7 +197,8 @@
android:layout_marginTop="5dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="5dp"
android:orientation="horizontal">
android:orientation="horizontal"
android:importantForAccessibility="no">
<TextView
android:id="@+id/tv_pre"
@ -239,12 +244,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
android:orientation="horizontal"
android:importantForAccessibility="no">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
android:layout_weight="1"
android:importantForAccessibility="no" />
<!--目录按钮-->
<LinearLayout
@ -283,7 +290,8 @@
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
android:layout_weight="2"
android:importantForAccessibility="no" />
<!--调节按钮-->
<LinearLayout
@ -322,7 +330,9 @@
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
android:layout_weight="2"
android:importantForAccessibility="no" />
<!--界面按钮-->
<LinearLayout
android:id="@+id/ll_font"
@ -360,7 +370,9 @@
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
android:layout_weight="2"
android:importantForAccessibility="no" />
<!--设置按钮-->
<LinearLayout
android:id="@+id/ll_setting"
@ -398,7 +410,9 @@
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
android:layout_weight="1"
android:importantForAccessibility="no" />
</LinearLayout>
<View

@ -11,7 +11,7 @@ buildscript {
maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:4.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'de.timfreiheit.resourceplaceholders:placeholders:0.3'
classpath 'com.google.gms:google-services:4.3.4'

Loading…
Cancel
Save