资源正则:
diff --git a/app/src/main/assets/web/index.js b/app/src/main/assets/web/index.js
index 9d09d50de..ba67e7801 100644
--- a/app/src/main/assets/web/index.js
+++ b/app/src/main/assets/web/index.js
@@ -383,15 +383,15 @@ $('.menu').addEventListener('click', e => {
ws.send(`{"tag":"${saveRule[0].bookSourceUrl}", "key":"${sKey}"}`);
};
ws.onmessage = (msg) => {
- DebugPrint(msg.data == 'finish' ? `\n[${Date().split(' ')[4]}] 调试任务已完成!` : msg.data);
- if (msg.data == 'finish') setRule(saveRule[0]);
+ console.log('[调试]', msg);
+ DebugPrint(msg.data);
};
ws.onerror = (err) => {
throw `${err.data}`;
}
ws.onclose = () => {
thisNode.setAttribute('class', '');
- DebugPrint(`[${Date().split(' ')[4]}] 调试服务已关闭!`);
+ DebugPrint(`\n调试服务已关闭!`);
}
} else throw `${sResult.errorMsg}`;
}).catch(err => {
diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt
index f4e902ba0..b8cbedf1c 100644
--- a/app/src/main/java/io/legado/app/constant/PreferKey.kt
+++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt
@@ -70,10 +70,12 @@ object PreferKey {
const val cAccent = "colorAccent"
const val cBackground = "colorBackground"
const val cBBackground = "colorBottomBackground"
+ const val bgImage = "backgroundImage"
const val cNPrimary = "colorPrimaryNight"
const val cNAccent = "colorAccentNight"
const val cNBackground = "colorBackgroundNight"
const val cNBBackground = "colorBottomBackgroundNight"
+ const val bgImageN = "backgroundImageNight"
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
index 3655e0117..7a40d4cc8 100644
--- a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
@@ -12,10 +12,10 @@ interface BookSourceDao {
fun liveDataAll(): LiveData
>
@Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey or bookSourceComment like :searchKey order by customOrder asc")
- fun liveDataSearch(searchKey: String = ""): LiveData>
+ fun liveDataSearch(searchKey: String): LiveData>
@Query("select * from book_sources where bookSourceGroup like :searchKey order by customOrder asc")
- fun liveDataGroupSearch(searchKey: String = ""): LiveData>
+ fun liveDataGroupSearch(searchKey: String): LiveData>
@Query("select * from book_sources where enabled = 1 order by customOrder asc")
fun liveDataEnabled(): LiveData>
@@ -32,18 +32,15 @@ interface BookSourceDao {
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and (bookSourceGroup like :key) order by customOrder asc")
fun liveGroupExplore(key: String): LiveData>
- @Query("select bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''")
+ @Query("select distinct bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''")
fun liveGroup(): LiveData>
- @Query("select bookSourceGroup from book_sources where enabled = 1 and trim(bookSourceGroup) <> ''")
+ @Query("select distinct bookSourceGroup from book_sources where enabled = 1 and trim(bookSourceGroup) <> ''")
fun liveGroupEnabled(): LiveData>
- @Query("select bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''")
+ @Query("select distinct bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''")
fun liveExploreGroup(): LiveData>
- @Query("select distinct enabled from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey")
- fun searchIsEnable(searchKey: String = ""): List
-
@Query("select * from book_sources where enabledExplore = 1 order by customOrder asc")
fun observeFind(): DataSource.Factory
@@ -68,6 +65,9 @@ interface BookSourceDao {
@get:Query("select * from book_sources where enabled = 1 and bookSourceType = 0 order by customOrder")
val allTextEnabled: List
+ @get:Query("select distinct bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''")
+ val allGroup: List
+
@Query("select * from book_sources where bookSourceUrl = :key")
fun getBookSource(key: String): BookSource?
diff --git a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
index ad2b25706..deaa45726 100644
--- a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
@@ -28,12 +28,21 @@ interface RssSourceDao {
@Query("SELECT * FROM rssSources where sourceGroup like :key order by customOrder")
fun liveGroupSearch(key: String): LiveData>
- @Query("SELECT * FROM rssSources where enabled = 1 and (sourceName like '%'||:searchKey||'%' or sourceGroup like '%'||:searchKey||'%' or sourceUrl like '%'||:searchKey||'%') order by customOrder")
+ @Query("SELECT * FROM rssSources where enabled = 1 order by customOrder")
+ fun liveEnabled(): LiveData>
+
+ @Query("SELECT * FROM rssSources where enabled = 1 and (sourceName like :searchKey or sourceGroup like :searchKey or sourceUrl like :searchKey) order by customOrder")
fun liveEnabled(searchKey: String): LiveData>
- @Query("select sourceGroup from rssSources where trim(sourceGroup) <> ''")
+ @Query("SELECT * FROM rssSources where enabled = 1 and sourceGroup like :searchKey order by customOrder")
+ fun liveEnabledByGroup(searchKey: String): LiveData>
+
+ @Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
fun liveGroup(): LiveData>
+ @get:Query("select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''")
+ val allGroup: List
+
@get:Query("select min(customOrder) from rssSources")
val minOrder: Int
diff --git a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt
index 74d33dfeb..234e24e0d 100644
--- a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt
@@ -63,6 +63,10 @@ interface SearchBookDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg searchBook: SearchBook): List
+ @Query("delete from searchBooks where name = :name and author = :author")
+ fun clear(name: String, author: String)
+
@Query("delete from searchBooks where time < :time")
fun clearExpired(time: Long)
+
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/DefaultData.kt b/app/src/main/java/io/legado/app/help/DefaultData.kt
index d2bab07f1..ba06316b9 100644
--- a/app/src/main/java/io/legado/app/help/DefaultData.kt
+++ b/app/src/main/java/io/legado/app/help/DefaultData.kt
@@ -2,6 +2,7 @@ package io.legado.app.help
import io.legado.app.App
import io.legado.app.data.entities.HttpTTS
+import io.legado.app.data.entities.RssSource
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
@@ -45,17 +46,25 @@ object DefaultData {
GSON.fromJsonArray(json)!!
}
+ val rssSources by lazy {
+ val json = String(
+ App.INSTANCE.assets.open("defaultData${File.separator}rssSources.json")
+ .readBytes()
+ )
+ GSON.fromJsonArray(json)!!
+ }
+
fun importDefaultHttpTTS() {
App.db.httpTTSDao.deleteDefault()
- httpTTS.let {
- App.db.httpTTSDao.insert(*it.toTypedArray())
- }
+ App.db.httpTTSDao.insert(*httpTTS.toTypedArray())
}
fun importDefaultTocRules() {
App.db.txtTocRule.deleteDefault()
- txtTocRules.let {
- App.db.txtTocRule.insert(*it.toTypedArray())
- }
+ App.db.txtTocRule.insert(*txtTocRules.toTypedArray())
+ }
+
+ fun importDefaultRssSources() {
+ App.db.rssSourceDao.insert(*rssSources.toTypedArray())
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/LocalConfig.kt b/app/src/main/java/io/legado/app/help/LocalConfig.kt
index ac0d3034d..6278c0c55 100644
--- a/app/src/main/java/io/legado/app/help/LocalConfig.kt
+++ b/app/src/main/java/io/legado/app/help/LocalConfig.kt
@@ -65,8 +65,11 @@ object LocalConfig {
get() = isLastVersion(1, "ruleHelpVersion")
val hasUpHttpTTS: Boolean
- get() = isLastVersion(1, "httpTtsVersion")
+ get() = !isLastVersion(2, "httpTtsVersion")
val hasUpTxtTocRule: Boolean
- get() = isLastVersion(1, "txtTocRuleVersion")
+ get() = !isLastVersion(1, "txtTocRuleVersion")
+
+ val hasUpRssSources: Boolean
+ get() = !isLastVersion(1, "rssSourceVersion")
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/ThemeConfig.kt b/app/src/main/java/io/legado/app/help/ThemeConfig.kt
index 9de5ceade..0b0298e91 100644
--- a/app/src/main/java/io/legado/app/help/ThemeConfig.kt
+++ b/app/src/main/java/io/legado/app/help/ThemeConfig.kt
@@ -204,6 +204,7 @@ object ThemeConfig {
var primaryColor: String,
var accentColor: String,
var backgroundColor: String,
+ var backgroundImage: String? = null,
var bottomBackground: String
)
diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt
index 1686df19e..a26f5174f 100644
--- a/app/src/main/java/io/legado/app/help/storage/Backup.kt
+++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt
@@ -29,7 +29,7 @@ object Backup {
"bookmark.json",
"bookGroup.json",
"bookSource.json",
- "rssSource.json",
+ "rssSources.json",
"rssStar.json",
"replaceRule.json",
"readRecord.json",
@@ -61,7 +61,7 @@ object Backup {
writeListToJson(App.db.bookmarkDao.all, "bookmark.json", backupPath)
writeListToJson(App.db.bookGroupDao.all, "bookGroup.json", backupPath)
writeListToJson(App.db.bookSourceDao.all, "bookSource.json", backupPath)
- writeListToJson(App.db.rssSourceDao.all, "rssSource.json", backupPath)
+ writeListToJson(App.db.rssSourceDao.all, "rssSources.json", backupPath)
writeListToJson(App.db.rssStarDao.all, "rssStar.json", backupPath)
writeListToJson(App.db.replaceRuleDao.all, "replaceRule.json", backupPath)
writeListToJson(App.db.readRecordDao.all, "readRecord.json", backupPath)
diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt
index b0d833b0c..212152849 100644
--- a/app/src/main/java/io/legado/app/help/storage/Restore.kt
+++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt
@@ -122,7 +122,7 @@ object Restore {
fileToListT(path, "bookSource.json")?.let {
App.db.bookSourceDao.insert(*it.toTypedArray())
}
- fileToListT(path, "rssSource.json")?.let {
+ fileToListT(path, "rssSources.json")?.let {
App.db.rssSourceDao.insert(*it.toTypedArray())
}
fileToListT(path, "rssStar.json")?.let {
diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt
index ec3d31097..c74a36b5e 100644
--- a/app/src/main/java/io/legado/app/model/Debug.kt
+++ b/app/src/main/java/io/legado/app/model/Debug.kt
@@ -16,8 +16,8 @@ import java.text.SimpleDateFormat
import java.util.*
object Debug {
- private var debugSource: String? = null
var callback: Callback? = null
+ private var debugSource: String? = null
private val tasks: CompositeCoroutine = CompositeCoroutine()
@SuppressLint("ConstantLocale")
@@ -59,12 +59,12 @@ object Debug {
}
}
- fun startDebug(rssSource: RssSource) {
+ fun startDebug(scope: CoroutineScope, rssSource: RssSource) {
cancelDebug()
debugSource = rssSource.sourceUrl
log(debugSource, "︾开始解析")
val sort = rssSource.sortUrls().entries.first()
- Rss.getArticles(sort.key, sort.value, rssSource, 1)
+ Rss.getArticles(scope, sort.key, sort.value, rssSource, 1)
.onSuccess {
if (it.articles.isEmpty()) {
log(debugSource, "⇒列表页解析成功,为空")
@@ -77,7 +77,7 @@ object Debug {
if (ruleContent.isNullOrEmpty()) {
log(debugSource, "⇒内容规则为空,默认获取整个网页", state = 1000)
} else {
- rssContentDebug(it.articles[0], ruleContent, rssSource)
+ rssContentDebug(scope, it.articles[0], ruleContent, rssSource)
}
} else {
log(debugSource, "⇒存在描述规则,不解析内容页")
@@ -90,9 +90,14 @@ object Debug {
}
}
- private fun rssContentDebug(rssArticle: RssArticle, ruleContent: String, rssSource: RssSource) {
+ private fun rssContentDebug(
+ scope: CoroutineScope,
+ rssArticle: RssArticle,
+ ruleContent: String,
+ rssSource: RssSource
+ ) {
log(debugSource, "︾开始解析内容页")
- Rss.getContent(rssArticle, ruleContent, rssSource)
+ Rss.getContent(scope, rssArticle, ruleContent, rssSource)
.onSuccess {
log(debugSource, it)
log(debugSource, "︽内容页解析完成", state = 1000)
diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
index 5445884bf..98e590cf9 100644
--- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
+++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
@@ -646,7 +646,9 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
bindings["chapter"] = chapter
bindings["title"] = chapter?.title
bindings["src"] = content
- return SCRIPT_ENGINE.eval(jsStr, bindings)
+ return runBlocking {
+ SCRIPT_ENGINE.eval(jsStr, bindings)
+ }
}
/**
diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
index 0a0146874..318c1dcd3 100644
--- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
+++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
@@ -309,7 +309,7 @@ class AnalyzeUrl(
}
return when (method) {
RequestMethod.POST -> {
- if (fieldMap.isNotEmpty()) {
+ if (fieldMap.isNotEmpty() || body.isNullOrBlank()) {
RxHttp.postForm(url)
.setAssemblyEnabled(false)
.setOkClient(HttpHelper.getProxyClient(proxy))
@@ -338,7 +338,7 @@ class AnalyzeUrl(
setCookie(tag)
return when (method) {
RequestMethod.POST -> {
- if (fieldMap.isNotEmpty()) {
+ if (fieldMap.isNotEmpty() || body.isNullOrBlank()) {
RxHttp.postForm(url)
.setAssemblyEnabled(false)
.setOkClient(HttpHelper.getProxyClient(proxy))
diff --git a/app/src/main/java/io/legado/app/model/rss/Rss.kt b/app/src/main/java/io/legado/app/model/rss/Rss.kt
index afa6c06fd..71aa5b2b3 100644
--- a/app/src/main/java/io/legado/app/model/rss/Rss.kt
+++ b/app/src/main/java/io/legado/app/model/rss/Rss.kt
@@ -14,11 +14,11 @@ import kotlin.coroutines.CoroutineContext
object Rss {
fun getArticles(
+ scope: CoroutineScope,
sortName: String,
sortUrl: String,
rssSource: RssSource,
page: Int,
- scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO
): Coroutine {
return Coroutine.async(scope, context) {
@@ -35,10 +35,10 @@ object Rss {
}
fun getContent(
+ scope: CoroutineScope,
rssArticle: RssArticle,
ruleContent: String,
rssSource: RssSource?,
- scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO
): Coroutine {
return Coroutine.async(scope, context) {
diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
index 1bba73ec1..1bdf1ca05 100644
--- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
@@ -93,15 +93,18 @@ object BookChapterList {
for (item in chapterDataList) {
downloadToc(
scope, item, book, bookSource,
- tocRule, listRule, chapterList, chapterDataList
+ tocRule, listRule, chapterList, chapterDataList,
+ {
+ block.resume(finish(book, chapterList, reverse))
+ }
) {
- block.resume(finish(book, chapterList, reverse))
+ block.cancel(it)
}
}
}
}
}.onFailure {
- block.resumeWithException(it)
+ block.cancel(it)
}
}
@@ -114,7 +117,8 @@ object BookChapterList {
listRule: String,
chapterList: ArrayList,
chapterDataList: ArrayList>,
- onFinish: () -> Unit
+ onFinish: () -> Unit,
+ onError: (error: Throwable) -> Unit
) {
Coroutine.async(scope = scope) {
val nextBody = AnalyzeUrl(
@@ -142,7 +146,7 @@ object BookChapterList {
}
}
}.onError {
- throw it
+ onError.invoke(it)
}
}
diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt
index aa3bd6799..6bd72e7bf 100644
--- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt
+++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt
@@ -8,9 +8,11 @@ import io.legado.app.base.BaseService
import io.legado.app.constant.AppConst
import io.legado.app.constant.EventBus
import io.legado.app.constant.IntentAction
+import io.legado.app.data.entities.BookSource
import io.legado.app.help.AppConfig
import io.legado.app.help.IntentHelp
import io.legado.app.help.coroutine.CompositeCoroutine
+import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.CheckSource
import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.utils.postEvent
@@ -21,7 +23,7 @@ import kotlin.math.min
class CheckSourceService : BaseService() {
private var threadCount = AppConfig.threadCount
- private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
+ private var searchCoroutine = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
private var tasks = CompositeCoroutine()
private val allIds = ArrayList()
private val checkedIds = ArrayList()
@@ -62,7 +64,7 @@ class CheckSourceService : BaseService() {
override fun onDestroy() {
super.onDestroy()
tasks.clear()
- searchPool.close()
+ searchCoroutine.close()
postEvent(EventBus.CHECK_SOURCE_DONE, 0)
}
@@ -96,18 +98,36 @@ class CheckSourceService : BaseService() {
if (index < allIds.size) {
val sourceUrl = allIds[index]
App.db.bookSourceDao.getBookSource(sourceUrl)?.let { source ->
- if (source.searchUrl.isNullOrEmpty()) {
- onNext(sourceUrl, source.bookSourceName)
- } else {
- CheckSource(source).check(this, searchPool) {
- onNext(it, source.bookSourceName)
- }
- }
+ check(source)
} ?: onNext(sourceUrl, "")
}
}
}
+ fun check(source: BookSource) {
+ execute(context = searchCoroutine) {
+ val webBook = WebBook(source)
+ val books = webBook.searchBookAwait(this, CheckSource.keyword)
+ val book = webBook.getBookInfoAwait(this, books.first().toBook())
+ val toc = webBook.getChapterListAwait(this, book)
+ val content = webBook.getContentAwait(this, book, toc.first())
+ if (content.isBlank()) {
+ throw Exception("正文内容为空")
+ }
+ }.timeout(60000L)
+ .onError {
+ source.addGroup("失效")
+ source.bookSourceComment =
+ "error:${it.localizedMessage}\n${source.bookSourceComment}"
+ App.db.bookSourceDao.update(source)
+ }.onSuccess {
+ source.removeGroup("失效")
+ App.db.bookSourceDao.update(source)
+ }.onFinally {
+ onNext(source.bookSourceUrl, source.bookSourceName)
+ }
+ }
+
private fun onNext(sourceUrl: String, sourceName: String) {
synchronized(this) {
check()
diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
index 2851173ed..a91771e66 100644
--- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
+++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
@@ -12,11 +12,17 @@ import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.FileUtils
import io.legado.app.utils.LogUtils
+import io.legado.app.utils.MD5Utils
import io.legado.app.utils.postEvent
+import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.isActive
+import org.jetbrains.anko.collections.forEachWithIndex
import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream
+import java.net.ConnectException
+import java.net.SocketTimeoutException
+import java.util.*
class HttpReadAloudService : BaseReadAloudService(),
MediaPlayer.OnPreparedListener,
@@ -55,12 +61,17 @@ class HttpReadAloudService : BaseReadAloudService(),
override fun play() {
if (contentList.isEmpty()) return
- if (nowSpeak == 0) {
- downloadAudio()
- } else {
- val file = getSpeakFile(nowSpeak)
- if (file.exists()) {
- playAudio(FileInputStream(file).fd)
+ ReadAloud.httpTTS?.let {
+ val fileName = md5SpeakFileName(it.url, AppConfig.ttsSpeechRate.toString(), contentList[nowSpeak])
+ if (nowSpeak == 0) {
+ downloadAudio()
+ } else {
+ val file = getSpeakFileAsMd5(fileName)
+ if (file.exists()) {
+ playAudio(FileInputStream(file).fd)
+ } else {
+ downloadAudio()
+ }
}
}
}
@@ -68,28 +79,53 @@ class HttpReadAloudService : BaseReadAloudService(),
private fun downloadAudio() {
task?.cancel()
task = execute {
- FileUtils.deleteFile(ttsFolder)
- for (index in 0 until contentList.size) {
- if (isActive) {
- ReadAloud.httpTTS?.let {
- AnalyzeUrl(
- it.url,
- speakText = contentList[index],
- speakSpeed = AppConfig.ttsSpeechRate
- ).getByteArray().let { bytes ->
- if (isActive) {
- val file = getSpeakFile(index)
- file.writeBytes(bytes)
- if (index == nowSpeak) {
- @Suppress("BlockingMethodInNonBlockingContext")
- val fis = FileInputStream(file)
- playAudio(fis.fd)
+ removeCacheFile()
+ ReadAloud.httpTTS?.let {
+ contentList.forEachWithIndex { index, item ->
+ if (isActive) {
+ val fileName =
+ md5SpeakFileName(it.url, AppConfig.ttsSpeechRate.toString(), item)
+
+ if (hasSpeakFile(fileName)) { //已经下载好的语音缓存
+ if (index == nowSpeak) {
+ val file = getSpeakFileAsMd5(fileName)
+
+ @Suppress("BlockingMethodInNonBlockingContext")
+ val fis = FileInputStream(file)
+ playAudio(fis.fd)
+ }
+ } else if (hasSpeakCacheFile(fileName)) { //缓存文件还在,可能还没下载完
+ return@let
+ } else { //没有下载并且没有缓存文件
+ try {
+ createSpeakCacheFile(fileName)
+ AnalyzeUrl(
+ it.url,
+ speakText = item,
+ speakSpeed = AppConfig.ttsSpeechRate
+ ).getByteArray().let { bytes ->
+ ensureActive()
+ val file = getSpeakFileAsMd5(fileName)
+ //val file = getSpeakFile(index)
+ file.writeBytes(bytes)
+ removeSpeakCacheFile(fileName)
+ if (index == nowSpeak) {
+ @Suppress("BlockingMethodInNonBlockingContext")
+ val fis = FileInputStream(file)
+ playAudio(fis.fd)
+ }
}
+ } catch (e: SocketTimeoutException) {
+ removeSpeakCacheFile(fileName)
+ // delay(2000)
+ // downloadAudio()
+ } catch (e: ConnectException) {
+ removeSpeakCacheFile(fileName)
+ } catch (e: Exception) {
+ removeSpeakCacheFile(fileName)
}
}
}
- } else {
- break
}
}
}
@@ -110,10 +146,47 @@ class HttpReadAloudService : BaseReadAloudService(),
}
}
- private fun getSpeakFile(index: Int = nowSpeak): File {
- return FileUtils.createFileIfNotExist("${ttsFolder}${File.separator}${index}.mp3")
+ private fun speakFilePath() = ttsFolder + File.separator
+ private fun md5SpeakFileName(url: String, ttsConfig: String, content: String): String {
+ return MD5Utils.md5Encode16(textChapter!!.title) + "_" + MD5Utils.md5Encode16("$url-|-$ttsConfig-|-$content")
}
+ private fun hasSpeakFile(name: String) =
+ FileUtils.exist("${speakFilePath()}$name.mp3")
+
+ private fun hasSpeakCacheFile(name: String) =
+ FileUtils.exist("${speakFilePath()}$name.mp3.cache")
+
+ private fun createSpeakCacheFile(name: String): File =
+ FileUtils.createFileWithReplace("${speakFilePath()}$name.mp3.cache")
+
+ private fun removeSpeakCacheFile(name: String) {
+ FileUtils.delete("${speakFilePath()}$name.mp3.cache")
+ }
+
+ private fun getSpeakFileAsMd5(name: String): File =
+ FileUtils.createFileIfNotExist("${speakFilePath()}$name.mp3")
+
+ private fun removeCacheFile() {
+ FileUtils.listDirsAndFiles(speakFilePath())?.forEach {
+ if (it == null) {
+ return@forEach
+ }
+ if (Regex(""".+\.mp3$""").matches(it.name)) { //mp3缓存文件
+ val reg =
+ """^${MD5Utils.md5Encode16(textChapter!!.title)}_[a-z0-9]{16}\.mp3$""".toRegex()
+ if (!reg.matches(it.name)) {
+ FileUtils.deleteFile(it.absolutePath)
+ }
+ } else {
+ if (Date().time - it.lastModified() > 30000) {
+ FileUtils.deleteFile(it.absolutePath)
+ }
+ }
+ }
+ }
+
+
override fun pauseReadAloud(pause: Boolean) {
super.pauseReadAloud(pause)
mediaPlayer.pause()
diff --git a/app/src/main/java/io/legado/app/service/help/CheckSource.kt b/app/src/main/java/io/legado/app/service/help/CheckSource.kt
index 596662429..d38e6c225 100644
--- a/app/src/main/java/io/legado/app/service/help/CheckSource.kt
+++ b/app/src/main/java/io/legado/app/service/help/CheckSource.kt
@@ -2,64 +2,35 @@ package io.legado.app.service.help
import android.content.Context
import android.content.Intent
-import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.IntentAction
import io.legado.app.data.entities.BookSource
-import io.legado.app.help.coroutine.Coroutine
-import io.legado.app.model.webBook.WebBook
import io.legado.app.service.CheckSourceService
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import org.jetbrains.anko.toast
-import kotlin.coroutines.CoroutineContext
-class CheckSource(val source: BookSource) {
+object CheckSource {
+ var keyword = "我的"
- companion object {
- var keyword = "我的"
-
- fun start(context: Context, sources: List) {
- if (sources.isEmpty()) {
- context.toast(R.string.non_select)
- return
- }
- val selectedIds: ArrayList = arrayListOf()
- sources.map {
- selectedIds.add(it.bookSourceUrl)
- }
- Intent(context, CheckSourceService::class.java).let {
- it.action = IntentAction.start
- it.putExtra("selectIds", selectedIds)
- context.startService(it)
- }
+ fun start(context: Context, sources: List) {
+ if (sources.isEmpty()) {
+ context.toast(R.string.non_select)
+ return
}
-
- fun stop(context: Context) {
- Intent(context, CheckSourceService::class.java).let {
- it.action = IntentAction.stop
- context.startService(it)
- }
+ val selectedIds: ArrayList = arrayListOf()
+ sources.map {
+ selectedIds.add(it.bookSourceUrl)
+ }
+ Intent(context, CheckSourceService::class.java).let {
+ it.action = IntentAction.start
+ it.putExtra("selectIds", selectedIds)
+ context.startService(it)
}
}
- fun check(
- scope: CoroutineScope,
- context: CoroutineContext,
- onNext: (sourceUrl: String) -> Unit
- ): Coroutine<*> {
- val webBook = WebBook(source)
- return webBook
- .searchBook(scope, keyword, context = context)
- .timeout(60000L)
- .onError(Dispatchers.IO) {
- source.addGroup("失效")
- App.db.bookSourceDao.update(source)
- }.onSuccess(Dispatchers.IO) {
- source.removeGroup("失效")
- App.db.bookSourceDao.update(source)
- }.onFinally(Dispatchers.IO) {
- onNext(source.bookSourceUrl)
- }
+ fun stop(context: Context) {
+ Intent(context, CheckSourceService::class.java).let {
+ it.action = IntentAction.stop
+ context.startService(it)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt
index c76386bd6..1a2d7b340 100644
--- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt
+++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt
@@ -183,9 +183,11 @@ object ReadBook {
}
}
- fun skipToPage(index: Int) {
+ fun skipToPage(index: Int, success: (() -> Unit)? = null) {
durChapterPos = curTextChapter?.getReadLength(index) ?: index
- callBack?.upContent()
+ callBack?.upContent() {
+ success?.invoke()
+ }
curPageChanged()
saveRead()
}
@@ -240,19 +242,28 @@ object ReadBook {
/**
* 加载章节内容
*/
- fun loadContent(resetPageOffset: Boolean) {
- loadContent(durChapterIndex, resetPageOffset = resetPageOffset)
+ fun loadContent(resetPageOffset: Boolean, success: (() -> Unit)? = null) {
+ loadContent(durChapterIndex, resetPageOffset = resetPageOffset) {
+ success?.invoke()
+ }
loadContent(durChapterIndex + 1, resetPageOffset = resetPageOffset)
loadContent(durChapterIndex - 1, resetPageOffset = resetPageOffset)
}
- fun loadContent(index: Int, upContent: Boolean = true, resetPageOffset: Boolean) {
+ fun loadContent(
+ index: Int,
+ upContent: Boolean = true,
+ resetPageOffset: Boolean,
+ success: (() -> Unit)? = null
+ ) {
book?.let { book ->
if (addLoading(index)) {
Coroutine.async {
App.db.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter ->
BookHelp.getContent(book, chapter)?.let {
- contentLoadFinish(book, chapter, it, upContent, resetPageOffset)
+ contentLoadFinish(book, chapter, it, upContent, resetPageOffset) {
+ success?.invoke()
+ }
removeLoading(chapter.index)
} ?: download(chapter, resetPageOffset = resetPageOffset)
} ?: removeLoading(index)
@@ -282,7 +293,11 @@ object ReadBook {
}
}
- private fun download(chapter: BookChapter, resetPageOffset: Boolean) {
+ private fun download(
+ chapter: BookChapter,
+ resetPageOffset: Boolean,
+ success: (() -> Unit)? = null
+ ) {
val book = book
val webBook = webBook
if (book != null && webBook != null) {
@@ -290,7 +305,9 @@ object ReadBook {
} else if (book != null) {
contentLoadFinish(
book, chapter, "没有书源", resetPageOffset = resetPageOffset
- )
+ ) {
+ success?.invoke()
+ }
removeLoading(chapter.index)
} else {
removeLoading(chapter.index)
@@ -379,7 +396,8 @@ object ReadBook {
chapter: BookChapter,
content: String,
upContent: Boolean = true,
- resetPageOffset: Boolean
+ resetPageOffset: Boolean,
+ success: (() -> Unit)? = null
) {
Coroutine.async {
ImageProvider.clearOut(durChapterIndex)
@@ -420,6 +438,8 @@ object ReadBook {
}.onError {
it.printStackTrace()
App.INSTANCE.toast("ChapterProvider ERROR:\n${it.getStackTraceString()}")
+ }.onSuccess {
+ success?.invoke()
}
}
@@ -460,10 +480,19 @@ object ReadBook {
interface CallBack {
fun loadChapterList(book: Book)
- fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true)
+
+ fun upContent(
+ relativePosition: Int = 0,
+ resetPageOffset: Boolean = true,
+ success: (() -> Unit)? = null
+ )
+
fun upView()
+
fun pageChanged()
+
fun contentLoadFinish()
+
fun upPageAnim()
}
diff --git a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
index ca805475b..992bf6931 100644
--- a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
+++ b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
@@ -9,6 +9,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import io.legado.app.App
import io.legado.app.R
+import io.legado.app.help.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.openUrl
@@ -24,19 +25,24 @@ class AboutFragment : PreferenceFragmentCompat() {
Pair("(QQ群VIP1)701903217", "-iolizL4cbJSutKRpeImHlXlpLDZnzeF"),
Pair("(QQ群VIP2)263949160", "xwfh7_csb2Gf3Aw2qexEcEtviLfLfd4L"),
Pair("(QQ群VIP3)680280282", "_N0i7yZObjKSeZQvzoe2ej7j02kLnOOK"),
+ Pair("(QQ群VIP4)680280282", "VF2UwvUCuaqlo6pddWTe_kw__a1_Fr8O"),
Pair("(QQ群1)805192012", "6GlFKjLeIk5RhQnR3PNVDaKB6j10royo"),
Pair("(QQ群2)773736122", "5Bm5w6OgLupXnICbYvbgzpPUgf0UlsJF"),
Pair("(QQ群3)981838750", "g_Sgmp2nQPKqcZQ5qPcKLHziwX_mpps9"),
Pair("(QQ群4)256929088", "czEJPLDnT4Pd9SKQ6RoRVzKhDxLchZrO"),
Pair("(QQ群5)811843556", "zKZ2UYGZ7o5CzcA6ylxzlqi21si_iqaX"),
Pair("(QQ群6)870270970", "FeCF8iSxfQbe90HPvGsvcqs5P5oSeY5n"),
- Pair("(QQ群7)15987187", "S2g2TMD0LGd3sefUADd1AbyPEW2o2XfC")
+ Pair("(QQ群7)15987187", "S2g2TMD0LGd3sefUADd1AbyPEW2o2XfC"),
+ Pair("(QQ群8)1079926194", "gg2qFH8q9IPFaCHV3H7CqCN-YljvazE1"),
)
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.about)
- findPreference("check_update")?.summary =
+ findPreference("update_log")?.summary =
"${getString(R.string.version)} ${App.versionName}"
+ if (AppConfig.isGooglePlay) {
+ preferenceScreen.removePreferenceRecursively("check_update")
+ }
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -58,6 +64,7 @@ class AboutFragment : PreferenceFragmentCompat() {
"qq" -> showQqGroups()
"gzGzh" -> requireContext().sendToClip(getString(R.string.legado_gzh))
"tg" -> openUrl(R.string.tg_url)
+ "discord" -> openUrl(R.string.discord_url)
}
return super.onPreferenceTreeClick(preference)
}
diff --git a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceDialog.kt b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceDialog.kt
index 6aa236744..0f3e6008f 100644
--- a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceDialog.kt
+++ b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceDialog.kt
@@ -10,10 +10,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
+import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
+import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.BookSource
import io.legado.app.databinding.DialogEditTextBinding
@@ -21,10 +23,8 @@ import io.legado.app.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemSourceImportBinding
import io.legado.app.help.AppConfig
import io.legado.app.lib.dialogs.alert
-import io.legado.app.utils.getViewModelOfActivity
-import io.legado.app.utils.putPrefBoolean
+import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
import org.jetbrains.anko.sdk27.listeners.onClick
/**
@@ -85,7 +85,14 @@ class ImportBookSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickList
when (item.itemId) {
R.id.menu_new_group -> {
alert(R.string.diy_edit_source_group) {
- val alertBinding = DialogEditTextBinding.inflate(layoutInflater)
+ val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
+ val groups = linkedSetOf()
+ App.db.bookSourceDao.allGroup.forEach { group ->
+ groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
+ }
+ editView.setFilterValues(groups.toList())
+ editView.dropDownHeight = 180.dp
+ }
customView = alertBinding.root
okButton {
alertBinding.editView.text?.toString()?.let { group ->
diff --git a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceDialog.kt b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceDialog.kt
index 41bda67fd..5ad9aeee7 100644
--- a/app/src/main/java/io/legado/app/ui/association/ImportRssSourceDialog.kt
+++ b/app/src/main/java/io/legado/app/ui/association/ImportRssSourceDialog.kt
@@ -10,10 +10,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.recyclerview.widget.LinearLayoutManager
+import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
+import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.RssSource
import io.legado.app.databinding.DialogEditTextBinding
@@ -21,10 +23,8 @@ import io.legado.app.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemSourceImportBinding
import io.legado.app.help.AppConfig
import io.legado.app.lib.dialogs.alert
-import io.legado.app.utils.getViewModelOfActivity
-import io.legado.app.utils.putPrefBoolean
+import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
-import io.legado.app.utils.visible
import org.jetbrains.anko.sdk27.listeners.onClick
/**
@@ -85,7 +85,14 @@ class ImportRssSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListe
when (item.itemId) {
R.id.menu_new_group -> {
alert(R.string.diy_edit_source_group) {
- val alertBinding = DialogEditTextBinding.inflate(layoutInflater)
+ val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
+ val groups = linkedSetOf()
+ App.db.rssSourceDao.allGroup.forEach { group ->
+ groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
+ }
+ editView.setFilterValues(groups.toList())
+ editView.dropDownHeight = 180.dp
+ }
customView = alertBinding.root
okButton {
alertBinding.editView.text?.toString()?.let { group ->
diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
index 1c5071173..fe26451fb 100644
--- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
+++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
@@ -84,10 +84,10 @@ class ChangeSourceDialog : BaseDialogFragment(),
binding.toolBar.inflateMenu(R.menu.change_source)
binding.toolBar.menu.applyTint(requireContext())
binding.toolBar.setOnMenuItemClickListener(this)
- binding.toolBar.menu.findItem(R.id.menu_load_info)?.isChecked =
- AppConfig.changeSourceLoadInfo
- binding.toolBar.menu.findItem(R.id.menu_load_toc)?.isChecked = AppConfig.changeSourceLoadToc
-
+ binding.toolBar.menu.findItem(R.id.menu_load_info)
+ ?.isChecked = AppConfig.changeSourceLoadInfo
+ binding.toolBar.menu.findItem(R.id.menu_load_toc)
+ ?.isChecked = AppConfig.changeSourceLoadToc
}
private fun initRecyclerView() {
diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
index 279cf7d79..0c738d93e 100644
--- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
@@ -88,6 +88,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
}
private fun searchFinish(searchBook: SearchBook) {
+ if (searchBooks.contains(searchBook)) return
App.db.searchBookDao.insert(searchBook)
if (screenKey.isEmpty()) {
searchBooks.add(searchBook)
@@ -99,6 +100,9 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
private fun startSearch() {
execute {
+ App.db.searchBookDao.clear(name, author)
+ searchBooks.clear()
+ upAdapter()
bookSourceList.clear()
if (searchGroup.isBlank()) {
bookSourceList.addAll(App.db.bookSourceDao.allEnabled)
@@ -137,7 +141,6 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} else {
searchFinish(searchBook)
}
- return@onSuccess
}
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
index 02040e26d..14482296a 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
@@ -51,10 +51,8 @@ import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult
import org.jetbrains.anko.toast
@@ -171,8 +169,6 @@ class ReadBookActivity : ReadBookBaseActivity(),
R.id.menu_group_on_line_ns -> item.isVisible = onLine
R.id.menu_group_local -> item.isVisible = !onLine
R.id.menu_group_text -> item.isVisible = book.isLocalTxt()
- R.id.menu_group_login ->
- item.isVisible = !ReadBook.webBook?.bookSource?.loginUrl.isNullOrEmpty()
else -> when (item.itemId) {
R.id.menu_enable_replace -> item.isChecked = book.getUseReplaceRule()
R.id.menu_re_segment -> item.isChecked = book.getReSegment()
@@ -245,12 +241,6 @@ class ReadBookActivity : ReadBookBaseActivity(),
supportFragmentManager,
ReadBook.book?.tocUrl
)
- R.id.menu_login -> ReadBook.webBook?.bookSource?.let {
- startActivity(
- Pair("sourceUrl", it.bookSourceUrl),
- Pair("loginUrl", it.loginUrl)
- )
- }
R.id.menu_set_charset -> showCharsetConfig()
R.id.menu_get_progress -> ReadBook.book?.let {
viewModel.syncBookProgress(it) { progress ->
@@ -310,6 +300,14 @@ class ReadBookActivity : ReadBookBaseActivity(),
return true
}
}
+ keyCode == KeyEvent.KEYCODE_PAGE_UP -> {
+ binding.readView.pageDelegate?.keyTurnPage(PageDirection.PREV)
+ return true
+ }
+ keyCode == KeyEvent.KEYCODE_PAGE_DOWN -> {
+ binding.readView.pageDelegate?.keyTurnPage(PageDirection.NEXT)
+ return true
+ }
keyCode == KeyEvent.KEYCODE_SPACE -> {
binding.readView.pageDelegate?.keyTurnPage(PageDirection.NEXT)
return true
@@ -531,13 +529,18 @@ class ReadBookActivity : ReadBookBaseActivity(),
/**
* 更新内容
*/
- override fun upContent(relativePosition: Int, resetPageOffset: Boolean) {
- autoPageProgress = 0
+ override fun upContent(
+ relativePosition: Int,
+ resetPageOffset: Boolean,
+ success: (() -> Unit)?
+ ) {
launch {
+ autoPageProgress = 0
binding.readView.upContent(relativePosition, resetPageOffset)
binding.readMenu.setSeekPage(ReadBook.durPageIndex())
+ loadStates = false
+ success?.invoke()
}
- loadStates = false
}
/**
@@ -715,6 +718,15 @@ class ReadBookActivity : ReadBookBaseActivity(),
upNavigationBarColor()
}
+ override fun showLogin() {
+ ReadBook.webBook?.bookSource?.let {
+ startActivity(
+ Pair("sourceUrl", it.bookSourceUrl),
+ Pair("loginUrl", it.loginUrl)
+ )
+ }
+ }
+
/**
* 朗读按钮
*/
@@ -775,7 +787,9 @@ class ReadBookActivity : ReadBookBaseActivity(),
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
- requestCodeEditSource -> viewModel.upBookSource()
+ requestCodeEditSource -> viewModel.upBookSource {
+ upView()
+ }
requestCodeChapterList ->
data?.getIntExtra("index", ReadBook.durChapterIndex)?.let { index ->
if (index != ReadBook.durChapterIndex) {
@@ -795,43 +809,35 @@ class ReadBookActivity : ReadBookBaseActivity(),
}
private fun skipToSearch(index: Int, indexWithinChapter: Int) {
- launch(IO) {
- viewModel.openChapter(index)
- // block until load correct chapter and pages
- var pages = ReadBook.curTextChapter?.pages
- while (ReadBook.durChapterIndex != index || pages == null) {
- delay(100L)
- pages = ReadBook.curTextChapter?.pages
- }
+ viewModel.openChapter(index) {
+ val pages = ReadBook.curTextChapter?.pages ?: return@openChapter
val positions = ReadBook.searchResultPositions(
pages,
indexWithinChapter,
viewModel.searchContentQuery
)
- while (ReadBook.durPageIndex() != positions[0]) {
- delay(100L)
- ReadBook.skipToPage(positions[0])
- }
- withContext(Main) {
- binding.readView.curPage.selectStartMoveIndex(0, positions[1], positions[2])
- delay(20L)
- when (positions[3]) {
- 0 -> binding.readView.curPage.selectEndMoveIndex(
- 0,
- positions[1],
- positions[2] + viewModel.searchContentQuery.length - 1
- )
- 1 -> binding.readView.curPage.selectEndMoveIndex(
- 0,
- positions[1] + 1,
- positions[4]
- )
- //consider change page, jump to scroll position
- -1 -> binding.readView.curPage
- .selectEndMoveIndex(1, 0, positions[4])
+ ReadBook.skipToPage(positions[0]) {
+ launch {
+ binding.readView.curPage.selectStartMoveIndex(0, positions[1], positions[2])
+ delay(20L)
+ when (positions[3]) {
+ 0 -> binding.readView.curPage.selectEndMoveIndex(
+ 0,
+ positions[1],
+ positions[2] + viewModel.searchContentQuery.length - 1
+ )
+ 1 -> binding.readView.curPage.selectEndMoveIndex(
+ 0,
+ positions[1] + 1,
+ positions[4]
+ )
+ //consider change page, jump to scroll position
+ -1 -> binding.readView.curPage
+ .selectEndMoveIndex(1, 0, positions[4])
+ }
+ binding.readView.isTextSelected = true
+ delay(100L)
}
- binding.readView.isTextSelected = true
- delay(100L)
}
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
index de9c080d7..d73f96658 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
@@ -250,7 +250,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
}
}
- fun openChapter(index: Int, durChapterPos: Int = 0) {
+ fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) {
ReadBook.prevTextChapter = null
ReadBook.curTextChapter = null
ReadBook.nextTextChapter = null
@@ -260,7 +260,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
ReadBook.durChapterPos = durChapterPos
}
ReadBook.saveRead()
- ReadBook.loadContent(resetPageOffset = true)
+ ReadBook.loadContent(resetPageOffset = true) {
+ success?.invoke()
+ }
}
fun removeFromBookshelf(success: (() -> Unit)?) {
@@ -271,13 +273,15 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
}
}
- fun upBookSource() {
+ fun upBookSource(success: (() -> Unit)?) {
execute {
ReadBook.book?.let { book ->
App.db.bookSourceDao.getBookSource(book.origin)?.let {
ReadBook.webBook = WebBook(it)
}
}
+ }.onSuccess {
+ success?.invoke()
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
index a73f3d4a2..63fa5c0f1 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
@@ -9,6 +9,7 @@ import android.view.WindowManager
import android.view.animation.Animation
import android.widget.FrameLayout
import android.widget.SeekBar
+import androidx.core.view.isGone
import androidx.core.view.isVisible
import io.legado.app.App
import io.legado.app.R
@@ -140,6 +141,9 @@ class ReadMenu @JvmOverloads constructor(
tvChapterUrl.onClick {
context.openUrl(binding.tvChapterUrl.text.toString())
}
+ tvLogin.onClick {
+ callBack.showLogin()
+ }
ivBrightnessAuto.onClick {
context.putPrefBoolean("brightnessAuto", !brightnessAuto())
upBrightnessState()
@@ -277,6 +281,7 @@ class ReadMenu @JvmOverloads constructor(
}
fun upBookView() {
+ binding.tvLogin.isGone = ReadBook.webBook?.bookSource?.loginUrl.isNullOrEmpty()
ReadBook.curTextChapter?.let {
binding.tvChapterName.text = it.title
binding.tvChapterName.visible()
@@ -323,6 +328,7 @@ class ReadMenu @JvmOverloads constructor(
fun upSystemUiVisibility()
fun onClickReadAloud()
fun showReadMenuHelp()
+ fun showLogin()
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
index 4d431f8a5..10a454109 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
@@ -347,8 +347,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
selectStart[0] = relativePage
selectStart[1] = lineIndex
selectStart[2] = charIndex
- val textLine = relativePage(relativePage).textLines[lineIndex]
- val textChar = textLine.textChars[charIndex]
+ val textLine = relativePage(relativePage).getLine(lineIndex)
+ val textChar = textLine.getTextChar(charIndex)
upSelectedStart(
textChar.start,
textLine.lineBottom + relativeOffset(relativePage),
@@ -364,8 +364,8 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
selectEnd[0] = relativePage
selectEnd[1] = lineIndex
selectEnd[2] = charIndex
- val textLine = relativePage(relativePage).textLines[lineIndex]
- val textChar = textLine.textChars[charIndex]
+ val textLine = relativePage(relativePage).getLine(lineIndex)
+ val textChar = textLine.getTextChar(charIndex)
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset(relativePage))
upSelectChars()
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
index 8bc72438b..2ded3399f 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
@@ -338,17 +338,8 @@ class ReadView(context: Context, attrs: AttributeSet) :
}
end -= 1
}
- kotlin.runCatching {
- curPage.selectStartMoveIndex(firstRelativePage, lineStart, start)
- curPage.selectEndMoveIndex(firstRelativePage, lineEnd, end)
- }.onFailure {
- print(
- """
- curPage.selectStartMoveIndex($firstRelativePage, $lineStart, $start)
- curPage.selectEndMoveIndex($firstRelativePage, $lineEnd, $end)
- """.trimIndent()
- )
- }
+ curPage.selectStartMoveIndex(firstRelativePage, lineStart, start)
+ curPage.selectEndMoveIndex(firstRelativePage, lineEnd, end)
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
index d921a217e..9d73757bb 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt
@@ -28,8 +28,10 @@ data class TextLine(
textChars.add(TextChar(charData, start = start, end = end))
}
- fun getTextCharAt(index: Int): TextChar {
- return textChars[index]
+ fun getTextChar(index: Int): TextChar {
+ return textChars.getOrElse(index) {
+ textChars.last()
+ }
}
fun getTextCharReverseAt(index: Int): TextChar {
diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
index e2d291141..ba57dbc48 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
@@ -25,6 +25,12 @@ data class TextPage(
val lineSize get() = textLines.size
val charSize get() = text.length
+ fun getLine(index: Int): TextLine {
+ return textLines.getOrElse(index) {
+ textLines.last()
+ }
+ }
+
fun upLinesPosition() = ChapterProvider.apply {
if (!ReadBookConfig.textBottomJustify) return@apply
if (textLines.size <= 1) return@apply
diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
index 0aeceded0..aa78e4a5b 100644
--- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
@@ -58,7 +58,7 @@ class BookSourceActivity : VMBaseActivity>? = null
- private var groups = linkedSetOf()
+ private val groups = linkedSetOf()
private var groupMenu: SubMenu? = null
private var sort = Sort.Default
private var sortAscending = true
@@ -130,6 +130,11 @@ class BookSourceActivity : VMBaseActivity {
+ item.isChecked = true
+ sortCheck(Sort.Enable)
+ initLiveDataBookSource(searchView.query?.toString())
+ }
R.id.menu_enabled_group -> {
searchView.setQuery(getString(R.string.enabled), true)
}
@@ -187,29 +192,44 @@ class BookSourceActivity : VMBaseActivity {
App.db.bookSourceDao.liveDataSearch("%$searchKey%")
}
- }
- bookSourceLiveDate?.observe(this, { data ->
- val sourceList =
- if (sortAscending) when (sort) {
- Sort.Weight -> data.sortedBy { it.weight }
- Sort.Name -> data.sortedWith { o1, o2 ->
- o1.bookSourceName.cnCompare(o2.bookSourceName)
+ }.apply {
+ observe(this@BookSourceActivity, { data ->
+ val sourceList =
+ if (sortAscending) when (sort) {
+ Sort.Weight -> data.sortedBy { it.weight }
+ Sort.Name -> data.sortedWith { o1, o2 ->
+ o1.bookSourceName.cnCompare(o2.bookSourceName)
+ }
+ Sort.Url -> data.sortedBy { it.bookSourceUrl }
+ Sort.Update -> data.sortedByDescending { it.lastUpdateTime }
+ Sort.Enable -> data.sortedWith { o1, o2 ->
+ var sort = -o1.enabled.compareTo(o2.enabled)
+ if (sort == 0) {
+ sort = o1.bookSourceName.cnCompare(o2.bookSourceName)
+ }
+ sort
+ }
+ else -> data
}
- Sort.Url -> data.sortedBy { it.bookSourceUrl }
- Sort.Update -> data.sortedByDescending { it.lastUpdateTime }
- else -> data
- }
- else when (sort) {
- Sort.Weight -> data.sortedByDescending { it.weight }
- Sort.Name -> data.sortedWith { o1, o2 ->
- o2.bookSourceName.cnCompare(o1.bookSourceName)
+ else when (sort) {
+ Sort.Weight -> data.sortedByDescending { it.weight }
+ Sort.Name -> data.sortedWith { o1, o2 ->
+ o2.bookSourceName.cnCompare(o1.bookSourceName)
+ }
+ Sort.Url -> data.sortedByDescending { it.bookSourceUrl }
+ Sort.Update -> data.sortedBy { it.lastUpdateTime }
+ Sort.Enable -> data.sortedWith { o1, o2 ->
+ var sort = o1.enabled.compareTo(o2.enabled)
+ if (sort == 0) {
+ sort = o1.bookSourceName.cnCompare(o2.bookSourceName)
+ }
+ sort
+ }
+ else -> data.reversed()
}
- Sort.Url -> data.sortedByDescending { it.bookSourceUrl }
- Sort.Update -> data.sortedBy { it.lastUpdateTime }
- else -> data.reversed()
- }
- adapter.setItems(sourceList, adapter.diffItemCallback)
- })
+ adapter.setItems(sourceList, adapter.diffItemCallback)
+ })
+ }
}
private fun showHelp() {
@@ -229,7 +249,7 @@ class BookSourceActivity : VMBaseActivity
+ it.forEach { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}
upGroupMenu()
@@ -483,6 +503,6 @@ class BookSourceActivity : VMBaseActivity clearCache()
PreferKey.defaultCover -> if (getPrefString(PreferKey.defaultCover).isNullOrEmpty()) {
- selectDefaultCover()
+ selectImage(requestCodeCover)
} else {
selector(items = arrayListOf("删除图片", "选择图片")) { _, i ->
if (i == 0) {
removePref(PreferKey.defaultCover)
} else {
- selectDefaultCover()
+ selectImage(requestCodeCover)
}
}
}
@@ -183,11 +183,12 @@ class OtherConfigFragment : BasePreferenceFragment(),
}.show()
}
- private fun selectDefaultCover() {
+ @Suppress("SameParameterValue")
+ private fun selectImage(requestCode: Int) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
- startActivityForResult(intent, requestCodeCover)
+ startActivityForResult(intent, requestCode)
}
private fun isProcessTextEnabled(): Boolean {
diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
index b7a29434e..8248a840a 100644
--- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
+++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
@@ -1,13 +1,17 @@
package io.legado.app.ui.config
import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Intent
import android.content.SharedPreferences
+import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
+import androidx.documentfile.provider.DocumentFile
import androidx.preference.Preference
import io.legado.app.App
import io.legado.app.R
@@ -19,24 +23,38 @@ import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.help.AppConfig
import io.legado.app.help.LauncherIconHelp
import io.legado.app.help.ThemeConfig
+import io.legado.app.help.permission.Permissions
+import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.alert
+import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.ATH
import io.legado.app.ui.widget.number.NumberPickerDialog
import io.legado.app.ui.widget.prefs.ColorPreference
-import io.legado.app.ui.widget.prefs.IconListPreference
+import io.legado.app.ui.widget.prefs.PreferenceCategory
import io.legado.app.utils.*
+import java.io.File
@Suppress("SameParameterValue")
class ThemeConfigFragment : BasePreferenceFragment(),
SharedPreferences.OnSharedPreferenceChangeListener {
+ private val requestCodeBgImage = 234
+ private val requestCodeBgImageN = 342
+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_theme)
if (Build.VERSION.SDK_INT < 26) {
- findPreference(PreferKey.launcherIcon)?.let {
- preferenceScreen.removePreference(it)
- }
+ preferenceScreen.removePreferenceRecursively(PreferKey.launcherIcon)
+ }
+ if (AppConfig.isGooglePlay) {
+ upPreferenceSummary(PreferKey.bgImage, getPrefString(PreferKey.bgImage))
+ upPreferenceSummary(PreferKey.bgImageN, getPrefString(PreferKey.bgImageN))
+ } else {
+ findPreference("dayThemeCategory")
+ ?.removePreferenceRecursively(PreferKey.bgImage)
+ findPreference("nightThemeCategory")
+ ?.removePreferenceRecursively(PreferKey.bgImageN)
}
upPreferenceSummary(PreferKey.barElevation, AppConfig.elevation.toString())
findPreference(PreferKey.cBackground)?.let {
@@ -168,10 +186,39 @@ class ThemeConfigFragment : BasePreferenceFragment(),
}
"themeList" -> ThemeListDialog().show(childFragmentManager, "themeList")
"saveDayTheme", "saveNightTheme" -> saveThemeAlert(key)
+ PreferKey.bgImage -> if (getPrefString(PreferKey.bgImage).isNullOrEmpty()) {
+ selectImage(requestCodeBgImage)
+ } else {
+ selector(items = arrayListOf("删除图片", "选择图片")) { _, i ->
+ if (i == 0) {
+ removePref(PreferKey.bgImage)
+ } else {
+ selectImage(requestCodeBgImage)
+ }
+ }
+ }
+ PreferKey.bgImageN -> if (getPrefString(PreferKey.bgImageN).isNullOrEmpty()) {
+ selectImage(requestCodeBgImageN)
+ } else {
+ selector(items = arrayListOf("删除图片", "选择图片")) { _, i ->
+ if (i == 0) {
+ removePref(PreferKey.bgImageN)
+ } else {
+ selectImage(requestCodeBgImageN)
+ }
+ }
+ }
}
return super.onPreferenceTreeClick(preference)
}
+ private fun selectImage(requestCode: Int) {
+ val intent = Intent(Intent.ACTION_GET_CONTENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "image/*"
+ startActivityForResult(intent, requestCode)
+ }
+
@SuppressLint("InflateParams")
private fun saveThemeAlert(key: String) {
alert(R.string.theme_name) {
@@ -211,6 +258,61 @@ class ThemeConfigFragment : BasePreferenceFragment(),
when (preferenceKey) {
PreferKey.barElevation -> preference.summary =
getString(R.string.bar_elevation_s, value)
+ else -> preference.summary = value
+ }
+ }
+
+ private fun setBgFromUri(uri: Uri, preferenceKey: String) {
+ if (uri.isContentScheme()) {
+ val doc = DocumentFile.fromSingleUri(requireContext(), uri)
+ doc?.name?.let {
+ var file = requireContext().externalFilesDir
+ file = FileUtils.createFileIfNotExist(file, preferenceKey, it)
+ kotlin.runCatching {
+ DocumentUtils.readBytes(requireContext(), doc.uri)
+ }.getOrNull()?.let { byteArray ->
+ file.writeBytes(byteArray)
+ putPrefString(preferenceKey, file.absolutePath)
+
+ } ?: toast("获取文件出错")
+ }
+ } else {
+ PermissionsCompat.Builder(this)
+ .addPermissions(
+ Permissions.READ_EXTERNAL_STORAGE,
+ Permissions.WRITE_EXTERNAL_STORAGE
+ )
+ .rationale(R.string.bg_image_per)
+ .onGranted {
+ RealPathUtil.getPath(requireContext(), uri)?.let { path ->
+ val imgFile = File(path)
+ if (imgFile.exists()) {
+ var file = requireContext().externalFilesDir
+ file = FileUtils.createFileIfNotExist(file, preferenceKey, imgFile.name)
+ file.writeBytes(imgFile.readBytes())
+ putPrefString(preferenceKey, file.absolutePath)
+
+ }
+ }
+ }
+ .request()
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ requestCodeBgImage -> if (resultCode == Activity.RESULT_OK) {
+ data?.data?.let { uri ->
+ setBgFromUri(uri, PreferKey.bgImage)
+ }
+ }
+ requestCodeBgImageN -> if (resultCode == Activity.RESULT_OK) {
+ data?.data?.let { uri ->
+ setBgFromUri(uri, PreferKey.bgImageN)
+ }
+ }
}
}
+
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
index 88969fe5d..c115e661a 100644
--- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
@@ -157,6 +157,9 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
if (LocalConfig.hasUpTxtTocRule) {
DefaultData.importDefaultTocRules()
}
+ if (LocalConfig.hasUpRssSources) {
+ DefaultData.importDefaultRssSources()
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
index 1fb43003b..ce6065e27 100644
--- a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
+++ b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
@@ -96,7 +96,7 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FilePickerDialog.C
}
if (AppConfig.isGooglePlay) {
findPreference("aboutCategory")
- ?.removePreference(findPreference("donate"))
+ ?.removePreferenceRecursively("donate")
}
}
diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt
index 60db06649..daf1dde0d 100644
--- a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt
+++ b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt
@@ -92,7 +92,7 @@ class RssFragment : VMBaseFragment(R.layout.fragment_rss),
}
override fun onQueryTextChange(newText: String?): Boolean {
- initData(newText ?: "")
+ initData(newText)
return false
}
})
@@ -105,7 +105,7 @@ class RssFragment : VMBaseFragment(R.layout.fragment_rss),
adapter.addHeaderView {
ItemRssBinding.inflate(layoutInflater, it, false).apply {
tvName.setText(R.string.rule_subscription)
- ivIcon.setImageResource(R.mipmap.ic_launcher)
+ ivIcon.setImageResource(R.drawable.image_legado)
root.onClick {
startActivity()
}
@@ -113,9 +113,16 @@ class RssFragment : VMBaseFragment(R.layout.fragment_rss),
}
}
- private fun initData(searchKey: String = "") {
+ private fun initData(searchKey: String? = null) {
liveRssData?.removeObservers(this)
- liveRssData = App.db.rssSourceDao.liveEnabled(searchKey).apply {
+ liveRssData = when {
+ searchKey.isNullOrEmpty() -> App.db.rssSourceDao.liveEnabled()
+ searchKey.startsWith("group:") -> {
+ val key = searchKey.substringAfter("group:")
+ App.db.rssSourceDao.liveEnabledByGroup("%$key%")
+ }
+ else -> App.db.rssSourceDao.liveEnabled("%$searchKey%")
+ }.apply {
observe(viewLifecycleOwner, {
adapter.setItems(it)
})
diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
index 548d9a481..af44e0c32 100644
--- a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt
@@ -154,14 +154,15 @@ class ReplaceRuleActivity : VMBaseActivity {
App.db.replaceRuleDao.liveDataSearch("%$searchKey%")
}
+ }.apply {
+ observe(this@ReplaceRuleActivity, {
+ if (dataInit) {
+ setResult(Activity.RESULT_OK)
+ }
+ adapter.setItems(it, adapter.diffItemCallBack)
+ dataInit = true
+ })
}
- replaceRuleLiveData?.observe(this, {
- if (dataInit) {
- setResult(Activity.RESULT_OK)
- }
- adapter.setItems(it, adapter.diffItemCallBack)
- dataInit = true
- })
}
private fun observeGroupData() {
@@ -239,7 +240,7 @@ class ReplaceRuleActivity : VMBaseActivity
@@ -60,7 +60,7 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
page++
val pageUrl = nextPageUrl
if (!pageUrl.isNullOrEmpty()) {
- Rss.getArticles(sortName, pageUrl, rssSource, page)
+ Rss.getArticles(this, sortName, pageUrl, rssSource, page)
.onSuccess(Dispatchers.IO) {
nextPageUrl = it.nextPageUrl
loadMoreSuccess(it.articles)
diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
index 0c09f71e6..452f8c87f 100644
--- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
@@ -94,7 +94,7 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
}
private fun loadContent(rssArticle: RssArticle, ruleContent: String) {
- Rss.getContent(rssArticle, ruleContent, rssSource, this)
+ Rss.getContent(this, rssArticle, ruleContent, rssSource)
.onSuccess(IO) { body ->
rssArticle.description = body
App.db.rssArticleDao.insert(rssArticle)
diff --git a/app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugModel.kt
index 2d069deab..d903da070 100644
--- a/app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugModel.kt
+++ b/app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugModel.kt
@@ -31,7 +31,7 @@ class RssSourceDebugModel(application: Application) : BaseViewModel(application)
rssSource?.let {
start?.invoke()
Debug.callback = this
- Debug.startDebug(it)
+ Debug.startDebug(this, it)
} ?: error?.invoke()
}
diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
index 0f7d10e8d..1e6156391 100644
--- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
@@ -92,6 +92,7 @@ class RssSourceActivity : VMBaseActivity viewModel.shareSelection(adapter.getSelection()) {
startActivity(Intent.createChooser(it, getString(R.string.share_selected_source)))
}
+ R.id.menu_import_default -> viewModel.importDefault()
R.id.menu_help -> showHelp()
else -> if (item.groupId == R.id.source_group) {
binding.titleBar.findViewById(R.id.search_view)
@@ -199,22 +200,22 @@ class RssSourceActivity : VMBaseActivity {
- App.db.rssSourceDao.liveAll()
- }
- searchKey.startsWith("group:") -> {
- val key = searchKey.substringAfter("group:")
- App.db.rssSourceDao.liveGroupSearch("%$key%")
- }
- else -> {
- App.db.rssSourceDao.liveSearch("%$searchKey%")
- }
+ sourceLiveData = when {
+ searchKey.isNullOrBlank() -> {
+ App.db.rssSourceDao.liveAll()
}
- sourceLiveData?.observe(this, {
- adapter.setItems(it, adapter.diffItemCallback)
- })
+ searchKey.startsWith("group:") -> {
+ val key = searchKey.substringAfter("group:")
+ App.db.rssSourceDao.liveGroupSearch("%$key%")
+ }
+ else -> {
+ App.db.rssSourceDao.liveSearch("%$searchKey%")
+ }
+ }.apply {
+ observe(this@RssSourceActivity, {
+ adapter.setItems(it, adapter.diffItemCallback)
+ })
+ }
}
private fun showHelp() {
diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
index 9a90932df..7ff23307c 100644
--- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
@@ -9,6 +9,7 @@ import io.legado.app.App
import io.legado.app.BuildConfig
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.RssSource
+import io.legado.app.help.DefaultData
import io.legado.app.utils.*
import org.jetbrains.anko.toast
import java.io.File
@@ -166,4 +167,10 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
}
}
+ fun importDefault() {
+ execute {
+ DefaultData.importDefaultRssSources()
+ }
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt
index 4263fc896..b6a6d23e2 100644
--- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt
+++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt
@@ -10,7 +10,7 @@ import java.util.*
val removeHtmlRegex = "?(?:div|p|br|hr|h\\d|article|dd|dl)[^>]*>".toRegex()
val imgRegex = "
]*>".toRegex()
-val notImgHtmlRegex = "?(?!img)\\w+[^>]*>".toRegex()
+val notImgHtmlRegex = "?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>".toRegex()
fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
diff --git a/app/src/main/java/io/legado/app/web/SourceDebugWebSocket.kt b/app/src/main/java/io/legado/app/web/SourceDebugWebSocket.kt
index 8b18d1179..747a741f5 100644
--- a/app/src/main/java/io/legado/app/web/SourceDebugWebSocket.kt
+++ b/app/src/main/java/io/legado/app/web/SourceDebugWebSocket.kt
@@ -1,6 +1,7 @@
package io.legado.app.web
+import android.os.Looper
import fi.iki.elonen.NanoHTTPD
import fi.iki.elonen.NanoWSD
import io.legado.app.App
@@ -22,12 +23,12 @@ class SourceDebugWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
override fun onOpen() {
launch(IO) {
- do {
- delay(30000)
- runCatching {
+ kotlin.runCatching {
+ while (isOpen) {
ping("ping".toByteArray())
+ delay(30000)
}
- } while (isOpen)
+ }
}
}
@@ -42,21 +43,21 @@ class SourceDebugWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :
override fun onMessage(message: NanoWSD.WebSocketFrame) {
if (!message.textPayload.isJson()) return
- kotlin.runCatching {
- val debugBean = GSON.fromJsonObject
预下载
预先下载10章正文
+ 启用排序
+ 背景图片
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index af90b7f86..73f4ea493 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -13,6 +13,7 @@
離線快取書籍備份
匯出本機同時備份到legado資料夾下exports目錄
備份路徑
+ 請選擇備份路徑
匯入舊版資料
匯入Github資料
淨化取代
@@ -29,6 +30,7 @@
提醒
編輯
刪除
+ 刪除所有
取代
取代淨化
配置取代淨化規則
@@ -388,11 +390,14 @@
源名稱(sourceName)
源URL(sourceUrl)
源分組(sourceGroup)
+ 自訂源分組
+ 輸入自訂源分組名稱
+ 【%s】
分類Url
登入URL(loginUrl)
+ 源注釋(sourceComment)
搜尋地址(url)
發現地址規則(url)
- 源注釋(sourceComment)
書籍列表規則(bookList)
書名規則(name)
詳情頁url規則(bookUrl)
@@ -415,6 +420,8 @@
正文規則(content)
正文下一頁URL規則(nextContentUrl)
WebViewJs(webJs)
+ 圖片樣式(imageStyle)
+ 取代規則(replaceRegex)
資源正則(sourceRegex)
圖示(sourceIcon)
@@ -449,6 +456,7 @@
請求頭(header)
除錯源
二維碼匯入
+ 分享選取源
掃描二維碼
選中時點擊可彈出選單
主題
@@ -684,6 +692,7 @@
中
訊息
切換布局
+ 文章字重轉換
主色調
@@ -702,16 +711,14 @@
夜間,底欄色
自動換源
文字兩端對齊
+ 文字底部對齊
自動翻頁速度
地址排序
- 文章字體轉換
- 請選擇備份路徑
本機和 WebDav 一起備份
優先從 WebDav 復原,長按從本機復原
選擇舊版備份資料夾
已啟用
已禁用
- 文字底部對齊
正在啟動下載
該書已在下載列表
點擊打開
@@ -729,8 +736,6 @@
復原忽略列表
復原時忽略一些內容不復原,方便不同手機配置不同
閱讀介面設定
- 圖片樣式(imageStyle)
- 取代規則(replaceRegex)
分組名稱
備註內容
預設啟用取代淨化
@@ -755,7 +760,6 @@
執行緒數
總閱讀時間
全不選
- 刪除所有
匯入
匯出
儲存主題配置
@@ -764,7 +768,6 @@
主題列表
使用儲存主題,匯入,分享主題
切換預設主題
- 分享選中源
時間排序
全文搜尋
關注公眾號[开源阅读]獲取訂閱源!
@@ -800,5 +803,7 @@
导入书单
预下载
预先下载10章正文
+ 启用排序
+ 背景图片
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 48eb53cb6..6ff4b0be6 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -803,5 +803,7 @@
导入书单
预下载
预先下载10章正文
+ 启用排序
+ 背景图片
diff --git a/app/src/main/res/values/pref_key_value.xml b/app/src/main/res/values/pref_key_value.xml
index 78e9f018d..ebaac231b 100644
--- a/app/src/main/res/values/pref_key_value.xml
+++ b/app/src/main/res/values/pref_key_value.xml
@@ -13,4 +13,5 @@
https://github.com/gedoor/legado/releases/latest
https://api.github.com/repos/gedoor/legado/releases/latest
https://t.me/yueduguanfang
+ https://discord.gg/qDE52P5xGW
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d2c078fec..2472dbe3c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -806,5 +806,7 @@
导入书单
预下载
预先下载10章正文
+ 启用排序
+ 背景图片
diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml
index e8881231a..69e339435 100644
--- a/app/src/main/res/xml/about.xml
+++ b/app/src/main/res/xml/about.xml
@@ -9,13 +9,15 @@
app:iconSpaceReserved="false" />
@@ -51,6 +53,12 @@
android:summary="@string/this_github_url"
app:iconSpaceReserved="false" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 4e4d97e17..5de0edc73 100644
--- a/build.gradle
+++ b/build.gradle
@@ -11,7 +11,7 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
- classpath "com.android.tools.build:gradle:4.1.1"
+ classpath 'com.android.tools.build:gradle:4.1.2'
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"