Merge remote-tracking branch 'origin/master'

This commit is contained in:
kunfei
2023-05-09 08:11:09 +08:00
19 changed files with 2100 additions and 118 deletions
+2 -2
View File
@@ -149,7 +149,7 @@ dependencies {
//noinspection GradleDependency,DifferentStdlibGradleVersion //noinspection GradleDependency,DifferentStdlibGradleVersion
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
//协程 //协程
def coroutines_version = '1.6.4' def coroutines_version = '1.7.0'
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version")
@@ -210,7 +210,7 @@ dependencies {
//网络 //网络
implementation('com.squareup.okhttp3:okhttp:4.11.0') implementation('com.squareup.okhttp3:okhttp:4.11.0')
appImplementation(fileTree(dir: 'cronetlib', include: ['*.jar', '*.aar'])) appImplementation(fileTree(dir: 'cronetlib', include: ['*.jar', '*.aar']))
appImplementation 'com.google.protobuf:protobuf-javalite:3.22.3' appImplementation 'com.google.protobuf:protobuf-javalite:3.22.4'
//Glide //Glide
def glideVersion = "4.15.1" def glideVersion = "4.15.1"
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
{"x86_64":"0dc99aac60adf1a59f87c27dcc3110d6","x86":"4792bb318202d8e34a2f9a4da69ee372","armeabi-v7a":"72c6a274cf45ac90eabf0aec303a8ed5","arm64-v8a":"fde78ffa83893ea3112918b97b85a0e7","version":"113.0.5672.61"} {"x86_64":"8fa0f4f53877dd089e49bcbaaac95108","x86":"6f030a93e7995bfd1675208ee8d83af5","armeabi-v7a":"d61a5169ad4ad0a99fa5d1f3c9702e9a","arm64-v8a":"dad33527a4038a9f455c7ef0dd3e2af6","version":"113.0.5672.76"}
+11 -1
View File
@@ -12,9 +12,19 @@
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。 * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
**2023/05/08**
* 更新cronet: 113.0.5672.76
* web: 全新传书页面 [MakeHui](https://github.com/MakeHui);修正源编辑列表操作逻辑和多选性能
* 优化部分文本提示toast
* webView: 忽略部分网站的证书错误
* 备份恢复webDav: 输入密码后自动验证
* 换源添加加载字数选项
* 修复全文搜索开始bug
* 其中一些更新由 Xwite, Horis, Jason Yao提供
**2023/05/03** **2023/05/03**
* 更新cronet: 113.0.5672.61
* 文件类书源文件后缀识别:自动识别失败后会读取下载链接UrlOption的type字段 * 文件类书源文件后缀识别:自动识别失败后会读取下载链接UrlOption的type字段
``` ```
@@ -19,9 +19,11 @@ object SourceHelp {
private val list18Plus by lazy { private val list18Plus by lazy {
try { try {
return@lazy String(appCtx.assets.open("18PlusList.txt").readBytes()) return@lazy String(appCtx.assets.open("18PlusList.txt").readBytes())
.splitNotBlank("\n") .splitNotBlank("\n").map {
EncoderUtils.base64Decode(it)
}.toHashSet()
} catch (e: Exception) { } catch (e: Exception) {
return@lazy arrayOf<String>() return@lazy hashSetOf<String>()
} }
} }
@@ -32,42 +34,42 @@ object SourceHelp {
} }
fun insertRssSource(vararg rssSources: RssSource) { fun insertRssSource(vararg rssSources: RssSource) {
rssSources.forEach { rssSource -> val rssSourcesGroup = rssSources.groupBy {
if (is18Plus(rssSource.sourceUrl)) { is18Plus(it.sourceUrl)
}
rssSourcesGroup[true]?.forEach {
handler.post { handler.post {
appCtx.toastOnUi("${rssSource.sourceName}是18+网址,禁止导入.") appCtx.toastOnUi("${it.sourceName}是18+网址,禁止导入.")
} }
} else {
appDb.rssSourceDao.insert(rssSource)
} }
rssSourcesGroup[false]?.let {
appDb.rssSourceDao.insert(*it.toTypedArray())
} }
} }
fun insertBookSource(vararg bookSources: BookSource) { fun insertBookSource(vararg bookSources: BookSource) {
bookSources.forEach { bookSource -> val bookSourcesGroup = bookSources.groupBy {
if (is18Plus(bookSource.bookSourceUrl)) { is18Plus(it.bookSourceUrl)
}
bookSourcesGroup[true]?.forEach {
handler.post { handler.post {
appCtx.toastOnUi("${bookSource.bookSourceName}是18+网址,禁止导入.") appCtx.toastOnUi("${it.bookSourceName}是18+网址,禁止导入.")
} }
} else {
appDb.bookSourceDao.insert(bookSource)
} }
bookSourcesGroup[false]?.let {
appDb.bookSourceDao.insert(*it.toTypedArray())
} }
} }
private fun is18Plus(url: String?): Boolean { private fun is18Plus(url: String?): Boolean {
url ?: return false url ?: return false
val baseUrl = NetworkUtils.getBaseUrl(url)
baseUrl ?: return false
if (AppConst.isPlayChannel) return false if (AppConst.isPlayChannel) return false
val baseUrl = NetworkUtils.getBaseUrl(url) ?: return false
kotlin.runCatching { kotlin.runCatching {
val host = baseUrl.split("//", ".") val host = baseUrl.split("//", ".").let {
val base64Url = EncoderUtils.base64Encode("${host[host.lastIndex - 1]}.${host.last()}") if (it.size > 2) "${it[it.lastIndex - 1]}.${it.last()}" else return false
list18Plus.forEach {
if (base64Url == it) {
return true
}
} }
return list18Plus.contains(host)
} }
return false return false
} }
@@ -307,7 +307,6 @@ object ReadBook : CoroutineScope by MainScope() {
contentLoadFinish(book, chapter, it, upContent, resetPageOffset) { contentLoadFinish(book, chapter, it, upContent, resetPageOffset) {
success?.invoke() success?.invoke()
} }
removeLoading(chapter.index)
} ?: download(this, chapter, resetPageOffset = resetPageOffset) } ?: download(this, chapter, resetPageOffset = resetPageOffset)
} ?: removeLoading(index) } ?: removeLoading(index)
}.onError { }.onError {
@@ -320,16 +319,15 @@ object ReadBook : CoroutineScope by MainScope() {
/** /**
* 下载正文 * 下载正文
*/ */
private fun download(index: Int) { private fun downloadIndex(index: Int) {
if (index < 0) return if (index < 0) return
if (index > chapterSize - 1) { if (index > chapterSize - 1) {
upToc() upToc()
return return
} }
book?.let { book -> val book = book ?: return
if (book.isLocal) return
if (addLoading(index)) { if (addLoading(index)) {
Coroutine.async { try {
appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter -> appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter ->
if (BookHelp.hasContent(book, chapter)) { if (BookHelp.hasContent(book, chapter)) {
removeLoading(chapter.index) removeLoading(chapter.index)
@@ -337,12 +335,11 @@ object ReadBook : CoroutineScope by MainScope() {
download(this, chapter, false) download(this, chapter, false)
} }
} ?: removeLoading(index) } ?: removeLoading(index)
}.onError { } catch (e: Exception) {
removeLoading(index) removeLoading(index)
} }
} }
} }
}
/** /**
* 下载正文 * 下载正文
@@ -353,20 +350,17 @@ object ReadBook : CoroutineScope by MainScope() {
resetPageOffset: Boolean, resetPageOffset: Boolean,
success: (() -> Unit)? = null success: (() -> Unit)? = null
) { ) {
val book = book val book = book ?: return removeLoading(chapter.index)
val bookSource = bookSource val bookSource = bookSource
if (book != null && bookSource != null) { if (bookSource != null) {
CacheBook.getOrCreate(bookSource, book).download(scope, chapter) CacheBook.getOrCreate(bookSource, book).download(scope, chapter)
} else if (book != null) { } else {
val msg = if (book.isLocal) "无内容" else "没有书源" val msg = if (book.isLocal) "无内容" else "没有书源"
contentLoadFinish( contentLoadFinish(
book, chapter, "加载正文失败\n$msg", resetPageOffset = resetPageOffset book, chapter, "加载正文失败\n$msg", resetPageOffset = resetPageOffset
) { ) {
success?.invoke() success?.invoke()
} }
removeLoading(chapter.index)
} else {
removeLoading(chapter.index)
} }
} }
@@ -415,10 +409,12 @@ object ReadBook : CoroutineScope by MainScope() {
curPageChanged() curPageChanged()
callBack?.contentLoadFinish() callBack?.contentLoadFinish()
} }
-1 -> { -1 -> {
prevTextChapter = textChapter prevTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset) if (upContent) callBack?.upContent(offset, resetPageOffset)
} }
1 -> { 1 -> {
nextTextChapter = textChapter nextTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset) if (upContent) callBack?.upContent(offset, resetPageOffset)
@@ -464,7 +460,7 @@ object ReadBook : CoroutineScope by MainScope() {
fun saveRead() { fun saveRead() {
Coroutine.async { Coroutine.async {
book?.let { book -> val book = book ?: return@async
book.lastCheckCount = 0 book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis() book.durChapterTime = System.currentTimeMillis()
book.durChapterIndex = durChapterIndex book.durChapterIndex = durChapterIndex
@@ -477,12 +473,12 @@ object ReadBook : CoroutineScope by MainScope() {
appDb.bookDao.update(book) appDb.bookDao.update(book)
} }
} }
}
/** /**
* 预下载 * 预下载
*/ */
private fun preDownload() { private fun preDownload() {
if (book?.isLocal == true) return
if (AppConfig.preDownloadNum < 2) { if (AppConfig.preDownloadNum < 2) {
return return
} }
@@ -491,12 +487,12 @@ object ReadBook : CoroutineScope by MainScope() {
val maxChapterIndex = durChapterIndex + AppConfig.preDownloadNum val maxChapterIndex = durChapterIndex + AppConfig.preDownloadNum
for (i in durChapterIndex.plus(2)..maxChapterIndex) { for (i in durChapterIndex.plus(2)..maxChapterIndex) {
delay(1000) delay(1000)
download(i) downloadIndex(i)
} }
val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) { for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
delay(1000) delay(1000)
download(i) downloadIndex(i)
} }
} }
} }
@@ -430,7 +430,7 @@ class AudioPlayService : BaseService(),
private fun upMediaSessionPlaybackState(state: Int) { private fun upMediaSessionPlaybackState(state: Int) {
mediaSessionCompat?.setPlaybackState( mediaSessionCompat?.setPlaybackState(
PlaybackStateCompat.Builder() PlaybackStateCompat.Builder()
.setActions(MediaHelp.MEDIA_SESSION_ACTIONS) .setActions(PlaybackStateCompat.ACTION_SEEK_TO)
.setState(state, exoPlayer.currentPosition, 1f) .setState(state, exoPlayer.currentPosition, 1f)
.setBufferedPosition(exoPlayer.bufferedPosition) .setBufferedPosition(exoPlayer.bufferedPosition)
.build() .build()
@@ -27,6 +27,7 @@ import io.legado.app.databinding.DialogBookChangeSourceBinding
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.widget.dialog.WaitDialog import io.legado.app.ui.widget.dialog.WaitDialog
@@ -86,7 +87,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor) binding.toolBar.setBackgroundColor(primaryColor)
viewModel.initData(arguments) viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)
showTitle() showTitle()
initMenu() initMenu()
initRecyclerView() initRecyclerView()
@@ -212,19 +213,23 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
viewModel.refresh() viewModel.refresh()
} }
R.id.menu_load_info -> { R.id.menu_load_info -> {
AppConfig.changeSourceLoadInfo = !item.isChecked AppConfig.changeSourceLoadInfo = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
} }
R.id.menu_load_toc -> { R.id.menu_load_toc -> {
AppConfig.changeSourceLoadToc = !item.isChecked AppConfig.changeSourceLoadToc = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
} }
R.id.menu_load_word_count -> { R.id.menu_load_word_count -> {
AppConfig.changeSourceLoadWordCount = !item.isChecked AppConfig.changeSourceLoadWordCount = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
viewModel.onLoadWordCountChecked(item.isChecked) viewModel.onLoadWordCountChecked(item.isChecked)
} }
R.id.menu_start_stop -> viewModel.startOrStopSearch() R.id.menu_start_stop -> viewModel.startOrStopSearch()
R.id.menu_source_manage -> startActivity<BookSourceActivity>() R.id.menu_source_manage -> startActivity<BookSourceActivity>()
R.id.menu_refresh_list -> viewModel.startRefreshList() R.id.menu_refresh_list -> viewModel.startRefreshList()
@@ -306,7 +311,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
} }
override fun setBookScore(searchBook: SearchBook, score: Int) { override fun setBookScore(searchBook: SearchBook, score: Int) {
viewModel.setBookScore(searchBook,score) viewModel.setBookScore(searchBook, score)
} }
override fun getBookScore(searchBook: SearchBook): Int { override fun getBookScore(searchBook: SearchBook): Int {
@@ -16,12 +16,14 @@ import io.legado.app.data.entities.BookChapter
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.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.book.BookHelp
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.SourceConfig import io.legado.app.help.config.SourceConfig
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -44,6 +46,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null
var name: String = "" var name: String = ""
var author: String = "" var author: String = ""
private var fromReadBookActivity = false
private var oldBook: Book? = null
private var tasks = CompositeCoroutine() private var tasks = CompositeCoroutine()
private var screenKey: String = "" private var screenKey: String = ""
private var bookSourceList = arrayListOf<BookSource>() private var bookSourceList = arrayListOf<BookSource>()
@@ -114,7 +118,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
} }
@CallSuper @CallSuper
open fun initData(arguments: Bundle?) { open fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {
arguments?.let { bundle -> arguments?.let { bundle ->
bundle.getString("name")?.let { bundle.getString("name")?.let {
name = it name = it
@@ -122,6 +126,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
bundle.getString("author")?.let { bundle.getString("author")?.let {
author = it.replace(AppPattern.authorRegex, "") author = it.replace(AppPattern.authorRegex, "")
} }
this.fromReadBookActivity = fromReadBookActivity
oldBook = book
} }
} }
@@ -180,25 +186,27 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
val task = Coroutine.async(scope = viewModelScope, context = searchPool!!) { val task = Coroutine.async(scope = viewModelScope, context = searchPool!!) {
val resultBooks = WebBook.searchBookAwait(source, name) val resultBooks = WebBook.searchBookAwait(source, name)
resultBooks.forEach { searchBook -> resultBooks.forEach { searchBook ->
if (searchBook.name == name) { if (searchBook.name != name) {
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author)) return@forEach
|| !AppConfig.changeSourceCheckAuthor }
) { if (AppConfig.changeSourceCheckAuthor && !searchBook.author.contains(author)) {
return@forEach
}
when { when {
searchBook.latestChapterTitle.isNullOrEmpty() && searchBook.latestChapterTitle.isNullOrEmpty() &&
(AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) -> { (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) -> {
loadBookInfo(source, searchBook.toBook()) loadBookInfo(source, searchBook.toBook())
} }
searchBook.chapterWordCountText.isNullOrBlank() && AppConfig.changeSourceLoadWordCount -> {
AppConfig.changeSourceLoadWordCount -> {
loadBookToc(source, searchBook.toBook()) loadBookToc(source, searchBook.toBook())
} }
else -> { else -> {
searchCallback?.searchSuccess(searchBook) searchCallback?.searchSuccess(searchBook)
} }
} }
} }
}
}
}.timeout(60000L) }.timeout(60000L)
.onError { .onError {
nextSearch() nextSearch()
@@ -211,7 +219,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
private suspend fun loadBookInfo(source: BookSource, book: Book) { private suspend fun loadBookInfo(source: BookSource, book: Book) {
WebBook.getBookInfoAwait(source, book) WebBook.getBookInfoAwait(source, book)
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { if (AppConfig.changeSourceLoadToc || AppConfig.changeSourceLoadWordCount) {
loadBookToc(source, book) loadBookToc(source, book)
} else { } else {
//从详情页里获取最新章节 //从详情页里获取最新章节
@@ -224,7 +232,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
val chapters = WebBook.getChapterListAwait(source, book).getOrThrow() val chapters = WebBook.getChapterListAwait(source, book).getOrThrow()
tocMap[book.bookUrl] = chapters tocMap[book.bookUrl] = chapters
bookMap[book.bookUrl] = book bookMap[book.bookUrl] = book
if (context.getPrefBoolean(PreferKey.changeSourceLoadWordCount)) { if (AppConfig.changeSourceLoadWordCount) {
loadBookWordCount(source, book, chapters) loadBookWordCount(source, book, chapters)
} else { } else {
val searchBook = book.toSearchBook() val searchBook = book.toSearchBook()
@@ -237,16 +245,26 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
book: Book, book: Book,
chapters: List<BookChapter> chapters: List<BookChapter>
) = coroutineScope { ) = coroutineScope {
val chapterIndex = ReadBook.curTextChapter?.chapter?.index ?: (chapters.size - 1) val chapterIndex = if (fromReadBookActivity) {
oldBook?.let {
BookHelp.getDurChapter(
it.durChapterIndex,
it.durChapterTitle,
chapters,
it.totalChapterNum
)
} ?: chapters.lastIndex
} else chapters.lastIndex
val bookChapter = chapters.getOrNull(chapterIndex) val bookChapter = chapters.getOrNull(chapterIndex)
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val pair = try { val pair = try {
if (bookChapter == null) throw NoStackTraceException("章节缺失,总章节数${chapters.size}") if (bookChapter == null) throw NoStackTraceException("章节缺失,总章节数${chapters.size}")
if (!isActive) return@coroutineScope val nextChapterUrl = chapters.getOrNull(chapterIndex + 1)?.url
WebBook.getContentAwait(source, book, bookChapter, null, false).length.let { WebBook.getContentAwait(source, book, bookChapter, nextChapterUrl, false).length.let {
it to "第${chapterIndex + 1}章 字数:${it}" it to "第${chapterIndex + 1}章 字数:${it}"
} }
} catch (t: Throwable) { } catch (t: Throwable) {
if (t is CancellationException) throw t
-1 to "第${chapterIndex + 1}章 获取字数失败:${t.localizedMessage}" -1 to "第${chapterIndex + 1}章 获取字数失败:${t.localizedMessage}"
} }
val endTime = System.currentTimeMillis() val endTime = System.currentTimeMillis()
@@ -13,6 +13,7 @@ import io.legado.app.base.adapter.DiffRecyclerAdapter
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.databinding.ItemChangeSourceBinding import io.legado.app.databinding.ItemChangeSourceBinding
import io.legado.app.help.config.AppConfig
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
import io.legado.app.utils.gone import io.legado.app.utils.gone
import io.legado.app.utils.invisible import io.legado.app.utils.invisible
@@ -55,6 +56,8 @@ class ChangeChapterSourceAdapter(
tvOrigin.text = item.originName tvOrigin.text = item.originName
tvAuthor.text = item.author tvAuthor.text = item.author
tvLast.text = item.getDisplayLastChapterTitle() tvLast.text = item.getDisplayLastChapterTitle()
tvCurrentChapterWordCount.text = item.chapterWordCountText
tvRespondTime.text = context.getString(R.string.respondTime, item.respondTime)
if (callBack.oldBookUrl == item.bookUrl) { if (callBack.oldBookUrl == item.bookUrl) {
ivChecked.visible() ivChecked.visible()
} else { } else {
@@ -82,13 +85,37 @@ class ChangeChapterSourceAdapter(
} else if (score < 0) { } else if (score < 0) {
binding.ivGood.gone() binding.ivGood.gone()
binding.ivBad.visible() binding.ivBad.visible()
DrawableCompat.setTint(binding.ivGood.drawable, appCtx.getCompatColor(R.color.md_red_100)) DrawableCompat.setTint(
DrawableCompat.setTint(binding.ivBad.drawable, appCtx.getCompatColor(R.color.md_blue_A200)) binding.ivGood.drawable,
appCtx.getCompatColor(R.color.md_red_100)
)
DrawableCompat.setTint(
binding.ivBad.drawable,
appCtx.getCompatColor(R.color.md_blue_A200)
)
} else { } else {
binding.ivGood.visible() binding.ivGood.visible()
binding.ivBad.visible() binding.ivBad.visible()
DrawableCompat.setTint(binding.ivGood.drawable, appCtx.getCompatColor(R.color.md_red_100)) DrawableCompat.setTint(
DrawableCompat.setTint(binding.ivBad.drawable, appCtx.getCompatColor(R.color.md_blue_100)) binding.ivGood.drawable,
appCtx.getCompatColor(R.color.md_red_100)
)
DrawableCompat.setTint(
binding.ivBad.drawable,
appCtx.getCompatColor(R.color.md_blue_100)
)
}
if (AppConfig.changeSourceLoadWordCount && !item.chapterWordCountText.isNullOrBlank()) {
tvCurrentChapterWordCount.visible()
} else {
tvCurrentChapterWordCount.gone()
}
if (AppConfig.changeSourceLoadWordCount && item.respondTime >= 0) {
tvRespondTime.visible()
} else {
tvRespondTime.gone()
} }
} }
} }
@@ -2,16 +2,20 @@ package io.legado.app.ui.book.changesource
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.KeyEvent
import android.view.KeyEvent.ACTION_UP import android.view.KeyEvent.ACTION_UP
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.Lifecycle.State.STARTED
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
@@ -28,11 +32,21 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.elevation import io.legado.app.lib.theme.elevation
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.* import io.legado.app.utils.StartActivityContract
import io.legado.app.utils.applyTint
import io.legado.app.utils.cnCompare
import io.legado.app.utils.dpToPx
import io.legado.app.utils.gone
import io.legado.app.utils.observeEvent
import io.legado.app.utils.setLayout
import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -99,7 +113,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor) binding.toolBar.setBackgroundColor(primaryColor)
viewModel.initData(arguments) viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)
showTitle() showTitle()
initMenu() initMenu()
initView() initView()
@@ -124,6 +138,8 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
?.isChecked = AppConfig.changeSourceLoadInfo ?.isChecked = AppConfig.changeSourceLoadInfo
binding.toolBar.menu.findItem(R.id.menu_load_toc) binding.toolBar.menu.findItem(R.id.menu_load_toc)
?.isChecked = AppConfig.changeSourceLoadToc ?.isChecked = AppConfig.changeSourceLoadToc
binding.toolBar.menu.findItem(R.id.menu_load_word_count)
?.isChecked = AppConfig.changeSourceLoadWordCount
} }
private fun initView() { private fun initView() {
@@ -239,6 +255,11 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
AppConfig.changeSourceLoadToc = !item.isChecked AppConfig.changeSourceLoadToc = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
} }
R.id.menu_load_word_count -> {
AppConfig.changeSourceLoadWordCount = !item.isChecked
item.isChecked = !item.isChecked
viewModel.onLoadWordCountChecked(item.isChecked)
}
R.id.menu_start_stop -> viewModel.startOrStopSearch() R.id.menu_start_stop -> viewModel.startOrStopSearch()
R.id.menu_source_manage -> startActivity<BookSourceActivity>() R.id.menu_source_manage -> startActivity<BookSourceActivity>()
else -> if (item?.groupId == R.id.source_group) { else -> if (item?.groupId == R.id.source_group) {
@@ -15,8 +15,8 @@ class ChangeChapterSourceViewModel(application: Application) :
var chapterIndex: Int = 0 var chapterIndex: Int = 0
var chapterTitle: String = "" var chapterTitle: String = ""
override fun initData(arguments: Bundle?) { override fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {
super.initData(arguments) super.initData(arguments, book, fromReadBookActivity)
arguments?.let { bundle -> arguments?.let { bundle ->
bundle.getString("chapterTitle")?.let { bundle.getString("chapterTitle")?.let {
chapterTitle = it chapterTitle = it
+2 -2
View File
@@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx3000m org.gradle.jvmargs=-Xmx6000m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@@ -39,7 +39,7 @@ android.defaults.buildfeatures.shaders=false
# 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=113.0.5672.61 CronetVersion=113.0.5672.76
CronetMainVersion=113.0.0.0 CronetMainVersion=113.0.0.0
android.injected.testOnly=false android.injected.testOnly=false
android.nonFinalResIds=true android.nonFinalResIds=true
+19 -1
View File
@@ -1,4 +1,5 @@
<template> <template>
<div class="title">{{ title }}</div>
<div v-for="(para, index) in carray" :key="index"> <div v-for="(para, index) in carray" :key="index">
<img <img
class="full" class="full"
@@ -16,7 +17,11 @@ import config from "../plugins/config";
import { getImageFromLegado, isLegadoUrl } from "../plugins/utils"; import { getImageFromLegado, isLegadoUrl } from "../plugins/utils";
const store = useBookStore(); const store = useBookStore();
defineProps(["carray"]); const props = defineProps({
carray: { type: Array, required: true },
title: { type: String, required: true },
spacing: { type: Object, required: true },
});
const fontFamily = computed(() => { const fontFamily = computed(() => {
if (store.config.font >= 0) { if (store.config.font >= 0) {
@@ -49,14 +54,27 @@ watch(fontSize, () => {
store.setShowContent(true); store.setShowContent(true);
}); });
}); });
const letterSpacing = computed(() => props.spacing.letter); //字间距 倍数
const lineSpacing = computed(() => 1 + props.spacing.line); //行距 基础行距1
const paragraphSpacing = computed(() => props.spacing.paragraph); //段距
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.title {
margin-bottom: 57px;
font: 24px / 32px PingFangSC-Regular, HelveticaNeue-Light,
"Helvetica Neue Light", "Microsoft YaHei", sans-serif;
}
p { p {
display: block; display: block;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
letter-spacing: calc(v-bind("letterSpacing") * 1em);
line-height: v-bind("lineSpacing");
margin: calc(v-bind("paragraphSpacing") * 1em) 0;
:deep(img) { :deep(img) {
height: 1em; height: 1em;
} }
+6 -1
View File
@@ -22,6 +22,11 @@ export const useBookStore = defineStore("book", {
readWidth: 800, readWidth: 800,
infiniteLoading: false, infiniteLoading: false,
customFontName: "", customFontName: "",
spacing: {
paragraph: 1,
line: 0.8,
letter: 0,
},
}, },
miniInterface: false, miniInterface: false,
readSettingsVisible: false, readSettingsVisible: false,
@@ -53,7 +58,7 @@ export const useBookStore = defineStore("book", {
this.readingBook = readingBook; this.readingBook = readingBook;
}, },
setConfig(config) { setConfig(config) {
this.config = config; Object.assign(this.config, config);
}, },
setReadSettingsVisible(visible) { setReadSettingsVisible(visible) {
this.readSettingsVisible = visible; this.readSettingsVisible = visible;
+11 -6
View File
@@ -22,15 +22,19 @@ export const useSourceStore = defineStore("source", {
}, },
getters: { getters: {
sources: (state) => (isBookSource ? state.bookSources : state.rssSources), sources: (state) => (isBookSource ? state.bookSources : state.rssSources),
sourceUrlKey: () => isBookSource ? "bookSourceUrl" : "sourceUrl", sourceUrlKey: () => (isBookSource ? "bookSourceUrl" : "sourceUrl"),
sourcesMap: (state) => { sourcesMap: (state) => {
let map = new Map(); let map = new Map();
state.sources.forEach(source => map.set(source[state.sourceUrlKey], source)); state.sources.forEach((source) =>
map.set(source[state.sourceUrlKey], source)
);
return map; return map;
}, },
savedSourcesMap: (state) => { savedSourcesMap: (state) => {
let map = new Map(); let map = new Map();
state.savedSources.forEach(source => map.set(source[state.sourceUrlKey], source)); state.savedSources.forEach((source) =>
map.set(source[state.sourceUrlKey], source)
);
return map; return map;
}, },
currentSourceUrl: (state) => currentSourceUrl: (state) =>
@@ -73,13 +77,14 @@ export const useSourceStore = defineStore("source", {
}, },
//保存当前编辑源 //保存当前编辑源
saveCurrentSource() { saveCurrentSource() {
let source = this.currentSource, map = this.sourcesMap; let source = this.currentSource,
map = this.sourcesMap;
map.set(source[this.sourceUrlKey], source); map.set(source[this.sourceUrlKey], source);
this.saveSources(Array.from(map.values())); this.saveSources(Array.from(map.values()));
}, },
// 更改当前编辑的源 // 更改当前编辑的源
changeCurrentSource(source) { changeCurrentSource(source) {
this.currentSource = JSON.parse(JSON.stringify((source))); this.currentSource = JSON.parse(JSON.stringify(source));
}, },
// update editTab tabName and editTab info // update editTab tabName and editTab info
changeTabName(tabName) { changeTabName(tabName) {
@@ -87,7 +92,7 @@ export const useSourceStore = defineStore("source", {
localStorage.setItem("tabName", tabName); localStorage.setItem("tabName", tabName);
}, },
changeEditTabSource(source) { changeEditTabSource(source) {
this.editTabSource = JSON.parse(JSON.stringify((source))); this.editTabSource = JSON.parse(JSON.stringify(source));
}, },
editHistory(history) { editHistory(history) {
let historyObj; let historyObj;
+6 -10
View File
@@ -82,10 +82,12 @@
<div class="content"> <div class="content">
<div class="top-bar" ref="top"></div> <div class="top-bar" ref="top"></div>
<div v-for="data in chapterData" :key="data.index" ref="chapter"> <div v-for="data in chapterData" :key="data.index" ref="chapter">
<div class="title" :index="data.index" v-if="showContent"> <chapter-content
{{ data.title }} :carray="data.content"
</div> :title="data.title"
<chapter-content :carray="data.content" v-if="showContent" /> :spacing="store.config.spacing"
v-if="showContent"
/>
</div> </div>
<div class="loading" ref="loading"></div> <div class="loading" ref="loading"></div>
<div class="bottom-bar" ref="bottom"></div> <div class="bottom-bar" ref="bottom"></div>
@@ -667,12 +669,6 @@ onUnmounted(() => {
font-family: "Microsoft YaHei", PingFangSC-Regular, HelveticaNeue-Light, font-family: "Microsoft YaHei", PingFangSC-Regular, HelveticaNeue-Light,
"Helvetica Neue Light", sans-serif; "Helvetica Neue Light", sans-serif;
.title {
margin-bottom: 57px;
font: 24px / 32px PingFangSC-Regular, HelveticaNeue-Light,
"Helvetica Neue Light", "Microsoft YaHei", sans-serif;
}
.bottom-bar, .bottom-bar,
.top-bar { .top-bar {
height: 64px; height: 64px;