diff --git a/README.md b/README.md index ac01e9389..1c7bf34df 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) ## 阅读3.0 -书源规则 https://celeter.github.io/?tdsourcetag=s_pctim_aiomsg +书源规则 https://celeter.github.io ## 免责声明 https://gedoor.github.io/MyBookshelf/disclaimer.html diff --git a/app/build.gradle b/app/build.gradle index e36182ccc..3c8a18f6c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -140,7 +140,7 @@ dependencies { implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version" //liveEventBus - implementation 'com.jeremyliao:live-event-bus-x:1.4.5' + implementation 'com.jeremyliao:live-event-bus-x:1.5.7' //协程 def coroutines_version = '1.3.3' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 2c9c17581..9af1203b9 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -169,7 +169,9 @@ -dontwarn com.hwangjr.rxbus.** -dontwarn okhttp3.** -dontwarn org.conscrypt.** +-dontwarn com.jeremyliao.liveeventbus.** +-keep class com.jeremyliao.liveeventbus.** { *; } -keep class retrofit2.**{*;} -keep class okhttp3.**{*;} -keep class okio.**{*;} @@ -186,6 +188,7 @@ -keep class com.gyf.barlibrary.* {*;} ##JSOUP -keep class org.jsoup.**{*;} +-keep class **.xpath.**{*;} -keep class org.slf4j.**{*;} -dontwarn org.slf4j.** diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f4d3af897..5256b0ebc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -138,8 +138,10 @@ android:launchMode="singleTop"> + + @@ -186,6 +188,8 @@ android:launchMode="singleTop" /> + @@ -219,7 +227,6 @@ - diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index b8633fd1f..7ace22ee4 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -2,6 +2,20 @@ * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 +**2020/03/06** +* 添加隐藏标题 +* 行距段距改成倍距,根据字体大小变化 +* 修复翻页时右下角页数闪烁 +* 修复朗读错行 +* 添加底部分隔线,开关在边距设置里 + +**2020/03/05** +* 修复翻页动画 +* 修复主题模式跟随 +* 修复滚动翻页切换章节时跳动 +* 适配阅读3.0的web做源 +* 本地目录规则网络导入 + **2020/03/04** * 修复仿真翻页动画 * 添加阅读记录同步,正常退出进入软件时同步阅读记录 diff --git a/app/src/main/assets/web/index.html b/app/src/main/assets/web/index.html index 452e521f3..29bebabe2 100644 --- a/app/src/main/assets/web/index.html +++ b/app/src/main/assets/web/index.html @@ -3,305 +3,370 @@ - 书源编辑器v3.8 - + 阅读3.0书源编辑器_V4.0 + + -
-
-
-
书源基础信息
-
-
书源名称:
- -
-
-
书源分组:
- -
-
-
书源域名:
- -
-
-
登录网页:
- -
-
书籍发现规则
-
-
发现菜单:
- -
-
-
结果列表:
- -
-
-
书籍名称:
- -
-
-
书籍作者:
- -
-
-
书籍分类:
- -
-
-
最新章节:
- -
-
-
简介内容:
- -
-
-
封面链接:
- -
-
-
详情链接:
- -
-
书籍搜索规则
-
-
搜索网址:
- -
-
-
结果验证:
- -
-
-
结果列表:
- -
-
-
书籍名称:
- -
-
-
书籍作者:
- -
-
-
书籍分类:
- -
-
-
最新章节:
- -
-
-
简介内容:
- -
-
-
封面链接:
- -
-
-
详情链接:
- -
-
书籍详情规则
-
-
页面处理:
- -
-
-
书籍名称:
- -
-
-
书籍作者:
- -
-
-
书籍分类:
- -
-
-
最新章节:
- -
-
-
简介内容:
- -
-
-
封面链接:
- -
-
-
目录链接:
- -
-
目录列表规则
-
-
目录翻页:
- -
-
-
目录列表:
- -
-
-
章节名称:
- -
-
-
章节链接:
- -
-
正文阅读规则
-
-
章节正文:
- -
-
-
正文翻页:
- -
-
其它规则
-
-
浏览标识:
- -
-
-
排序编号:
- -
-
-
搜索权重:
- -
-
-
是否启用:
- +
+
+
+
基本
+
+
源URL :
+ +
+
+
源类型 :
+ +
+
+
源名称 :
+ +
+
+
源分组 :
+ +
+
+
登录地址:
+ +
+
+
链接验证:
+ +
+
+
请求头 :
+ +
+

+
搜索
+
+
搜索地址:
+ +
+
+
列表规则:
+ +
+
+
书名规则:
+ +
+
+
作者规则:
+ +
+
+
分类规则:
+ +
+
+
字数规则:
+ +
+
+
最新章节:
+ +
+
+
简介规则:
+ +
+
+
封面规则:
+ +
+
+
详情地址:
+ +
+

+
发现
+
+
发现地址:
+ +
+
+
列表规则:
+ +
+
+
书名规则:
+ +
+
+
作者规则:
+ +
+
+
分类规则:
+ +
+
+
字数规则:
+ +
+
+
最新章节:
+ +
+
+
简介规则:
+ +
+
+
封面规则:
+ +
+
+
详情地址:
+ +
+

+
详情
+
+
预处理 :
+ +
+
+
书名规则:
+ +
+
+
作者规则:
+ +
+
+
分类规则:
+ +
+
+
字数规则:
+ +
+
+
最新章节:
+ +
+
+
简介规则:
+ +
+
+
封面规则:
+ +
+
+
目录地址:
+ +
+

+
目录
+
+
列表规则:
+ +
+
+
章节名称:
+ +
+
+
章节地址:
+ +
+
+
收费标识:
+ +
+
+
章节信息:
+ +
+
+
翻页规则:
+ +
+

+
正文
+
+
正文规则:
+ +
+
+
翻页规则:
+ +
+
+
脚本注入:
+ +
+
+
资源正则:
+ +
+

+
其它规则
+
+
启用搜索:
+ +
+
+
启用发现:
+ +
+
+
搜索权重:
+ +
+
+
排序编号:
+ +
+
+
更新时间:
+ +
-
- -
-
-
-
编辑书源
-
调试书源
-
书源列表
-
帮助信息
-
-
-
- -
-
- - -
-
-
- - - - + +
+
+
+
编辑书源
+
调试书源
+
书源列表
+
帮助信息
+
+
+
+ +
+
+ + +
+
+ +
+ + + + +
+
-
-
-
-
-
- + \ No newline at end of file diff --git a/app/src/main/assets/web/index.js b/app/src/main/assets/web/index.js index 9463800ce..fb2298bbd 100644 --- a/app/src/main/assets/web/index.js +++ b/app/src/main/assets/web/index.js @@ -20,11 +20,40 @@ function hashParam(key, val) { // 创建书源规则容器对象 const RuleJSON = (() => { let ruleJson = {}; - $$('.rules textarea').forEach(item => ruleJson[item.id] = ''); -// for (let item of $$('.rules textarea')) ruleJson[item.id] = ''; - ruleJson.serialNumber = 0; + let searchJson = {}; + let exploreJson = {}; + let bookInfoJson = {}; + let tocJson = {}; + let contentJson = {}; + + // 基本以及其他 + $$('.rules .base').forEach(item => ruleJson[item.title] = ''); + ruleJson.lastUpdateTime = 0; + ruleJson.customOrder = 0; ruleJson.weight = 0; - ruleJson.enable = true; + ruleJson.enabled = true; + ruleJson.enabledExplore = true; + + // 搜索规则 + $$('.rules .ruleSearch').forEach(item => searchJson[item.title] = ''); + ruleJson.ruleSearch = JSON.stringify(searchJson); + + // 发现规则 + $$('.rules .ruleExplore').forEach(item => exploreJson[item.title] = ''); + ruleJson.ruleExplore = JSON.stringify(exploreJson); + + // 详情页规则 + $$('.rules .ruleBookInfo').forEach(item => bookInfoJson[item.title] = ''); + ruleJson.ruleBookInfo = JSON.stringify(bookInfoJson); + + // 目录规则 + $$('.rules .ruleToc').forEach(item => tocJson[item.title] = ''); + ruleJson.ruleToc = JSON.stringify(tocJson); + + // 正文规则 + $$('.rules .ruleContent').forEach(item => contentJson[item.title] = ''); + ruleJson.ruleContent = JSON.stringify(contentJson); + return ruleJson; })(); // 选项卡Tab切换事件处理 @@ -72,15 +101,113 @@ function HttpPost(url, data) { } // 将书源表单转化为书源对象 function rule2json() { - Object.keys(RuleJSON).forEach((key) => RuleJSON[key] = $('#' + key).value); - RuleJSON.serialNumber = RuleJSON.serialNumber == '' ? 0 : parseInt(RuleJSON.serialNumber); + // 转换base + Object.keys(RuleJSON).forEach(key => { + if (!key.startsWith("rule")) { + RuleJSON[key] = $('#' + key).value; + } + }); + + // 转换搜索规则 + let searchJson = {}; + Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { + searchJson[key] = $('#' + 'ruleSearch_' + key).value; + }); + RuleJSON.ruleSearch = JSON.stringify(searchJson); + + // 转换发现规则 + let exploreJson = {}; + Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => { + exploreJson[key] = $('#' + 'ruleExplore_' + key).value; + }); + RuleJSON.ruleExplore = JSON.stringify(exploreJson); + + // 转换详情页规则 + let bookInfoJson = {}; + Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => { + bookInfoJson[key] = $('#' + 'ruleBookInfo_' + key).value; + }); + RuleJSON.ruleBookInfo = JSON.stringify(bookInfoJson); + + // 转换目录规则 + let tocJson = {}; + Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => { + tocJson[key] = $('#' + 'ruleToc_' + key).value; + }); + RuleJSON.ruleToc = JSON.stringify(tocJson); + + // 转换正文规则 + let contentJson = {}; + Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => { + contentJson[key] = $('#' + 'ruleContent_' + key).value; + }); + RuleJSON.ruleContent = JSON.stringify(contentJson); + + RuleJSON.lastUpdateTime = RuleJSON.lastUpdateTime == '' ? 0 : parseInt(RuleJSON.lastUpdateTime); + RuleJSON.customOrder = RuleJSON.customOrder == '' ? 0 : parseInt(RuleJSON.customOrder); RuleJSON.weight = RuleJSON.weight == '' ? 0 : parseInt(RuleJSON.weight); - RuleJSON.enable = RuleJSON.enable == '' || RuleJSON.enable.toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true'; + RuleJSON.bookSourceType == RuleJSON.bookSourceType == '' ? 0 : parseInt(RuleJSON.weight); + RuleJSON.enabled = RuleJSON.enabled == '' || String(RuleJSON.enabled).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true'; + RuleJSON.enabledExplore = RuleJSON.enabledExplore == '' || String(RuleJSON.enabledExplore).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true'; return RuleJSON; } // 将书源对象填充到书源表单 function json2rule(RuleEditor) { - Object.keys(RuleJSON).forEach((key) => $("#" + key).value = RuleEditor[key] ? RuleEditor[key] : ''); + // 转换base + Object.keys(RuleJSON).forEach(key => { + if (!key.startsWith("rule")) { + let val = RuleEditor[key]; + if (typeof val == "number") { + $("#" + key).value = val ? String(val) : '0'; + } + else if (typeof val == "boolean") { + $("#" + key).value = val ? String(val) : 'false'; + } + else { + $("#" + key).value = val ? String(val) : ''; + } + } + }); + + // 转换搜索规则 + if (RuleEditor.ruleSearch) { + let searchJson = JSON.parse(RuleEditor.ruleSearch); + Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => { + $('#' + 'ruleSearch_' + key).value = searchJson[key] ? searchJson[key] : ''; + }); + } + + // 转换发现规则 + if (RuleEditor.ruleExplore) { + let exploreJson = JSON.parse(RuleEditor.ruleExplore); + Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => { + $('#' + 'ruleExplore_' + key).value = exploreJson[key] ? exploreJson[key] : ''; + }); + } + + // 转换详情页规则 + if (RuleEditor.ruleBookInfo) { + let bookInfoJson = JSON.parse(RuleEditor.ruleBookInfo); + Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => { + $('#' + 'ruleBookInfo_' + key).value = bookInfoJson[key] ? bookInfoJson[key] : ''; + }); + } + + // 转换目录规则 + if (RuleEditor.ruleToc) { + let tocJson = JSON.parse(RuleEditor.ruleToc); + Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => { + $('#' + 'ruleToc_' + key).value = tocJson[key] ? tocJson[key] : ''; + }); + } + + // 转换正文规则 + if (RuleEditor.ruleContent) { + let contentJson = JSON.parse(RuleEditor.ruleContent); + Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => { + $('#' + 'ruleContent_' + key).value = contentJson[key] ? contentJson[key] : ''; + }); + } } // 记录操作过程 var course = { "old": [], "now": {}, "new": [] }; @@ -153,16 +280,16 @@ $('.menu').addEventListener('click', e => { }); failMsg = '\n推送失败的书源将用红色字体标注!'; } - alert(`批量推送书源到「阅读APP」\n共计: ${RuleSources.length} 条\n成功: ${okData.length} 条\n失败: ${RuleSources.length - okData.length} 条${failMsg}`); + alert(`批量推送书源到「阅读3.0APP」\n共计: ${RuleSources.length} 条\n成功: ${okData.length} 条\n失败: ${RuleSources.length - okData.length} 条${failMsg}`); } else { - alert(`批量推送书源到「阅读APP」成功!\n共计: ${RuleSources.length} 条`); + alert(`批量推送书源到「阅读3.0APP」成功!\n共计: ${RuleSources.length} 条`); } } else { alert(`批量推送书源失败!\nErrorMsg: ${json.errorMsg}`); } - }).catch(err => { alert(`批量推送书源失败,无法连接到「阅读APP」!\n${err}`); }); + }).catch(err => { alert(`批量推送书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); thisNode.setAttribute('class', ''); })(); return; @@ -181,7 +308,7 @@ $('.menu').addEventListener('click', e => { else { alert(`批量拉取书源失败!\nErrorMsg: ${json.errorMsg}`); } - }).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读APP」!\n${err}`); }); + }).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); thisNode.setAttribute('class', ''); })(); return; @@ -244,9 +371,9 @@ $('.menu').addEventListener('click', e => { (async () => { let saveRule = [rule2json()]; await HttpPost(`/saveSources`, saveRule).then(json => { - alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`); + alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读3.0APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`); setRule(saveRule[0]); - }).catch(err => { alert(`保存书源失败,无法连接到「阅读APP」!\n${err}`); }); + }).catch(err => { alert(`保存书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); thisNode.setAttribute('class', ''); })(); return; @@ -261,6 +388,26 @@ $('#DebugKey').addEventListener('keydown', e => { $('#debug').dispatchEvent(clickEvent); } }); +$('#Filter').addEventListener('keydown', e => { + if (e.keyCode == 13) { + let cashList = []; + $('#RuleList').innerHTML = ""; + let sKey = Filter.value ? Filter.value : ''; + if (sKey == '') { + cashList = RuleSources; + } else { + let patt = new RegExp(sKey); + RuleSources.forEach(source => { + if (patt.test(source.bookSourceUrl) || patt.test(source.bookSourceName) || patt.test(source.bookSourceGroup)) { + cashList.push(source); + } + }) + } + cashList.forEach(source => { + $('#RuleList').innerHTML += newRule(source); + }) + } +}); // 列表规则更改事件 $('#RuleList').addEventListener('click', e => { @@ -348,7 +495,7 @@ $('.tab3>.titlebar').addEventListener('click', e => { console.log(deleteSources); console.log(`以上书源已删除!`) } - }).catch(err => { alert(`删除书源失败,无法连接到「阅读APP」!\n${err}`); }); + }).catch(err => { alert(`删除书源失败,无法连接到「阅读3.0APP」!\n${err}`); }); } break; case 'ClrAll': diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index 88379b333..d1a3e9464 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -50,7 +50,7 @@ class App : Application() { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId() applyDayNight() - LiveEventBus.get() + LiveEventBus .config() .supportBroadcast(this) .lifecycleObserverAlwaysActive(true) diff --git a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt index e893e34c4..84e9d4b0b 100644 --- a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt +++ b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt @@ -1,6 +1,7 @@ package io.legado.app.base import android.os.Bundle +import android.view.View import androidx.fragment.app.DialogFragment import io.legado.app.help.coroutine.Coroutine import kotlinx.coroutines.CoroutineScope @@ -18,6 +19,14 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope { job = Job() } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + onFragmentCreated(view, savedInstanceState) + observeLiveBus() + } + + abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?) + override fun onDestroy() { super.onDestroy() job.cancel() @@ -30,4 +39,7 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope { ): Coroutine { return Coroutine.async(scope, context) { block() } } + + open fun observeLiveBus() { + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/base/BaseFragment.kt b/app/src/main/java/io/legado/app/base/BaseFragment.kt index c4b712349..c19033aa8 100644 --- a/app/src/main/java/io/legado/app/base/BaseFragment.kt +++ b/app/src/main/java/io/legado/app/base/BaseFragment.kt @@ -1,5 +1,6 @@ package io.legado.app.base +import android.annotation.SuppressLint import android.os.Bundle import android.view.* import androidx.appcompat.view.SupportMenuInflater @@ -18,6 +19,7 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID), private set val menuInflater: MenuInflater + @SuppressLint("RestrictedApi") get() = SupportMenuInflater(requireContext()) override val coroutineContext: CoroutineContext diff --git a/app/src/main/java/io/legado/app/constant/Pattern.kt b/app/src/main/java/io/legado/app/constant/AppPattern.kt similarity index 76% rename from app/src/main/java/io/legado/app/constant/Pattern.kt rename to app/src/main/java/io/legado/app/constant/AppPattern.kt index fd623bf69..396edb73d 100644 --- a/app/src/main/java/io/legado/app/constant/Pattern.kt +++ b/app/src/main/java/io/legado/app/constant/AppPattern.kt @@ -2,7 +2,9 @@ package io.legado.app.constant import java.util.regex.Pattern -object Pattern { +object AppPattern { val JS_PATTERN: Pattern = Pattern.compile("([\\w\\W]*?|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE) val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}") + + val authorRegex = "作\\s*者\\s*[::]".toRegex() } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/constant/EventBus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt index 402827cb4..88031b4fc 100644 --- a/app/src/main/java/io/legado/app/constant/EventBus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -20,6 +20,5 @@ object EventBus { const val SHOW_RSS = "showRss" const val WEB_SERVICE_STOP = "webServiceStop" const val UP_DOWNLOAD = "upDownload" - const val UP_TABS = "upTabs" const val SAVE_CONTENT = "saveContent" } \ No newline at end of file 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 b427f687b..1ba9ba6cf 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -27,6 +27,7 @@ object PreferKey { const val fontFolder = "fontFolder" const val backupPath = "backupUri" const val threadCount = "threadCount" + const val webPort = "webPort" const val keepLight = "keep_light" const val webService = "webService" const val webDavUrl = "web_dav_url" diff --git a/app/src/main/java/io/legado/app/constant/RSSKeywords.kt b/app/src/main/java/io/legado/app/constant/RSSKeywords.kt deleted file mode 100644 index 686842950..000000000 --- a/app/src/main/java/io/legado/app/constant/RSSKeywords.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.legado.app.constant - -object RSSKeywords { - - const val RSS_ITEM = "item" - const val RSS_ITEM_TITLE = "title" - const val RSS_ITEM_LINK = "link" - const val RSS_ITEM_CATEGORY = "category" - const val RSS_ITEM_THUMBNAIL = "media:thumbnail" - const val RSS_ITEM_ENCLOSURE = "enclosure" - const val RSS_ITEM_DESCRIPTION = "description" - const val RSS_ITEM_CONTENT = "content:encoded" - const val RSS_ITEM_PUB_DATE = "pubDate" - const val RSS_ITEM_TIME = "time" - const val RSS_ITEM_URL = "url" - const val RSS_ITEM_TYPE = "type" -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt index 5812b419c..1bd502a09 100644 --- a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt @@ -16,6 +16,9 @@ interface TxtTocRuleDao { @get:Query("select * from txtTocRules where enable = 1 order by serialNumber") val enabled: List + @get:Query("select ifNull(max(serialNumber), 0) from txtTocRules") + val lastOrderNum: Int + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg rule: TxtTocRule) diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index d29ce696e..833561f1b 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -5,6 +5,7 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.Index import androidx.room.PrimaryKey +import io.legado.app.constant.AppPattern import io.legado.app.constant.BookType import io.legado.app.utils.GSON import io.legado.app.utils.fromJsonObject @@ -86,7 +87,7 @@ data class Book( @IgnoredOnParcel override var tocHtml: String? = null - fun getRealAuthor() = author.replace("作者:", "") + fun getRealAuthor() = author.replace(AppPattern.authorRegex, "") fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0) diff --git a/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt b/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt index 8744ce2cb..dc1364559 100644 --- a/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt @@ -1,11 +1,14 @@ package io.legado.app.data.entities import android.os.Parcelable +import android.text.TextUtils import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey import kotlinx.android.parcel.Parcelize +import java.util.regex.Pattern +import java.util.regex.PatternSyntaxException @Parcelize @Entity( @@ -37,4 +40,20 @@ data class ReplaceRule( override fun hashCode(): Int { return id.hashCode() } + + fun isValid(): Boolean{ + if (TextUtils.isEmpty(pattern)){ + return false; + } + //判断正则表达式是否正确 + if (isRegex){ + try { + Pattern.compile(pattern); + } + catch (ex: PatternSyntaxException){ + return false; + } + } + return true; + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/BlurTransformation.kt b/app/src/main/java/io/legado/app/help/BlurTransformation.kt index fec7d0eef..4493228c4 100644 --- a/app/src/main/java/io/legado/app/help/BlurTransformation.kt +++ b/app/src/main/java/io/legado/app/help/BlurTransformation.kt @@ -8,10 +8,8 @@ import android.renderscript.Allocation import android.renderscript.Element import android.renderscript.RenderScript import android.renderscript.ScriptIntrinsicBlur - import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool -import com.bumptech.glide.load.resource.bitmap.BitmapTransformation - +import com.bumptech.glide.load.resource.bitmap.CenterCrop import java.security.MessageDigest import kotlin.math.min import kotlin.math.roundToInt @@ -21,15 +19,16 @@ import kotlin.math.roundToInt * 模糊 * @radius: 0..25 */ -class BlurTransformation(context: Context, private val radius: Int) : BitmapTransformation() { +class BlurTransformation(context: Context, private val radius: Int) : CenterCrop() { private val rs: RenderScript = RenderScript.create(context) @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap { + val transform = super.transform(pool, toTransform, outWidth, outHeight) //图片缩小1/2 - val width = (min(outWidth, toTransform.width) / 2f).roundToInt() - val height = (min(outHeight, toTransform.height) / 2f).roundToInt() - val blurredBitmap = Bitmap.createScaledBitmap(toTransform, width, height, false); + val width = (min(outWidth, transform.width) / 2f).roundToInt() + val height = (min(outHeight, transform.height) / 2f).roundToInt() + val blurredBitmap = Bitmap.createScaledBitmap(transform, width, height, false); // Allocate memory for Renderscript to work with //分配用于渲染脚本的内存 val input = Allocation.createFromBitmap( diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index 31f0d4b5e..304df9997 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -180,7 +180,7 @@ object BookHelp { fun formatAuthor(author: String?): String { return author - ?.replace("作\\s*者[\\s::]*".toRegex(), "") + ?.replace("作\\s*者\\s*[::]\n*".toRegex(), "") ?.replace("\\s+".toRegex(), " ") ?.trim { it <= ' ' } ?: "" diff --git a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt index dcef91c72..77dc45e9a 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -165,10 +165,21 @@ object ReadBookConfig { if (shareLayout) shareConfig.paragraphSpacing = value else durConfig.paragraphSpacing = value - var titleCenter: Boolean - get() = if (shareLayout) shareConfig.titleCenter else durConfig.titleCenter + var titleMode: Int + get() = if (shareLayout) shareConfig.titleMode else durConfig.titleMode set(value) = - if (shareLayout) shareConfig.titleCenter = value else durConfig.titleCenter = value + if (shareLayout) shareConfig.titleMode = value else durConfig.titleMode = value + + var titleTopSpacing: Int + get() = if (shareLayout) shareConfig.titleTopSpacing else durConfig.titleTopSpacing + set(value) = + if (shareLayout) shareConfig.titleTopSpacing = value + else durConfig.titleTopSpacing = value + var titleBottomSpacing: Int + get() = if (shareLayout) shareConfig.titleBottomSpacing else durConfig.titleBottomSpacing + set(value) = + if (shareLayout) shareConfig.titleBottomSpacing = value + else durConfig.titleBottomSpacing = value var paddingBottom: Int get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom @@ -238,6 +249,12 @@ object ReadBookConfig { if (shareLayout) shareConfig.footerPaddingTop = value else durConfig.footerPaddingTop = value + var showFooterLine: Boolean + get() = if (shareLayout) shareConfig.showFooterLine else durConfig.showFooterLine + set(value) = + if (shareLayout) shareConfig.showFooterLine = value + else durConfig.showFooterLine = value + @Keep class Config( private var bgStr: String = "#EEEEEE",//白天背景 @@ -253,7 +270,9 @@ object ReadBookConfig { var letterSpacing: Float = 0.5f,//字间距 var lineSpacingExtra: Int = 12,//行间距 var paragraphSpacing: Int = 12,//段距 - var titleCenter: Boolean = true,//标题居中 + var titleMode: Int = 0,//标题居中 + var titleTopSpacing: Int = 0, + var titleBottomSpacing: Int = 0, var paddingBottom: Int = 6, var paddingLeft: Int = 16, var paddingRight: Int = 16, @@ -265,7 +284,8 @@ object ReadBookConfig { var footerPaddingBottom: Int = 6, var footerPaddingLeft: Int = 16, var footerPaddingRight: Int = 16, - var footerPaddingTop: Int = 6 + var footerPaddingTop: Int = 6, + var showFooterLine: Boolean = true ) { fun setBg(bgType: Int, bg: String) { if (AppConfig.isNightTheme) { diff --git a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt index 8d0c9cd2c..4af39cbb2 100644 --- a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt +++ b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt @@ -136,13 +136,17 @@ object ImportOldData { return bookSources.size } + + fun importOldReplaceRule(json: String): Int { val replaceRules = mutableListOf() val items: List> = Restore.jsonPath.parse(json).read("$") for (item in items) { val jsonItem = Restore.jsonPath.parse(item) OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let { - replaceRules.add(it) + if (it.isValid()){ + replaceRules.add(it) + } } } App.db.replaceRuleDao().insert(*replaceRules.toTypedArray()) 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 8cdce0ae5..5393249c3 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 @@ -2,6 +2,7 @@ package io.legado.app.help.storage import android.content.Context import android.net.Uri +import androidx.appcompat.app.AppCompatDelegate import androidx.documentfile.provider.DocumentFile import com.jayway.jsonpath.Configuration import com.jayway.jsonpath.JsonPath @@ -9,8 +10,10 @@ import com.jayway.jsonpath.Option import com.jayway.jsonpath.ParseContext import io.legado.app.App import io.legado.app.BuildConfig +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.* +import io.legado.app.help.AppConfig import io.legado.app.help.LauncherIconHelp import io.legado.app.help.ReadBookConfig import io.legado.app.service.help.ReadBook @@ -128,9 +131,16 @@ object Restore { ReadBook.loadContent() } withContext(Main) { - App.INSTANCE.applyDayNight() - if (!BuildConfig.DEBUG) + if (AppConfig.isNightTheme && AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_YES) { + App.INSTANCE.applyDayNight() + } else if (!AppConfig.isNightTheme && AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) { + App.INSTANCE.applyDayNight() + } else { + postEvent(EventBus.RECREATE, "true") + } + if (!BuildConfig.DEBUG) { LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon)) + } } } diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt index e7360da7e..1cc90b89e 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt @@ -1,12 +1,14 @@ package io.legado.app.model.analyzeRule import android.text.TextUtils +import androidx.annotation.Keep import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.ReadContext import io.legado.app.utils.splitNotBlank import java.util.* import java.util.regex.Pattern +@Keep class AnalyzeByJSonPath { private var ctx: ReadContext? = null diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt index 38fb633c5..7e49ac677 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt @@ -2,6 +2,7 @@ package io.legado.app.model.analyzeRule import android.text.TextUtils.isEmpty import android.text.TextUtils.join +import androidx.annotation.Keep import io.legado.app.utils.splitNotBlank import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -15,7 +16,7 @@ import java.util.* * Created by GKF on 2018/1/25. * 书源规则解析 */ - +@Keep class AnalyzeByJSoup { private var element: Element? = null diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt index 5475df744..05851b423 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt @@ -1,8 +1,10 @@ package io.legado.app.model.analyzeRule +import androidx.annotation.Keep import java.util.* import java.util.regex.Pattern +@Keep object AnalyzeByRegex { fun getElement(res: String, regs: Array, index: Int = 0): List? { diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt index 707ab9a22..52f3b7560 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt @@ -1,6 +1,7 @@ package io.legado.app.model.analyzeRule import android.text.TextUtils +import androidx.annotation.Keep import io.legado.app.utils.splitNotBlank import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -9,6 +10,7 @@ import org.seimicrawler.xpath.JXDocument import org.seimicrawler.xpath.JXNode import java.util.* +@Keep class AnalyzeByXPath { private var jxDocument: JXDocument? = null private var jxNode: JXNode? = null 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 f88a5ee85..9b1e7d3b0 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 @@ -3,7 +3,7 @@ package io.legado.app.model.analyzeRule import android.text.TextUtils import androidx.annotation.Keep import io.legado.app.constant.AppConst.SCRIPT_ENGINE -import io.legado.app.constant.Pattern.JS_PATTERN +import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BookChapter import io.legado.app.help.JsExtensions 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 9ac402267..7b1488c96 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 @@ -4,8 +4,8 @@ import android.annotation.SuppressLint import android.text.TextUtils import androidx.annotation.Keep import io.legado.app.constant.AppConst.SCRIPT_ENGINE -import io.legado.app.constant.Pattern.EXP_PATTERN -import io.legado.app.constant.Pattern.JS_PATTERN +import io.legado.app.constant.AppPattern.EXP_PATTERN +import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.data.entities.BaseBook import io.legado.app.help.JsExtensions import io.legado.app.help.http.* diff --git a/app/src/main/java/io/legado/app/model/rss/RssParser.kt b/app/src/main/java/io/legado/app/model/rss/RssParser.kt index 816a3858f..7bda16e6d 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParser.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParser.kt @@ -1,6 +1,5 @@ package io.legado.app.model.rss -import io.legado.app.constant.RSSKeywords import io.legado.app.data.entities.RssArticle import io.legado.app.model.Debug import org.xmlpull.v1.XmlPullParser @@ -34,26 +33,26 @@ object RssParser { // Start parsing the item if (eventType == XmlPullParser.START_TAG) { when { - xmlPullParser.name.equals(RSSKeywords.RSS_ITEM, true) -> + xmlPullParser.name.equals(RSS_ITEM, true) -> insideItem = true - xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TITLE, true) -> + xmlPullParser.name.equals(RSS_ITEM_TITLE, true) -> if (insideItem) currentArticle.title = xmlPullParser.nextText().trim() - xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_LINK, true) -> + xmlPullParser.name.equals(RSS_ITEM_LINK, true) -> if (insideItem) currentArticle.link = xmlPullParser.nextText().trim() - xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_THUMBNAIL, true) -> + xmlPullParser.name.equals(RSS_ITEM_THUMBNAIL, true) -> if (insideItem) currentArticle.image = - xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL) - xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_ENCLOSURE, true) -> + xmlPullParser.getAttributeValue(null, RSS_ITEM_URL) + xmlPullParser.name.equals(RSS_ITEM_ENCLOSURE, true) -> if (insideItem) { val type = - xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_TYPE) + xmlPullParser.getAttributeValue(null, RSS_ITEM_TYPE) if (type != null && type.contains("image/")) { currentArticle.image = - xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL) + xmlPullParser.getAttributeValue(null, RSS_ITEM_URL) } } xmlPullParser.name - .equals(RSSKeywords.RSS_ITEM_DESCRIPTION, true) -> + .equals(RSS_ITEM_DESCRIPTION, true) -> if (insideItem) { val description = xmlPullParser.nextText() currentArticle.description = description.trim() @@ -61,7 +60,7 @@ object RssParser { currentArticle.image = getImageUrl(description) } } - xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_CONTENT, true) -> + xmlPullParser.name.equals(RSS_ITEM_CONTENT, true) -> if (insideItem) { val content = xmlPullParser.nextText().trim() currentArticle.content = content @@ -70,7 +69,7 @@ object RssParser { } } xmlPullParser.name - .equals(RSSKeywords.RSS_ITEM_PUB_DATE, true) -> + .equals(RSS_ITEM_PUB_DATE, true) -> if (insideItem) { val nextTokenType = xmlPullParser.next() if (nextTokenType == XmlPullParser.TEXT) { @@ -79,7 +78,7 @@ object RssParser { // Skip to be able to find date inside 'tag' tag continue@loop } - xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TIME, true) -> + xmlPullParser.name.equals(RSS_ITEM_TIME, true) -> if (insideItem) currentArticle.pubDate = xmlPullParser.nextText() } } else if (eventType == XmlPullParser.END_TAG @@ -129,4 +128,17 @@ object RssParser { } return url } + + private const val RSS_ITEM = "item" + private const val RSS_ITEM_TITLE = "title" + private const val RSS_ITEM_LINK = "link" + private const val RSS_ITEM_CATEGORY = "category" + private const val RSS_ITEM_THUMBNAIL = "media:thumbnail" + private const val RSS_ITEM_ENCLOSURE = "enclosure" + private const val RSS_ITEM_DESCRIPTION = "description" + private const val RSS_ITEM_CONTENT = "content:encoded" + private const val RSS_ITEM_PUB_DATE = "pubDate" + private const val RSS_ITEM_TIME = "time" + private const val RSS_ITEM_URL = "url" + private const val RSS_ITEM_TYPE = "type" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt index a9c193545..b06950031 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt @@ -1,5 +1,6 @@ package io.legado.app.model.rss +import androidx.annotation.Keep import io.legado.app.App import io.legado.app.R import io.legado.app.data.entities.RssArticle @@ -8,6 +9,7 @@ import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.utils.NetworkUtils +@Keep object RssParserByRule { @Throws(Exception::class) diff --git a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt index 3e25fc9c3..70c1a92be 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt @@ -2,6 +2,7 @@ package io.legado.app.model.webBook import io.legado.app.App import io.legado.app.R +import io.legado.app.constant.AppPattern import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookSource import io.legado.app.model.Debug @@ -38,7 +39,7 @@ object BookInfo { Debug.log(bookSource.bookSourceUrl, "└${book.name}") Debug.log(bookSource.bookSourceUrl, "┌获取作者") analyzeRule.getString(infoRule.author).let { - if (it.isNotEmpty()) book.author = it + if (it.isNotEmpty()) book.author = it.replace(AppPattern.authorRegex, "") } Debug.log(bookSource.bookSourceUrl, "└${book.author}") Debug.log(bookSource.bookSourceUrl, "┌获取分类") diff --git a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt index e1e3f8620..3ad351ca7 100644 --- a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt @@ -124,16 +124,16 @@ abstract class BaseReadAloudService : BaseService(), open fun play() { pause = false - postEvent(EventBus.ALOUD_STATE, Status.PLAY) upNotification() + postEvent(EventBus.ALOUD_STATE, Status.PLAY) } @CallSuper open fun pauseReadAloud(pause: Boolean) { - postEvent(EventBus.ALOUD_STATE, Status.PAUSE) BaseReadAloudService.pause = pause upNotification() upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED) + postEvent(EventBus.ALOUD_STATE, Status.PAUSE) } @CallSuper 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 9b4b56ebe..7796ffce0 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -13,7 +13,6 @@ import io.legado.app.utils.FileUtils import io.legado.app.utils.LogUtils import io.legado.app.utils.postEvent import kotlinx.coroutines.isActive -import org.jetbrains.anko.toast import java.io.File import java.io.FileDescriptor import java.io.FileInputStream @@ -84,8 +83,6 @@ class HttpReadAloudService : BaseReadAloudService(), break } } - }.onError { - toast("下载朗读文件出错:${it.localizedMessage}") } } diff --git a/app/src/main/java/io/legado/app/service/WebService.kt b/app/src/main/java/io/legado/app/service/WebService.kt index e9d223956..bbf317066 100644 --- a/app/src/main/java/io/legado/app/service/WebService.kt +++ b/app/src/main/java/io/legado/app/service/WebService.kt @@ -3,12 +3,12 @@ package io.legado.app.service import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat -import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseService -import io.legado.app.constant.IntentAction import io.legado.app.constant.AppConst import io.legado.app.constant.EventBus +import io.legado.app.constant.IntentAction +import io.legado.app.constant.PreferKey import io.legado.app.help.IntentHelp import io.legado.app.utils.NetworkUtils import io.legado.app.utils.getPrefInt @@ -96,7 +96,7 @@ class WebService : BaseService() { } private fun getPort(): Int { - var port = App.INSTANCE.getPrefInt("webPort", 1122) + var port = getPrefInt(PreferKey.webPort, 1122) if (port > 65530 || port < 1024) { port = 1122 } diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt index 502e49986..bece64c93 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt @@ -11,7 +11,7 @@ import android.widget.SeekBar import androidx.lifecycle.Observer import com.bumptech.glide.RequestBuilder import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.RequestOptions.bitmapTransform import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.constant.EventBus @@ -120,19 +120,17 @@ class AudioPlayActivity : ImageLoader.load(this, path) .placeholder(R.drawable.image_cover_default) .error(R.drawable.image_cover_default) - .centerCrop() .into(iv_cover) ImageLoader.load(this, path) .transition(DrawableTransitionOptions.withCrossFade(1500)) .thumbnail(defaultCover()) - .centerCrop() - .apply(RequestOptions.bitmapTransform(BlurTransformation(this, 25))) + .apply(bitmapTransform(BlurTransformation(this, 25))) .into(iv_bg) } private fun defaultCover(): RequestBuilder { return ImageLoader.load(this, R.drawable.image_cover_default) - .apply(RequestOptions.bitmapTransform(BlurTransformation(this, 25))) + .apply(bitmapTransform(BlurTransformation(this, 25))) } private fun playButton() { diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt index 7a091c145..85df58172 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt @@ -3,19 +3,24 @@ package io.legado.app.ui.book.changecover import android.os.Bundle import android.util.DisplayMetrics import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.fragment.app.DialogFragment +import androidx.appcompat.widget.Toolbar import androidx.fragment.app.FragmentManager import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.GridLayoutManager import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.constant.Theme +import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.dialog_change_source.* -class ChangeCoverDialog : DialogFragment(), - ChangeCoverViewModel.CallBack, +class ChangeCoverDialog : BaseDialogFragment(), + Toolbar.OnMenuItemClickListener, CoverAdapter.CallBack { companion object { @@ -35,7 +40,7 @@ class ChangeCoverDialog : DialogFragment(), private var callBack: CallBack? = null private lateinit var viewModel: ChangeCoverViewModel - override lateinit var adapter: CoverAdapter + lateinit var adapter: CoverAdapter override fun onStart() { super.onStart() @@ -51,30 +56,57 @@ class ChangeCoverDialog : DialogFragment(), ): View? { callBack = activity as? CallBack viewModel = getViewModel(ChangeCoverViewModel::class.java) - viewModel.callBack = this return inflater.inflate(R.layout.dialog_change_cover, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewModel.searchStateData.observe(viewLifecycleOwner, Observer { - refresh_progress_bar.isAutoLoading = it - }) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { tool_bar.setTitle(R.string.change_cover_source) - arguments?.let { bundle -> - bundle.getString("name")?.let { - viewModel.name = it - } - bundle.getString("author")?.let { - viewModel.author = it - } - } + viewModel.initData(arguments) + initMenu() + initView() + } + + private fun initMenu() { + tool_bar.inflateMenu(R.menu.change_cover) + tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.setOnMenuItemClickListener(this) + } + + private fun initView() { recycler_view.layoutManager = GridLayoutManager(requireContext(), 3) adapter = CoverAdapter(requireContext(), this) recycler_view.adapter = adapter - viewModel.initData() + viewModel.loadDbSearchBook() } + override fun observeLiveBus() { + super.observeLiveBus() + viewModel.searchStateData.observe(viewLifecycleOwner, Observer { + refresh_progress_bar.isAutoLoading = it + if (it) { + stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp) + } else { + stopMenuItem?.setIcon(R.drawable.ic_refresh_black_24dp) + } + tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + }) + viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer { + val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it)) + adapter.setItems(it) + diffResult.dispatchUpdatesTo(adapter) + }) + } + + override fun onMenuItemClick(item: MenuItem?): Boolean { + when (item?.itemId) { + R.id.menu_stop -> viewModel.stopSearch() + } + return false + } + + private val stopMenuItem: MenuItem? + get() = tool_bar.menu.findItem(R.id.menu_stop) + override fun changeTo(coverUrl: String) { callBack?.coverChangeTo(coverUrl) dismiss() diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index 3ef77e076..627bacc09 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt @@ -1,35 +1,51 @@ package io.legado.app.ui.book.changecover import android.app.Application +import android.os.Bundle import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppPattern +import io.legado.app.data.entities.SearchBook import io.legado.app.help.AppConfig import io.legado.app.help.coroutine.Coroutine import io.legado.app.model.WebBook import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.withContext import java.util.concurrent.Executors class ChangeCoverViewModel(application: Application) : BaseViewModel(application) { private var searchPool = Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - var callBack: CallBack? = null var name: String = "" var author: String = "" private var task: Coroutine<*>? = null val searchStateData = MutableLiveData() + val searchBooksLiveData = MutableLiveData>() + private val searchBooks = ArrayList() - fun initData() { + fun initData(arguments: Bundle?) { + arguments?.let { bundle -> + bundle.getString("name")?.let { + name = it + } + bundle.getString("author")?.let { + author = it.replace(AppPattern.authorRegex, "") + } + } + } + + fun loadDbSearchBook() { execute { - App.db.searchBookDao().getEnableHasCover(name, author) - }.onSuccess { - it?.let { - callBack?.adapter?.setItems(it) + App.db.searchBookDao().getEnableHasCover(name, author).let { + searchBooks.addAll(it) + if (it.size <= 1) { + searchBooksLiveData.postValue(searchBooks) + search() + } else { + searchBooksLiveData.postValue(searchBooks) + } } - }.onFinally { - search() } } @@ -48,12 +64,9 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application && !searchBook.coverUrl.isNullOrEmpty() ) { App.db.searchBookDao().insert(searchBook) - callBack?.adapter?.let { adapter -> - if (!adapter.getItems().contains(searchBook)) { - withContext(Dispatchers.Main) { - adapter.addItem(searchBook) - } - } + if (!searchBooks.contains(searchBook)) { + searchBooks.add(searchBook) + searchBooksLiveData.postValue(searchBooks) } } } @@ -66,12 +79,17 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application } } + fun stopSearch() { + if (task?.isActive == true) { + task?.cancel() + } else { + search() + } + } + override fun onCleared() { super.onCleared() searchPool.close() } - interface CallBack { - var adapter: CoverAdapter - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/changecover/DiffCallBack.kt new file mode 100644 index 000000000..ffdac0f79 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/changecover/DiffCallBack.kt @@ -0,0 +1,52 @@ +package io.legado.app.ui.book.changecover + +import android.os.Bundle +import androidx.recyclerview.widget.DiffUtil +import io.legado.app.data.entities.SearchBook + +class DiffCallBack(private val oldItems: List, private val newItems: List) : + DiffUtil.Callback() { + + override fun getOldListSize(): Int { + return oldItems.size + } + + override fun getNewListSize(): Int { + return newItems.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldItems[oldItemPosition] + val newItem = newItems[newItemPosition] + return oldItem.bookUrl == newItem.bookUrl + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldItems[oldItemPosition] + val newItem = newItems[newItemPosition] + if (oldItem.originName != newItem.originName) { + return false + } + if (oldItem.coverUrl != newItem.coverUrl) { + return false + } + return true + } + + override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { + val oldItem = oldItems[oldItemPosition] + val newItem = newItems[newItemPosition] + val payload = Bundle() + if (oldItem.originName != newItem.originName) { + payload.putString("name", newItem.originName) + } + if (oldItem.coverUrl != newItem.coverUrl) { + payload.putString("coverUrl", newItem.coverUrl) + } + if (payload.isEmpty) { + return null + } + return payload + } + +} \ No newline at end of file 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 4ef9d56a3..bb60c19d4 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 @@ -8,13 +8,13 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.constant.PreferKey import io.legado.app.constant.Theme import io.legado.app.data.entities.Book @@ -27,7 +27,7 @@ import io.legado.app.utils.putPrefBoolean import kotlinx.android.synthetic.main.dialog_change_source.* -class ChangeSourceDialog : DialogFragment(), +class ChangeSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener, ChangeSourceAdapter.CallBack { @@ -67,8 +67,7 @@ class ChangeSourceDialog : DialogFragment(), return inflater.inflate(R.layout.dialog_change_source, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { viewModel.initData(arguments) showTitle() initMenu() @@ -76,7 +75,6 @@ class ChangeSourceDialog : DialogFragment(), initSearchView() initLiveData() viewModel.loadDbSearchBook() - viewModel.search() } private fun showTitle() { @@ -113,15 +111,16 @@ class ChangeSourceDialog : DialogFragment(), } private fun initSearchView() { - search_view.setOnCloseListener { + val searchView = tool_bar.menu.findItem(R.id.menu_screen).actionView as SearchView + searchView.setOnCloseListener { showTitle() false } - search_view.setOnSearchClickListener { + searchView.setOnSearchClickListener { tool_bar.title = "" tool_bar.subtitle = "" } - search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { return false } @@ -137,6 +136,12 @@ class ChangeSourceDialog : DialogFragment(), private fun initLiveData() { viewModel.searchStateData.observe(viewLifecycleOwner, Observer { refresh_progress_bar.isAutoLoading = it + if (it) { + stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp) + } else { + stopMenuItem?.setIcon(R.drawable.ic_refresh_black_24dp) + } + tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) }) viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer { val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it)) @@ -145,12 +150,16 @@ class ChangeSourceDialog : DialogFragment(), }) } + private val stopMenuItem: MenuItem? + get() = tool_bar.menu.findItem(R.id.menu_stop) + override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_load_toc -> { putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked) item.isChecked = !item.isChecked } + R.id.menu_stop -> viewModel.stopSearch() } return false } 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 548038e59..e0800c0a1 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 @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppPattern import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.data.entities.SearchBook @@ -35,7 +36,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio name = it } bundle.getString("author")?.let { - author = it + author = it.replace(AppPattern.authorRegex, "") } } } @@ -44,7 +45,12 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio execute { App.db.searchBookDao().getByNameAuthorEnable(name, author).let { searchBooks.addAll(it) - upAdapter() + if (it.size <= 1) { + upAdapter() + search() + } else { + upAdapter() + } } } } @@ -145,6 +151,14 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } + fun stopSearch() { + if (task?.isActive == true) { + task?.cancel() + } else { + search() + } + } + override fun onCleared() { super.onCleared() searchPool.close() diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt index d20cf203b..621a41fa9 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.content.Intent import android.graphics.drawable.Drawable import android.os.Bundle -import android.text.method.ScrollingMovementMethod import android.view.Menu import android.view.MenuItem import androidx.lifecycle.Observer @@ -55,7 +54,6 @@ class BookInfoActivity : override fun onActivityCreated(savedInstanceState: Bundle?) { setSupportActionBar(toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) - tv_intro.movementMethod = ScrollingMovementMethod.getInstance() viewModel.bookData.observe(this, Observer { showBook(it) }) viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) viewModel.initData(intent) @@ -105,7 +103,7 @@ class BookInfoActivity : private fun showBook(book: Book) { showCover(book) tv_name.text = book.name - tv_author.text = getString(R.string.author_show, book.author) + tv_author.text = getString(R.string.author_show, book.getRealAuthor()) tv_origin.text = getString(R.string.origin_show, book.originName) tv_lasted.text = getString(R.string.lasted_show, book.latestChapterTitle) tv_toc.text = getString(R.string.toc_s, getString(R.string.loading)) @@ -126,7 +124,6 @@ class BookInfoActivity : ImageLoader.load(this, book.getDisplayCover()) .transition(DrawableTransitionOptions.withCrossFade(1500)) .thumbnail(defaultCover()) - .centerCrop() .apply(bitmapTransform(BlurTransformation(this, 25))) .into(bg_book) //模糊、渐变、缩小效果 } 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 de1f1d728..e85a0db31 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 @@ -152,6 +152,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo PaddingConfigDialog().show(supportFragmentManager, "paddingConfig") } + fun showTypeConfig() { + ReadTypeDialog().show(supportFragmentManager, "readTypeDialog") + } + fun showBgTextConfig() { BgTextConfigDialog().show(supportFragmentManager, "bgTextConfig") } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 5a3d3ec8b..73f85dce0 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.DialogInterface import android.content.Intent import android.graphics.Color +import android.net.Uri import android.os.Bundle import android.util.DisplayMetrics import android.view.Gravity @@ -13,11 +14,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.documentfile.provider.DocumentFile -import androidx.fragment.app.DialogFragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.jaredrummler.android.colorpicker.ColorPickerDialog import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.constant.EventBus @@ -31,9 +32,8 @@ import kotlinx.android.synthetic.main.dialog_read_bg_text.* import kotlinx.android.synthetic.main.item_bg_image.view.* import org.jetbrains.anko.sdk27.listeners.onCheckedChange import org.jetbrains.anko.sdk27.listeners.onClick -import java.io.File -class BgTextConfigDialog : DialogFragment() { +class BgTextConfigDialog : BaseDialogFragment() { companion object { const val TEXT_COLOR = 121 @@ -69,8 +69,7 @@ class BgTextConfigDialog : DialogFragment() { return inflater.inflate(R.layout.dialog_read_bg_text, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { initData() initView() } @@ -171,39 +170,44 @@ class BgTextConfigDialog : DialogFragment() { resultSelectBg -> { if (resultCode == RESULT_OK) { data?.data?.let { uri -> - if (uri.toString().isContentPath()) { - val doc = DocumentFile.fromSingleUri(requireContext(), uri) - doc?.let { - var file = requireContext().getExternalFilesDir(null) - ?: requireContext().filesDir - file = - FileUtils.createFileIfNotExist(file.absolutePath + File.separator + "bg" + File.separator + doc.name) - DocumentUtils.readBytes(requireContext(), uri)?.let { - file.writeBytes(it) - ReadBookConfig.durConfig.setBg(2, file.absolutePath) - ReadBookConfig.upBg() - postEvent(EventBus.UP_CONFIG, false) - } - } - } 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 -> - ReadBookConfig.durConfig.setBg(2, path) - ReadBookConfig.upBg() - postEvent(EventBus.UP_CONFIG, false) - } - } - .request() - } + setBgFromUri(uri) } } } } } + + private fun setBgFromUri(uri: Uri) { + if (uri.toString().isContentPath()) { + val doc = DocumentFile.fromSingleUri(requireContext(), uri) + doc?.name?.let { + var file = requireContext().getExternalFilesDir(null) + ?: requireContext().filesDir + file = FileUtils.createFileIfNotExist(file, it, "bg") + kotlin.runCatching { + DocumentUtils.readBytes(requireContext(), doc.uri) + }.getOrNull()?.let { byteArray -> + file.writeBytes(byteArray) + ReadBookConfig.durConfig.setBg(2, file.absolutePath) + ReadBookConfig.upBg() + postEvent(EventBus.UP_CONFIG, false) + } ?: 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 -> + ReadBookConfig.durConfig.setBg(2, path) + ReadBookConfig.upBg() + postEvent(EventBus.UP_CONFIG, false) + } + } + .request() + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index 1155e1d7d..a19789fd3 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.book.read.config +import android.content.DialogInterface import android.os.Bundle import android.util.DisplayMetrics import android.view.LayoutInflater @@ -11,6 +12,7 @@ import io.legado.app.constant.EventBus import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.Help import io.legado.app.utils.dp +import io.legado.app.utils.gone import io.legado.app.utils.postEvent import io.legado.app.utils.visible import kotlinx.android.synthetic.main.dialog_read_padding.* @@ -46,19 +48,18 @@ class PaddingConfigDialog : DialogFragment() { initView() } - override fun onDestroy() { - super.onDestroy() + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) ReadBookConfig.save() } - private fun initData() = with(ReadBookConfig) { + private fun initData() = ReadBookConfig.apply { if (hideStatusBar) { - tv_header_padding.visible() - dsb_header_padding_top.visible() - dsb_header_padding_bottom.visible() - dsb_header_padding_left.visible() - dsb_header_padding_right.visible() + ll_header_padding.visible() tv_body_padding.setPadding(0, 10.dp, 0, 10.dp) + } else { + ll_header_padding.gone() + tv_body_padding.setPadding(0, 0.dp, 0, 10.dp) } //正文 dsb_padding_top.progress = paddingTop @@ -75,6 +76,7 @@ class PaddingConfigDialog : DialogFragment() { dsb_footer_padding_bottom.progress = footerPaddingBottom dsb_footer_padding_left.progress = footerPaddingLeft dsb_footer_padding_right.progress = footerPaddingRight + cb_show_line.isChecked = showFooterLine } private fun initView() = with(ReadBookConfig) { @@ -129,6 +131,12 @@ class PaddingConfigDialog : DialogFragment() { footerPaddingRight = it postEvent(EventBus.UP_CONFIG, true) } + cb_show_line.onCheckedChangeListener = { cb, isChecked -> + if (cb.isPressed) { + showFooterLine = isChecked + postEvent(EventBus.UP_CONFIG, true) + } + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt index 8cce7d4d2..5540d0386 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt @@ -7,8 +7,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.SeekBar -import androidx.fragment.app.DialogFragment import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.constant.EventBus import io.legado.app.help.AppConfig import io.legado.app.lib.theme.bottomBackground @@ -23,7 +23,7 @@ import kotlinx.android.synthetic.main.dialog_read_aloud.* import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onLongClick -class ReadAloudDialog : DialogFragment() { +class ReadAloudDialog : BaseDialogFragment() { var callBack: CallBack? = null override fun onStart() { @@ -53,21 +53,17 @@ class ReadAloudDialog : DialogFragment() { return inflater.inflate(R.layout.dialog_read_aloud, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground) - initData() initOnChange() + initData() initOnClick() } private fun initData() { - observeEvent(EventBus.ALOUD_STATE) { upPlayState() } - observeEvent(EventBus.TTS_DS) { seek_timer.progress = it } upPlayState() + upTimerText(BaseReadAloudService.timeMinute) seek_timer.progress = BaseReadAloudService.timeMinute - tv_timer.text = - requireContext().getString(R.string.timer_m, BaseReadAloudService.timeMinute) cb_tts_follow_sys.isChecked = requireContext().getPrefBoolean("ttsFollowSys", true) seek_tts_SpeechRate.isEnabled = !cb_tts_follow_sys.isChecked seek_tts_SpeechRate.progress = AppConfig.ttsSpeechRate @@ -94,7 +90,7 @@ class ReadAloudDialog : DialogFragment() { }) seek_timer.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - tv_timer.text = requireContext().getString(R.string.timer_m, progress) + upTimerText(progress) } override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit @@ -131,6 +127,10 @@ class ReadAloudDialog : DialogFragment() { } } + private fun upTimerText(timeMinute: Int) { + tv_timer.text = requireContext().getString(R.string.timer_m, timeMinute) + } + private fun upTtsSpeechRate() { ReadAloud.upTtsSpeechRate(requireContext()) if (!BaseReadAloudService.pause) { @@ -139,6 +139,11 @@ class ReadAloudDialog : DialogFragment() { } } + override fun observeLiveBus() { + observeEvent(EventBus.ALOUD_STATE) { upPlayState() } + observeEvent(EventBus.TTS_DS) { seek_timer.progress = it } + } + interface CallBack { fun showMenuBar() fun openChapterList() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index b51d8c088..3c2bbb78a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.book.read.config +import android.content.DialogInterface import android.os.Bundle import android.util.DisplayMetrics import android.view.Gravity @@ -32,6 +33,7 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { val callBack get() = activity as? ReadBookActivity + lateinit var titleModes: Array override fun onStart() { super.onStart() @@ -61,24 +63,19 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + titleModes = requireContext().resources.getStringArray(R.array.title_mode) initView() initData() initViewEvent() } - override fun onDestroy() { - super.onDestroy() + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) ReadBookConfig.save() } private fun initView() { root_view.setBackgroundColor(requireContext().bottomBackground) - dsb_text_size.valueFormat = { - (it + 5).toString() - } - dsb_text_letter_spacing.valueFormat = { - ((it - 50) / 100f).toString() - } } private fun initData() { @@ -97,12 +94,12 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { chinese_converter.onChanged { postEvent(EventBus.UP_CONFIG, true) } - tv_title_center.onClick { - ReadBookConfig.apply { - titleCenter = !titleCenter - tv_title_center.isSelected = titleCenter + tv_title_mode.onClick { + requireContext().selector("标题模式", titleModes.toList()) { _, index -> + ReadBookConfig.titleMode = index + tv_title_mode.text = titleModes[index] + postEvent(EventBus.UP_CONFIG, true) } - postEvent(EventBus.UP_CONFIG, true) } tv_text_bold.onClick { ReadBookConfig.apply { @@ -127,21 +124,9 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { dismiss() callBack?.showPaddingConfig() } - dsb_text_size.onChanged = { - ReadBookConfig.textSize = it + 5 - postEvent(EventBus.UP_CONFIG, true) - } - dsb_text_letter_spacing.onChanged = { - ReadBookConfig.letterSpacing = (it - 50) / 100f - postEvent(EventBus.UP_CONFIG, true) - } - dsb_line_size.onChanged = { - ReadBookConfig.lineSpacingExtra = it - postEvent(EventBus.UP_CONFIG, true) - } - dsb_paragraph_spacing.onChanged = { - ReadBookConfig.paragraphSpacing = it - postEvent(EventBus.UP_CONFIG, true) + tv_type.onClick { + dismiss() + callBack?.showTypeConfig() } rg_page_anim.onCheckedChange { _, checkedId -> rg_page_anim.getIndexById(checkedId).let { @@ -156,6 +141,21 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { postEvent(EventBus.UP_CONFIG, true) } } + iv_default1.onClick { + ReadBookConfig.lineSpacingExtra = 16 + ReadBookConfig.paragraphSpacing = 6 + postEvent(EventBus.UP_CONFIG, true) + } + iv_default2.onClick { + ReadBookConfig.lineSpacingExtra = 13 + ReadBookConfig.paragraphSpacing = 3 + postEvent(EventBus.UP_CONFIG, true) + } + iv_default3.onClick { + ReadBookConfig.lineSpacingExtra = 10 + ReadBookConfig.paragraphSpacing = 0 + postEvent(EventBus.UP_CONFIG, true) + } bg0.onClick { changeBg(0) } bg0.onLongClick { showBgTextConfig(0) } bg1.onClick { changeBg(1) } @@ -187,12 +187,8 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { private fun upStyle() { ReadBookConfig.let { - tv_title_center.isSelected = it.titleCenter + tv_title_mode.text = titleModes.getOrElse(it.titleMode) { titleModes[0] } tv_text_bold.isSelected = it.textBold - dsb_text_size.progress = it.textSize - 5 - dsb_text_letter_spacing.progress = (it.letterSpacing * 100).toInt() + 50 - dsb_line_size.progress = it.lineSpacingExtra - dsb_paragraph_spacing.progress = it.paragraphSpacing } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadTypeDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadTypeDialog.kt new file mode 100644 index 000000000..c5247a455 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadTypeDialog.kt @@ -0,0 +1,91 @@ +package io.legado.app.ui.book.read.config + +import android.content.DialogInterface +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import io.legado.app.R +import io.legado.app.base.BaseDialogFragment +import io.legado.app.constant.EventBus +import io.legado.app.help.ReadBookConfig +import io.legado.app.ui.book.read.Help +import io.legado.app.utils.postEvent +import kotlinx.android.synthetic.main.dialog_read_type.* + +class ReadTypeDialog : BaseDialogFragment() { + + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.let { + Help.upSystemUiVisibility(it) + it.windowManager?.defaultDisplay?.getMetrics(dm) + } + dialog?.window?.let { + val attr = it.attributes + attr.dimAmount = 0.0f + it.attributes = attr + it.setLayout((dm.widthPixels * 0.9).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_read_type, container) + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + ReadBookConfig.save() + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + initView() + initViewEvent() + upStyle() + } + + private fun initView() { + dsb_text_size.valueFormat = { + (it + 5).toString() + } + dsb_text_letter_spacing.valueFormat = { + ((it - 50) / 100f).toString() + } + dsb_line_size.valueFormat = { ((it - 10) / 10f).toString() } + dsb_paragraph_spacing.valueFormat = { (it / 10f).toString() } + } + + private fun initViewEvent() { + dsb_text_size.onChanged = { + ReadBookConfig.textSize = it + 5 + postEvent(EventBus.UP_CONFIG, true) + } + dsb_text_letter_spacing.onChanged = { + ReadBookConfig.letterSpacing = (it - 50) / 100f + postEvent(EventBus.UP_CONFIG, true) + } + dsb_line_size.onChanged = { + ReadBookConfig.lineSpacingExtra = it + postEvent(EventBus.UP_CONFIG, true) + } + dsb_paragraph_spacing.onChanged = { + ReadBookConfig.paragraphSpacing = it + postEvent(EventBus.UP_CONFIG, true) + } + } + + private fun upStyle() { + ReadBookConfig.let { + dsb_text_size.progress = it.textSize - 5 + dsb_text_letter_spacing.progress = (it.letterSpacing * 100).toInt() + 50 + dsb_line_size.progress = it.lineSpacingExtra + dsb_paragraph_spacing.progress = it.paragraphSpacing + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt index f2046ac4b..aa816f93f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt @@ -15,6 +15,7 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseDialogFragment @@ -27,9 +28,10 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.cancelButton import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.okButton -import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.ui.widget.recycler.VerticalDivider -import io.legado.app.utils.applyTint +import io.legado.app.ui.widget.text.AutoCompleteTextView +import io.legado.app.utils.* +import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_toc_regex.* import kotlinx.android.synthetic.main.dialog_toc_regex_edit.view.* import kotlinx.android.synthetic.main.item_toc_regex.view.* @@ -40,11 +42,12 @@ import java.util.* class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { - + private val importTocRuleKey = "tocRuleUrl" private lateinit var adapter: TocRegexAdapter private var tocRegexLiveData: LiveData>? = null var selectedName: String? = null private var durRegex: String? = null + lateinit var viewModel: TocRegexViewModel override fun onStart() { super.onStart() @@ -58,11 +61,11 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { container: ViewGroup?, savedInstanceState: Bundle? ): View? { + viewModel = getViewModel(TocRegexViewModel::class.java) return inflater.inflate(R.layout.dialog_toc_regex, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { durRegex = arguments?.getString("tocRegex") tool_bar.setTitle(R.string.txt_toc_regex) tool_bar.inflateMenu(R.menu.txt_toc_regex) @@ -122,17 +125,51 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_add -> editRule() - R.id.menu_default -> importDefault() + R.id.menu_default -> viewModel.importDefault() + R.id.menu_import -> showImportDialog() } return false } - private fun importDefault() { - launch(IO) { - AnalyzeTxtFile.getDefaultRules().let { - App.db.txtTocRule().insert(*it.toTypedArray()) - } + @SuppressLint("InflateParams") + private fun showImportDialog() { + val aCache = ACache.get(requireContext(), cacheDir = false) + val defaultUrl = "https://gitee.com/fisher52/YueDuJson/raw/master/myTxtChapterRule.json" + val cacheUrls: MutableList = aCache + .getAsString(importTocRuleKey) + ?.splitNotBlank(",") + ?.toMutableList() + ?: mutableListOf() + if (!cacheUrls.contains(defaultUrl)) { + cacheUrls.add(0, defaultUrl) } + requireContext().alert(titleResource = R.string.import_book_source_on_line) { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = this.edit_view + edit_view.setFilterValues(cacheUrls) + edit_view.delCallBack = { + cacheUrls.remove(it) + aCache.put(importTocRuleKey, cacheUrls.joinToString(",")) + } + } + } + okButton { + val text = editText?.text?.toString() + text?.let { + if (!cacheUrls.contains(it)) { + cacheUrls.add(0, it) + aCache.put(importTocRuleKey, cacheUrls.joinToString(",")) + } + Snackbar.make(tool_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() + viewModel.importOnLine(it) { msg -> + tool_bar.snackbar(msg) + } + } + } + cancelButton() + }.show().applyTint() } @SuppressLint("InflateParams") @@ -152,25 +189,13 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { rootView?.apply { tocRule.name = tv_rule_name.text.toString() tocRule.rule = tv_rule_regex.text.toString() - saveRule(tocRule, rule) + viewModel.saveRule(tocRule, rule) } } cancelButton() }.show().applyTint() } - private fun saveRule(rule: TxtTocRule, oldRule: TxtTocRule? = null) { - launch(IO) { - if (rule.serialNumber < 0) { - rule.serialNumber = adapter.getItems().lastOrNull()?.serialNumber ?: 0 + 1 - } - oldRule?.let { - App.db.txtTocRule().delete(oldRule) - } - App.db.txtTocRule().insert(rule) - } - } - inner class TocRegexAdapter(context: Context) : SimpleRecyclerAdapter(context, R.layout.item_toc_regex), ItemTouchCallback.OnItemTouchCallbackListener { @@ -241,7 +266,6 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { } } - companion object { fun show(fragmentManager: FragmentManager, tocRegex: String? = null) { val dialog = TocRegexDialog() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt new file mode 100644 index 000000000..4a63120c8 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/config/TocRegexViewModel.kt @@ -0,0 +1,48 @@ +package io.legado.app.ui.book.read.config + +import android.app.Application +import io.legado.app.App +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.TxtTocRule +import io.legado.app.help.http.HttpHelper +import io.legado.app.model.localBook.AnalyzeTxtFile +import io.legado.app.utils.GSON +import io.legado.app.utils.fromJsonArray + +class TocRegexViewModel(application: Application) : BaseViewModel(application) { + + fun saveRule(rule: TxtTocRule, oldRule: TxtTocRule? = null) { + execute { + if (rule.serialNumber < 0) { + rule.serialNumber = App.db.txtTocRule().lastOrderNum + 1 + } + oldRule?.let { + App.db.txtTocRule().delete(oldRule) + } + App.db.txtTocRule().insert(rule) + } + } + + fun importDefault() { + execute { + AnalyzeTxtFile.getDefaultRules().let { + App.db.txtTocRule().insert(*it.toTypedArray()) + } + } + } + + fun importOnLine(url: String, finally: (msg: String) -> Unit) { + execute { + HttpHelper.simpleGetAsync(url)?.let { json -> + GSON.fromJsonArray(json)?.let { + App.db.txtTocRule().insert(*it.toTypedArray()) + } + } + }.onSuccess { + finally("导入成功") + }.onError { + finally("导入失败") + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt index 20afe26cf..3366c019d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt @@ -13,10 +13,7 @@ import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextPage -import io.legado.app.utils.dp -import io.legado.app.utils.getPrefString -import io.legado.app.utils.removePref -import io.legado.app.utils.toStringArray +import io.legado.app.utils.* @Suppress("DEPRECATION") @@ -51,34 +48,16 @@ object ChapterProvider { val pageLines = arrayListOf() val pageLengths = arrayListOf() val stringBuilder = StringBuilder() - var surplusText = content + val contents = content.split("\n") var durY = 0f textPages.add(TextPage()) - while (surplusText.isNotEmpty()) { - if (textPages.first().textLines.isEmpty()) { - //title - val end = surplusText.indexOf("\n") - if (end > 0) { - val title = surplusText.substring(0, end) - surplusText = surplusText.substring(end + 1) - durY = setTypeText( - title, durY, textPages, pageLines, pageLengths, stringBuilder, true - ) - } - } else { - //正文 - val end = surplusText.indexOf("\n") - val text: String - if (end >= 0) { - text = surplusText.substring(0, end) - surplusText = surplusText.substring(end + 1) - } else { - text = surplusText - surplusText = "" - } - durY = - setTypeText(text, durY, textPages, pageLines, pageLengths, stringBuilder, false) + for ((index, text) in contents.withIndex()) { + val isTitle = index == 0 + if (isTitle && ReadBookConfig.titleMode == 2) { + continue } + durY = + setTypeText(text, durY, textPages, pageLines, pageLengths, stringBuilder, isTitle) } textPages.last().height = durY + 20.dp textPages.last().text = stringBuilder.toString() @@ -125,44 +104,45 @@ object ChapterProvider { Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true ) for (lineIndex in 0 until layout.lineCount) { - textPages.last().height = durY val textLine = TextLine(isTitle = isTitle) - if (durY + textPaint.textHeight < visibleHeight) { - textPages.last().textLines.add(textLine) - durY += textPaint.textHeight + lineSpacingExtra - } else { - textPages.last().text = stringBuilder.toString() - stringBuilder.clear() - pageLines.add(textPages.last().textLines.size) - pageLengths.add(textPages.last().text.length) - //新页面 - durY = textPaint.textHeight + lineSpacingExtra - textPages.add(TextPage()) - textPages.last().textLines.add(textLine) - } - textLine.lineBottom = paddingTop + durY - lineSpacingExtra - textLine.lineBase = textLine.lineBottom - textPaint.fontMetrics.descent - textLine.lineTop = textLine.lineBottom - textPaint.textHeight val words = text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) - stringBuilder.append(words) textLine.text = words val desiredWidth = layout.getLineWidth(lineIndex) + var isLastLine = false if (lineIndex == 0 && layout.lineCount > 1 && !isTitle) { //第一行 addCharsToLineFirst(textLine, words, textPaint, desiredWidth) } else if (lineIndex == layout.lineCount - 1) { //最后一行 - val x = if (isTitle && ReadBookConfig.titleCenter) + isLastLine = true + val x = if (isTitle && ReadBookConfig.titleMode == 1) (visibleWidth - layout.getLineWidth(lineIndex)) / 2 else 0f - addCharsToLineLast(textLine, words, stringBuilder, textPaint, x) + addCharsToLineLast(textLine, words, textPaint, x) } else { //中间行 addCharsToLineMiddle(textLine, words, textPaint, desiredWidth, 0f) } + if (durY + textPaint.textHeight > visibleHeight) { + //当前页面结束,设置各种值 + textPages.last().text = stringBuilder.toString() + pageLines.add(textPages.last().textLines.size) + pageLengths.add(textPages.last().text.length) + textPages.last().height = durY + //新建页面 + textPages.add(TextPage()) + stringBuilder.clear() + durY = 0f + } + stringBuilder.append(words) + if (isLastLine) stringBuilder.append("\n") + textPages.last().textLines.add(textLine) + textLine.upTopBottom(durY, textPaint) + durY += textPaint.textHeight * lineSpacingExtra / 10f + textPages.last().height = durY } - durY += paragraphSpacing + durY += textPaint.textHeight * paragraphSpacing / 10f return durY } @@ -223,11 +203,9 @@ object ChapterProvider { private fun addCharsToLineLast( textLine: TextLine, words: String, - stringBuilder: StringBuilder, textPaint: TextPaint, startX: Float ) { - stringBuilder.append("\n") textLine.text = "$words\n" var x = startX words.toStringArray().forEach { @@ -291,10 +269,10 @@ object ChapterProvider { val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL contentPaint.typeface = Typeface.create(typeface, bold) //间距 - lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp - paragraphSpacing = ReadBookConfig.paragraphSpacing.dp - titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat() - contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat() + lineSpacingExtra = ReadBookConfig.lineSpacingExtra + paragraphSpacing = ReadBookConfig.paragraphSpacing + titlePaint.textSize = (ReadBookConfig.textSize + 2).sp.toFloat() + contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() upSize() } @@ -311,8 +289,6 @@ object ChapterProvider { visibleBottom = paddingTop + visibleHeight } - private val TextPaint.textHeight: Float - get() { - return this.fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading - } + val TextPaint.textHeight: Float + get() = this.fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index 7f1dcec1c..8e0bf1d55 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -60,6 +60,7 @@ class ContentView(context: Context) : FrameLayout(context) { footerPaddingRight.dp, footerPaddingBottom.dp ) + vw_bottom_divider.visible(showFooterLine) content_text_view.upVisibleRect() durConfig.textColor().let { tv_top_left.setTextColor(it) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt index c973fc87c..05437cd70 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt @@ -18,14 +18,16 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { } override fun onDraw(canvas: Canvas) { + if (!isRunning) return val offsetX = touchX - startX if ((mDirection == Direction.NEXT && offsetX > 0) || (mDirection == Direction.PREV && offsetX < 0) - ) return + ) { + return + } val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth - if (!isRunning) return if (mDirection == Direction.PREV) { bitmapMatrix.setTranslate(distanceX, 0.toFloat()) curBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index d28f8c816..e85a073cf 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -65,13 +65,13 @@ abstract class PageDelegate(protected val pageView: PageView) : var mDirection = Direction.NONE var isCancel = false var isRunning = false - var isStarted = false + private var isStarted = false var isTextSelected = false - var selectedOnDown = false + private var selectedOnDown = false - var firstRelativePage = 0 - var firstLineIndex: Int = 0 - var firstCharIndex: Int = 0 + private var firstRelativePage = 0 + private var firstLineIndex: Int = 0 + private var firstCharIndex: Int = 0 init { curPage.resetPageOffset() @@ -127,10 +127,12 @@ abstract class PageDelegate(protected val pageView: PageView) : } private fun stopScroll() { - isMoved = false - isRunning = false isStarted = false - pageView.invalidate() + pageView.post { + isMoved = false + isRunning = false + pageView.invalidate() + } } open fun setViewSize(width: Int, height: Int) { @@ -194,7 +196,6 @@ abstract class PageDelegate(protected val pageView: PageView) : @CallSuper open fun onTouch(event: MotionEvent) { if (isStarted) return - detector.setIsLongpressEnabled(false) if (!detector.onTouchEvent(event)) { //GestureDetector.onFling小幅移动不会触发,所以要自己判断 when (event.action) { 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 a9c84fb68..c8a9c9ca0 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 @@ -1,5 +1,9 @@ package io.legado.app.ui.book.read.page.entities +import android.text.TextPaint +import io.legado.app.ui.book.read.page.ChapterProvider +import io.legado.app.ui.book.read.page.ChapterProvider.textHeight + data class TextLine( var text: String = "", val textChars: ArrayList = arrayListOf(), @@ -10,6 +14,12 @@ data class TextLine( var isReadAloud: Boolean = false ) { + fun upTopBottom(durY: Float, textPaint: TextPaint) { + lineTop = ChapterProvider.paddingTop + durY + lineBottom = lineTop + textPaint.textHeight + lineBase = lineBottom - textPaint.fontMetrics.descent + } + fun addTextChar(charData: String, start: Float, end: Float) { textChars.add(TextChar(charData, start = start, end = end)) } 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 e8782b6c3..f42fcdb5d 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 @@ -34,14 +34,15 @@ data class TextPage( (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))) textLine.lineBottom = textLine.lineBase + ChapterProvider.contentPaint.fontMetrics.descent - var x = (ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2 + var x = ChapterProvider.paddingLeft + + (ChapterProvider.visibleWidth - layout.getLineMax(lineIndex)) / 2 textLine.text = text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) for (i in textLine.text.indices) { val char = textLine.text[i].toString() val cw = StaticLayout.getDesiredWidth(char, ChapterProvider.contentPaint) val x1 = x + cw - textLine.textChars.add(TextChar(charData = char, start = x, end = x1)) + textLine.addTextChar(charData = char, start = x, end = x1) x = x1 } textLines.add(textLine) 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 c05294965..31b1c2028 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 @@ -214,7 +214,8 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity customView { layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { editText = edit_view - edit_view.setFilterValues(cacheUrls) { + edit_view.setFilterValues(cacheUrls) + edit_view.delCallBack = { cacheUrls.remove(it) aCache.put(importRecordKey, cacheUrls.joinToString(",")) } diff --git a/app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt b/app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt index a685ccd13..6d7bfcfe7 100644 --- a/app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt +++ b/app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt @@ -3,7 +3,9 @@ package io.legado.app.ui.config import android.os.Bundle import io.legado.app.R import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.EventBus import io.legado.app.utils.getViewModel +import io.legado.app.utils.observeEvent import kotlinx.android.synthetic.main.activity_config.* class ConfigActivity : VMBaseActivity(R.layout.activity_config) { @@ -47,4 +49,10 @@ class ConfigActivity : VMBaseActivity(R.layout.activity_config) } + override fun observeLiveBus() { + super.observeLiveBus() + observeEvent(EventBus.RECREATE) { + recreate() + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt index e8ea335c0..58caa351d 100644 --- a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt @@ -18,6 +18,7 @@ import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.lib.theme.ATH import io.legado.app.receiver.SharedReceiverActivity +import io.legado.app.service.WebService import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.widget.number.NumberPickerDialog @@ -34,12 +35,14 @@ class OtherConfigFragment : PreferenceFragmentCompat(), App.INSTANCE, SharedReceiverActivity::class.java.name ) + private val webPort get() = getPrefInt(PreferKey.webPort, 1122) override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { putPrefBoolean(PreferKey.processText, isProcessTextEnabled()) addPreferencesFromResource(R.xml.pref_config_other) upPreferenceSummary(PreferKey.downloadPath, BookHelp.downloadPath) upPreferenceSummary(PreferKey.threadCount, AppConfig.threadCount.toString()) + upPreferenceSummary(PreferKey.webPort, webPort.toString()) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -63,6 +66,14 @@ class OtherConfigFragment : PreferenceFragmentCompat(), .show { AppConfig.threadCount = it } + PreferKey.webPort -> NumberPickerDialog(requireContext()) + .setTitle(getString(R.string.web_port_title)) + .setMaxValue(60000) + .setMinValue(1024) + .setValue(webPort) + .show { + putPrefInt(PreferKey.webPort, it) + } PreferKey.downloadPath -> selectDownloadPath() PreferKey.cleanCache -> { BookHelp.clearCache() @@ -78,9 +89,15 @@ class OtherConfigFragment : PreferenceFragmentCompat(), upPreferenceSummary(key, BookHelp.downloadPath) } PreferKey.threadCount -> upPreferenceSummary( - PreferKey.threadCount, - AppConfig.threadCount.toString() + key, AppConfig.threadCount.toString() ) + PreferKey.webPort -> { + upPreferenceSummary(key, webPort.toString()) + if (WebService.isRun) { + WebService.stop(requireContext()) + WebService.start(requireContext()) + } + } PreferKey.recordLog -> LogUtils.upLevel() PreferKey.processText -> sharedPreferences?.let { setProcessTextEnable(it.getBoolean(key, true)) @@ -93,6 +110,7 @@ class OtherConfigFragment : PreferenceFragmentCompat(), val preference = findPreference(preferenceKey) ?: return when (preferenceKey) { PreferKey.threadCount -> preference.summary = getString(R.string.threads_num, value) + PreferKey.webPort -> preference.summary = getString(R.string.web_port_summary, value) else -> if (preference is ListPreference) { val index = preference.findIndexOfValue(value) // Set the summary to reflect the new value. diff --git a/app/src/main/java/io/legado/app/ui/login/SourceLogin.kt b/app/src/main/java/io/legado/app/ui/login/SourceLogin.kt index 35a62200c..767545d38 100644 --- a/app/src/main/java/io/legado/app/ui/login/SourceLogin.kt +++ b/app/src/main/java/io/legado/app/ui/login/SourceLogin.kt @@ -76,4 +76,8 @@ class SourceLogin : BaseActivity(R.layout.activity_source_login) { return super.onCompatOptionsItemSelected(item) } + override fun onDestroy() { + super.onDestroy() + web_view.destroy() + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 8b4705ae0..0d43b28c2 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -34,14 +34,18 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), get() = getViewModel(MainViewModel::class.java) private var pagePosition = 0 - private val fragmentList = arrayListOf() - private var rssFragment: RssFragment? = null + private val fragmentId = arrayOf(0, 1, 2, 3) + private val fragmentMap = mapOf( + Pair(fragmentId[0], BookshelfFragment()), + Pair(fragmentId[1], ExploreFragment()), + Pair(fragmentId[2], RssFragment()), + Pair(fragmentId[3], MyFragment()) + ) override fun onActivityCreated(savedInstanceState: Bundle?) { ATH.applyEdgeEffectColor(view_pager_main) ATH.applyBottomNavigationColor(bottom_navigation_view) view_pager_main.offscreenPageLimit = 3 - upFragmentList() view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager) view_pager_main.addOnPageChangeListener(this) bottom_navigation_view.setOnNavigationItemSelectedListener(this) @@ -69,22 +73,6 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), return false } - private fun upFragmentList() { - if (fragmentList.isEmpty()) { - fragmentList.add(BookshelfFragment()) - fragmentList.add(ExploreFragment()) - fragmentList.add(RssFragment()) - fragmentList.add(MyFragment()) - } - if (AppConfig.isShowRSS && fragmentList.size < 4) { - fragmentList.add(2, - rssFragment ?: RssFragment().apply { rssFragment = this }) - } - if (!AppConfig.isShowRSS && fragmentList.size == 4) { - fragmentList.removeAt(2) - } - } - private fun upVersion() { if (getPrefInt(PreferKey.versionCode) != App.INSTANCE.versionCode) { putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) @@ -144,7 +132,6 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } observeEvent(EventBus.SHOW_RSS) { bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS - upFragmentList() view_pager_main.adapter?.notifyDataSetChanged() if (AppConfig.isShowRSS) { view_pager_main.setCurrentItem(3, false) @@ -160,7 +147,16 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } override fun getItem(position: Int): Fragment { - return fragmentList[position] + return when (position) { + 0 -> fragmentMap.getValue(fragmentId[0]) + 1 -> fragmentMap.getValue(fragmentId[1]) + 2 -> if (AppConfig.isShowRSS) { + fragmentMap.getValue(fragmentId[2]) + } else { + fragmentMap.getValue(fragmentId[3]) + } + else -> fragmentMap.getValue(fragmentId[3]) + } } override fun getCount(): Int { @@ -168,4 +164,5 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index a3a228dd7..9cb7dc9de 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt @@ -15,7 +15,6 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseFragment import io.legado.app.constant.AppConst -import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.BookGroup import io.legado.app.lib.dialogs.alert @@ -25,10 +24,10 @@ import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor import io.legado.app.ui.book.arrange.ArrangeBookActivity -import io.legado.app.ui.book.group.GroupManageDialog -import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.download.DownloadActivity +import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.local.ImportBookActivity +import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.* @@ -99,9 +98,6 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b TabLayoutMediator(tab_layout, view_pager_bookshelf) { tab, position -> tab.text = bookGroups[position].groupName }.attach() - observeEvent(EventBus.UP_TABS) { - tab_layout.getTabAt(it)?.select() - } } private fun initBookGroupData() { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt index 95b5b1431..5d922cae9 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt @@ -57,9 +57,6 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), } initRecyclerView() upRecyclerData() - observeEvent(EventBus.UP_BOOK) { - booksAdapter.notification(it) - } } private fun initRecyclerView() { @@ -135,4 +132,10 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), return bookUrl in activityViewModel.updateList } + override fun observeLiveBus() { + super.observeLiveBus() + observeEvent(EventBus.UP_BOOK) { + booksAdapter.notification(it) + } + } } \ 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 de1f63ae1..ddb045c9e 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 @@ -23,6 +23,7 @@ import io.legado.app.ui.config.ConfigActivity import io.legado.app.ui.config.ConfigViewModel import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.replacerule.ReplaceRuleActivity +import io.legado.app.ui.widget.prefs.NameListPreference import io.legado.app.ui.widget.prefs.SwitchPreference import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_title_bar.* @@ -74,6 +75,12 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog. observeEvent(EventBus.WEB_SERVICE_STOP) { webServicePre?.isChecked = false } + findPreference(PreferKey.themeMode)?.let { + it.setOnPreferenceChangeListener { _, _ -> + view?.post { App.INSTANCE.applyDayNight() } + true + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -96,7 +103,6 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog. key: String? ) { when (key) { - PreferKey.themeMode -> App.INSTANCE.applyDayNight() PreferKey.webService -> { if (requireContext().getPrefBoolean("webService")) { WebService.start(requireContext()) diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index 91f7e7b75..16ffee4a1 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -213,7 +213,8 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi customView { layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { editText = edit_view - edit_view.setFilterValues(cacheUrls) { + edit_view.setFilterValues(cacheUrls) + edit_view.delCallBack = { cacheUrls.remove(it) aCache.put(importRecordKey, cacheUrls.joinToString(",")) } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt index d125674c5..73c64a0e1 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt @@ -16,6 +16,7 @@ import io.legado.app.constant.Theme import io.legado.app.data.entities.ReplaceRule import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel +import io.legado.app.utils.toast import kotlinx.android.synthetic.main.dialog_replace_edit.* class ReplaceEditDialog : DialogFragment(), @@ -73,9 +74,15 @@ class ReplaceEditDialog : DialogFragment(), override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_save -> { - viewModel.save(getReplaceRule()) { - callBack?.onReplaceRuleSave() - dismiss() + val rule = getReplaceRule(); + if (!rule.isValid()){ + toast(R.string.replace_rule_invalid) + } + else{ + viewModel.save(rule) { + callBack?.onReplaceRuleSave() + dismiss() + } } } } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 10504efd7..1764a1f32 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -203,4 +203,9 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } } + override fun onDestroy() { + super.onDestroy() + web_view.destroy() + } + } 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 c3dd008c8..35934e4d6 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 @@ -227,7 +227,8 @@ class RssSourceActivity : VMBaseActivity(R.layout.activity_r customView { layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { editText = edit_view - edit_view.setFilterValues(cacheUrls) { + edit_view.setFilterValues(cacheUrls) + edit_view.delCallBack = { cacheUrls.remove(it) aCache.put(importRecordKey, cacheUrls.joinToString(",")) } diff --git a/app/src/main/java/io/legado/app/ui/widget/ArcView.kt b/app/src/main/java/io/legado/app/ui/widget/ArcView.kt new file mode 100644 index 000000000..b9834132c --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/ArcView.kt @@ -0,0 +1,79 @@ +package io.legado.app.ui.widget + +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.View +import io.legado.app.R + +class ArcView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + private var mWidth = 0 + private var mHeight = 0 + + //弧形高度 + private val mArcHeight: Int + + //背景颜色 + private val mBgColor: Int + private val mPaint: Paint = Paint() + private val mDirectionTop: Boolean + val rect = Rect() + val path = Path() + + init { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ArcView) + mArcHeight = typedArray.getDimensionPixelSize(R.styleable.ArcView_arcHeight, 0) + mBgColor = typedArray.getColor( + R.styleable.ArcView_bgColor, + Color.parseColor("#303F9F") + ) + mDirectionTop = typedArray.getBoolean(R.styleable.ArcView_arcDirectionTop, false) + typedArray.recycle() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + mPaint.style = Paint.Style.FILL + mPaint.color = mBgColor + if (mDirectionTop) { + rect.set(0, mArcHeight, mWidth, mHeight) + canvas.drawRect(rect, mPaint) + path.reset() + path.moveTo(0f, mArcHeight.toFloat()) + path.quadTo(mWidth / 2.toFloat(), 0f, mWidth.toFloat(), mArcHeight.toFloat()) + canvas.drawPath(path, mPaint) + } else { + rect.set(0, 0, mWidth, mHeight - mArcHeight) + canvas.drawRect(rect, mPaint) + path.reset() + path.moveTo(0f, mHeight - mArcHeight.toFloat()) + path.quadTo( + mWidth / 2.toFloat(), + mHeight.toFloat(), + mWidth.toFloat(), + mHeight - mArcHeight.toFloat() + ) + canvas.drawPath(path, mPaint) + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val widthSize = MeasureSpec.getSize(widthMeasureSpec) + val widthMode = MeasureSpec.getMode(widthMeasureSpec) + val heightSize = MeasureSpec.getSize(heightMeasureSpec) + val heightMode = MeasureSpec.getMode(heightMeasureSpec) + if (widthMode == MeasureSpec.EXACTLY) { + mWidth = widthSize + } + if (heightMode == MeasureSpec.EXACTLY) { + mHeight = heightSize + } + setMeasuredDimension(mWidth, mHeight) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt index bbeb7f04e..8157ed870 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt @@ -60,8 +60,7 @@ class FontSelectDialog : BaseDialogFragment(), return inflater.inflate(R.layout.dialog_font_select, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { tool_bar.setTitle(R.string.select_font) tool_bar.inflateMenu(R.menu.font_select) tool_bar.setOnMenuItemClickListener(this) diff --git a/app/src/main/java/io/legado/app/ui/widget/text/AutoCompleteTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/AutoCompleteTextView.kt index ac0a5a8ac..1319aac5e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/text/AutoCompleteTextView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/text/AutoCompleteTextView.kt @@ -42,15 +42,13 @@ class AutoCompleteTextView : AppCompatAutoCompleteTextView { return super.onTouchEvent(event) } - fun setFilterValues(values: List?, delCallBack: ((value: String) -> Unit)? = null) { - this.delCallBack = delCallBack + fun setFilterValues(values: List?) { values?.let { setAdapter(MyAdapter(context, values)) } } - fun setFilterValues(vararg value: String, delCallBack: ((value: String) -> Unit)? = null) { - this.delCallBack = delCallBack + fun setFilterValues(vararg value: String) { setAdapter(MyAdapter(context, value.toMutableList())) } diff --git a/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt b/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt new file mode 100644 index 000000000..d0b579cd4 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt @@ -0,0 +1,81 @@ +package io.legado.app.ui.widget.text + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.appcompat.widget.AppCompatTextView + +class ScrollTextView(context: Context?, attrs: AttributeSet?) : AppCompatTextView(context, attrs) { + //滑动距离的最大边界 + private var mOffsetHeight = 0 + + //是否到顶或者到底的标志 + private var mBottomFlag = false + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + initOffsetHeight() + } + + override fun onTextChanged( + text: CharSequence, + start: Int, + lengthBefore: Int, + lengthAfter: Int + ) { + super.onTextChanged(text, start, lengthBefore, lengthAfter) + initOffsetHeight() + } + + private fun initOffsetHeight() { + val mLayoutHeight: Int + + //获得内容面板 + val mLayout = layout ?: return + //获得内容面板的高度 + mLayoutHeight = mLayout.height + //获取上内边距 + val paddingTop: Int = totalPaddingTop + //获取下内边距 + val paddingBottom: Int = totalPaddingBottom + + //获得控件的实际高度 + val mHeight: Int = measuredHeight + + //计算滑动距离的边界 + mOffsetHeight = mLayoutHeight + paddingTop + paddingBottom - mHeight + if (mOffsetHeight <= 0) { + scrollTo(0, 0) + } + } + + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_DOWN) { + //如果是新的按下事件,则对mBottomFlag重新初始化 + mBottomFlag = mOffsetHeight <= 0 + } + //如果已经不要这次事件,则传出取消的信号,这里的作用不大 + if (mBottomFlag) { + event.action = MotionEvent.ACTION_CANCEL + } + return super.dispatchTouchEvent(event) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + val result = super.onTouchEvent(event) + //如果是需要拦截,则再拦截,这个方法会在onScrollChanged方法之后再调用一次 + if (!mBottomFlag) parent.requestDisallowInterceptTouchEvent(true) + return result + } + + override fun onScrollChanged(horiz: Int, vert: Int, oldHoriz: Int, oldVert: Int) { + super.onScrollChanged(horiz, vert, oldHoriz, oldVert) + if (vert == mOffsetHeight || vert == 0) { + //这里触发父布局或祖父布局的滑动事件 + parent.requestDisallowInterceptTouchEvent(false) + mBottomFlag = true + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/EventBusKt.kt b/app/src/main/java/io/legado/app/utils/EventBusKt.kt index 82c3f0f30..6731e06d6 100644 --- a/app/src/main/java/io/legado/app/utils/EventBusKt.kt +++ b/app/src/main/java/io/legado/app/utils/EventBusKt.kt @@ -4,13 +4,14 @@ import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import com.jeremyliao.liveeventbus.LiveEventBus +import com.jeremyliao.liveeventbus.core.Observable -inline fun eventObservable(tag: String): LiveEventBus.Observable { - return LiveEventBus.get().with(tag, EVENT::class.java) +inline fun eventObservable(tag: String): Observable { + return LiveEventBus.get(tag, EVENT::class.java) } inline fun postEvent(tag: String, event: EVENT) { - LiveEventBus.get().with(tag, EVENT::class.java).post(event) + LiveEventBus.get(tag).post(event) } inline fun AppCompatActivity.observeEvent( diff --git a/app/src/main/res/drawable/ic_type_default_1.xml b/app/src/main/res/drawable/ic_type_default_1.xml new file mode 100644 index 000000000..14d903eee --- /dev/null +++ b/app/src/main/res/drawable/ic_type_default_1.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_type_default_2.xml b/app/src/main/res/drawable/ic_type_default_2.xml new file mode 100644 index 000000000..a0c29bc99 --- /dev/null +++ b/app/src/main/res/drawable/ic_type_default_2.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_type_default_3.xml b/app/src/main/res/drawable/ic_type_default_3.xml new file mode 100644 index 000000000..feb07717f --- /dev/null +++ b/app/src/main/res/drawable/ic_type_default_3.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_book_info.xml b/app/src/main/res/layout/activity_book_info.xml index 693fb53a1..8e58e34cb 100644 --- a/app/src/main/res/layout/activity_book_info.xml +++ b/app/src/main/res/layout/activity_book_info.xml @@ -9,227 +9,335 @@ - + android:orientation="vertical"> - - - + + + + + + + + + + + + + + android:paddingLeft="10dp" + android:paddingTop="8dp" + android:paddingRight="10dp" + android:paddingBottom="3dp"> + android:text="@string/book_name" + android:textColor="@color/tv_text_default" + android:textSize="18sp" + tools:ignore="RtlHardcoded" /> - + android:layout_gravity="center" + android:gravity="center" + android:visibility="gone" /> - + + + android:scrollbarStyle="outsideInset" + android:scrollbars="vertical" + android:fillViewport="true" + android:fitsSystemWindows="false" + android:focusable="true" > - + - + - - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + android:background="@color/background" + android:orientation="vertical" + android:paddingTop="12dp" + android:paddingBottom="12dp" + app:layout_constraintBottom_toTopOf="@+id/fl_action" + app:layout_constraintTop_toBottomOf="@+id/view_info"> - + + + + + + + android:layout_height="1px" + android:background="@color/bg_divider_line" /> - + - + - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_change_source.xml b/app/src/main/res/layout/dialog_change_source.xml index f007fae87..cc7486b05 100644 --- a/app/src/main/res/layout/dialog_change_source.xml +++ b/app/src/main/res/layout/dialog_change_source.xml @@ -5,26 +5,14 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - + android:background="@color/background_menu" + android:elevation="5dp" + app:displayHomeAsUp="false" + app:fitStatusBar="false" /> - - - - - - - - - - - - - - + android:orientation="horizontal" + android:gravity="center_vertical"> - + - + + + + + + + - + + + + + + + + + + + + + + + android:paddingTop="6dp" + android:paddingBottom="6dp" + android:gravity="center_vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + android:background="@color/btn_bg_press" /> + android:text="@string/page_anim" /> + android:gravity="center"> + android:background="@color/btn_bg_press" /> - - - + android:orientation="horizontal" + android:gravity="center_vertical"> - + + + + + + + + - - - - - - - - - - - - \ No newline at end of file + android:orientation="horizontal" + android:gravity="center_vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_read_padding.xml b/app/src/main/res/layout/dialog_read_padding.xml index 1b600959e..83bb14c71 100644 --- a/app/src/main/res/layout/dialog_read_padding.xml +++ b/app/src/main/res/layout/dialog_read_padding.xml @@ -7,51 +7,55 @@ android:padding="10dp" android:orientation="vertical"> - - - - - - - - - + android:orientation="vertical"> + + + + + + + + + + + + @@ -84,13 +88,33 @@ app:title="@string/padding_right" app:max="100" /> - + android:gravity="center_vertical" + android:orientation="horizontal"> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_search.xml b/app/src/main/res/layout/item_search.xml index 73154072b..78e5a44da 100644 --- a/app/src/main/res/layout/item_search.xml +++ b/app/src/main/res/layout/item_search.xml @@ -43,6 +43,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="vertical" + android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="@id/iv_cover" app:layout_constraintLeft_toLeftOf="@+id/tv_name" app:layout_constraintRight_toRightOf="@id/tv_name" diff --git a/app/src/main/res/menu/change_cover.xml b/app/src/main/res/menu/change_cover.xml new file mode 100644 index 000000000..cce382d12 --- /dev/null +++ b/app/src/main/res/menu/change_cover.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/change_source.xml b/app/src/main/res/menu/change_source.xml index f9f49cdd6..36f9bee6e 100644 --- a/app/src/main/res/menu/change_source.xml +++ b/app/src/main/res/menu/change_source.xml @@ -3,6 +3,14 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + \ No newline at end of file diff --git a/app/src/main/res/values/array_values.xml b/app/src/main/res/values/array_values.xml new file mode 100644 index 000000000..2aa918837 --- /dev/null +++ b/app/src/main/res/values/array_values.xml @@ -0,0 +1,42 @@ + + + + + ic_launcher + launcher1 + launcher2 + launcher3 + + + + + 0 + 60 + 120 + 180 + -1 + + + + 0 + 1 + 2 + + + + 0 + 1 + 3 + 4 + 5 + 103 + 106 + 110 + 111 + 11 + 6 + 9 + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 4cd90b40e..15deecdec 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -20,21 +20,6 @@ 百度主持 - - 0 - 1 - 3 - 4 - 5 - 103 - 106 - 110 - 111 - 11 - 6 - 9 - - @string/indent_0 @string/indent_1 @@ -61,12 +46,6 @@ 暗色主题 - - 0 - 1 - 2 - - 自动 黑色 @@ -82,14 +61,6 @@ 常亮 - - 0 - 60 - 120 - 180 - -1 - - @string/default_path @string/sys_folder_picker @@ -103,15 +74,6 @@ @string/screen_sensor - - - - - - - - - iconMain icon1 @@ -119,13 +81,6 @@ icon3 - - ic_launcher - launcher1 - launcher2 - launcher3 - - 关闭 繁体转简体 @@ -137,4 +92,11 @@ 系统衬线字体 系统等宽字体 + + + 标题靠左 + 标题居中 + 标题隐藏 + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index e42a82a0d..cc7adbeb8 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -137,6 +137,15 @@ + + + + + + + + + @@ -154,4 +163,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68f3af591..368df1c1d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -204,6 +204,7 @@ 清除缓存会删除所有已保存章节,是否确认删除? 书源共享 替换规则名称 + 替换规则为空或者不满足正则表达式要求 选择操作 全选 全选(%d/%d) @@ -226,13 +227,14 @@ 打开外部书籍 来源:%s 本地导入 - 网络导入 + 导入在线规则 检查更新间隔 按阅读时间 按更新时间 按书名 手动排序 阅读方式 + 排版 删除所选 是否确认删除? 默认字体 @@ -629,4 +631,7 @@ 其它朗读设置 系统内置字体样式 是否删除源文件 + 预设一 + 预设二 + 预设三 diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index 4ecfb8a8e..b049bcf22 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -37,6 +37,11 @@ android:title="@string/threads_num_title" app:iconSpaceReserved="false" /> + +