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 1b0c7d4b0..037194e73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ android { signingConfig signingConfigs.myConfig } applicationIdSuffix '.release' - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { @@ -75,8 +75,11 @@ android { } compileOptions { - sourceCompatibility = '1.8' - targetCompatibility = '1.8' + // Flag to enable support for the new language APIs + //coreLibraryDesugaringEnabled true + // Sets Java compatibility to Java 8 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { @@ -103,7 +106,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" //fireBase - implementation 'com.google.firebase:firebase-core:17.2.2' + implementation 'com.google.firebase:firebase-core:17.2.3' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' //androidX @@ -137,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' @@ -164,7 +167,7 @@ dependencies { implementation 'org.nanohttpd:nanohttpd-websocket:2.3.1' //二维码 - implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.6' + implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.7' //颜色选择 implementation 'com.jaredrummler:colorpicker:1.1.0' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 008a0715b..9af1203b9 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -154,6 +154,8 @@ -keep class **.analyzeRule.**{*;} # 保持web类 -keep class **.web.**{*;} +#数据类 +-keep class **.data.**{*;} -dontwarn rx.** @@ -166,7 +168,10 @@ -dontnote org.python.core.** -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.**{*;} @@ -183,6 +188,7 @@ -keep class com.gyf.barlibrary.* {*;} ##JSOUP -keep class org.jsoup.**{*;} +-keep class **.xpath.**{*;} -keep class org.slf4j.**{*;} -dontwarn org.slf4j.** @@ -206,8 +212,6 @@ -keep class javax.script.** { *; } -keep class com.sun.script.javascript.** { *; } -keep class org.mozilla.javascript.** { *; } --dontwarn org.mozilla.javascript.** --dontwarn sun.** ###EPUB -dontwarn nl.siegmann.epublib.** @@ -222,9 +226,6 @@ -keepclassmembers class * { public (org.json.JSONObject); } --keep public class com.kunfei.bookshelf.R$*{ - public static final int *; -} -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e4bc75603..67af4688e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -133,18 +133,24 @@ android:name=".ui.about.DonateActivity" android:launchMode="singleTask" /> - + + + - + @@ -159,7 +165,7 @@ + android:launchMode="singleTop"> @@ -171,16 +177,43 @@ android:scheme="yuedu" /> - - - - - - - - - - + + + + + + + + + + + + @@ -195,7 +228,6 @@ - diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index b78537486..fe74ee832 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -2,10 +2,57 @@ * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 +**2020/03/09** +* 底部文字对齐 +* 主题添加阴影调节 by yangyxd + +**2020/03/08** +* 订阅长按保存图片 +* 订阅全屏播放 +* 书架全部分组可以隐藏了 +* 内置web书架基本能用了 by 六月 +* 书架整理加入未分组 +* 显示总进度 +* 隐藏状态栏时,标题显示在上方 + +**2020/03/07** +* 添加标题上下间距调整 +* 添加标题大小调整 +* 书籍整理添加批量启用禁用更新 +* 换源禁用书源不显示 +* 修复搜索界面简介最下面显示半行文字 +* 搜索历史改为多行 + +**2020/03/06** +* 添加隐藏标题 +* 行距段距改成倍距,根据字体大小变化 +* 修复翻页时右下角页数闪烁 +* 修复朗读错行 +* 添加底部分隔线,开关在边距设置里 + +**2020/03/05** +* 修复翻页动画 +* 修复主题模式跟随 +* 修复滚动翻页切换章节时跳动 +* 适配阅读3.0的web做源 +* 本地目录规则网络导入 + +**2020/03/04** +* 修复仿真翻页动画 +* 添加阅读记录同步,正常退出进入软件时同步阅读记录 + +**2020/03/03** +* 修复bug +* 优化排版,确保段距为0时每行在相同的位置 +* 修复底部遮挡 + **2020/03/02** * 添加书源登录 * 替换规则实时生效 * 页面最后一行计算是否能放下时不计算行距 +* 优化翻页动画 +* 优化书源校验 +* 按键翻页有动画了 **2020/03/01** * 修复书源解析的一个bug diff --git a/app/src/main/assets/web/bookshelf.html b/app/src/main/assets/web/bookshelf.html index 485622373..86639426f 100644 --- a/app/src/main/assets/web/bookshelf.html +++ b/app/src/main/assets/web/bookshelf.html @@ -1,32 +1,39 @@  + - - 阅读书架 - + + 阅读3.0书架 + + + - - - - + + + + -
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
- + + \ No newline at end of file diff --git a/app/src/main/assets/web/bookshelf.js b/app/src/main/assets/web/bookshelf.js index c182f49fa..7aa35df0f 100644 --- a/app/src/main/assets/web/bookshelf.js +++ b/app/src/main/assets/web/bookshelf.js @@ -1,11 +1,14 @@ var $ = document.querySelector.bind(document) , $$ = document.querySelectorAll.bind(document) , $c = document.createElement.bind(document) - , randomImg = "http://acg.bakayun.cn/randbg.php?t=dfzh" + , randomImg = "http://api.mtyqx.cn/api/random.php" , randomImg2 = "http://img.xjh.me/random_img.php" , books ; +var now_chapter = -1; +var sum_chapter = 0; + var formatTime = value => { return new Date(value).toLocaleString('zh-CN', { hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" @@ -13,13 +16,13 @@ var formatTime = value => { }; var apiMap = { - getBookshelf: "/getBookshelf", - getChapterList: "/getChapterList", - getBookContent: "/getBookContent", - saveBook: "/saveBook" + "getBookshelf": "/getBookshelf", + "getChapterList": "/getChapterList", + "getBookContent": "/getBookContent", + "saveBook": "/saveBook" }; -var apiAddress = (apiName, url) => { +var apiAddress = (apiName, url, index) => { let address = $('#address').value || window.location.host; if (!(/^http|^\/\//).test(address)) { address = "//" + address; @@ -28,6 +31,9 @@ var apiAddress = (apiName, url) => { address += ":1122"; } localStorage.setItem('address', address); + if (apiName == "getBookContent") { + return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "") + "&index=" + index; + } return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : ""); }; @@ -41,12 +47,12 @@ var init = () => { alert(getBookshelf.errorMsg); return; } - books = data.data.sort((book1, book2) => book1.serialNumber - book2.serialNumber); - books.forEach(book => { + books = data.data; + books.forEach((book, i) => { let bookDiv = $c("div"); let img = $c("img"); img.src = book.coverUrl || randomImg; - img.setAttribute("data-series-num", book.serialNumber); + img.setAttribute("data-series-num", i); bookDiv.appendChild(img); bookDiv.innerHTML += ` @@ -59,6 +65,8 @@ var init = () => { }); $$('#books img').forEach(bookImg => bookImg.addEventListener("click", () => { + now_chapter = -1; + sum_chapter = 0; $('#allcontent').classList.add("read"); var book = books[bookImg.getAttribute("data-series-num")]; $("#info").innerHTML = ` @@ -85,11 +93,13 @@ var init = () => { data.data.forEach(chapter => { let ch = $c("button"); - ch.setAttribute("data-url", chapter.durChapterUrl); - ch.setAttribute("title", chapter.durChapterName); - ch.innerHTML = chapter.durChapterName.length > 15 ? chapter.durChapterName.substring(0, 14) + "..." : chapter.durChapterName; + ch.setAttribute("data-url", chapter.bookUrl); + ch.setAttribute("data-index", chapter.index); + ch.setAttribute("title", chapter.title); + ch.innerHTML = chapter.title.length > 15 ? chapter.title.substring(0, 14) + "..." : chapter.title; $("#chapter").appendChild(ch); }); + sum_chapter = data.data.length; $('#chapter').scrollTop = 0; $("#content").innerHTML = "章节列表加载完成!"; }); @@ -126,15 +136,47 @@ $('#showchapter').addEventListener("click", () => { window.location.hash = "#chapter"; }); +$('#up').addEventListener('click', e => { + if (now_chapter > 0) { + now_chapter--; + let clickEvent = document.createEvent('MouseEvents'); + clickEvent.initEvent("click", true, false); + $('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent); + } else if (now_chapter == 0) { + alert("已经是第一章了^_^!") + } else { + + } +}); + +$('#down').addEventListener('click', e => { + if (now_chapter > -1) { + if (now_chapter < sum_chapter - 1) { + now_chapter++; + let clickEvent = document.createEvent('MouseEvents'); + clickEvent.initEvent("click", true, false); + $('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent); + + } else { + alert("已经是最后一章了^_^!") + } + } +}); + $('#chapter').addEventListener("click", (e) => { if (e.target.tagName === "BUTTON") { var url = e.target.getAttribute("data-url"); + var index = e.target.getAttribute("data-index"); var name = e.target.getAttribute("title"); if (!url) { - alert("未取得章节地址"); + alert("未取得书籍地址"); + } + if (!index && (0 != index)) { + alert("未取得章节索引"); } + now_chapter = parseInt(index); $("#content").innerHTML = "

" + name + " 加载中...

"; - fetch(apiAddress("getBookContent", url), { mode: "cors" }) + fetch(apiAddress("getBookContent", url, index), { mode: "cors" }) .then(res => res.json()) .then(data => { if (!data.isSuccess) { 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 95f29cd64..7cb9eff1f 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -1,13 +1,11 @@ package io.legado.app -import android.app.Activity import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.res.Configuration import android.os.Build -import android.os.Bundle import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate import com.jeremyliao.liveeventbus.LiveEventBus @@ -48,19 +46,15 @@ class App : Application() { versionCode = it.versionCode versionName = it.versionName } - - if (!ThemeStore.isConfigured(this, versionCode)) applyTheme() - initNightMode() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId() - - LiveEventBus.get() + applyDayNight() + LiveEventBus .config() .supportBroadcast(this) .lifecycleObserverAlwaysActive(true) .autoClear(false) - registerActivityLife() + registerActivityLifecycleCallbacks(ActivityHelp) } override fun onConfigurationChanged(newConfig: Configuration) { @@ -120,9 +114,7 @@ class App : Application() { */ @RequiresApi(Build.VERSION_CODES.O) private fun createChannelId() { - val notificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager - notificationManager?.let { + (getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager)?.let { //用唯一的ID创建渠道对象 val downloadChannel = NotificationChannel( channelIdDownload, @@ -161,32 +153,4 @@ class App : Application() { } } - private fun registerActivityLife() { - registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { - override fun onActivityPaused(activity: Activity) { - } - - override fun onActivityResumed(activity: Activity) { - } - - override fun onActivityStarted(activity: Activity) { - - } - - override fun onActivityDestroyed(activity: Activity) { - ActivityHelp.remove(activity) - } - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { - } - - override fun onActivityStopped(activity: Activity) { - } - - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - ActivityHelp.add(activity) - } - - }) - } } 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/AppConst.kt b/app/src/main/java/io/legado/app/constant/AppConst.kt index 96cc7567f..810e6deb2 100644 --- a/app/src/main/java/io/legado/app/constant/AppConst.kt +++ b/app/src/main/java/io/legado/app/constant/AppConst.kt @@ -4,8 +4,6 @@ import android.annotation.SuppressLint import io.legado.app.App import io.legado.app.R import io.legado.app.data.entities.BookGroup -import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.putPrefBoolean import java.text.SimpleDateFormat import javax.script.ScriptEngine import javax.script.ScriptEngineManager @@ -29,14 +27,18 @@ object AppConst { ScriptEngineManager().getEngineByName("rhino") } - val TIME_FORMAT: SimpleDateFormat by lazy { + val timeFormat: SimpleDateFormat by lazy { SimpleDateFormat("HH:mm") } - val DATE_FORMAT: SimpleDateFormat by lazy { + val dateFormat: SimpleDateFormat by lazy { SimpleDateFormat("yyyy/MM/dd HH:mm") } + val fileNameFormat: SimpleDateFormat by lazy { + SimpleDateFormat("yy-MM-dd-HH-mm-ss") + } + val keyboardToolChars: List by lazy { arrayListOf( "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", "$", "#", "!", ".", @@ -48,18 +50,6 @@ object AppConst { val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local)) val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio)) - var bookGroupLocalShow: Boolean - get() = App.INSTANCE.getPrefBoolean("bookGroupLocal", false) - set(value) { - App.INSTANCE.putPrefBoolean("bookGroupLocal", value) - } - - var bookGroupAudioShow: Boolean - get() = App.INSTANCE.getPrefBoolean("bookGroupAudio", false) - set(value) { - App.INSTANCE.putPrefBoolean("bookGroupAudio", value) - } - const val notificationIdRead = 1144771 const val notificationIdAudio = 1144772 const val notificationIdWeb = 1144773 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 72d8c6b15..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,11 +27,13 @@ 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" const val webDavAccount = "web_dav_account" const val webDavPassword = "web_dav_password" + const val webDavCreateDir = "webDavCreateDir" const val changeSourceLoadToc = "changeSourceLoadToc" const val chineseConverterType = "chineseConverterType" const val launcherIcon = "launcherIcon" 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/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index e548f4584..f32d6a199 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -32,7 +32,7 @@ abstract class AppDatabase : RoomDatabase() { .fallbackToDestructiveMigration() .addCallback(object : Callback() { override fun onDestructiveMigration(db: SupportSQLiteDatabase) { - GlobalScope.launch { Restore.restore(Backup.backupPath) } + GlobalScope.launch { Restore.restoreDatabase(Backup.backupPath) } } }) .build() diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index b0c07339c..b323a2711 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookDao.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData import androidx.room.* import io.legado.app.constant.BookType import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookProgress @Dao interface BookDao { @@ -26,6 +27,9 @@ interface BookDao { @Query("SELECT * FROM books WHERE (`group` & :group) > 0") fun observeByGroup(group: Int): LiveData> + @Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") + fun observeNoGroup(): LiveData> + @Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'") fun liveDataSearch(key: String): LiveData> @@ -67,4 +71,23 @@ interface BookDao { @Query("update books set `group` = :newGroupId where `group` = :oldGroupId") fun upGroup(oldGroupId: Int, newGroupId: Int) + + @get:Query("select bookUrl, durChapterIndex, durChapterPos, durChapterTime, durChapterTitle from books") + val allBookProgress: List + + @Query( + """ + update books set + durChapterIndex = :durChapterIndex, durChapterPos = :durChapterPos, + durChapterTime = :durChapterTime, durChapterTitle = :durChapterTitle + where bookUrl = :bookUrl and durChapterTime < :durChapterTime + """ + ) + fun upBookProgress( + bookUrl: String, + durChapterIndex: Int, + durChapterPos: Int, + durChapterTime: Long, + durChapterTitle: String? + ) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt index 0abc76dc8..5643ef975 100644 --- a/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt @@ -33,14 +33,20 @@ interface ReplaceRuleDao { fun findByIds(vararg ids: Long): List @Query( - """SELECT * FROM replace_rules WHERE isEnabled = 1 - AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')""" + """ + SELECT * FROM replace_rules WHERE isEnabled = 1 + AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '') + order by sortOrder + """ ) fun findEnabledByScope(scope: String): List @Query( - """SELECT * FROM replace_rules WHERE isEnabled = 1 - AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')""" + """ + SELECT * FROM replace_rules WHERE isEnabled = 1 + AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '') + order by sortOrder + """ ) fun findEnabledByScope(name: String, origin: String): List diff --git a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt index 779cfd1e2..c02450091 100644 --- a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt @@ -27,7 +27,7 @@ interface SearchBookDao { select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder from searchBooks as t1 inner join book_sources as t2 on t1.origin = t2.bookSourceUrl - where t1.name = :name and t1.author = :author + where t1.name = :name and t1.author = :author and t2.enabled = 1 order by t2.customOrder """ ) @@ -38,7 +38,7 @@ interface SearchBookDao { select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder from searchBooks as t1 inner join book_sources as t2 on t1.origin = t2.bookSourceUrl - where t1.name = :name and t1.author = :author and originName like '%'||:key||'%' + where t1.name = :name and t1.author = :author and originName like '%'||:key||'%' and t2.enabled = 1 order by t2.customOrder """ ) @@ -49,7 +49,7 @@ interface SearchBookDao { select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder from searchBooks as t1 inner join book_sources as t2 on t1.origin = t2.bookSourceUrl - where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' + where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' and t2.enabled = 1 order by t2.customOrder """ ) 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/BookProgress.kt b/app/src/main/java/io/legado/app/data/entities/BookProgress.kt new file mode 100644 index 000000000..c0d693284 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/BookProgress.kt @@ -0,0 +1,9 @@ +package io.legado.app.data.entities + +data class BookProgress( + val bookUrl: String, + val durChapterIndex: Int, + val durChapterPos: Int, + val durChapterTime: Long, + val durChapterTitle: String? +) \ No newline at end of file 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/ActivityHelp.kt b/app/src/main/java/io/legado/app/help/ActivityHelp.kt index 6138c0c7e..b14c76670 100644 --- a/app/src/main/java/io/legado/app/help/ActivityHelp.kt +++ b/app/src/main/java/io/legado/app/help/ActivityHelp.kt @@ -1,13 +1,15 @@ package io.legado.app.help import android.app.Activity +import android.app.Application +import android.os.Bundle import java.lang.ref.WeakReference import java.util.* /** * Activity管理器,管理项目中Activity的状态 */ -object ActivityHelp { +object ActivityHelp : Application.ActivityLifecycleCallbacks { private val activities: MutableList> = arrayListOf() @@ -86,4 +88,27 @@ object ActivityHelp { } } + override fun onActivityPaused(activity: Activity) { + } + + override fun onActivityResumed(activity: Activity) { + } + + override fun onActivityStarted(activity: Activity) { + + } + + override fun onActivityDestroyed(activity: Activity) { + remove(activity) + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { + } + + override fun onActivityStopped(activity: Activity) { + } + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + add(activity) + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt index f71652c23..7e9c05834 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -92,4 +92,28 @@ object AppConfig { set(value) { App.INSTANCE.putPrefInt(PreferKey.systemTypefaces, value) } + + var bookGroupAllShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupAll", true) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupAll", value) + } + + var bookGroupLocalShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupLocal", false) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupLocal", value) + } + + var bookGroupAudioShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupAudio", false) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupAudio", value) + } + + var elevation: Int + get() = App.INSTANCE.getPrefInt("elevation", -1) + set(value) { + App.INSTANCE.putPrefInt("elevation", value) + } } 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 7df2540c5..b7b45baea 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -11,7 +11,10 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.ReplaceRule import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.utils.* +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.withContext import org.apache.commons.text.similarity.JaccardSimilarity +import org.jetbrains.anko.toast import java.io.File import kotlin.math.min @@ -177,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 <= ' ' } ?: "" @@ -262,7 +265,7 @@ object BookHelp { } } - fun disposeContent( + suspend fun disposeContent( title: String, name: String, origin: String?, @@ -272,13 +275,19 @@ object BookHelp { var c = content if (enableReplace) { upReplaceRules(name, origin) - for (item in replaceRules) { + replaceRules.forEach { item -> item.pattern.let { if (it.isNotEmpty()) { - c = if (item.isRegex) { - c.replace(it.toRegex(), item.replacement) - } else { - c.replace(it, item.replacement) + try { + c = if (item.isRegex) { + c.replace(it.toRegex(), item.replacement) + } else { + c.replace(it, item.replacement) + } + } catch (e: Exception) { + withContext(Main) { + App.INSTANCE.toast("${item.name}替换出错") + } } } } @@ -291,6 +300,8 @@ object BookHelp { 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) } - return c.replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}") + return c + .replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}") + .replace("[\\n\\s]+$".toRegex(), "") //移除尾部空行 } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 32da4bb06..493ede9c3 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -1,13 +1,14 @@ package io.legado.app.help import android.util.Base64 -import io.legado.app.constant.AppConst.DATE_FORMAT +import androidx.annotation.Keep +import io.legado.app.constant.AppConst.dateFormat import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.EncoderUtils import io.legado.app.utils.MD5Utils import java.util.* - +@Keep @Suppress("unused") interface JsExtensions { @@ -49,6 +50,6 @@ interface JsExtensions { } fun timeFormat(time: Long): String { - return DATE_FORMAT.format(Date(time)) + return dateFormat.format(Date(time)) } } 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 a3a6de521..d0e67e901 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -4,6 +4,7 @@ import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable +import androidx.annotation.Keep import io.legado.app.App import io.legado.app.R import io.legado.app.constant.PreferKey @@ -15,6 +16,7 @@ import java.io.File /** * 阅读界面配置 */ +@Keep object ReadBookConfig { const val readConfigFileName = "readConfig.json" private val configFilePath = @@ -27,6 +29,7 @@ object ReadBookConfig { val durConfig get() = getConfig(styleSelect) private val shareConfig get() = getConfig(5) var bg: Drawable? = null + var bgMeanColor: Int = 0 init { upConfig() @@ -67,7 +70,13 @@ object ReadBookConfig { val dm = resources.displayMetrics val width = dm.widthPixels val height = dm.heightPixels - bg = durConfig.bgDrawable(width, height) + bg = durConfig.bgDrawable(width, height).apply { + if (this is BitmapDrawable) { + bgMeanColor = BitmapUtils.getMeanColor(bitmap) + } else if (this is ColorDrawable) { + bgMeanColor = color + } + } } fun save() { @@ -155,10 +164,24 @@ 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.titleMode = value else durConfig.titleMode = value + var titleSize: Int + get() = if (shareLayout) shareConfig.titleSize else durConfig.titleSize + set(value) = + if (shareLayout) shareConfig.titleSize = value else durConfig.titleSize = 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.titleCenter = value else durConfig.titleCenter = value + if (shareLayout) shareConfig.titleBottomSpacing = value + else durConfig.titleBottomSpacing = value var paddingBottom: Int get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom @@ -228,6 +251,19 @@ object ReadBookConfig { if (shareLayout) shareConfig.footerPaddingTop = value else durConfig.footerPaddingTop = value + var showHeaderLine: Boolean + get() = if (shareLayout) shareConfig.showHeaderLine else durConfig.showHeaderLine + set(value) = + if (shareLayout) shareConfig.showHeaderLine = value + else durConfig.showHeaderLine = 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",//白天背景 private var bgStrNight: String = "#000000",//夜间背景 @@ -239,10 +275,13 @@ object ReadBookConfig { private var textColorNight: String = "#ADADAD",//夜间文字颜色 var textBold: Boolean = false,//是否粗体字 var textSize: Int = 20,//文字大小 - var letterSpacing: Float = 1f,//字间距 + var letterSpacing: Float = 0.5f,//字间距 var lineSpacingExtra: Int = 12,//行间距 var paragraphSpacing: Int = 12,//段距 - var titleCenter: Boolean = true,//标题居中 + var titleMode: Int = 0,//标题居中 + var titleSize: Int = 0, + var titleTopSpacing: Int = 0, + var titleBottomSpacing: Int = 0, var paddingBottom: Int = 6, var paddingLeft: Int = 16, var paddingRight: Int = 16, @@ -254,7 +293,9 @@ object ReadBookConfig { var footerPaddingBottom: Int = 6, var footerPaddingLeft: Int = 16, var footerPaddingRight: Int = 16, - var footerPaddingTop: Int = 6 + var footerPaddingTop: Int = 6, + var showHeaderLine: Boolean = false, + var showFooterLine: Boolean = true ) { fun setBg(bgType: Int, bg: String) { if (AppConfig.isNightTheme) { diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index a37e053fb..df417e2f9 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -57,6 +57,16 @@ object HttpHelper { return null } + suspend fun simpleGetByteAsync(url: String): ByteArray? { + NetworkUtils.getBaseUrl(url)?.let { baseUrl -> + return getByteRetrofit(baseUrl) + .create(HttpGetApi::class.java) + .getMapByteAsync(url, mapOf(), mapOf()) + .body() + } + return null + } + inline fun getApiService(baseUrl: String, encode: String? = null): T { return getRetrofit(baseUrl, encode).create(T::class.java) } 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 9087c3e9f..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 @@ -36,7 +36,7 @@ object ImportOldData { try {// Book source val sourceFile = - FileUtils.createFileIfNotExist(file, "myBookSource.json") + FileUtils.getFile(file, "myBookSource.json") val json = sourceFile.readText() val importCount = importOldSource(json) withContext(Dispatchers.Main) { @@ -49,12 +49,17 @@ object ImportOldData { } try {// Replace rules - val ruleFile = - FileUtils.createFileIfNotExist(file, "myBookReplaceRule.json") - val json = ruleFile.readText() - val importCount = importOldReplaceRule(json) - withContext(Dispatchers.Main) { - context.toast("成功导入替换规则${importCount}") + val ruleFile = FileUtils.getFile(file, "myBookReplaceRule.json") + if (ruleFile.exists()) { + val json = ruleFile.readText() + val importCount = importOldReplaceRule(json) + withContext(Dispatchers.Main) { + context.toast("成功导入替换规则${importCount}") + } + } else { + withContext(Dispatchers.Main) { + context.toast("未找到替换规则") + } } } catch (e: Exception) { withContext(Dispatchers.Main) { @@ -131,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 d9be6e9bd..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,20 +2,25 @@ 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 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 import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.withContext import org.jetbrains.anko.defaultSharedPreferences import java.io.File @@ -60,10 +65,11 @@ object Restore { } } } - restore(Backup.backupPath) + restoreDatabase() + restoreConfig() } - suspend fun restore(path: String) { + suspend fun restoreDatabase(path: String = Backup.backupPath) { withContext(IO) { fileToListT(path, "bookshelf.json")?.let { App.db.bookDao().insert(*it.toTypedArray()) @@ -83,11 +89,16 @@ object Restore { fileToListT(path, "replaceRule.json")?.let { App.db.replaceRuleDao().insert(*it.toTypedArray()) } + } + } + + suspend fun restoreConfig(path: String = Backup.backupPath) { + withContext(IO) { try { val file = FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName) val configFile = - File(App.INSTANCE.filesDir.absolutePath + File.separator + ReadBookConfig.readConfigFileName) + FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName) if (file.exists()) { file.copyTo(configFile, true) ReadBookConfig.upConfig() @@ -119,7 +130,18 @@ object Restore { ChapterProvider.upStyle() ReadBook.loadContent() } - LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon)) + withContext(Main) { + 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)) + } + } } private inline fun fileToListT(path: String, fileName: String): List? { diff --git a/app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt b/app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt new file mode 100644 index 000000000..ddb4d6ef5 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt @@ -0,0 +1,50 @@ +package io.legado.app.help.storage + +import io.legado.app.App +import io.legado.app.data.entities.BookProgress +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.lib.webdav.WebDav +import io.legado.app.utils.FileUtils +import io.legado.app.utils.GSON +import io.legado.app.utils.fromJsonArray + +@Suppress("BlockingMethodInNonBlockingContext") +object SyncBookProgress { + const val fileName = "bookProgress.json" + private val file = FileUtils.createFileIfNotExist(App.INSTANCE.cacheDir, fileName) + private val webDavUrl = "${WebDavHelp.rootWebDavUrl}$fileName" + + fun uploadBookProgress() { + Coroutine.async { + val value = App.db.bookDao().allBookProgress + if (value.isNotEmpty()) { + val json = GSON.toJson(value) + file.writeText(json) + if (WebDavHelp.initWebDav()) { + WebDav(webDavUrl).upload(file.absolutePath) + } + } + } + } + + fun downloadBookProgress() { + Coroutine.async { + if (WebDavHelp.initWebDav()) { + WebDav(webDavUrl).downloadTo(file.absolutePath, true) + if (file.exists()) { + val json = file.readText() + GSON.fromJsonArray(json)?.forEach { + App.db.bookDao().upBookProgress( + it.bookUrl, + it.durChapterIndex, + it.durChapterPos, + it.durChapterTime, + it.durChapterTitle + ) + } + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt index a1386f9f7..ad4c93a2a 100644 --- a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt +++ b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt @@ -10,6 +10,7 @@ import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.http.HttpAuth import io.legado.app.utils.FileUtils import io.legado.app.utils.ZipUtils +import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefString import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main @@ -25,31 +26,36 @@ object WebDavHelp { private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/" private val zipFilePath = "${FileUtils.getCachePath()}${File.separator}backup.zip" - private fun getWebDavUrl(): String { + val rootWebDavUrl: String + get() { var url = App.INSTANCE.getPrefString(PreferKey.webDavUrl) if (url.isNullOrEmpty()) { url = defaultWebDavUrl } - if (!url.endsWith("/")) url += "/" + if (!url.endsWith("/")) url = "${url}/" + if (App.INSTANCE.getPrefBoolean(PreferKey.webDavCreateDir, true)) { + url = "${url}legado/" + } return url } - private fun initWebDav(): Boolean { + fun initWebDav(): Boolean { val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount) val password = App.INSTANCE.getPrefString(PreferKey.webDavPassword) if (!account.isNullOrBlank() && !password.isNullOrBlank()) { HttpAuth.auth = HttpAuth.Auth(account, password) + WebDav(rootWebDavUrl).makeAsDir() return true } return false } private fun getWebDavFileNames(): ArrayList { - val url = getWebDavUrl() + val url = rootWebDavUrl val names = arrayListOf() if (initWebDav()) { try { - var files = WebDav(url + "legado/").listFiles() + var files = WebDav(url).listFiles() files = files.reversed() for (index: Int in 0 until min(10, files.size)) { files[index].displayName?.let { @@ -81,12 +87,17 @@ object WebDavHelp { private fun restoreWebDav(name: String, success: () -> Unit) { Coroutine.async { - getWebDavUrl().let { - val file = WebDav(it + "legado/" + name) - file.downloadTo(zipFilePath, true) - @Suppress("BlockingMethodInNonBlockingContext") - ZipUtils.unzipFile(zipFilePath, Backup.backupPath) - Restore.restore(Backup.backupPath) + rootWebDavUrl.let { + if (name == SyncBookProgress.fileName) { + SyncBookProgress.downloadBookProgress() + } else { + val webDav = WebDav(it + name) + webDav.downloadTo(zipFilePath, true) + @Suppress("BlockingMethodInNonBlockingContext") + ZipUtils.unzipFile(zipFilePath, Backup.backupPath) + Restore.restoreDatabase() + Restore.restoreConfig() + } } }.onSuccess { success.invoke() @@ -102,10 +113,9 @@ object WebDavHelp { } FileUtils.deleteFile(zipFilePath) if (ZipUtils.zipFiles(paths, zipFilePath)) { - WebDav(getWebDavUrl() + "legado").makeAsDir() - val putUrl = getWebDavUrl() + "legado/backup" + - SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) - .format(Date(System.currentTimeMillis())) + ".zip" + val backupDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + .format(Date(System.currentTimeMillis())) + val putUrl = "${rootWebDavUrl}backup${backupDate}.zip" WebDav(putUrl).upload(zipFilePath) } } diff --git a/app/src/main/java/io/legado/app/lib/theme/ATHUtils.kt b/app/src/main/java/io/legado/app/lib/theme/ATHUtils.kt index 73e342713..ddd6fc816 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ATHUtils.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ATHUtils.kt @@ -19,4 +19,16 @@ object ATHUtils { a.recycle() } } + + @JvmOverloads + fun resolveFloat(context: Context, @AttrRes attr: Int, fallback: Float = 0.0f): Float { + val a = context.theme.obtainStyledAttributes(intArrayOf(attr)) + return try { + a.getFloat(0, fallback) + } catch (e: Exception) { + fallback + } finally { + a.recycle() + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt index fab853c6b..bc898d065 100644 --- a/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt +++ b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt @@ -107,4 +107,7 @@ val Context.isDarkTheme: Boolean get() = ColorUtils.isColorLight(ThemeStore.primaryColor(this)) val Fragment.isDarkTheme: Boolean - get() = requireContext().isDarkTheme \ No newline at end of file + get() = requireContext().isDarkTheme + +val Context.elevation: Float + get() = ThemeStore.elevation(this) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt index 927fc2fb0..5b3997081 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt @@ -284,6 +284,14 @@ private constructor(private val mContext: Context) : ThemeStoreInterface { ) } + @CheckResult + fun elevation(context: Context): Float { + return prefs(context).getFloat( + ThemeStorePrefKeys.KEY_ELEVATION, + ATHUtils.resolveFloat(context, android.R.attr.elevation, context.resources.getDimension(R.dimen.design_appbar_elevation)) + ) + } + @CheckResult @ColorInt fun bottomBackground(context: Context): Int { diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt index a2ab19d60..ca8e85fc1 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt @@ -27,4 +27,6 @@ object ThemeStorePrefKeys { const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar" const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar" const val KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark" + + const val KEY_ELEVATION = "elevation" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt index 32b79ce79..47aa59344 100644 --- a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt +++ b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt @@ -202,7 +202,6 @@ constructor(urlStr: String) { * 上传文件 */ @Throws(IOException::class) - @JvmOverloads fun upload(localPath: String, contentType: String? = null): Boolean { val file = File(localPath) if (!file.exists()) return false diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 5a51b486c..69dfb943c 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -84,8 +84,8 @@ class WebBook(val bookSource: BookSource) { scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO ): Coroutine { - book.type = bookSource.bookSourceType return Coroutine.async(scope, context) { + book.type = bookSource.bookSourceType val body = if (!book.infoHtml.isNullOrEmpty()) { book.infoHtml @@ -111,8 +111,8 @@ class WebBook(val bookSource: BookSource) { scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO ): Coroutine> { - book.type = bookSource.bookSourceType return Coroutine.async(scope, context) { + book.type = bookSource.bookSourceType val body = if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) { book.tocHtml @@ -139,36 +139,50 @@ class WebBook(val bookSource: BookSource) { context: CoroutineContext = Dispatchers.IO ): Coroutine { return Coroutine.async(scope, context) { - if (bookSource.getContentRule().content.isNullOrEmpty()) { - Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") - return@async bookChapter.url - } - val body = - if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { - book.tocHtml - } else { - val analyzeUrl = - AnalyzeUrl( - book = book, - ruleUrl = bookChapter.url, - baseUrl = book.tocUrl, - headerMapF = bookSource.getHeaderMap() - ) - analyzeUrl.getResponseAwait( - bookSource.bookSourceUrl, - jsStr = bookSource.getContentRule().webJs, - sourceRegex = bookSource.getContentRule().sourceRegex - ).body - } - BookContent.analyzeContent( - this, - body, - book, - bookChapter, - bookSource, - bookChapter.url, - nextChapterUrl + getContentSuspend( + book, bookChapter, nextChapterUrl, scope ) } } + + /** + * 章节内容 + */ + suspend fun getContentSuspend( + book: Book, + bookChapter: BookChapter, + nextChapterUrl: String? = null, + scope: CoroutineScope = Coroutine.DEFAULT + ): String { + if (bookSource.getContentRule().content.isNullOrEmpty()) { + Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") + return bookChapter.url + } + val body = + if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { + book.tocHtml + } else { + val analyzeUrl = + AnalyzeUrl( + book = book, + ruleUrl = bookChapter.url, + baseUrl = book.tocUrl, + headerMapF = bookSource.getHeaderMap() + ) + analyzeUrl.getResponseAwait( + bookSource.bookSourceUrl, + jsStr = bookSource.getContentRule().webJs, + sourceRegex = bookSource.getContentRule().sourceRegex + ).body + } + return BookContent.analyzeContent( + scope, + body, + book, + bookChapter, + bookSource, + bookChapter.url, + nextChapterUrl + ) + } } \ No newline at end of file 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/localBook/AnalyzeTxtFile.kt b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt index fad747b34..16426a036 100644 --- a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt @@ -20,7 +20,7 @@ object AnalyzeTxtFile { private const val BUFFER_SIZE = 512 * 1024 //没有标题的时候,每个章节的最大长度 private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024 - private val cacheFolder: File by lazy { + val cacheFolder: File by lazy { val rootFile = App.INSTANCE.getExternalFilesDir(null) ?: App.INSTANCE.externalCacheDir ?: App.INSTANCE.cacheDir diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 44ee337b2..9a893b8a3 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -1,8 +1,10 @@ package io.legado.app.model.localBook +import android.net.Uri import androidx.documentfile.provider.DocumentFile import io.legado.app.App import io.legado.app.data.entities.Book +import io.legado.app.utils.FileUtils object LocalBook { @@ -27,4 +29,17 @@ object LocalBook { } } + fun deleteBook(book: Book, deleteOriginal: Boolean) { + kotlin.runCatching { + if (book.isTxt()) { + val bookFile = FileUtils.getFile(AnalyzeTxtFile.cacheFolder, book.originName) + bookFile.delete() + } + + if (deleteOriginal) { + val uri = Uri.parse(book.bookUrl) + DocumentFile.fromSingleUri(App.INSTANCE, uri)?.delete() + } + } + } } \ No newline at end of file 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/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index 14ea49b25..568615aa1 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -39,7 +39,7 @@ object BookContent { var contentData = analyzeContent( book, baseUrl, body, contentRule, bookChapter, bookSource ) - content.append(contentData.content.replace(bookChapter.title, "")) + content.append(contentData.content.replace(bookChapter.title, "")).append("\n") if (contentData.nextUrl.size == 1) { var nextUrl = contentData.nextUrl[0] val nextChapterUrl = if (!nextChapterUrlF.isNullOrEmpty()) @@ -64,7 +64,7 @@ object BookContent { ) nextUrl = if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else "" - content.append(contentData.content) + content.append(contentData.content).append("\n") } } Debug.log(bookSource.bookSourceUrl, "◇本章总页数:${nextUrlList.size}") @@ -91,7 +91,7 @@ object BookContent { } } for (item in contentDataList) { - content.append(item.content) + content.append(item.content).append("\n") } } 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/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index d94b3a6d1..336dfb18d 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -18,11 +18,12 @@ import org.jetbrains.anko.toast import java.util.concurrent.Executors class CheckSourceService : BaseService() { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + private val threadCount = AppConfig.threadCount + private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher() private var task: Coroutine<*>? = null - private val allIds = LinkedHashSet() - private val checkedIds = LinkedHashSet() + private val allIds = ArrayList() + private val checkedIds = ArrayList() + private var processIndex = 0 override fun onCreate() { super.onCreate() @@ -50,30 +51,44 @@ class CheckSourceService : BaseService() { allIds.clear() checkedIds.clear() allIds.addAll(ids) + processIndex = 0 updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) - task = execute(context = searchPool) { - allIds.forEach { sourceUrl -> - App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> - val webBook = WebBook(source) - webBook.searchBook("我的", scope = this, context = searchPool) - .onError(IO) { - source.addGroup("失效") - App.db.bookSourceDao().update(source) - }.onFinally { - checkedIds.add(sourceUrl) - updateNotification( - checkedIds.size, - getString(R.string.progress_show, checkedIds.size, allIds.size) - ) - } - } + task = execute { + for (i in 0 until threadCount) { + check() } }.onError { toast("校验书源出错:${it.localizedMessage}") } + } + - task?.invokeOnCompletion { - stopSelf() + private fun check() { + synchronized(this) { + processIndex++ + } + if (processIndex < allIds.size) { + val sourceUrl = allIds[processIndex] + App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> + val webBook = WebBook(source) + webBook.searchBook("我的", scope = this, context = searchPool) + .onError(IO) { + source.addGroup("失效") + App.db.bookSourceDao().update(source) + }.onFinally(IO) { + check() + checkedIds.add(sourceUrl) + updateNotification( + checkedIds.size, + getString(R.string.progress_show, checkedIds.size, allIds.size) + ) + synchronized(this) { + if (processIndex >= allIds.size + threadCount - 1) { + stopSelf() + } + } + } + } } } diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt index b5b983476..732506449 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -29,8 +29,10 @@ class DownloadService : BaseService() { private val handler = Handler() private var runnable: Runnable = Runnable { upDownload() } private val downloadMap = hashMapOf>() + private val downloadCount = hashMapOf(); private val finalMap = hashMapOf>() private var notificationContent = "正在启动下载" + private val notificationBuilder by lazy { val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload) .setSmallIcon(R.drawable.ic_download) @@ -97,6 +99,11 @@ class DownloadService : BaseService() { finalMap.remove(bookUrl) } + private fun updateNotification(downloadCount:DownloadCount, totalCount: Int, content: String){ + notificationContent = + "进度:${downloadCount.downloadFinishedCount}/$totalCount,成功:${downloadCount.successCount},$content" + } + private fun download() { val task = Coroutine.async(this, context = searchPool) { downloadMap.forEach { entry -> @@ -106,6 +113,9 @@ class DownloadService : BaseService() { val bookSource = App.db.bookSourceDao().getBookSource(book.origin) ?: return@async val webBook = WebBook(bookSource) + + downloadCount[entry.key] = DownloadCount() + entry.value.forEach { chapter -> if (!isActive) return@async if (downloadMap.containsKey(book.bookUrl)) { @@ -116,16 +126,19 @@ class DownloadService : BaseService() { scope = this, context = searchPool ) - .onStart { - notificationContent = chapter.title - } + //.onStart { + // notificationContent = "启动:" + chapter.title + //} .onSuccess(IO) { content -> content?.let { + downloadCount[entry.key]?.increaseSuccess() BookHelp.saveContent(book, chapter, content) } } .onFinally(IO) { synchronized(this@DownloadService) { + downloadCount[entry.key]?.increaseFinished() + downloadCount[entry.key]?.let { updateNotification(it, entry.value.size, chapter.title) } val chapterMap = finalMap[book.bookUrl] ?: linkedSetOf().apply { @@ -135,9 +148,14 @@ class DownloadService : BaseService() { if (chapterMap.size == entry.value.size) { downloadMap.remove(book.bookUrl) finalMap.remove(book.bookUrl) + downloadCount.remove(entry.key) } } } + } else{ + //无需下载的,设置为增加成功 + downloadCount[entry.key]?.increaseSuccess() + downloadCount[entry.key]?.increaseFinished() } } } @@ -176,4 +194,17 @@ class DownloadService : BaseService() { val notification = builder.build() startForeground(AppConst.notificationIdDownload, notification) } +} + +class DownloadCount{ + @Volatile public var downloadFinishedCount = 0 // 下载完成的条目数量 + @Volatile public var successCount = 0 //下载成功的条目数量 + + fun increaseSuccess(){ + ++successCount; + } + + fun increaseFinished(){ + ++downloadFinishedCount; + } } \ No newline at end of file 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/TTSReadAloudService.kt b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt index 6fd32219d..e54900db7 100644 --- a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt @@ -175,9 +175,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener } override fun onError(s: String) { - launch { - toast(s) - } + pauseReadAloud(true) } } 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/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index 207c34ec6..85f2fff45 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -69,11 +69,11 @@ object ReadBook { nextTextChapter = null book?.let { if (curTextChapter == null) { - loadContent(durChapterIndex) + loadContent(durChapterIndex, upContent) } else if (upContent) { callBack?.upContent() } - loadContent(durChapterIndex.plus(1)) + loadContent(durChapterIndex.plus(1), upContent) GlobalScope.launch(Dispatchers.IO) { for (i in 2..10) { delay(100) @@ -99,11 +99,11 @@ object ReadBook { prevTextChapter = null book?.let { if (curTextChapter == null) { - loadContent(durChapterIndex) + loadContent(durChapterIndex, upContent) } else if (upContent) { callBack?.upContent() } - loadContent(durChapterIndex.minus(1)) + loadContent(durChapterIndex.minus(1), upContent) GlobalScope.launch(Dispatchers.IO) { for (i in -5..-2) { delay(100) @@ -190,13 +190,13 @@ object ReadBook { loadContent(durChapterIndex - 1) } - fun loadContent(index: Int) { + fun loadContent(index: Int, upContent: Boolean = true) { book?.let { book -> if (addLoading(index)) { Coroutine.async { App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter -> BookHelp.getContent(book, chapter)?.let { - contentLoadFinish(chapter, it) + contentLoadFinish(chapter, it, upContent) removeLoading(chapter.index) } ?: download(chapter) } ?: removeLoading(index) @@ -262,7 +262,11 @@ object ReadBook { /** * 内容加载完成 */ - private fun contentLoadFinish(chapter: BookChapter, content: String) { + private fun contentLoadFinish( + chapter: BookChapter, + content: String, + upContent: Boolean = true + ) { Coroutine.async { if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) { val c = BookHelp.disposeContent( @@ -275,18 +279,18 @@ object ReadBook { when (chapter.index) { durChapterIndex -> { curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) - callBack?.upContent() + if (upContent) callBack?.upContent() callBack?.upView() curPageChanged() callBack?.contentLoadFinish() } durChapterIndex - 1 -> { prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) - callBack?.upContent(-1) + if (upContent) callBack?.upContent(-1) } durChapterIndex + 1 -> { nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) - callBack?.upContent(1) + if (upContent) callBack?.upContent(1) } } } diff --git a/app/src/main/java/io/legado/app/ui/README.md b/app/src/main/java/io/legado/app/ui/README.md index a8bf2b216..b408eb6b9 100644 --- a/app/src/main/java/io/legado/app/ui/README.md +++ b/app/src/main/java/io/legado/app/ui/README.md @@ -7,14 +7,14 @@ * book\read 书籍阅读界面 * book\search 搜索书籍界面 * book\source 搜索书源界面 -* changeCover 封面换源界面 -* changeSource 换源界面 -* chapterList 目录界面 -* config 配置界面 -* download 下载界面 -* explore 发现界面 +* book\changeCover 封面换源界面 +* book\changeSource 换源界面 +* book\chapterList 目录界面 +* book\download 下载界面 +* book\explore 发现界面 +* book\local 书籍导入界面 * fileChooser 文件选择界面 -* importBook 书籍导入界面 +* config 配置界面 * main 主界面 * qrCode 二维码扫描界面 * replaceRule 替换净化界面 diff --git a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt index 8fc7d1294..d86cb39a1 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt @@ -1,13 +1,19 @@ package io.legado.app.ui.about import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan import android.view.Menu import android.view.MenuItem import io.legado.app.R import io.legado.app.base.BaseActivity +import io.legado.app.lib.theme.accentColor import io.legado.app.utils.openUrl +import kotlinx.android.synthetic.main.activity_about.* import org.jetbrains.anko.share + class AboutActivity : BaseActivity(R.layout.activity_about) { override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -17,6 +23,16 @@ class AboutActivity : BaseActivity(R.layout.activity_about) { supportFragmentManager.beginTransaction() .replace(R.id.fl_fragment, aboutFragment, fTag) .commit() + tv_app_summary.post { + val span = ForegroundColorSpan(accentColor) + val spannableString = SpannableString(tv_app_summary.text) + val start = spannableString.indexOf("开源阅读软件") + spannableString.setSpan( + span, start, start + 6, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + tv_app_summary.text = spannableString + } } override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt index 0270b3f77..f82fa1b64 100644 --- a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt +++ b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt @@ -2,7 +2,6 @@ package io.legado.app.ui.about import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.View import androidx.preference.Preference @@ -12,7 +11,6 @@ import io.legado.app.utils.ACache import io.legado.app.utils.openUrl import io.legado.app.utils.sendToClip import org.jetbrains.anko.longToast -import java.net.URLEncoder class DonateFragment : PreferenceFragmentCompat() { @@ -36,7 +34,6 @@ class DonateFragment : PreferenceFragmentCompat() { "zfbHbRwm" -> requireContext().openUrl(zfbHbRwmUrl) "zfbSkRwm" -> requireContext().openUrl(zfbSkRwmUrl) "qqSkRwm" -> requireContext().openUrl(qqSkRwmUrl) - "zfbSk" -> aliDonate(requireContext()) "zfbHbSsm" -> getZfbHb(requireContext()) "gzGzh" -> requireContext().sendToClip("开源阅读软件") } @@ -59,19 +56,4 @@ class DonateFragment : PreferenceFragmentCompat() { } } - private fun aliDonate(context: Context) { - try { - val qrCode = URLEncoder.encode( - "https://qr.alipay.com/tsx06677nwdk3javroq4ef0?_s=Dweb-other", - "utf-8" - ) - val aliPayQr = - "alipayqr://platformapi/startapp?saId=10000007&qrcode=$qrCode&_t=${System.currentTimeMillis()}" - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(aliPayQr)) - context.startActivity(intent) - } catch (e: Exception) { - e.printStackTrace() - } - } - } \ No newline at end of file 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 dffd6a50f..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 @@ -25,8 +25,8 @@ import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.okButton import io.legado.app.service.AudioPlayService import io.legado.app.service.help.AudioPlay -import io.legado.app.ui.changesource.ChangeSourceDialog -import io.legado.app.ui.chapterlist.ChapterListActivity +import io.legado.app.ui.book.changesource.ChangeSourceDialog +import io.legado.app.ui.book.chapterlist.ChapterListActivity import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_audio_play.* import org.apache.commons.lang3.time.DateFormatUtils @@ -46,8 +46,9 @@ class AudioPlayActivity : private var adjustProgress = false override fun onActivityCreated(savedInstanceState: Bundle?) { - title_bar.background.alpha = 0 - AudioPlay.titleData.observe(this, Observer { title_bar.title = it }) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + AudioPlay.titleData.observe(this, Observer { toolbar.title = it }) AudioPlay.coverData.observe(this, Observer { upCover(it) }) viewModel.initData(intent) initView() @@ -119,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/arrange/ArrangeBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt index c0907f002..f3b0d5ee5 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt @@ -32,6 +32,7 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi get() = getViewModel(ArrangeBookViewModel::class.java) override val groupList: ArrayList = arrayListOf() private val groupRequestCode = 22 + private val addToGroupRequestCode = 34 private lateinit var adapter: ArrangeBookAdapter private var groupLiveData: LiveData>? = null private var booksLiveData: LiveData>? = null @@ -99,10 +100,10 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi -1 -> App.db.bookDao().observeAll() -2 -> App.db.bookDao().observeLocal() -3 -> App.db.bookDao().observeAudio() + -11 -> App.db.bookDao().observeNoGroup() else -> App.db.bookDao().observeByGroup(groupId) } booksLiveData?.observe(this, Observer { - adapter.selectedBooks.clear() adapter.setItems(it) upSelectCount() }) @@ -112,28 +113,29 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi when (item.itemId) { R.id.menu_group_manage -> GroupManageDialog() .show(supportFragmentManager, "groupManage") + R.id.menu_no_group -> { + title_bar.subtitle = getString(R.string.no_group) + groupId = -11 + initBookData() + } R.id.menu_all -> { title_bar.subtitle = item.title groupId = -1 - adapter.selectedBooks.clear() initBookData() } R.id.menu_local -> { title_bar.subtitle = item.title groupId = -2 - adapter.selectedBooks.clear() initBookData() } R.id.menu_audio -> { title_bar.subtitle = item.title groupId = -3 - adapter.selectedBooks.clear() initBookData() } else -> if (item.groupId == R.id.menu_group) { title_bar.subtitle = item.title groupId = item.itemId - adapter.selectedBooks.clear() initBookData() } } @@ -144,9 +146,14 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi when (item?.itemId) { R.id.menu_del_selection -> alert(titleResource = R.string.draw, messageResource = R.string.sure_del) { - okButton { viewModel.deleteBook(*adapter.selectedBooks.toTypedArray()) } + okButton { viewModel.deleteBook(*adapter.selectedBooks()) } noButton { } }.show().applyTint() + R.id.menu_update_enable -> + viewModel.upCanUpdate(adapter.selectedBooks(), true) + R.id.menu_update_disable -> + viewModel.upCanUpdate(adapter.selectedBooks(), false) + R.id.menu_add_to_group -> selectGroup(0, addToGroupRequestCode) } return false } @@ -168,7 +175,7 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi when (requestCode) { groupRequestCode -> { val books = arrayListOf() - adapter.selectedBooks.forEach { + adapter.selectedBooks().forEach { books.add(it.copy(group = groupId)) } viewModel.updateBook(*books.toTypedArray()) @@ -178,11 +185,18 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi viewModel.updateBook(it.copy(group = groupId)) } } + addToGroupRequestCode -> { + val books = arrayListOf() + adapter.selectedBooks().forEach { + books.add(it.copy(group = it.group or groupId)) + } + viewModel.updateBook(*books.toTypedArray()) + } } } override fun upSelectCount() { - select_action_bar.upCountView(adapter.selectedBooks.size, adapter.getItems().size) + select_action_bar.upCountView(adapter.selectedBooks().size, adapter.getItems().size) } override fun deleteBook(book: Book) { diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt index 66eff5706..96fe1344e 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt @@ -13,7 +13,7 @@ import org.jetbrains.anko.sdk27.listeners.onClick class ArrangeBookAdapter(context: Context, val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_arrange_book) { val groupRequestCode = 12 - val selectedBooks: HashSet = hashSetOf() + private val selectedBooks: HashSet = hashSetOf() var actionItem: Book? = null fun selectAll(selectAll: Boolean) { @@ -40,6 +40,16 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) : callBack.upSelectCount() } + fun selectedBooks(): Array { + val books = arrayListOf() + selectedBooks.forEach { + if (getItems().contains(it)) { + books.add(it) + } + } + return books.toTypedArray() + } + override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList) { with(holder.itemView) { tv_name.text = if (item.author.isEmpty()) { diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt index 4f4972de9..82ec712d2 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt @@ -8,6 +8,15 @@ import io.legado.app.data.entities.Book class ArrangeBookViewModel(application: Application) : BaseViewModel(application) { + fun upCanUpdate(books: Array, canUpdate: Boolean) { + execute { + books.forEach { + it.canUpdate = canUpdate + } + App.db.bookDao().update(*books) + } + } + fun updateBook(vararg book: Book) { execute { App.db.bookDao().update(*book) diff --git a/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverDialog.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt similarity index 53% rename from app/src/main/java/io/legado/app/ui/changecover/ChangeCoverDialog.kt rename to app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt index d1c081f76..85df58172 100644 --- a/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt @@ -1,21 +1,26 @@ -package io.legado.app.ui.changecover +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_source, container) + return inflater.inflate(R.layout.dialog_change_cover, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - viewModel.searchStateData.observe(this, 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/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt similarity index 62% rename from app/src/main/java/io/legado/app/ui/changecover/ChangeCoverViewModel.kt rename to app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index d8ed01d9f..627bacc09 100644 --- a/app/src/main/java/io/legado/app/ui/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.changecover +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/changecover/CoverAdapter.kt b/app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt similarity index 96% rename from app/src/main/java/io/legado/app/ui/changecover/CoverAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt index eae791b2c..55a841fcd 100644 --- a/app/src/main/java/io/legado/app/ui/changecover/CoverAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.changecover +package io.legado.app.ui.book.changecover import android.content.Context import io.legado.app.R 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/changesource/ChangeSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt similarity index 97% rename from app/src/main/java/io/legado/app/ui/changesource/ChangeSourceAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt index 1fa9284c1..132858e0f 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.changesource +package io.legado.app.ui.book.changesource import android.content.Context import android.os.Bundle diff --git a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt similarity index 87% rename from app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt rename to app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt index d1ce27f2e..bb60c19d4 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.changesource +package io.legado.app.ui.book.changesource import android.os.Bundle import android.util.DisplayMetrics @@ -8,24 +8,26 @@ 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 import io.legado.app.data.entities.SearchBook import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.applyTint import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getViewModel import io.legado.app.utils.putPrefBoolean import kotlinx.android.synthetic.main.dialog_change_source.* -class ChangeSourceDialog : DialogFragment(), +class ChangeSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener, ChangeSourceAdapter.CallBack { @@ -65,18 +67,14 @@ 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() - tool_bar.inflateMenu(R.menu.change_source) - tool_bar.setOnMenuItemClickListener(this) - initRecyclerView() initMenu() + initRecyclerView() initSearchView() initLiveData() viewModel.loadDbSearchBook() - viewModel.search() } private fun showTitle() { @@ -85,6 +83,9 @@ class ChangeSourceDialog : DialogFragment(), } private fun initMenu() { + tool_bar.inflateMenu(R.menu.change_source) + tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) + tool_bar.setOnMenuItemClickListener(this) tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = getPrefBoolean(PreferKey.changeSourceLoadToc) } @@ -110,7 +111,7 @@ class ChangeSourceDialog : DialogFragment(), } private fun initSearchView() { - val searchView = tool_bar.menu.findItem(R.id.menu_search).actionView as SearchView + val searchView = tool_bar.menu.findItem(R.id.menu_screen).actionView as SearchView searchView.setOnCloseListener { showTitle() false @@ -135,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)) @@ -143,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/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt similarity index 91% rename from app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt rename to app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index 1a683427f..e0800c0a1 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.changesource +package io.legado.app.ui.book.changesource import android.app.Application import android.os.Bundle @@ -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/changesource/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt similarity index 97% rename from app/src/main/java/io/legado/app/ui/changesource/DiffCallBack.kt rename to app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt index 91e640ac3..b0a8f19ef 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.changesource +package io.legado.app.ui.book.changesource import android.os.Bundle import androidx.recyclerview.widget.DiffUtil diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkAdapter.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/chapterlist/BookmarkAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt index ea3f41801..05e3751c9 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.chapterlist +package io.legado.app.ui.book.chapterlist import android.os.AsyncTask.execute import android.view.LayoutInflater diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkFragment.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt rename to app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkFragment.kt index b2337d0d9..6aa36d62e 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkFragment.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.chapterlist +package io.legado.app.ui.book.chapterlist import android.app.Activity import android.content.Intent diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListActivity.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/chapterlist/ChapterListActivity.kt rename to app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt index a0d401a5e..7420fc047 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.chapterlist +package io.legado.app.ui.book.chapterlist import android.os.Bundle import android.view.Menu diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/chapterlist/ChapterListAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt index 08b222970..b9d20920b 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.chapterlist +package io.legado.app.ui.book.chapterlist import android.content.Context import android.widget.TextView diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt similarity index 99% rename from app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt rename to app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt index 0bfc9a765..9e9b5c813 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListFragment.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.chapterlist +package io.legado.app.ui.book.chapterlist import android.app.Activity.RESULT_OK import android.content.Intent diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListViewModel.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt similarity index 94% rename from app/src/main/java/io/legado/app/ui/chapterlist/ChapterListViewModel.kt rename to app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt index 4156c4ac9..ad3361fd1 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/ChapterListViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.chapterlist +package io.legado.app.ui.book.chapterlist import android.app.Application diff --git a/app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt rename to app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt index cdcbf9746..fec1ed537 100644 --- a/app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.download +package io.legado.app.ui.book.download import android.app.Activity import android.content.Intent @@ -127,7 +127,7 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow FilePicker.selectFolder(this, exportRequestCode) { val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey) if (path.isNullOrEmpty()) { - toast("没有默认路径") + toast(R.string.no_default_path) } else { startExport(path) } diff --git a/app/src/main/java/io/legado/app/ui/download/DownloadAdapter.kt b/app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/download/DownloadAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt index 57cafc78a..082e5aecc 100644 --- a/app/src/main/java/io/legado/app/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.download +package io.legado.app.ui.book.download import android.content.Context import android.widget.ImageView diff --git a/app/src/main/java/io/legado/app/ui/download/DownloadViewModel.kt b/app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/download/DownloadViewModel.kt rename to app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt index da12d3d53..e5886d100 100644 --- a/app/src/main/java/io/legado/app/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.download +package io.legado.app.ui.book.download import android.app.Application import android.net.Uri diff --git a/app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt rename to app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt index 08a96a340..04bf86188 100644 --- a/app/src/main/java/io/legado/app/ui/explore/ExploreShowActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.explore +package io.legado.app.ui.book.explore import android.os.Bundle import androidx.lifecycle.Observer diff --git a/app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt similarity index 97% rename from app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt index 2204dae03..322e4a7a3 100644 --- a/app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.explore +package io.legado.app.ui.book.explore import android.content.Context import io.legado.app.R diff --git a/app/src/main/java/io/legado/app/ui/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt similarity index 97% rename from app/src/main/java/io/legado/app/ui/explore/ExploreShowViewModel.kt rename to app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt index 849b1ca87..d34e21916 100644 --- a/app/src/main/java/io/legado/app/ui/explore/ExploreShowViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.explore +package io.legado.app.ui.book.explore import android.app.Application import android.content.Intent diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt index 1a4322da6..ea4d2b61e 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt @@ -20,9 +20,9 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.AppConst import io.legado.app.constant.Theme import io.legado.app.data.entities.BookGroup +import io.legado.app.help.AppConfig import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView @@ -42,7 +42,7 @@ import kotlin.collections.ArrayList class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { private lateinit var viewModel: GroupViewModel private lateinit var adapter: GroupAdapter - private var callBack: CallBack? = null + private val callBack: CallBack? get() = parentFragment as? CallBack override fun onStart() { super.onStart() @@ -62,19 +62,12 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - callBack = parentFragment as? CallBack + tool_bar.title = getString(R.string.group_manage) initData() + initMenu() } private fun initData() { - tool_bar.title = getString(R.string.group_manage) - tool_bar.inflateMenu(R.menu.book_group_manage) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) - tool_bar.setOnMenuItemClickListener(this) - tool_bar.menu.findItem(R.id.menu_group_local) - .isChecked = AppConst.bookGroupLocalShow - tool_bar.menu.findItem(R.id.menu_group_audio) - .isChecked = AppConst.bookGroupAudioShow adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.addItemDecoration(VerticalDivider(requireContext())) @@ -90,17 +83,36 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) } + private fun initMenu() { + tool_bar.setOnMenuItemClickListener(this) + tool_bar.inflateMenu(R.menu.book_group_manage) + tool_bar.menu.let { + it.applyTint(requireContext(), Theme.getTheme()) + it.findItem(R.id.menu_group_all) + .isChecked = AppConfig.bookGroupAllShow + it.findItem(R.id.menu_group_local) + .isChecked = AppConfig.bookGroupLocalShow + it.findItem(R.id.menu_group_audio) + .isChecked = AppConfig.bookGroupAudioShow + } + } + override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_add -> addGroup() + R.id.menu_group_all -> { + item.isChecked = !item.isChecked + AppConfig.bookGroupAllShow = item.isChecked + callBack?.upGroup() + } R.id.menu_group_local -> { item.isChecked = !item.isChecked - AppConst.bookGroupLocalShow = item.isChecked + AppConfig.bookGroupLocalShow = item.isChecked callBack?.upGroup() } R.id.menu_group_audio -> { item.isChecked = !item.isChecked - AppConst.bookGroupAudioShow = item.isChecked + AppConfig.bookGroupAudioShow = item.isChecked callBack?.upGroup() } } 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 efcddba7c..42e56f206 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 @@ -20,14 +19,15 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.BlurTransformation import io.legado.app.help.ImageLoader import io.legado.app.help.IntentDataHelp +import io.legado.app.lib.dialogs.alert import io.legado.app.ui.audio.AudioPlayActivity +import io.legado.app.ui.book.changecover.ChangeCoverDialog +import io.legado.app.ui.book.changesource.ChangeSourceDialog +import io.legado.app.ui.book.chapterlist.ChapterListActivity import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity -import io.legado.app.ui.changecover.ChangeCoverDialog -import io.legado.app.ui.changesource.ChangeSourceDialog -import io.legado.app.ui.chapterlist.ChapterListActivity import io.legado.app.utils.getViewModel import io.legado.app.utils.gone import io.legado.app.utils.visible @@ -52,8 +52,8 @@ class BookInfoActivity : get() = getViewModel(BookInfoViewModel::class.java) override fun onActivityCreated(savedInstanceState: Bundle?) { - title_bar.background.alpha = 0 - tv_intro.movementMethod = ScrollingMovementMethod.getInstance() + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) viewModel.bookData.observe(this, Observer { showBook(it) }) viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) viewModel.initData(intent) @@ -86,8 +86,13 @@ class BookInfoActivity : } } R.id.menu_can_update -> { - viewModel.bookData.value?.let { - it.canUpdate = !it.canUpdate + if (viewModel.inBookshelf) { + viewModel.bookData.value?.let { + it.canUpdate = !it.canUpdate + viewModel.saveBook() + } + } else { + toast(R.string.after_add_bookshelf) } } } @@ -103,7 +108,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)) @@ -124,7 +129,6 @@ class BookInfoActivity : ImageLoader.load(this, book.getDisplayCover()) .transition(DrawableTransitionOptions.withCrossFade(1500)) .thumbnail(defaultCover()) - .centerCrop() .apply(bitmapTransform(BlurTransformation(this, 25))) .into(bg_book) //模糊、渐变、缩小效果 } @@ -186,9 +190,7 @@ class BookInfoActivity : } tv_shelf.onClick { if (viewModel.inBookshelf) { - viewModel.delBook { - upTvBookshelf() - } + deleteBook() } else { viewModel.addToBookshelf { upTvBookshelf() @@ -205,7 +207,7 @@ class BookInfoActivity : ChangeSourceDialog.show(supportFragmentManager, it.name, it.author) } } - tv_toc.onClick { + tv_toc_view.onClick { if (!viewModel.inBookshelf) { viewModel.saveBook { viewModel.saveChapterList { @@ -216,13 +218,39 @@ class BookInfoActivity : openChapterList() } } - tv_group.onClick { + tv_change_group.onClick { viewModel.bookData.value?.let { GroupSelectDialog.show(supportFragmentManager, it.group) } } } + private fun deleteBook() { + viewModel.bookData.value?.let { + if (it.isLocalBook()) { + alert( + titleResource = R.string.sure, + messageResource = R.string.sure_delete_book_file + ) { + positiveButton(R.string.yes) { + viewModel.delBook(true) { + finish() + } + } + negativeButton(R.string.no) { + viewModel.delBook(false) { + finish() + } + } + }.show() + } else { + viewModel.delBook { + upTvBookshelf() + } + } + } + } + private fun openChapterList() { if (viewModel.chapterListData.value.isNullOrEmpty()) { toast(R.string.chapter_list_empty) @@ -320,7 +348,7 @@ class BookInfoActivity : } } else { if (!viewModel.inBookshelf) { - viewModel.delBook(null) + viewModel.delBook() } } } diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index 1a675e21a..485d704ce 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -11,6 +11,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp import io.legado.app.model.WebBook import io.legado.app.model.localBook.AnalyzeTxtFile +import io.legado.app.model.localBook.LocalBook import kotlinx.coroutines.Dispatchers.IO class BookInfoViewModel(application: Application) : BaseViewModel(application) { @@ -198,12 +199,15 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { } } - fun delBook(success: (() -> Unit)?) { + fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) { execute { bookData.value?.let { App.db.bookDao().delete(it) + inBookshelf = false + if (it.isLocalBook()) { + LocalBook.deleteBook(it, deleteOriginal) + } } - inBookshelf = false }.onSuccess { success?.invoke() } diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt index 6bcae9257..969b1ee45 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.Observer import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.data.entities.Book -import io.legado.app.ui.changecover.ChangeCoverDialog +import io.legado.app.ui.book.changecover.ChangeCoverDialog import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_book_info_edit.* import org.jetbrains.anko.sdk27.listeners.onClick diff --git a/app/src/main/java/io/legado/app/ui/importbook/ImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt similarity index 99% rename from app/src/main/java/io/legado/app/ui/importbook/ImportBookActivity.kt rename to app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt index 8afa2793b..ed6bd4c74 100644 --- a/app/src/main/java/io/legado/app/ui/importbook/ImportBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.importbook +package io.legado.app.ui.book.local import android.annotation.SuppressLint import android.app.Activity diff --git a/app/src/main/java/io/legado/app/ui/importbook/ImportBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt similarity index 97% rename from app/src/main/java/io/legado/app/ui/importbook/ImportBookAdapter.kt rename to app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt index 6cb49d10f..153356754 100644 --- a/app/src/main/java/io/legado/app/ui/importbook/ImportBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.importbook +package io.legado.app.ui.book.local import android.content.Context import androidx.documentfile.provider.DocumentFile @@ -87,7 +87,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) : ll_brief.visible() tv_tag.text = item.name.substringAfterLast(".") tv_size.text = StringUtils.toSize(item.size) - tv_date.text = AppConst.DATE_FORMAT.format(item.date) + tv_date.text = AppConst.dateFormat.format(item.date) cb_select.isChecked = selectedUris.contains(item.uri.toString()) } tv_name.text = item.name diff --git a/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt similarity index 96% rename from app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt rename to app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt index 1250da682..847a692bd 100644 --- a/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.importbook +package io.legado.app.ui.book.local import android.app.Application import android.net.Uri 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 09d29a7f3..2ca4555a3 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 @@ -23,6 +23,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp import io.legado.app.help.ReadBookConfig import io.legado.app.help.coroutine.Coroutine +import io.legado.app.help.storage.SyncBookProgress import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.okButton @@ -31,6 +32,8 @@ import io.legado.app.receiver.TimeBatteryReceiver import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook +import io.legado.app.ui.book.changesource.ChangeSourceDialog +import io.legado.app.ui.book.chapterlist.ChapterListActivity import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.read.config.* import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR @@ -40,8 +43,6 @@ import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.delegate.PageDelegate import io.legado.app.ui.book.source.edit.BookSourceEditActivity -import io.legado.app.ui.changesource.ChangeSourceDialog -import io.legado.app.ui.chapterlist.ChapterListActivity import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.replacerule.edit.ReplaceEditDialog @@ -215,6 +216,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo R.id.menu_enable_replace -> ReadBook.book?.let { it.useReplaceRule = !it.useReplaceRule menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule + onReplaceRuleSave() } R.id.menu_book_info -> ReadBook.book?.let { startActivity(Pair("bookUrl", it.bookUrl)) @@ -270,18 +272,18 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } KeyEvent.KEYCODE_SPACE -> { - page_view.moveToNextPage() + page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT) return true } getPrefInt(PreferKey.prevKey) -> { if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - page_view.moveToPrevPage() + page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV) return true } } getPrefInt(PreferKey.nextKey) -> { if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - page_view.moveToNextPage() + page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT) return true } } @@ -439,11 +441,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo if (getPrefBoolean("volumeKeyPageOnPlay") || BaseReadAloudService.pause ) { - when (direction) { - PageDelegate.Direction.PREV -> page_view.moveToPrevPage() - PageDelegate.Direction.NEXT -> page_view.moveToNextPage() - else -> return true - } + page_view.pageDelegate?.keyTurnPage(direction) return true } } @@ -481,6 +479,8 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo if (!ReadBook.isLocalBook) { tv_chapter_url.text = it.url tv_chapter_url.visible() + } else { + tv_chapter_url.gone() } seek_read_page.max = it.pageSize().minus(1) seek_read_page.progress = ReadBook.durPageIndex @@ -663,6 +663,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo mHandler.removeCallbacks(keepScreenRunnable) textActionMenu?.dismiss() page_view.onDestroy() + SyncBookProgress.uploadBookProgress() } override fun observeLiveBus() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt index 7ea2cff28..254d037ad 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt @@ -12,8 +12,8 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig -import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.buttonDisabledColor import io.legado.app.service.help.ReadBook import io.legado.app.utils.* @@ -49,7 +49,7 @@ class ReadMenu : FrameLayout { fabNightTheme.setImageResource(R.drawable.ic_brightness) } initAnimation() - ATH.applyBackgroundTint(fl_bottom_bg) + ll_bottom_bg.setBackgroundColor(context.bottomBackground) vw_bg.onClick { } vwNavigationBar.onClick { } seek_brightness.progress = context.getPrefInt("brightness", 100) 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/MoreConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt index 9b975e7e8..6f472f270 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt @@ -16,6 +16,7 @@ import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.ReadBookConfig import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.bottomBackground import io.legado.app.ui.book.read.Help import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.postEvent @@ -32,7 +33,7 @@ class MoreConfigDialog : DialogFragment() { } dialog?.window?.let { it.setBackgroundDrawableResource(R.color.background) - it.decorView.setPadding(0, 5, 0, 0) + it.decorView.setPadding(0, 0, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f attr.gravity = Gravity.BOTTOM @@ -47,7 +48,7 @@ class MoreConfigDialog : DialogFragment() { savedInstanceState: Bundle? ): View? { val view = LinearLayout(context) - view.setBackgroundResource(R.color.background) + view.setBackgroundColor(requireContext().bottomBackground) view.id = R.id.tag1 container?.addView(view) return view 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..ed1c296de 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,8 @@ class PaddingConfigDialog : DialogFragment() { dsb_footer_padding_bottom.progress = footerPaddingBottom dsb_footer_padding_left.progress = footerPaddingLeft dsb_footer_padding_right.progress = footerPaddingRight + cb_show_top_line.isChecked = showHeaderLine + cb_show_bottom_line.isChecked = showFooterLine } private fun initView() = with(ReadBookConfig) { @@ -129,6 +132,18 @@ class PaddingConfigDialog : DialogFragment() { footerPaddingRight = it postEvent(EventBus.UP_CONFIG, true) } + cb_show_top_line.onCheckedChangeListener = { cb, isChecked -> + if (cb.isPressed) { + showHeaderLine = isChecked + postEvent(EventBus.UP_CONFIG, true) + } + } + cb_show_bottom_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 6040c0880..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,10 +7,11 @@ 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 import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook @@ -22,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() { @@ -52,20 +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) - initData() + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground) 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 @@ -92,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 @@ -129,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) { @@ -137,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 996eaa25c..f29c4ddba 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,7 @@ package io.legado.app.ui.book.read.config +import android.annotation.SuppressLint +import android.content.DialogInterface import android.os.Bundle import android.util.DisplayMetrics import android.view.Gravity @@ -12,18 +14,18 @@ import io.legado.app.R import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.ReadBookConfig +import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.selector import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.primaryColor import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.widget.font.FontSelectDialog -import io.legado.app.utils.getIndexById -import io.legado.app.utils.getPrefString -import io.legado.app.utils.postEvent -import io.legado.app.utils.putPrefString +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.dialog_read_book_style.* +import kotlinx.android.synthetic.main.dialog_title_config.view.* import org.jetbrains.anko.sdk27.listeners.onCheckedChange import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onLongClick @@ -41,7 +43,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } dialog?.window?.let { it.setBackgroundDrawableResource(R.color.background) - it.decorView.setPadding(0, 5, 0, 0) + it.decorView.setPadding(0, 0, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f attr.gravity = Gravity.BOTTOM @@ -65,18 +67,21 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { 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() } + dsb_line_size.valueFormat = { ((it - 10) / 10f).toString() } + dsb_paragraph_spacing.valueFormat = { (it / 10f).toString() } } private fun initData() { @@ -95,19 +100,15 @@ 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 - } - postEvent(EventBus.UP_CONFIG, true) + tv_title_mode.onClick { + showTitleConfig() } tv_text_bold.onClick { ReadBookConfig.apply { textBold = !textBold tv_text_bold.isSelected = textBold } - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, true) } tv_text_font.onClick { FontSelectDialog().show(childFragmentManager, "fontSelectDialog") @@ -125,6 +126,17 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { dismiss() callBack?.showPaddingConfig() } + rg_page_anim.onCheckedChange { _, checkedId -> + ReadBookConfig.pageAnim = rg_page_anim.getIndexById(checkedId) + callBack?.page_view?.upPageAnim() + } + cb_share_layout.onCheckedChangeListener = { checkBox, isChecked -> + if (checkBox.isPressed) { + ReadBookConfig.shareLayout = isChecked + upStyle() + postEvent(EventBus.UP_CONFIG, true) + } + } dsb_text_size.onChanged = { ReadBookConfig.textSize = it + 5 postEvent(EventBus.UP_CONFIG, true) @@ -141,19 +153,6 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { ReadBookConfig.paragraphSpacing = it postEvent(EventBus.UP_CONFIG, true) } - rg_page_anim.onCheckedChange { _, checkedId -> - rg_page_anim.getIndexById(checkedId).let { - ReadBookConfig.pageAnim = it - callBack?.page_view?.upPageAnim() - } - } - cb_share_layout.onCheckedChangeListener = { checkBox, isChecked -> - if (checkBox.isPressed) { - ReadBookConfig.shareLayout = isChecked - upStyle() - postEvent(EventBus.UP_CONFIG, true) - } - } bg0.onClick { changeBg(0) } bg0.onLongClick { showBgTextConfig(0) } bg1.onClick { changeBg(1) } @@ -166,6 +165,36 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { bg4.onLongClick { showBgTextConfig(4) } } + @SuppressLint("InflateParams") + private fun showTitleConfig() = ReadBookConfig.apply { + requireContext().alert(R.string.title) { + val rootView = LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_title_config, null).apply { + rg_title_mode.checkByIndex(titleMode) + dsb_title_size.progress = titleSize + dsb_title_top.progress = titleTopSpacing + dsb_title_bottom.progress = titleBottomSpacing + rg_title_mode.onCheckedChange { _, checkedId -> + titleMode = rg_title_mode.getIndexById(checkedId) + postEvent(EventBus.UP_CONFIG, true) + } + dsb_title_size.onChanged = { + titleSize = it + postEvent(EventBus.UP_CONFIG, true) + } + dsb_title_top.onChanged = { + titleTopSpacing = it + postEvent(EventBus.UP_CONFIG, true) + } + dsb_title_bottom.onChanged = { + titleBottomSpacing = it + postEvent(EventBus.UP_CONFIG, true) + } + } + customView = rootView + }.show() + } + private fun changeBg(index: Int) { if (ReadBookConfig.styleSelect != index) { ReadBookConfig.styleSelect = index @@ -185,7 +214,6 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { private fun upStyle() { ReadBookConfig.let { - tv_title_center.isSelected = it.titleCenter tv_text_bold.isSelected = it.textBold dsb_text_size.progress = it.textSize - 5 dsb_text_letter_spacing.progress = (it.letterSpacing * 100).toInt() + 50 @@ -194,12 +222,12 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { } } - private fun setBg() { - bg0.setTextColor(ReadBookConfig.getConfig(0).textColor()) - bg1.setTextColor(ReadBookConfig.getConfig(1).textColor()) - bg2.setTextColor(ReadBookConfig.getConfig(2).textColor()) - bg3.setTextColor(ReadBookConfig.getConfig(3).textColor()) - bg4.setTextColor(ReadBookConfig.getConfig(4).textColor()) + private fun setBg() = ReadBookConfig.apply { + bg0.setTextColor(getConfig(0).textColor()) + bg1.setTextColor(getConfig(1).textColor()) + bg2.setTextColor(getConfig(2).textColor()) + bg3.setTextColor(getConfig(3).textColor()) + bg4.setTextColor(getConfig(4).textColor()) for (i in 0..4) { val iv = when (i) { 1 -> bg1 @@ -208,22 +236,22 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { 4 -> bg4 else -> bg0 } - iv.setImageDrawable(ReadBookConfig.getConfig(i).bgDrawable(100, 150)) + iv.setImageDrawable(getConfig(i).bgDrawable(100, 150)) } } - private fun upBg() { - bg0.borderColor = requireContext().primaryColor - bg1.borderColor = requireContext().primaryColor - bg2.borderColor = requireContext().primaryColor - bg3.borderColor = requireContext().primaryColor - bg4.borderColor = requireContext().primaryColor + private fun upBg() = requireContext().apply { + bg0.borderColor = primaryColor + bg1.borderColor = primaryColor + bg2.borderColor = primaryColor + bg3.borderColor = primaryColor + bg4.borderColor = primaryColor when (ReadBookConfig.styleSelect) { - 1 -> bg1.borderColor = requireContext().accentColor - 2 -> bg2.borderColor = requireContext().accentColor - 3 -> bg3.borderColor = requireContext().accentColor - 4 -> bg4.borderColor = requireContext().accentColor - else -> bg0.borderColor = requireContext().accentColor + 1 -> bg1.borderColor = accentColor + 2 -> bg2.borderColor = accentColor + 3 -> bg3.borderColor = accentColor + 4 -> bg4.borderColor = accentColor + else -> bg0.borderColor = accentColor } } 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 430db0e53..b7cf7182e 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,9 +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.* @Suppress("DEPRECATION") @@ -28,8 +26,10 @@ object ChapterProvider { var visibleHeight = 0 var visibleRight = 0 var visibleBottom = 0 - private var lineSpacingExtra = 0f + private var lineSpacingExtra = 0 private var paragraphSpacing = 0 + private var titleTopSpacing = 0 + private var titleBottomSpacing = 0 var typeface: Typeface = Typeface.SANS_SERIF var titlePaint = TextPaint() var contentPaint = TextPaint() @@ -50,31 +50,16 @@ object ChapterProvider { val pageLines = arrayListOf() val pageLengths = arrayListOf() val stringBuilder = StringBuilder() - var surplusText = content - var durY = 0 + 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 = joinTitle(title, durY, textPages, pageLines, pageLengths, stringBuilder) - } - } 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 = joinBody(text, durY, textPages, pageLines, pageLengths, stringBuilder) + 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() @@ -90,6 +75,7 @@ object ChapterProvider { item.chapterIndex = bookChapter.index item.chapterSize = chapterSize item.title = bookChapter.title + item.upLinesPosition(visibleHeight) } return TextChapter( bookChapter.index, @@ -103,119 +89,64 @@ object ChapterProvider { } /** - * 标题 + * 排版文字 */ - private fun joinTitle( - title: String, - y: Int, + private fun setTypeText( + text: String, + y: Float, textPages: ArrayList, pageLines: ArrayList, pageLengths: ArrayList, - stringBuilder: StringBuilder - ): Int { - var durY = y + stringBuilder: StringBuilder, + isTitle: Boolean + ): Float { + var durY = if (isTitle) y + titleTopSpacing else y + val textPaint = if (isTitle) titlePaint else contentPaint val layout = StaticLayout( - title, titlePaint, visibleWidth, - Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true + text, textPaint, visibleWidth, + Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true ) for (lineIndex in 0 until layout.lineCount) { - textPages.last().height = durY - val textLine = TextLine(isTitle = true) - if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex) - + contentPaint.fontMetrics.descent < visibleHeight - ) { - textPages.last().textLines.add(textLine) - durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) - } else { - textPages.last().text = stringBuilder.toString() - stringBuilder.clear() - pageLines.add(textPages.last().textLines.size) - pageLengths.add(textPages.last().text.length) - //新页面 - durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) - textPages.add(TextPage()) - textPages.last().textLines.add(textLine) - } - textLine.lineTop = (paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() - textLine.lineBase = (paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat() - textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent + val textLine = TextLine(isTitle = isTitle) val words = - title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) - stringBuilder.append(words) + text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) textLine.text = words val desiredWidth = layout.getLineWidth(lineIndex) - if (lineIndex != layout.lineCount - 1) { - addCharsToLineMiddle(textLine, words, titlePaint, desiredWidth, 0f) - } else { + var isLastLine = false + if (lineIndex == 0 && layout.lineCount > 1 && !isTitle) { + //第一行 + addCharsToLineFirst(textLine, words, textPaint, desiredWidth) + } else if (lineIndex == layout.lineCount - 1) { //最后一行 - val x = if (ReadBookConfig.titleCenter) + isLastLine = true + val x = if (isTitle && ReadBookConfig.titleMode == 1) (visibleWidth - layout.getLineWidth(lineIndex)) / 2 else 0f - addCharsToLineLast(textLine, words, stringBuilder, titlePaint, x) - } - } - durY += paragraphSpacing - return durY - } - - /** - * 正文 - */ - private fun joinBody( - text: String, - y: Int, - textPages: ArrayList, - pageLines: ArrayList, - pageLengths: ArrayList, - stringBuilder: StringBuilder - ): Int { - var durY = y - val layout = StaticLayout( - text, contentPaint, visibleWidth, - Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true - ) - for (lineIndex in 0 until layout.lineCount) { - textPages.last().height = durY - val textLine = TextLine() - if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex) - + contentPaint.fontMetrics.descent < visibleHeight - ) { - textPages.last().textLines.add(textLine) - durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) + addCharsToLineLast(textLine, words, textPaint, x) } else { + //中间行 + addCharsToLineMiddle(textLine, words, textPaint, desiredWidth, 0f) + } + if (durY + textPaint.textHeight > visibleHeight) { + //当前页面结束,设置各种值 textPages.last().text = stringBuilder.toString() - stringBuilder.clear() pageLines.add(textPages.last().textLines.size) pageLengths.add(textPages.last().text.length) - //新页面 - durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) + textPages.last().height = durY + //新建页面 textPages.add(TextPage()) - textPages.last().textLines.add(textLine) + stringBuilder.clear() + durY = 0f } - textLine.lineTop = (paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() - textLine.lineBase = (paddingTop + durY - - (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat() - textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent - val words = - text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) stringBuilder.append(words) - textLine.text = words - val desiredWidth = layout.getLineWidth(lineIndex) - if (lineIndex == 0 && layout.lineCount > 1) { - //第一行 - addCharsToLineFirst(textLine, words, contentPaint, desiredWidth) - } else if (lineIndex == layout.lineCount - 1) { - //最后一行 - addCharsToLineLast(textLine, words, stringBuilder, contentPaint, 0f) - } else { - //中间行 - addCharsToLineMiddle(textLine, words, contentPaint, desiredWidth, 0f) - } + 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 + if (isTitle) durY += titleBottomSpacing + durY += textPaint.textHeight * paragraphSpacing / 10f return durY } @@ -231,10 +162,10 @@ object ChapterProvider { var x = 0f val bodyIndent = ReadBookConfig.bodyIndent val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length - for (i in 0..bodyIndent.lastIndex) { + bodyIndent.toStringArray().forEach { val x1 = x + icw textLine.addTextChar( - charData = bodyIndent[i].toString(), + charData = it, start = paddingLeft + x, end = paddingLeft + x1 ) @@ -257,12 +188,11 @@ object ChapterProvider { val gapCount: Int = words.length - 1 val d = (visibleWidth - desiredWidth) / gapCount var x = startX - for (i in words.indices) { - val char = words[i] - val cw = StaticLayout.getDesiredWidth(char.toString(), textPaint) + for ((i, char) in words.toStringArray().withIndex()) { + val cw = StaticLayout.getDesiredWidth(char, textPaint) val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw) textLine.addTextChar( - charData = char.toString(), + charData = char, start = paddingLeft + x, end = paddingLeft + x1 ) @@ -277,19 +207,16 @@ 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 - for (i in words.indices) { - val char = words[i].toString() - val cw = StaticLayout.getDesiredWidth(char, textPaint) + words.toStringArray().forEach { + val cw = StaticLayout.getDesiredWidth(it, textPaint) val x1 = x + cw textLine.addTextChar( - charData = char, + charData = it, start = paddingLeft + x, end = paddingLeft + x1 ) @@ -339,18 +266,19 @@ object ChapterProvider { titlePaint.color = ReadBookConfig.durConfig.textColor() titlePaint.letterSpacing = ReadBookConfig.letterSpacing titlePaint.typeface = Typeface.create(typeface, Typeface.BOLD) + titlePaint.textSize = with(ReadBookConfig) { textSize + titleSize }.sp.toFloat() //正文 contentPaint.isAntiAlias = true contentPaint.color = ReadBookConfig.durConfig.textColor() contentPaint.letterSpacing = ReadBookConfig.letterSpacing - val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL - contentPaint.typeface = Typeface.create(typeface, bold) + val style = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL + contentPaint.typeface = Typeface.create(typeface, style) + contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() //间距 - lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp.toFloat() - paragraphSpacing = ReadBookConfig.paragraphSpacing.dp - titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat() - contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat() - + lineSpacingExtra = ReadBookConfig.lineSpacingExtra + paragraphSpacing = ReadBookConfig.paragraphSpacing + titleTopSpacing = ReadBookConfig.titleTopSpacing.dp + titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dp upSize() } @@ -366,4 +294,6 @@ object ChapterProvider { visibleBottom = paddingTop + visibleHeight } + val TextPaint.textHeight: Float + get() = 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/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index e8beebf6a..44baa68f6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -170,13 +170,13 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } else if (!pageFactory.hasNext() && pageOffset < 0) { pageOffset = 0f } else if (pageOffset > 0) { - pageFactory.moveToPrev() + pageFactory.moveToPrev(false) textPage = pageFactory.currentPage pageOffset -= textPage.height upView?.invoke(textPage) } else if (pageOffset < -textPage.height) { pageOffset += textPage.height - pageFactory.moveToNext() + pageFactory.moveToNext(false) textPage = pageFactory.currentPage upView?.invoke(textPage) } 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..4e820008e 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 @@ -7,7 +7,7 @@ import android.view.MotionEvent import android.widget.FrameLayout import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap import io.legado.app.R -import io.legado.app.constant.AppConst.TIME_FORMAT +import io.legado.app.constant.AppConst.timeFormat import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.entities.TextPage @@ -18,6 +18,8 @@ import java.util.* class ContentView(context: Context) : FrameLayout(context) { + private var battery = 100 + init { //设置背景防止切换背景时文字重叠 setBackgroundColor(context.getCompatColor(R.color.background)) @@ -26,8 +28,7 @@ class ContentView(context: Context) : FrameLayout(context) { upStyle() upTime() content_text_view.upView = { - tv_bottom_left.text = it.title - setPageIndex(it.index, it.pageSize) + setProgress(it) } } @@ -60,12 +61,22 @@ class ContentView(context: Context) : FrameLayout(context) { footerPaddingRight.dp, footerPaddingBottom.dp ) + vw_top_divider.visible(showHeaderLine) + vw_bottom_divider.visible(showFooterLine) content_text_view.upVisibleRect() durConfig.textColor().let { tv_top_left.setTextColor(it) tv_top_right.setTextColor(it) tv_bottom_left.setTextColor(it) tv_bottom_right.setTextColor(it) + battery_view.setColor(it) + } + if (hideStatusBar) { + tv_bottom_left.text = timeFormat.format(Date(System.currentTimeMillis())) + battery_view.visible() + battery_view.setBattery(battery) + } else { + battery_view.gone() } } } @@ -84,21 +95,20 @@ class ContentView(context: Context) : FrameLayout(context) { } fun upTime() { - tv_top_left.text = TIME_FORMAT.format(Date(System.currentTimeMillis())) + if (ReadBookConfig.hideStatusBar) { + tv_bottom_right.text = timeFormat.format(Date(System.currentTimeMillis())) + } } fun upBattery(battery: Int) { - tv_top_right.text = context.getString(R.string.battery_show, battery) + this.battery = battery + if (ReadBookConfig.hideStatusBar) { + battery_view.setBattery(battery) + } } fun setContent(textPage: TextPage) { - tv_bottom_left.text = when (AppConfig.chineseConverterType) { - 1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) - 2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) - else -> textPage.title - } - setPageIndex(textPage.index, textPage.pageSize) - content_text_view.resetPageOffset() + setProgress(textPage) content_text_view.setContent(textPage) } @@ -107,9 +117,19 @@ class ContentView(context: Context) : FrameLayout(context) { } @SuppressLint("SetTextI18n") - fun setPageIndex(pageIndex: Int?, pageSize: Int) { - pageIndex?.let { - tv_bottom_right.text = "${pageIndex.plus(1)}/${pageSize}" + fun setProgress(textPage: TextPage) = textPage.apply { + val title = when (AppConfig.chineseConverterType) { + 1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) + 2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) + else -> textPage.title + } + if (ReadBookConfig.hideStatusBar) { + tv_top_left.text = title + tv_top_right.text = readProgress + tv_bottom_left.text = "${index.plus(1)}/$pageSize" + } else { + tv_bottom_left.text = title + tv_bottom_right.text = "${index.plus(1)}/$pageSize $readProgress" } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt b/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt index 1d188f598..4f7afa04f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt @@ -16,4 +16,6 @@ interface DataSource { fun hasNextChapter(): Boolean fun hasPrevChapter(): Boolean + + fun upContent(relativePosition: Int = 0) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt index 7a04ae5ec..75fd79094 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt @@ -6,9 +6,9 @@ abstract class PageFactory(protected val dataSource: DataSource) { abstract fun moveToLast() - abstract fun moveToNext():Boolean + abstract fun moveToNext(upContent: Boolean): Boolean - abstract fun moveToPrev(): Boolean + abstract fun moveToPrev(upContent: Boolean): Boolean abstract val nextPage: DATA diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index c6839b214..dbde99c7e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -26,12 +26,12 @@ class PageView(context: Context, attrs: AttributeSet) : init { callBack = activity as CallBack - prevPage = ContentView(context) - addView(prevPage) nextPage = ContentView(context) addView(nextPage) curPage = ContentView(context) addView(curPage) + prevPage = ContentView(context) + addView(prevPage) upBg() setWillNotDraw(false) pageFactory = TextPageFactory(this) @@ -40,6 +40,7 @@ class PageView(context: Context, attrs: AttributeSet) : override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) + prevPage.x = -w.toFloat() pageDelegate?.setViewSize(w, h) if (oldw != 0 && oldh != 0) { ReadBook.loadContent() @@ -75,12 +76,10 @@ class PageView(context: Context, attrs: AttributeSet) : fun fillPage(direction: PageDelegate.Direction) { when (direction) { PageDelegate.Direction.PREV -> { - pageFactory.moveToPrev() - upContent() + pageFactory.moveToPrev(true) } PageDelegate.Direction.NEXT -> { - pageFactory.moveToNext() - upContent() + pageFactory.moveToNext(true) } else -> Unit } @@ -99,7 +98,7 @@ class PageView(context: Context, attrs: AttributeSet) : upContent() } - fun upContent(relativePosition: Int = 0) { + override fun upContent(relativePosition: Int) { if (ReadBookConfig.isScroll) { curPage.setContent(pageFactory.currentPage) } else { @@ -116,22 +115,6 @@ class PageView(context: Context, attrs: AttributeSet) : callBack.screenOffTimerStart() } - fun moveToPrevPage(noAnim: Boolean = true) { - if (noAnim) { - fillPage(PageDelegate.Direction.PREV) - } else { - pageDelegate?.prevPageByAnim() - } - } - - fun moveToNextPage(noAnim: Boolean = true) { - if (noAnim) { - fillPage(PageDelegate.Direction.NEXT) - } else { - pageDelegate?.nextPageByAnim() - } - } - fun upStyle() { ChapterProvider.upStyle() curPage.upStyle() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt index 571f0d68f..a867daf3d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt @@ -31,25 +31,27 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } ?: ReadBook.setPageIndex(0) } - override fun moveToNext(): Boolean = with(dataSource) { + override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) { return if (hasNext()) { if (currentChapter?.isLastIndex(pageIndex) == true) { - ReadBook.moveToNextChapter(false) + ReadBook.moveToNextChapter(upContent) } else { ReadBook.setPageIndex(pageIndex.plus(1)) } + if (upContent) upContent() true } else false } - override fun moveToPrev(): Boolean = with(dataSource) { + override fun moveToPrev(upContent: Boolean): Boolean = with(dataSource) { return if (hasPrev()) { if (pageIndex <= 0) { - ReadBook.moveToPrevChapter(false) + ReadBook.moveToPrevChapter(upContent) } else { ReadBook.setPageIndex(pageIndex.minus(1)) } + if (upContent) upContent() true } else false 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 21ffcee77..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 @@ -6,9 +6,8 @@ import android.graphics.drawable.GradientDrawable import io.legado.app.ui.book.read.page.PageView class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { - - private val shadowDrawableR: GradientDrawable private val bitmapMatrix = Matrix() + private val shadowDrawableR: GradientDrawable init { val shadowColors = intArrayOf(0x66111111, 0x00000000) @@ -18,6 +17,46 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT } + override fun onDraw(canvas: Canvas) { + if (!isRunning) return + val offsetX = touchX - startX + + if ((mDirection == Direction.NEXT && offsetX > 0) + || (mDirection == Direction.PREV && offsetX < 0) + ) { + return + } + + val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth + if (mDirection == Direction.PREV) { + bitmapMatrix.setTranslate(distanceX, 0.toFloat()) + curBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } + prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } + addShadow(distanceX.toInt(), canvas) + } else if (mDirection == Direction.NEXT) { + bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) + nextBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) } + curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } + addShadow(distanceX.toInt(), canvas) + } + } + + private fun addShadow(left: Int, canvas: Canvas) { + if (left < 0) { + shadowDrawableR.setBounds(left + viewWidth, 0, left + viewWidth + 30, viewHeight) + shadowDrawableR.draw(canvas) + } else if (left > 0) { + shadowDrawableR.setBounds(left, 0, left + 30, viewHeight) + shadowDrawableR.draw(canvas) + } + } + + override fun onAnimStop() { + if (!isCancel) { + pageView.fillPage(mDirection) + } + } + override fun onAnimStart() { val distanceX: Float when (mDirection) { @@ -38,43 +77,7 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { viewWidth - (touchX - startX) } } - startScroll(touchX.toInt(), 0, distanceX.toInt(), 0) } - override fun onAnimStop() { - curPage.x = 0.toFloat() - if (!isCancel) { - pageView.fillPage(mDirection) - } - } - - override fun onDraw(canvas: Canvas) { - val offsetX = touchX - startX - - if ((mDirection == Direction.NEXT && offsetX > 0) - || (mDirection == Direction.PREV && offsetX < 0) - ) return - - val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth - bitmap?.let { - if (mDirection == Direction.PREV) { - bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - canvas.drawBitmap(it, bitmapMatrix, null) - } else if (mDirection == Direction.NEXT) { - curPage.translationX = offsetX - } - addShadow(distanceX.toInt(), canvas) - } - } - - private fun addShadow(left: Int, canvas: Canvas) { - if (left < 0) { - shadowDrawableR.setBounds(left + viewWidth, 0, left + viewWidth + 30, viewHeight) - shadowDrawableR.draw(canvas) - } else if (left > 0) { - shadowDrawableR.setBounds(left, 0, left + 30, viewHeight) - shadowDrawableR.draw(canvas) - } - } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt index 700728b14..8fe4fb699 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt @@ -1,13 +1,44 @@ package io.legado.app.ui.book.read.page.delegate +import android.graphics.Bitmap import android.view.MotionEvent import io.legado.app.ui.book.read.page.PageView -import kotlin.math.abs +import io.legado.app.utils.screenshot abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) { + protected var curBitmap: Bitmap? = null + protected var prevBitmap: Bitmap? = null + protected var nextBitmap: Bitmap? = null + + override fun setDirection(direction: Direction) { + super.setDirection(direction) + setBitmap() + } + + private fun setBitmap() { + when (mDirection) { + Direction.PREV -> { + prevBitmap?.recycle() + prevBitmap = prevPage.screenshot() + curBitmap?.recycle() + curBitmap = curPage.screenshot() + } + Direction.NEXT -> { + nextBitmap?.recycle() + nextBitmap = nextPage.screenshot() + curBitmap?.recycle() + curBitmap = curPage.screenshot() + } + else -> Unit + } + } + override fun onTouch(event: MotionEvent) { when (event.action) { + MotionEvent.ACTION_DOWN -> { + abort() + } MotionEvent.ACTION_MOVE -> { if (isTextSelected) { selectText(event) @@ -20,19 +51,37 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie } private fun onScroll(event: MotionEvent) { + + val action: Int = event.action + val pointerUp = + action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP + val skipIndex = if (pointerUp) event.actionIndex else -1 + // Determine focal point + var sumX = 0f + var sumY = 0f + val count: Int = event.pointerCount + for (i in 0 until count) { + if (skipIndex == i) continue + sumX += event.getX(i) + sumY += event.getY(i) + } + val div = if (pointerUp) count - 1 else count + val focusX = sumX / div + val focusY = sumY / div //判断是否移动了 if (!isMoved) { - isMoved = abs(startX - event.x) > slop - || abs(startX - event.x) > abs(startY - event.y) + val deltaX = (focusX - startX).toInt() + val deltaY = (focusY - startY).toInt() + val distance = deltaX * deltaX + deltaY * deltaY + isMoved = distance > slopSquare if (isMoved) { - if (event.x - startX > 0) { + if (sumX - startX > 0) { //如果上一页不存在 if (!hasPrev()) { noNext = true return } setDirection(Direction.PREV) - setBitmap() } else { //如果不存在表示没有下一页了 if (!hasNext()) { @@ -40,31 +89,41 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie return } setDirection(Direction.NEXT) - setBitmap() } } } if (isMoved) { - isCancel = if (mDirection == Direction.NEXT) touchX > lastX else touchX < lastX + isCancel = if (mDirection == Direction.NEXT) sumX > lastX else sumX < lastX isRunning = true //设置触摸点 - setTouchPoint(event.x, event.y) + setTouchPoint(sumX, sumY) } } override fun nextPageByAnim() { + super.nextPageByAnim() if (!hasNext()) return setDirection(Direction.NEXT) - setBitmap() setTouchPoint(viewWidth.toFloat(), 0f) onAnimStart() } override fun prevPageByAnim() { + super.prevPageByAnim() if (!hasPrev()) return setDirection(Direction.PREV) - setBitmap() setTouchPoint(0f, 0f) onAnimStart() } + + override fun onDestroy() { + super.onDestroy() + prevBitmap?.recycle() + prevBitmap = null + curBitmap?.recycle() + curBitmap = null + nextBitmap?.recycle() + nextBitmap = null + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt index 52dc2aeeb..cf18cb1db 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt @@ -1,19 +1,14 @@ package io.legado.app.ui.book.read.page.delegate -import android.graphics.Canvas import io.legado.app.ui.book.read.page.PageView class NoAnimPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { - override fun onAnimStart() { - startScroll(touchX.toInt(), 0, 0, 0) - } - - override fun onDraw(canvas: Canvas) { - } - override fun onAnimStop() { + override fun onAnimStart() { if (!isCancel) { pageView.fillPage(mDirection) } + stopScroll() } + } \ No newline at end of file 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 76b40fb78..4173ffb09 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 @@ -1,7 +1,6 @@ package io.legado.app.ui.book.read.page.delegate import android.content.Context -import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.RectF import android.view.GestureDetector @@ -15,7 +14,6 @@ import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.PageView -import io.legado.app.utils.screenshot import kotlin.math.abs abstract class PageDelegate(protected val pageView: PageView) : @@ -25,7 +23,7 @@ abstract class PageDelegate(protected val pageView: PageView) : pageView.width * 0.66f, pageView.height * 0.66f ) protected val context: Context = pageView.context - protected val slop = ViewConfiguration.get(context).scaledTouchSlop + //起始点 protected var startX: Float = 0f protected var startY: Float = 0f @@ -40,23 +38,26 @@ abstract class PageDelegate(protected val pageView: PageView) : protected val curPage: ContentView get() = pageView.curPage protected val prevPage: ContentView get() = pageView.prevPage - protected var bitmap: Bitmap? = null - protected var viewWidth: Int = pageView.width protected var viewHeight: Int = pageView.height - private val snackBar: Snackbar by lazy { - Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT) - } - private val scroller: Scroller by lazy { Scroller(pageView.context, DecelerateInterpolator()) } + protected val slopSquare by lazy { + val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop + scaledTouchSlop * scaledTouchSlop + } + private val detector: GestureDetector by lazy { GestureDetector(pageView.context, this) } + private val snackBar: Snackbar by lazy { + Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT) + } + var isMoved = false var noNext = true @@ -64,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() @@ -125,12 +126,13 @@ abstract class PageDelegate(protected val pageView: PageView) : pageView.invalidate() } - private fun stopScroll() { - isRunning = false + protected fun stopScroll() { isStarted = false - pageView.invalidate() - bitmap?.recycle() - bitmap = null + pageView.post { + isMoved = false + isRunning = false + pageView.invalidate() + } } open fun setViewSize(width: Int, height: Int) { @@ -166,23 +168,28 @@ abstract class PageDelegate(protected val pageView: PageView) : open fun onScroll() {}//移动contentView, slidePage - abstract fun nextPageByAnim() + open fun nextPageByAnim() { + abort() + } - abstract fun prevPageByAnim() + open fun prevPageByAnim() { + abort() + } + + open fun keyTurnPage(direction: Direction) { + if (isRunning) return + when (direction) { + Direction.NEXT -> nextPageByAnim() + Direction.PREV -> prevPageByAnim() + else -> return + } + } @CallSuper open fun setDirection(direction: Direction) { mDirection = direction } - open fun setBitmap() { - bitmap = when (mDirection) { - Direction.NEXT -> nextPage.screenshot() - Direction.PREV -> prevPage.screenshot() - else -> null - } - } - /** * 触摸事件处理 */ @@ -191,11 +198,14 @@ abstract class PageDelegate(protected val pageView: PageView) : if (isStarted) return if (!detector.onTouchEvent(event)) { //GestureDetector.onFling小幅移动不会触发,所以要自己判断 - if (event.action == MotionEvent.ACTION_UP && isMoved) { - if (selectedOnDown) { - selectedOnDown = false + when (event.action) { + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> if (isMoved) { + if (selectedOnDown) { + selectedOnDown = false + } + if (!noNext) onAnimStart() } - if (!noNext) onAnimStart() } } } @@ -232,6 +242,10 @@ abstract class PageDelegate(protected val pageView: PageView) : selectedOnDown = false return true } + if (isMoved) { + if (!noNext) onAnimStart() + return true + } val x = e.x val y = e.y if (centerRectF.contains(x, y)) { @@ -321,7 +335,7 @@ abstract class PageDelegate(protected val pageView: PageView) : } open fun onDestroy() { - bitmap?.recycle() + } enum class Direction { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt index a3434aeed..3fa333cbf 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt @@ -4,7 +4,6 @@ import android.view.MotionEvent import android.view.VelocityTracker import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.ui.book.read.page.PageView -import kotlin.math.abs class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { @@ -46,9 +45,28 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { private fun onScroll(event: MotionEvent) { mVelocity.addMovement(event) mVelocity.computeCurrentVelocity(velocityDuration) - setTouchPoint(event.x, event.y) + val action: Int = event.action + val pointerUp = + action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP + val skipIndex = if (pointerUp) event.actionIndex else -1 + // Determine focal point + var sumX = 0f + var sumY = 0f + val count: Int = event.pointerCount + for (i in 0 until count) { + if (skipIndex == i) continue + sumX += event.getX(i) + sumY += event.getY(i) + } + val div = if (pointerUp) count - 1 else count + val focusX = sumX / div + val focusY = sumY / div + setTouchPoint(sumX, sumY) if (!isMoved) { - isMoved = abs(startX - event.x) > slop || abs(startY - event.y) > slop + val deltaX = (focusX - startX).toInt() + val deltaY = (focusY - startY).toInt() + val distance = deltaX * deltaX + deltaY * deltaY + isMoved = distance > slopSquare } if (isMoved) { isRunning = true @@ -61,10 +79,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { } override fun nextPageByAnim() { + super.nextPageByAnim() startScroll(0, 0, 0, -ChapterProvider.visibleHeight) } override fun prevPageByAnim() { + super.prevPageByAnim() startScroll(0, 0, 0, ChapterProvider.visibleHeight) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt index 6d7dd7bf8..616a41614 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt @@ -3,8 +3,8 @@ package io.legado.app.ui.book.read.page.delegate import android.graphics.* import android.graphics.drawable.GradientDrawable import android.os.Build +import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.PageView -import io.legado.app.utils.screenshot import kotlin.math.* @Suppress("DEPRECATION") @@ -73,10 +73,6 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL } - private var curBitmap: Bitmap? = null - private var prevBitmap: Bitmap? = null - private var nextBitmap: Bitmap? = null - init { //设置颜色数组 val color = intArrayOf(0x333333, -0x4fcccccd) @@ -119,20 +115,20 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi } override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) { - super.setStartPoint(x, y, invalidate) + super.setStartPoint(x, y, false) calcCornerXY(x, y) } override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) { super.setTouchPoint(x, y, false) //触摸y中间位置吧y变成屏幕高度 - if ((startY > viewHeight * 0.33 && startY < viewHeight * 0.66) + if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3) || mDirection == Direction.PREV ) { touchY = viewHeight.toFloat() } - if (startY > viewHeight * 0.33 && startY < viewHeight / 2.0 + if (startY > viewHeight / 3 && startY < viewHeight / 2 && mDirection == Direction.NEXT ) { touchY = 1f @@ -145,33 +141,19 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi when (direction) { Direction.PREV -> //上一页滑动不出现对角 - if (startX > viewWidth / 2.0) { + if (startX > viewWidth / 2) { calcCornerXY(startX, viewHeight.toFloat()) } else { calcCornerXY(viewWidth - startX, viewHeight.toFloat()) } Direction.NEXT -> - if (viewWidth / 2.0 > startX) { + if (viewWidth / 2 > startX) { calcCornerXY(viewWidth - startX, startY) } else -> Unit } } - override fun setBitmap() { - when (mDirection) { - Direction.PREV -> { - prevBitmap = prevPage.screenshot() - curBitmap = curPage.screenshot() - } - Direction.NEXT -> { - nextBitmap = nextPage.screenshot() - curBitmap = curPage.screenshot() - } - else -> Unit - } - } - override fun onAnimStart() { var dx: Float val dy: Float @@ -209,15 +191,10 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi if (!isCancel) { pageView.fillPage(mDirection) } - prevBitmap?.recycle() - prevBitmap = null - nextBitmap?.recycle() - nextBitmap = null - curBitmap?.recycle() - curBitmap = null } override fun onDraw(canvas: Canvas) { + if (!isRunning) return when (mDirection) { Direction.NEXT -> { calcPoints() @@ -292,6 +269,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi mMatrix.setValues(mMatrixArray) mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y) mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y) + canvas.drawColor(ReadBookConfig.bgMeanColor) canvas.drawBitmap(bitmap, mMatrix, mPaint) mPaint.colorFilter = null canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y) @@ -480,8 +458,8 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi * 计算拖拽点对应的拖拽脚 */ private fun calcCornerXY(x: Float, y: Float) { - mCornerX = if (x <= viewWidth / 2.0) 0 else viewWidth - mCornerY = if (y <= viewHeight / 2.0) 0 else viewHeight + mCornerX = if (x <= viewWidth / 2) 0 else viewWidth + mCornerY = if (y <= viewHeight / 2) 0 else viewHeight mIsRtOrLb = (mCornerX == 0 && mCornerY == viewHeight) || (mCornerY == 0 && mCornerX == viewWidth) } @@ -492,21 +470,23 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi mMiddleX = (mTouchX + mCornerX) / 2 mMiddleY = (mTouchY + mCornerY) / 2 - mBezierControl1.x = mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX) mBezierControl1.y = mCornerY.toFloat() - mBezierControl2.x = mCornerX.toFloat() - mBezierControl2.y = if ((mCornerY - mMiddleY).toInt() == 0) { - mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f + + val f4 = mCornerY - mMiddleY + if (f4 == 0f) { + mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f + } else { - mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) + mBezierControl2.y = + mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) } - mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2 mBezierStart1.y = mCornerY.toFloat() - //固定左边上下两个点 + + // 固定左边上下两个点 if (mTouchX > 0 && mTouchX < viewWidth) { if (mBezierStart1.x < 0 || mBezierStart1.x > viewWidth) { if (mBezierStart1.x < 0) @@ -515,6 +495,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi val f1 = abs(mCornerX - mTouchX) val f2 = viewWidth * f1 / mBezierStart1.x mTouchX = abs(mCornerX - f2) + val f3 = abs(mCornerX - mTouchX) * abs(mCornerY - mTouchY) / f1 mTouchY = abs(mCornerY - f3) @@ -526,10 +507,14 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi mBezierControl1.y = mCornerY.toFloat() mBezierControl2.x = mCornerX.toFloat() - mBezierControl2.y = if ((mCornerY - mMiddleY).toInt() == 0) { - mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f + + val f5 = mCornerY - mMiddleY + if (f5 == 0f) { + mBezierControl2.y = + mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f } else { - mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) + mBezierControl2.y = + mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) } mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2 @@ -538,12 +523,19 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi mBezierStart2.x = mCornerX.toFloat() mBezierStart2.y = mBezierControl2.y - (mCornerY - mBezierControl2.y) / 2 - mTouchToCornerDis = hypot(mTouchX - mCornerX, touchY - mCornerY) + mTouchToCornerDis = hypot( + (mTouchX - mCornerX).toDouble(), + (mTouchY - mCornerY).toDouble() + ).toFloat() - mBezierEnd1 = - getCross(PointF(mTouchX, mTouchY), mBezierControl1, mBezierStart1, mBezierStart2) - mBezierEnd2 = - getCross(PointF(mTouchX, mTouchY), mBezierControl2, mBezierStart1, mBezierStart2) + mBezierEnd1 = getCross( + PointF(mTouchX, mTouchY), mBezierControl1, mBezierStart1, + mBezierStart2 + ) + mBezierEnd2 = getCross( + PointF(mTouchX, mTouchY), mBezierControl2, mBezierStart1, + mBezierStart2 + ) mBezierVertex1.x = (mBezierStart1.x + 2 * mBezierControl1.x + mBezierEnd1.x) / 4 mBezierVertex1.y = (2 * mBezierControl1.y + mBezierStart1.y + mBezierEnd1.y) / 4 diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt index 0308d965e..197b65c39 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt @@ -28,7 +28,6 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { viewWidth - (touchX - startX) } } - startScroll(touchX.toInt(), 0, distanceX.toInt(), 0) } @@ -38,27 +37,22 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { if ((mDirection == Direction.NEXT && offsetX > 0) || (mDirection == Direction.PREV && offsetX < 0) ) return - val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth - bitmap?.let { + if (!isRunning) return + if (mDirection == Direction.PREV) { + bitmapMatrix.setTranslate(distanceX + viewWidth, 0.toFloat()) + curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } + bitmapMatrix.setTranslate(distanceX, 0.toFloat()) + prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } + } else if (mDirection == Direction.NEXT) { bitmapMatrix.setTranslate(distanceX, 0.toFloat()) - canvas.drawBitmap(it, bitmapMatrix, null) + nextBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } + bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat()) + curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) } } } - override fun onScroll() { - val offsetX = touchX - startX - - if ((mDirection == Direction.NEXT && offsetX > 0) - || (mDirection == Direction.PREV && offsetX < 0) - ) return - - curPage.translationX = offsetX - } - override fun onAnimStop() { - curPage.x = 0.toFloat() - if (!isCancel) { pageView.fillPage(mDirection) } 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 4e6089ddd..6c4e27073 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 @@ -5,6 +5,7 @@ import android.text.StaticLayout import io.legado.app.App import io.legado.app.R import io.legado.app.ui.book.read.page.ChapterProvider +import java.text.DecimalFormat data class TextPage( var index: Int = 0, @@ -14,9 +15,24 @@ data class TextPage( var pageSize: Int = 0, var chapterSize: Int = 0, var chapterIndex: Int = 0, - var height: Int = 0 + var height: Float = 0f ) { + fun upLinesPosition(visibleHeight: Int) { + if (textLines.size <= 1) return + if (visibleHeight - height > with(textLines.last()) { lineBottom - lineTop }) return + val surplus = (visibleHeight - textLines.last().lineBottom) + if (surplus == 0f) return + height += surplus + val tj = surplus / (textLines.size - 1) + for (i in 1 until textLines.size) { + val line = textLines[i] + line.lineTop = line.lineTop + tj * i + line.lineBase = line.lineBase + tj * i + line.lineBottom = line.lineBottom + tj * i + } + } + @Suppress("DEPRECATION") fun format(): TextPage { if (textLines.isEmpty() && ChapterProvider.visibleWidth > 0) { @@ -34,19 +50,20 @@ 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) } - height = ChapterProvider.visibleHeight + height = ChapterProvider.visibleHeight.toFloat() } return this } @@ -83,4 +100,21 @@ data class TextPage( lineStart += textLine.text.length } } + + val readProgress: String + get() { + val df = DecimalFormat("0.0%") + if (chapterSize == 0 || pageSize == 0 && chapterIndex == 0) { + return "0.0%" + } else if (pageSize == 0) { + return df.format((chapterIndex + 1.0f) / chapterSize.toDouble()) + } + var percent = + df.format(chapterIndex * 1.0f / chapterSize + 1.0f / chapterSize * (index + 1) / pageSize.toDouble()) + if (percent == "100.0%" && (chapterIndex + 1 != chapterSize || index + 1 != pageSize)) { + percent = "99.9%" + } + return percent + } + } diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt index 1227f4232..2d1054859 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.flexbox.FlexboxLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity @@ -31,6 +32,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.startActivity +import java.text.Collator class SearchActivity : VMBaseActivity(R.layout.activity_book_search), @@ -49,7 +51,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se private var bookData: LiveData>? = null private var menu: Menu? = null private var precisionSearchMenuItem: MenuItem? = null - private var groups = hashSetOf() + private var groups = linkedSetOf() private var refreshTime = System.currentTimeMillis() override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -133,11 +135,10 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se ATH.applyEdgeEffectColor(rv_bookshelf_search) ATH.applyEdgeEffectColor(rv_history_key) bookAdapter = BookAdapter(this, this) - rv_bookshelf_search.layoutManager = - LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) + rv_bookshelf_search.layoutManager = FlexboxLayoutManager(this) rv_bookshelf_search.adapter = bookAdapter historyKeyAdapter = HistoryKeyAdapter(this, this) - rv_history_key.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) + rv_history_key.layoutManager = FlexboxLayoutManager(this) rv_history_key.adapter = historyKeyAdapter adapter = SearchAdapter(this, this) recycler_view.layoutManager = LinearLayoutManager(this) @@ -229,12 +230,13 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se if (selectedGroup == "") { item?.isChecked = true } - groups.map { - item = menu?.add(R.id.source_group, Menu.NONE, Menu.NONE, it) - if (it == selectedGroup) { - item?.isChecked = true + groups.sortedWith(Collator.getInstance(java.util.Locale.CHINESE)) + .map { + item = menu?.add(R.id.source_group, Menu.NONE, Menu.NONE, it) + if (it == selectedGroup) { + item?.isChecked = true + } } - } menu?.setGroupCheckable(R.id.source_group, true, true) } @@ -266,7 +268,8 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se } else { App.db.searchKeywordDao().liveDataSearch(key) } - historyData?.observe(this, Observer { historyKeyAdapter.setItems(it) + historyData?.observe(this, Observer { + historyKeyAdapter.setItems(it) if (it.isEmpty()) { tv_clear_history.invisible() } else { @@ -280,12 +283,11 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se */ @Synchronized private fun upSearchItems(items: List, isMandatoryUpdate: Boolean) { - if (!isMandatoryUpdate && System.currentTimeMillis() - refreshTime < 1000) { - return + if (isMandatoryUpdate || System.currentTimeMillis() - refreshTime > 500) { + refreshTime = System.currentTimeMillis() + val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), items)) + adapter.setItems(items, diffResult) } - refreshTime = System.currentTimeMillis() - val diffResult = DiffUtil.calculateDiff(DiffCallBack(ArrayList(adapter.getItems()), items)) - adapter.setItems(items, diffResult) } /** 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..385088640 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 @@ -40,6 +40,7 @@ import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivityForResult import org.jetbrains.anko.toast import java.io.File +import java.text.Collator class BookSourceActivity : VMBaseActivity(R.layout.activity_book_source), PopupMenu.OnMenuItemClickListener, @@ -84,7 +85,12 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity R.id.menu_group_manage -> GroupManageDialog().show(supportFragmentManager, "groupManage") R.id.menu_import_source_local -> FilePicker - .selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json")) + .selectFile( + this, + importRequestCode, + type = "text/*", + allowExtensions = arrayOf("txt", "json") + ) R.id.menu_import_source_onLine -> showImportDialog() } if (item.groupId == R.id.source_group) { @@ -197,9 +203,10 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity private fun upGroupMenu() { groupMenu?.removeGroup(R.id.source_group) - groups.map { - groupMenu?.add(R.id.source_group, Menu.NONE, Menu.NONE, it) - } + groups.sortedWith(Collator.getInstance(java.util.Locale.CHINESE)) + .map { + groupMenu?.add(R.id.source_group, Menu.NONE, Menu.NONE, it) + } } @SuppressLint("InflateParams") @@ -214,7 +221,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/BackupRestoreUi.kt b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt index 1bec0a184..913f78971 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt @@ -114,7 +114,8 @@ object BackupRestoreUi { .onGranted { Coroutine.async { AppConfig.backupPath = path - Restore.restore(path) + Restore.restoreDatabase(path) + Restore.restoreConfig(path) }.onSuccess { fragment.toast(R.string.restore_success) } 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/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index 0652350c5..17a3bcfe1 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -2,7 +2,6 @@ package io.legado.app.ui.config import android.content.SharedPreferences import android.os.Bundle -import android.os.Handler import android.view.View import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -17,8 +16,9 @@ import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ColorUtils +import io.legado.app.lib.theme.elevation +import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.utils.* -import org.jetbrains.anko.defaultSharedPreferences class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { @@ -27,7 +27,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_config_theme) - onSharedPreferenceChanged(requireContext().defaultSharedPreferences, "defaultTheme") + upPreferenceSummary("barElevation", AppConfig.elevation.toString()) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -49,9 +49,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar sharedPreferences ?: return when (key) { PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key)) - "transparentStatusBar" -> { - recreateActivities() - } + "transparentStatusBar" -> recreateActivities() "colorPrimary", "colorAccent", "colorBackground", @@ -100,7 +98,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar upTheme(true) } } - "defaultTheme" -> findPreference(key)?.summary = items[getPrefInt(key)] } } @@ -147,11 +144,23 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar AppConfig.isNightTheme = true } } - putPrefInt("defaultTheme", which) App.INSTANCE.applyDayNight() recreateActivities() } }.show().applyTint() + "barElevation" -> NumberPickerDialog(requireContext()) + .setTitle(getString(R.string.bar_elevation)) + .setMaxValue(32) + .setMinValue(0) + .setValue(AppConfig.elevation) + .setCustomButton((R.string.btn_default_s)) { + AppConfig.elevation = App.INSTANCE.resources.getDimension(R.dimen.design_appbar_elevation).toInt() + recreateActivities() + } + .show { + AppConfig.elevation = it + recreateActivities() + } } return super.onPreferenceTreeClick(preference) } @@ -176,14 +185,21 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar private fun upTheme(isNightTheme: Boolean) { if (AppConfig.isNightTheme == isNightTheme) { - App.INSTANCE.applyTheme() - recreateActivities() + listView.post { + App.INSTANCE.applyTheme() + recreateActivities() + } } } private fun recreateActivities() { postEvent(EventBus.RECREATE, "") - Handler().postDelayed({ activity?.recreate() }, 100L) } + private fun upPreferenceSummary(preferenceKey: String, value: String?) { + val preference = findPreference(preferenceKey) ?: return + when (preferenceKey) { + "barElevation" -> preference.summary = getString(R.string.bar_elevation_s, value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt index 9260fc8bd..8f5549709 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt @@ -14,8 +14,13 @@ import org.jetbrains.anko.toast @Suppress("unused") object FilePicker { - fun selectFolder(activity: AppCompatActivity, requestCode: Int, default: (() -> Unit)? = null) { - activity.alert(titleResource = R.string.select_folder) { + fun selectFolder( + activity: AppCompatActivity, + requestCode: Int, + title: String = activity.getString(R.string.select_folder), + default: (() -> Unit)? = null + ) { + activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -45,9 +50,14 @@ object FilePicker { }.show() } - fun selectFolder(fragment: Fragment, requestCode: Int, default: (() -> Unit)? = null) { + fun selectFolder( + fragment: Fragment, + requestCode: Int, + title: String = fragment.getString(R.string.select_folder), + default: (() -> Unit)? = null + ) { fragment.requireContext() - .alert(titleResource = R.string.select_folder) { + .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -80,11 +90,12 @@ object FilePicker { fun selectFile( activity: BaseActivity, requestCode: Int, + title: String = activity.getString(R.string.select_file), type: String, allowExtensions: Array?, default: (() -> Unit)? = null ) { - activity.alert(titleResource = R.string.select_file) { + activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -119,12 +130,13 @@ object FilePicker { fun selectFile( fragment: Fragment, requestCode: Int, + title: String = fragment.getString(R.string.select_file), type: String, allowExtensions: Array, default: (() -> Unit)? = null ) { fragment.requireContext() - .alert(titleResource = R.string.select_file) { + .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { 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 f49cf2ea6..3608a79ed 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,9 +15,9 @@ 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.help.AppConfig import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton @@ -25,10 +25,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.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.download.DownloadActivity -import io.legado.app.ui.importbook.ImportBookActivity import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.* @@ -99,9 +99,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() { @@ -112,11 +109,13 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b synchronized(this) { tab_layout.removeOnTabSelectedListener(this) bookGroups.clear() - bookGroups.add(AppConst.bookGroupAll) - if (AppConst.bookGroupLocalShow) { + if (AppConfig.bookGroupAllShow) { + bookGroups.add(AppConst.bookGroupAll) + } + if (AppConfig.bookGroupLocalShow) { bookGroups.add(AppConst.bookGroupLocal) } - if (AppConst.bookGroupAudioShow) { + if (AppConfig.bookGroupAudioShow) { bookGroups.add(AppConst.bookGroupAudio) } bookGroups.addAll(it) @@ -138,13 +137,17 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b override fun upGroup() { synchronized(this) { + bookGroups.remove(AppConst.bookGroupAll) bookGroups.remove(AppConst.bookGroupLocal) bookGroups.remove(AppConst.bookGroupAudio) - if (getPrefBoolean("bookGroupAudio", true)) { - bookGroups.add(1, AppConst.bookGroupAudio) + if (AppConfig.bookGroupAudioShow) { + bookGroups.add(0, AppConst.bookGroupAudio) } - if (getPrefBoolean("bookGroupLocal", true)) { - bookGroups.add(1, AppConst.bookGroupLocal) + if (AppConfig.bookGroupLocalShow) { + bookGroups.add(0, AppConst.bookGroupLocal) + } + if (AppConfig.bookGroupAllShow) { + bookGroups.add(0, AppConst.bookGroupAll) } bookshelfAdapter.notifyDataSetChanged() } @@ -200,13 +203,9 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b }.show().applyTint() } - override fun onTabReselected(tab: TabLayout.Tab?) { + override fun onTabReselected(tab: TabLayout.Tab?) = Unit - } - - override fun onTabUnselected(tab: TabLayout.Tab?) { - - } + override fun onTabUnselected(tab: TabLayout.Tab?) = Unit override fun onTabSelected(tab: TabLayout.Tab?) { tab?.position?.let { 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 49cadb56c..43375288e 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 @@ -7,7 +7,6 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment @@ -57,9 +56,6 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), } initRecyclerView() upRecyclerData() - observeEvent(EventBus.UP_BOOK) { - booksAdapter.notification(it) - } } private fun initRecyclerView() { @@ -78,21 +74,6 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), booksAdapter = BooksAdapterGrid(requireContext(),this) } rv_bookshelf.adapter = booksAdapter - booksAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - super.onItemRangeInserted(positionStart, itemCount) - if (positionStart == 0) { - rv_bookshelf.scrollToPosition(0) - } - } - - override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { - super.onItemRangeMoved(fromPosition, toPosition, itemCount) - if (toPosition == 0) { - rv_bookshelf.scrollToPosition(0) - } - } - }) } private fun upRecyclerData() { @@ -111,7 +92,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), else -> list.sortedByDescending { it.durChapterTime } } val diffResult = DiffUtil - .calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books)) + .calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), books)) booksAdapter.setItems(books, diffResult) }) } @@ -135,4 +116,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/explore/ExploreFragment.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt index 78cdd141a..c8f93fc64 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt @@ -17,14 +17,15 @@ import io.legado.app.base.VMBaseFragment import io.legado.app.data.entities.BookSource import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.ui.book.explore.ExploreShowActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity -import io.legado.app.ui.explore.ExploreShowActivity import io.legado.app.utils.getViewModel import io.legado.app.utils.splitNotBlank import io.legado.app.utils.startActivity import kotlinx.android.synthetic.main.fragment_find_book.* import kotlinx.android.synthetic.main.view_search.* import kotlinx.android.synthetic.main.view_title_bar.* +import java.text.Collator class ExploreFragment : VMBaseFragment(R.layout.fragment_find_book), @@ -119,9 +120,10 @@ class ExploreFragment : VMBaseFragment(R.layout.fragment_find_ private fun upGroupsMenu() { groupsMenu?.let { subMenu -> subMenu.removeGroup(R.id.menu_group_text) - groups.forEach { - subMenu.add(R.id.menu_group_text, Menu.NONE, Menu.NONE, it) - } + groups.sortedWith(Collator.getInstance(java.util.Locale.CHINESE)) + .forEach { + subMenu.add(R.id.menu_group_text, Menu.NONE, Menu.NONE, it) + } } } 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 e5be539c5..fd0a591b6 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 @@ -141,8 +141,8 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi } private fun observeReplaceRuleData(key: String? = null) { - replaceRuleLiveData?.removeObservers(this) dataInit = false + replaceRuleLiveData?.removeObservers(this) replaceRuleLiveData = if (key.isNullOrEmpty()) { App.db.replaceRuleDao().liveDataAll() } else { @@ -180,7 +180,12 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_local -> FilePicker - .selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json")) + .selectFile( + this, + importRequestCode, + type = "text/*", + allowExtensions = arrayOf("txt", "json") + ) } return super.onCompatOptionsItemSelected(item) } @@ -213,7 +218,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(",")) } @@ -304,22 +310,27 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi } override fun update(vararg rule: ReplaceRule) { + setResult(Activity.RESULT_OK) viewModel.update(*rule) } override fun delete(rule: ReplaceRule) { + setResult(Activity.RESULT_OK) viewModel.delete(rule) } override fun edit(rule: ReplaceRule) { + setResult(Activity.RESULT_OK) ReplaceEditDialog.show(supportFragmentManager, rule.id) } override fun toTop(rule: ReplaceRule) { + setResult(Activity.RESULT_OK) viewModel.toTop(rule) } override fun upOrder() { + setResult(Activity.RESULT_OK) viewModel.upOrder() } } \ No newline at end of file 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/replacerule/edit/ReplaceEditViewModel.kt b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt index 722deb738..e4a0d443c 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt @@ -23,7 +23,7 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application bundle.getString("pattern")?.let { pattern -> val isRegex = bundle.getBoolean("isRegex") replaceRuleData.postValue( - ReplaceRule(pattern = pattern, isRegex = isRegex) + ReplaceRule(name = pattern, pattern = pattern, isRegex = isRegex) ) } } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt index 3b53a6475..31ac9cdda 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt @@ -60,7 +60,7 @@ class RssArticlesActivity : VMBaseActivity(R.layout.activi R.id.menu_clear -> { viewModel.url?.let { refresh_progress_bar.isAutoLoading = true - viewModel.clear() + viewModel.clearArticles() } } } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt index 42bc5c2d4..ccfff44df 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt @@ -106,7 +106,7 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application } } - fun clear() { + fun clearArticles() { execute { url?.let { App.db.rssArticleDao().delete(it) 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..79ea599d1 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 @@ -1,37 +1,42 @@ package io.legado.app.ui.rss.read import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.res.Configuration import android.os.Bundle -import android.view.KeyEvent -import android.view.Menu -import android.view.MenuItem -import android.webkit.WebResourceRequest -import android.webkit.WebSettings -import android.webkit.WebView -import android.webkit.WebViewClient +import android.view.* +import android.webkit.* +import androidx.core.view.size import androidx.lifecycle.Observer import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.lib.theme.DrawableUtils import io.legado.app.lib.theme.primaryTextColor -import io.legado.app.utils.NetworkUtils -import io.legado.app.utils.getViewModel -import io.legado.app.utils.openUrl +import io.legado.app.ui.filechooser.FileChooserDialog +import io.legado.app.ui.filechooser.FilePicker +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_rss_read.* import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils import org.jetbrains.anko.share +import org.jetbrains.anko.toast import org.jsoup.Jsoup import org.jsoup.safety.Whitelist + class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read), + FileChooserDialog.CallBack, ReadRssViewModel.CallBack { override val viewModel: ReadRssViewModel get() = getViewModel(ReadRssViewModel::class.java) - + private val savePathRequestCode = 132 + private val imagePathKey = "" private var starMenuItem: MenuItem? = null private var ttsMenuItem: MenuItem? = null + private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null + private var webPic: String? = null override fun onActivityCreated(savedInstanceState: Bundle?) { viewModel.callBack = this @@ -41,6 +46,20 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r viewModel.initData(intent) } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + when (newConfig.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } + Configuration.ORIENTATION_PORTRAIT -> { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) + } + } + } + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.rss_read, menu) return super.onCompatCreateOptionsMenu(menu) @@ -65,6 +84,20 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } private fun initWebView() { + web_view.webChromeClient = object : WebChromeClient() { + override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR + ll_view.invisible() + custom_web_view.addView(view) + customWebViewCallback = callback + } + + override fun onHideCustomView() { + custom_web_view.removeAllViews() + ll_view.visible() + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + } web_view.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?, @@ -95,6 +128,30 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r domStorageEnabled = true allowContentAccess = true } + web_view.setOnLongClickListener { + val hitTestResult = web_view.hitTestResult + if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || + hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE + ) { + hitTestResult.extra?.let { + webPic = it + saveImage() + return@setOnLongClickListener true + } + } + return@setOnLongClickListener false + } + } + + private fun saveImage() { + FilePicker.selectFolder(this, savePathRequestCode, getString(R.string.save_image)) { + val path = ACache.get(this).getAsString(imagePathKey) + if (path.isNullOrEmpty()) { + toast(R.string.no_default_path) + } else { + viewModel.saveImage(webPic, path) + } + } } @SuppressLint("SetJavaScriptEnabled") @@ -113,7 +170,6 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r url )//不想用baseUrl进else } else { - //webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码 web_view.loadDataWithBaseURL( null, html, @@ -175,7 +231,10 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r event?.let { when (keyCode) { KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && web_view.canGoBack()) { - if (web_view.copyBackForwardList().size > 1) { + if (custom_web_view.size > 0) { + customWebViewCallback?.onCustomViewHidden() + return true + } else if (web_view.copyBackForwardList().size > 1) { web_view.goBack() return true } @@ -203,4 +262,27 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } } + override fun onFilePicked(requestCode: Int, currentPath: String) { + when (requestCode) { + savePathRequestCode -> { + ACache.get(this).put(imagePathKey, currentPath) + viewModel.saveImage(webPic, currentPath) + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + savePathRequestCode -> data?.data?.let { + onFilePicked(requestCode, it.toString()) + } + } + } + + override fun onDestroy() { + super.onDestroy() + web_view.destroy() + } + } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index 17b66f41c..6d90fd181 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -2,19 +2,31 @@ package io.legado.app.ui.rss.read import android.app.Application import android.content.Intent +import android.net.Uri import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener +import android.util.Base64 +import android.webkit.URLUtil +import androidx.documentfile.provider.DocumentFile 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.AppConst import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource +import io.legado.app.help.http.HttpHelper import io.legado.app.model.Rss import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.utils.DocumentUtils +import io.legado.app.utils.FileUtils +import io.legado.app.utils.isContentPath +import io.legado.app.utils.writeBytes import kotlinx.coroutines.launch +import java.io.File import java.util.* + class ReadRssViewModel(application: Application) : BaseViewModel(application), TextToSpeech.OnInitListener { var callBack: CallBack? = null @@ -85,6 +97,37 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } } + fun saveImage(webPic: String?, path: String) { + webPic ?: return + execute { + val fileName = "${AppConst.fileNameFormat.format(Date(System.currentTimeMillis()))}.jpg" + webData2bitmap(webPic)?.let { biteArray -> + if (path.isContentPath()) { + val uri = Uri.parse(path) + DocumentFile.fromTreeUri(context, uri)?.let { doc -> + DocumentUtils.createFileIfNotExist(doc, fileName) + ?.writeBytes(context, biteArray) + } + } else { + val file = FileUtils.createFileIfNotExist(File(path), fileName) + file.writeBytes(biteArray) + } + } ?: throw Throwable("NULL") + }.onError { + toast("保存图片失败:${it.localizedMessage}") + }.onSuccess { + toast("保存成功") + } + } + + private suspend fun webData2bitmap(data: String): ByteArray? { + return if (URLUtil.isValidUrl(data)) { + HttpHelper.simpleGetByteAsync(data) + } else { + Base64.decode(data.split(",").toTypedArray()[1], Base64.DEFAULT) + } + } + fun clHtml(content: String): String { return if (content.contains("
书名:${book.name}