diff --git a/.gitignore b/.gitignore
index 5f0e23e18..2543443bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,6 @@
/captures
.externalNativeBuild
/release
-/tmp
\ No newline at end of file
+/tmp
+node_modules/
+package-lock.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..ace01cdff
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,4 @@
+# 1.0.0 (2020-02-09)
+
+
+
diff --git a/README.md b/README.md
index d65e3dbdc..ac01e9389 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,9 @@
# legado
+
+[![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://gedoor.github.io/MyBookshelf/disclaimer.html
diff --git a/app/build.gradle b/app/build.gradle
index 18d4ac8ed..585099975 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -20,13 +20,15 @@ def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], projec
android {
compileSdkVersion 29
signingConfigs {
- myConfig {
- storeFile file(RELEASE_STORE_FILE)
- storePassword RELEASE_STORE_PASSWORD
- keyAlias RELEASE_KEY_ALIAS
- keyPassword RELEASE_KEY_PASSWORD
- v1SigningEnabled true
- v2SigningEnabled true
+ if (project.hasProperty("RELEASE_STORE_FILE")) {
+ myConfig {
+ storeFile file(RELEASE_STORE_FILE)
+ storePassword RELEASE_STORE_PASSWORD
+ keyAlias RELEASE_KEY_ALIAS
+ keyPassword RELEASE_KEY_PASSWORD
+ v1SigningEnabled true
+ v2SigningEnabled true
+ }
}
}
defaultConfig {
@@ -38,15 +40,27 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
project.ext.set("archivesBaseName", name + "_" + version)
multiDexEnabled true
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments = [
+ "room.incremental" : "true",
+ "room.expandProjection": "true"
+ ]
+ }
+ }
}
buildTypes {
release {
- signingConfig signingConfigs.myConfig
+ if (project.hasProperty("RELEASE_STORE_FILE")) {
+ signingConfig signingConfigs.myConfig
+ }
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
- signingConfig signingConfigs.myConfig
+ if (project.hasProperty("RELEASE_STORE_FILE")) {
+ signingConfig signingConfigs.myConfig
+ }
applicationIdSuffix '.debug'
versionNameSuffix 'debug'
minifyEnabled false
@@ -88,22 +102,23 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//fireBase
- implementation 'com.google.firebase:firebase-core:17.2.1'
+ implementation 'com.google.firebase:firebase-core:17.2.2'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
//androidX
- implementation 'androidx.core:core-ktx:1.2.0-rc01'
+ implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.media:media:1.1.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
- implementation 'com.google.android.material:material:1.2.0-alpha03'
+ implementation 'com.google.android.material:material:1.1.0'
implementation 'com.google.android:flexbox:1.1.0'
+ implementation 'com.google.code.gson:gson:2.8.5'
//lifecycle
- def lifecycle_version = '2.1.0'
+ def lifecycle_version = '2.2.0'
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@@ -129,13 +144,12 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
//规则相关
- implementation 'com.google.code.gson:gson:2.8.5'
implementation 'org.jsoup:jsoup:1.12.1'
implementation 'cn.wanghaomiao:JsoupXpath:2.3.2'
implementation 'com.jayway.jsonpath:json-path:2.4.0'
- //JS
- implementation 'com.github.gedoor:rhino-android:1.3'
+ //JS rhino
+ implementation 'com.github.gedoor:rhino-android:1.4'
//Retrofit
implementation 'com.squareup.okhttp3:logging-interceptor:4.1.0'
@@ -161,6 +175,9 @@ dependencies {
//MarkDown
implementation 'ru.noties.markwon:core:3.0.2'
+ // 转换繁体
+ implementation 'com.github.houbb:opencc4j:1.4.0'
+
}
apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bc92ac136..a8ebd70da 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -22,14 +22,53 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
- android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme.Light"
- android:requestLegacyExternalStorage="false"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:name=".ui.welcome.Launcher3"
+ android:icon="@mipmap/launcher3"
+ android:enabled="false">
@@ -39,22 +78,43 @@
android:resource="@xml/shortcuts"
android:launchMode="singleTask" />
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
-
@@ -95,13 +150,12 @@
-
+
+
+
diff --git a/app/src/main/assets/readConfig.json b/app/src/main/assets/readConfig.json
index ee546dc37..c26c81554 100644
--- a/app/src/main/assets/readConfig.json
+++ b/app/src/main/assets/readConfig.json
@@ -1,72 +1,62 @@
[
{
- "bgStr": "羊皮纸2.jpg",
- "bgType": 1,
+ "bgStr": "#EBD9BB",
+ "bgStrNight": "#1E2021",
+ "textColor": "#63543C",
+ "textColorNight": "#DCDFE1",
+ "bgType": 0,
+ "bgTypeNight": 0,
"darkStatusIcon": true,
- "textColor": "#5E432E",
"textSize": 24,
"letterSpacing": 0,
- "lineSpacingExtra": 10,
- "lineSpacingMultiplier": 1.2,
- "paddingLeft": 16,
- "paddingRight": 16,
- "paddingTop": 0,
- "paddingBottom": 0
+ "lineSpacingExtra": 10
},
{
- "bgStr": "#C6BAA1",
+ "bgStr": "#DDC090",
+ "bgStrNight": "#3C3F43",
+ "textColor": "#3E3422",
+ "textColorNight": "#DCDFE1",
"bgType": 0,
+ "bgTypeNight": 0,
"darkStatusIcon": true,
- "textColor": "#5E432E",
"textSize": 24,
"letterSpacing": 0,
- "lineSpacingExtra": 10,
- "lineSpacingMultiplier": 1.2,
- "paddingLeft": 16,
- "paddingRight": 16,
- "paddingTop": 0,
- "paddingBottom": 0
+ "lineSpacingExtra": 10
},
{
- "bgStr": "#015A86",
+ "bgStr": "#C2D8AA",
+ "bgStrNight": "#3C3F43",
+ "textColor": "#596C44",
+ "textColorNight": "#88C16F",
"bgType": 0,
+ "bgTypeNight": 0,
"darkStatusIcon": false,
- "textColor": "#FFFFFF",
"textSize": 24,
"letterSpacing": 0,
- "lineSpacingExtra": 10,
- "lineSpacingMultiplier": 1.2,
- "paddingLeft": 16,
- "paddingRight": 16,
- "paddingTop": 0,
- "paddingBottom": 0
+ "lineSpacingExtra": 10
},
{
- "bgStr": "宁静夜色",
- "bgType": 1,
+ "bgStr": "#DBB8E2",
+ "bgStrNight": "#3C3F43",
+ "textColor": "#68516C",
+ "textColorNight": "#F6AEAE",
+ "bgType": 0,
+ "bgTypeNight": 0,
"darkStatusIcon": false,
- "textColor": "#adadad",
"textSize": 24,
"letterSpacing": 0,
- "lineSpacingExtra": 10,
- "lineSpacingMultiplier": 1.2,
- "paddingLeft": 16,
- "paddingRight": 16,
- "paddingTop": 0,
- "paddingBottom": 0
+ "lineSpacingExtra": 10
},
{
- "bgStr": "#000000",
+ "bgStr": "#ABCEE0",
+ "bgStrNight": "#3C3F43",
+ "textColor": "#3D4C54",
+ "textColorNight": "#90BFF5",
"bgType": 0,
+ "bgTypeNight": 0,
"darkStatusIcon": false,
- "textColor": "#adadad",
"textSize": 24,
"letterSpacing": 0,
- "lineSpacingExtra": 10,
- "lineSpacingMultiplier": 1.2,
- "paddingLeft": 16,
- "paddingRight": 16,
- "paddingTop": 0,
- "paddingBottom": 0
+ "lineSpacingExtra": 10
}
]
\ No newline at end of file
diff --git a/app/src/main/assets/txtChapterRule.json b/app/src/main/assets/txtChapterRule.json
deleted file mode 100644
index b035fccb5..000000000
--- a/app/src/main/assets/txtChapterRule.json
+++ /dev/null
@@ -1,38 +0,0 @@
-[
- {
- "enable": true,
- "name": "默认正则1",
- "rule": "^(.{0,8})(第)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([章节卷集部篇回场])(.{0,30})$",
- "serialNumber": 0
- },
- {
- "enable": true,
- "name": "默认正则2",
- "rule": "^([0-9]{1,5})([\\,\\.,-])(.{1,20})$",
- "serialNumber": 1
- },
- {
- "enable": true,
- "name": "默认正则3",
- "rule": "^(\\s{0,4})([\\(【《]?(卷)?)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([\\.:: \f\t])(.{0,30})$",
- "serialNumber": 2
- },
- {
- "enable": true,
- "name": "默认正则4",
- "rule": "^(\\s{0,4})([\\((【《])(.{0,30})([\\))】》])(\\s{0,2})$",
- "serialNumber": 3
- },
- {
- "enable": true,
- "name": "默认正则5",
- "rule": "^(\\s{0,4})(正文)(.{0,20})$",
- "serialNumber": 4
- },
- {
- "enable": true,
- "name": "默认正则6",
- "rule": "^(.{0,4})(Chapter|chapter)(\\s{0,4})([0-9]{1,4})(.{0,30})$",
- "serialNumber": 5
- }
-]
\ No newline at end of file
diff --git a/app/src/main/assets/txtTocRule.json b/app/src/main/assets/txtTocRule.json
new file mode 100644
index 000000000..1b0651e76
--- /dev/null
+++ b/app/src/main/assets/txtTocRule.json
@@ -0,0 +1,56 @@
+[
+ {
+ "enable": true,
+ "name": "数字 分隔符 标题名称",
+ "rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$",
+ "serialNumber": 0
+ },
+ {
+ "enable": true,
+ "name": "特殊符号 序号 标题",
+ "rule": "^[ \\t]{0,4}[\\[〈「『〖〔《(【\\(]?[第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[\\.:: \f\t].{0,30}$",
+ "serialNumber": 1
+ },
+ {
+ "enable": true,
+ "name": "特殊符号 标题",
+ "rule": "^[ \\t]{0,4}[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$",
+ "serialNumber": 2
+ },
+ {
+ "enable": false,
+ "name": "特殊符号 标题(不匹配空白字符)",
+ "rule": "(?<=\\s)[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$",
+ "serialNumber": 3
+ },
+ {
+ "enable": true,
+ "name": "Chapter/Section/Part 序号 标题",
+ "rule": "^[ \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art)\\s{0,4}\\d{1,4}.{0,30}$",
+ "serialNumber": 4
+ },
+ {
+ "enable": true,
+ "name": "正文 标题/序号",
+ "rule": "^[ \\t]{0,4}正文\\s{1,4}.{0,20}$",
+ "serialNumber": 5
+ },
+ {
+ "enable": true,
+ "name": "目录",
+ "rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
+ "serialNumber": 6
+ },
+ {
+ "enable": false,
+ "name": "目录(不匹配行前空白,正文不出现双标题)",
+ "rule": "^(?<=\\s)(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
+ "serialNumber": 7
+ },
+ {
+ "enable": false,
+ "name": "目录(古典小说备用)",
+ "rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|场(?![和合比电是])|篇(?!张))).{0,30}$",
+ "serialNumber": 8
+ }
+]
diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md
index 06c0bcaac..697c57b4b 100644
--- a/app/src/main/assets/updateLog.md
+++ b/app/src/main/assets/updateLog.md
@@ -1,8 +1,114 @@
## 更新日志
-
* 旧版数据导入教程:
-* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】,提示存储权限,选择允许即可导入成功。
-* 注意:由于安卓10更改了权限策略,还需要给「允许安装其他应用」的权限才能导入源。MIUI11也需要此权限。
+* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
+
+**2020/02/21**
+* 重写了阅读界面,实现了段距调整,两端对齐,页眉页脚调整
+* 选择文本暂不可用,滚动暂不可用,仿真翻页还有问题
+
+**2020/02/19**
+* 导出功能完成
+* 其它一些优化,仿真翻页有点问题,还没找到问题所在
+
+**2020/02/15**
+* 修复bug
+* 添加一个图标
+* 阅读界面文本选择开关
+* 书源管理发现开启关闭标志
+
+**2020/02/14**
+* 书籍分组支持一本书籍在多个分组,既可以在追更,又可以在玄幻
+* 搜索界面限制刷新频率,每秒刷新一次
+* 添加一种图标,2.0的老图标
+
+**2020/02/13**
+* 修复BUG
+* 优化已下载检测,解决目录卡顿
+* 添加切换图标
+
+**2020/02/12**
+* 修复bug
+* 优化,网页编码优先使用书源配置的编码
+* 其它一些优化
+* 添加简繁转换
+
+**2020/02/10**
+* 多页目录并行获取解析
+* 优化详情页
+* 优化换源页面,添加换源是否加载目录配置
+* 换源顺序按书源顺序排列
+
+**2020/02/09**
+* 优化书源管理,备份恢复
+* 主题色修改,底部操作栏更明显
+
+**2020/02/08**
+* 书架分组调整顺序后,书架及时变动
+
+**2020/02/07**
+* 优化
+* 书源校验
+* 书架整理
+
+**2020/02/05**
+* 修复bug
+* Rss收藏功能完成
+* Rss已读标记不会再丢失
+
+**2020/02/04**
+* 主界面切换时自动隐藏键盘
+* 添加本地书籍完成,解析txt文件完成,本地txt可以看了
+* 封面换源,书籍信息界面点击封面弹出封面换源界面
+* 默认封面绘制书名和作者
+* 修复在线朗读遇到单独标点,停止朗读的问题
+
+**2020/02/02**
+* merged commit e584606, rss修复BaseURL模式下部分图片无法加载, 修复可能出现的乱码
+* 菜单添加网址功能完成
+
+**2020/01/31**
+* 修复搜索闪退,因为默认线程为0了
+
+**2020/01/30**
+* 优化缓存文件夹选择,不再需要存储权限
+* 修复替换净化导入报错的bug
+
+**2020/01/27**
+* 添加根据系统主题切换夜间模式
+* 合并Modificator提交的代码
+
+**2020/01/26**
+* 修复bug
+* 未加入书架可查看目录
+
+**2020/01/24**
+* 添加线程数配置
+* 记住退出时的书架
+* 添加屏幕超时配置
+
+**2020/01/11**
+* RSS阅读界面添加朗读功能
+* 其它一些优化
+* 合并KKL369提交的代码,重写LinearLayoutManager,修复书籍目录模糊搜索后scrollToPosition在可见范围不置顶
+
+**2020/01/10**
+* 合并KKL369提交的代码
+
+**2020/01/08**
+* 导入本地源不再需要存储权限
+
+**2020/01/07**
+* 修复备份问题
+* 设置背景不再需要存储权限
+
+**2020/01/06**
+* 适配Android 10 权限
+* 备份恢复不再需要存储权限
+
+**2020/01/03**
+* 适配Android 10 权限
+* 导入旧版本配置不在需要存储权限
+* 选择字体不在需要存储权限
**2019/12/30**
* 修改书源调试
@@ -51,13 +157,13 @@
* 最近感冒了,发热咳嗽还没好,继续咸鱼
**2019/12/12**
-* [fix]web服务停止问题
+* web服务停止问题
* 默认显示沉浸式状态栏
**2019/12/09**
-* [add]其他设置->清理缓存
-* [mod]调整深色模式配色,预适配Android10
-* [mod]启用web服务
+* 其他设置->清理缓存
+* 调整深色模式配色,预适配Android10
+* 启用web服务
**2019/12/03**
* from Celeter:
diff --git a/app/src/main/assets/web/bookshelf.js b/app/src/main/assets/web/bookshelf.js
index 22c8ff87e..c182f49fa 100644
--- a/app/src/main/assets/web/bookshelf.js
+++ b/app/src/main/assets/web/bookshelf.js
@@ -45,15 +45,15 @@ var init = () => {
books.forEach(book => {
let bookDiv = $c("div");
let img = $c("img");
- img.src = book.bookInfoBean.coverUrl || randomImg;
+ img.src = book.coverUrl || randomImg;
img.setAttribute("data-series-num", book.serialNumber);
bookDiv.appendChild(img);
bookDiv.innerHTML += `
- 书名: | ${book.bookInfoBean.name} |
- 作者: | ${book.bookInfoBean.author} |
- 阅读: | ${book.durChapterName} ${formatTime(book.finalDate)} |
- 更新: | ${book.lastChapterName} ${formatTime(book.finalRefreshData)} |
- 来源: | ${book.bookInfoBean.origin} |
+ 书名: | ${book.name} |
+ 作者: | ${book.author} |
+ 阅读: | ${book.durChapterTitle} ${formatTime(book.durChapterTime)} |
+ 更新: | ${book.latestChapterTitle} ${formatTime(book.latestChapterTime)} |
+ 来源: | ${book.origin} |
`;
$('#books').appendChild(bookDiv);
});
@@ -62,19 +62,19 @@ var init = () => {
$('#allcontent').classList.add("read");
var book = books[bookImg.getAttribute("data-series-num")];
$("#info").innerHTML = `
- 来源:${book.bookInfoBean.origin}
- 书名:${book.bookInfoBean.name}
- 作者:${book.bookInfoBean.author}
+ 来源:${book.origin}
+ 书名:${book.name}
+ 作者:${book.author}
阅读章节:${book.durChapterName}
- 阅读时间:${formatTime(book.finalDate)}
- 最新章节:${book.lastChapterName}
- 检查时间:${formatTime(book.finalRefreshData)}
- 简介:${book.bookInfoBean.introduce.trim().replace(/\n/g, "
")}
`;
+ 阅读时间:${formatTime(book.durChapterTime)}
+ 最新章节:${book.latestChapterTitle}
+ 检查时间:${formatTime(book.lastCheckTime)}
+ 简介:${book.intro.trim().replace(/\n/g, "
")}
`;
window.location.hash = "";
window.location.hash = "#info";
$("#content").innerHTML = "章节列表加载中...";
$("#chapter").innerHTML = "";
- fetch(apiAddress("getChapterList", book.noteUrl), { mode: "cors" })
+ fetch(apiAddress("getChapterList", book.bookUrl), { 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 2c47adb7f..452e521f3 100644
--- a/app/src/main/assets/web/index.html
+++ b/app/src/main/assets/web/index.html
@@ -294,6 +294,7 @@
(?i) 前缀表示忽略大小写
代码在线运行工具
+ 阅读书架(测试)
diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt
index ee3e4f5f6..7310dcef8 100644
--- a/app/src/main/java/io/legado/app/App.kt
+++ b/app/src/main/java/io/legado/app/App.kt
@@ -15,13 +15,12 @@ import io.legado.app.constant.AppConst.channelIdReadAloud
import io.legado.app.constant.AppConst.channelIdWeb
import io.legado.app.data.AppDatabase
import io.legado.app.help.ActivityHelp
+import io.legado.app.help.AppConfig
import io.legado.app.help.CrashHandler
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ThemeStore
-import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefInt
-import io.legado.app.utils.isNightTheme
@Suppress("DEPRECATION")
class App : Application() {
@@ -50,7 +49,7 @@ class App : Application() {
}
if (!ThemeStore.isConfigured(this, versionCode)) applyTheme()
- initNightTheme()
+ initNightMode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId()
@@ -67,13 +66,13 @@ class App : Application() {
* 更新主题
*/
fun applyTheme() {
- if (isNightTheme) {
+ if (AppConfig.isNightTheme) {
ThemeStore.editTheme(this)
.primaryColor(
- getPrefInt("colorPrimaryNight", getCompatColor(R.color.shine_color))
+ getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600))
)
.accentColor(
- getPrefInt("colorAccentNight", getCompatColor(R.color.lightBlue_color))
+ getPrefInt("colorAccentNight", getCompatColor(R.color.md_brown_800))
)
.backgroundColor(
getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color))
@@ -82,31 +81,32 @@ class App : Application() {
} else {
ThemeStore.editTheme(this)
.primaryColor(
- getPrefInt("colorPrimary", getCompatColor(R.color.md_grey_100))
+ getPrefInt("colorPrimary", getCompatColor(R.color.md_indigo_800))
)
.accentColor(
- getPrefInt("colorAccent", getCompatColor(R.color.lightBlue_color))
+ getPrefInt("colorAccent", getCompatColor(R.color.md_red_600))
)
.backgroundColor(
getPrefInt("colorBackground", getCompatColor(R.color.md_grey_100))
)
.apply()
}
- ChapterProvider.upReadAloudSpan()
+// ChapterProvider.upReadAloudSpan()
}
fun applyDayNight() {
ReadBookConfig.upBg()
applyTheme()
- initNightTheme()
+ initNightMode()
}
- private fun initNightTheme() {
- val targetMode = if (isNightTheme) {
- AppCompatDelegate.MODE_NIGHT_YES
- } else {
- AppCompatDelegate.MODE_NIGHT_NO
- }
+ private fun initNightMode() {
+ val targetMode =
+ if (AppConfig.isNightTheme) {
+ AppCompatDelegate.MODE_NIGHT_YES
+ } else {
+ AppCompatDelegate.MODE_NIGHT_NO
+ }
AppCompatDelegate.setDefaultNightMode(targetMode)
}
diff --git a/app/src/main/java/io/legado/app/README.md b/app/src/main/java/io/legado/app/README.md
new file mode 100644
index 000000000..b23d4196d
--- /dev/null
+++ b/app/src/main/java/io/legado/app/README.md
@@ -0,0 +1,12 @@
+## 文件结构介绍
+
+* base 基类
+* constant 常量
+* data 数据
+* help 帮助
+* lib 库
+* model 解析
+* receiver 广播侦听
+* service 服务
+* ui 界面
+* web web服务
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt
index 2517cc616..e0d4713d7 100644
--- a/app/src/main/java/io/legado/app/base/BaseActivity.kt
+++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt
@@ -53,8 +53,11 @@ abstract class BaseActivity(
}
override fun onMenuOpened(featureId: Int, menu: Menu?): Boolean {
- menu?.applyOpenTint(this)
- return super.onMenuOpened(featureId, menu)
+ menu?.let {
+ menu.applyOpenTint(this)
+ return super.onMenuOpened(featureId, menu)
+ }
+ return true
}
open fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
diff --git a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
new file mode 100644
index 000000000..8bb119625
--- /dev/null
+++ b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
@@ -0,0 +1,24 @@
+package io.legado.app.base
+
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlin.coroutines.CoroutineContext
+
+abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
+ override val coroutineContext: CoroutineContext
+ get() = job + Dispatchers.Main
+ private lateinit var job: Job
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ job = Job()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ job.cancel()
+ }
+}
\ 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 d870939d0..c4b712349 100644
--- a/app/src/main/java/io/legado/app/base/BaseFragment.kt
+++ b/app/src/main/java/io/legado/app/base/BaseFragment.kt
@@ -32,6 +32,14 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID),
return super.onCreateView(inflater, container, savedInstanceState)
}
+ 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()
@@ -52,6 +60,8 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID),
}
}
+ open fun observeLiveBus() {
+ }
open fun onCompatCreateOptionsMenu(menu: Menu) {
}
diff --git a/app/src/main/java/io/legado/app/base/BaseService.kt b/app/src/main/java/io/legado/app/base/BaseService.kt
index c67acb95c..163ac09ec 100644
--- a/app/src/main/java/io/legado/app/base/BaseService.kt
+++ b/app/src/main/java/io/legado/app/base/BaseService.kt
@@ -3,17 +3,27 @@ package io.legado.app.base
import android.app.Service
import android.content.Intent
import android.os.IBinder
+import io.legado.app.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
+import kotlin.coroutines.CoroutineContext
abstract class BaseService : Service(), CoroutineScope by MainScope() {
+ fun execute(
+ scope: CoroutineScope = this,
+ context: CoroutineContext = Dispatchers.IO,
+ block: suspend CoroutineScope.() -> T
+ ): Coroutine {
+ return Coroutine.async(scope, context) { block() }
+ }
+
override fun onBind(intent: Intent?): IBinder? {
return null
}
-
override fun onDestroy() {
super.onDestroy()
cancel()
diff --git a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
index 679ba6f89..25e6b25c4 100644
--- a/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
+++ b/app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
@@ -5,6 +5,7 @@ import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.util.*
@@ -15,13 +16,17 @@ import java.util.*
*
* 通用的adapter 可添加header,footer,以及不同类型item
*/
-abstract class CommonRecyclerAdapter- (protected val context: Context) : RecyclerView.Adapter() {
+abstract class CommonRecyclerAdapter
- (protected val context: Context) :
+ RecyclerView.Adapter() {
- constructor(context: Context, vararg delegates: Pair>) : this(context) {
+ constructor(context: Context, vararg delegates: ItemViewDelegate
- ) : this(context) {
addItemViewDelegates(*delegates)
}
- constructor(context: Context, vararg delegates: ItemViewDelegate
- ) : this(context) {
+ constructor(
+ context: Context,
+ vararg delegates: Pair>
+ ) : this(context) {
addItemViewDelegates(*delegates)
}
@@ -122,7 +127,7 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
}
}
- fun setItems(items: List
- ?, notify: Boolean = true) {
+ fun setItems(items: List
- ?) {
synchronized(lock) {
if (this.items.isNotEmpty()) {
this.items.clear()
@@ -130,9 +135,19 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
if (items != null) {
this.items.addAll(items)
}
- if (notify) {
- notifyDataSetChanged()
+ notifyDataSetChanged()
+ }
+ }
+
+ fun setItems(items: List
- ?, diffResult: DiffUtil.DiffResult) {
+ synchronized(lock) {
+ if (this.items.isNotEmpty()) {
+ this.items.clear()
+ }
+ if (items != null) {
+ this.items.addAll(items)
}
+ diffResult.dispatchUpdatesTo(this)
}
}
@@ -236,7 +251,11 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
synchronized(lock) {
val size = getActualItemCount()
if (fromPosition in 0 until size && toPosition in 0 until size) {
- notifyItemRangeChanged(fromPosition + getHeaderCount(), toPosition - fromPosition + 1, payloads)
+ notifyItemRangeChanged(
+ fromPosition + getHeaderCount(),
+ toPosition - fromPosition + 1,
+ payloads
+ )
}
}
}
@@ -271,7 +290,8 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
return footerItems?.size() ?: 0
}
- fun getItem(position: Int): ITEM? = if (position in 0 until items.size) items[position] else null
+ fun getItem(position: Int): ITEM? =
+ if (position in 0 until items.size) items[position] else null
fun getItems(): List
- = items
@@ -294,7 +314,9 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
return when {
isHeader(position) -> TYPE_HEADER_VIEW + position
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount()
- else -> getItem(getActualPosition(position))?.let { getItemViewType(it, getActualPosition(position)) } ?: 0
+ else -> getItem(getActualPosition(position))?.let {
+ getItemViewType(it, getActualPosition(position))
+ } ?: 0
}
}
@@ -309,7 +331,16 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
}
else -> {
- val holder = ItemViewHolder(inflater.inflate(itemDelegates.getValue(viewType).layoutId, parent, false))
+ val holder = ItemViewHolder(
+ inflater.inflate(
+ itemDelegates.getValue(viewType).layoutId,
+ parent,
+ false
+ )
+ )
+
+ itemDelegates.getValue(viewType)
+ .registerListener(holder)
if (itemClickListener != null) {
holder.itemView.setOnClickListener {
@@ -336,7 +367,11 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
}
- final override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList) {
+ final override fun onBindViewHolder(
+ holder: ItemViewHolder,
+ position: Int,
+ payloads: MutableList
+ ) {
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
getItem(holder.layoutPosition - getHeaderCount())?.let {
itemDelegates.getValue(getItemViewType(holder.layoutPosition))
@@ -385,10 +420,6 @@ abstract class CommonRecyclerAdapter
- (protected val context: Context) : Rec
}
private fun addAnimation(holder: ItemViewHolder) {
- if (itemAnimation == null) {
- itemAnimation = ItemAnimation.create().enabled(true)
- }
-
itemAnimation?.let {
if (it.itemAnimEnabled) {
if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) {
diff --git a/app/src/main/java/io/legado/app/base/adapter/ItemViewDelegate.kt b/app/src/main/java/io/legado/app/base/adapter/ItemViewDelegate.kt
index 2ac1090f2..866a53b6f 100644
--- a/app/src/main/java/io/legado/app/base/adapter/ItemViewDelegate.kt
+++ b/app/src/main/java/io/legado/app/base/adapter/ItemViewDelegate.kt
@@ -9,6 +9,17 @@ import android.content.Context
*/
abstract class ItemViewDelegate
- (protected val context: Context, val layoutId: Int) {
+ /**
+ * 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,
+ * 使用getItem(holder.layoutPosition)来获取item,
+ * 或者使用registerListener(holder: ItemViewHolder, position: Int)
+ */
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList)
+
+ /**
+ * 注册事件
+ */
+ abstract fun registerListener(holder: ItemViewHolder)
+
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/base/adapter/SimpleRecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/SimpleRecyclerAdapter.kt
index c65cfabf4..81c831ff4 100644
--- a/app/src/main/java/io/legado/app/base/adapter/SimpleRecyclerAdapter.kt
+++ b/app/src/main/java/io/legado/app/base/adapter/SimpleRecyclerAdapter.kt
@@ -15,8 +15,20 @@ abstract class SimpleRecyclerAdapter
- (context: Context, private val layoutI
this@SimpleRecyclerAdapter.convert(holder, item, payloads)
}
+ override fun registerListener(holder: ItemViewHolder) {
+ this@SimpleRecyclerAdapter.registerListener(holder)
+ }
})
}
+ /**
+ * 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,
+ * 使用getItem(holder.layoutPosition)来获取item
+ */
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList)
+
+ /**
+ * 注册事件
+ */
+ abstract fun registerListener(holder: ItemViewHolder)
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/constant/Bus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt
similarity index 85%
rename from app/src/main/java/io/legado/app/constant/Bus.kt
rename to app/src/main/java/io/legado/app/constant/EventBus.kt
index 7795773e6..402827cb4 100644
--- a/app/src/main/java/io/legado/app/constant/Bus.kt
+++ b/app/src/main/java/io/legado/app/constant/EventBus.kt
@@ -1,11 +1,11 @@
package io.legado.app.constant
-object Bus {
+object EventBus {
const val MEDIA_BUTTON = "mediaButton"
const val RECREATE = "RECREATE"
const val UP_BOOK = "sourceDebugLog"
const val ALOUD_STATE = "aloud_state"
- const val TTS_START = "ttsStart"
+ const val TTS_PROGRESS = "ttsStart"
const val TTS_DS = "ttsDs"
const val BATTERY_CHANGED = "batteryChanged"
const val TIME_CHANGED = "timeChanged"
@@ -20,4 +20,6 @@ object Bus {
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/Action.kt b/app/src/main/java/io/legado/app/constant/IntentAction.kt
similarity index 91%
rename from app/src/main/java/io/legado/app/constant/Action.kt
rename to app/src/main/java/io/legado/app/constant/IntentAction.kt
index a13a8cca3..f55e486ac 100644
--- a/app/src/main/java/io/legado/app/constant/Action.kt
+++ b/app/src/main/java/io/legado/app/constant/IntentAction.kt
@@ -1,6 +1,6 @@
package io.legado.app.constant
-object Action {
+object IntentAction {
const val start = "start"
const val play = "play"
const val stop = "stop"
@@ -17,4 +17,5 @@ object Action {
const val next = "next"
const val moveTo = "moveTo"
const val init = "init"
+ const val remove = "remove"
}
\ 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 c777fdb9a..320bc87ee 100644
--- a/app/src/main/java/io/legado/app/constant/PreferKey.kt
+++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt
@@ -1,11 +1,17 @@
package io.legado.app.constant
object PreferKey {
-
+ const val versionCode = "versionCode"
+ const val themeMode = "themeMode"
const val downloadPath = "downloadPath"
const val hideStatusBar = "hideStatusBar"
+ const val clickAllNext = "clickAllNext"
const val hideNavigationBar = "hideNavigationBar"
const val precisionSearch = "precisionSearch"
+ const val readAloudOnLine = "readAloudOnLine"
+ const val readAloudByPage = "readAloudByPage"
+ const val ttsSpeechRate = "ttsSpeechRate"
+ const val ttsSpeechPer = "ttsSpeechPer"
const val prevKey = "prevKeyCode"
const val nextKey = "nextKeyCode"
const val showRss = "showRss"
@@ -13,7 +19,21 @@ object PreferKey {
const val recordLog = "recordLog"
const val processText = "process_text"
const val cleanCache = "cleanCache"
- const val lastGroup = "lastGroup"
+ const val saveTabPosition = "saveTabPosition"
const val pageAnim = "pageAnim"
const val readBookFont = "readBookFont"
+ const val fontFolder = "fontFolder"
+ const val backupPath = "backupUri"
+ const val threadCount = "threadCount"
+ 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 changeSourceLoadToc = "changeSourceLoadToc"
+ const val chineseConverterType = "chineseConverterType"
+ const val launcherIcon = "launcherIcon"
+ const val textSelectAble = "selectText"
+ const val lastBackup = "lastBackup"
+ const val bodyIndent = "textIndent"
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/constant/Theme.kt b/app/src/main/java/io/legado/app/constant/Theme.kt
index 40467a688..b8587997b 100644
--- a/app/src/main/java/io/legado/app/constant/Theme.kt
+++ b/app/src/main/java/io/legado/app/constant/Theme.kt
@@ -1,14 +1,13 @@
package io.legado.app.constant
-import io.legado.app.App
-import io.legado.app.utils.isNightTheme
+import io.legado.app.help.AppConfig
enum class Theme {
Dark, Light, Auto;
companion object {
fun getTheme(): Theme {
- return if (App.INSTANCE.isNightTheme) {
+ return if (AppConfig.isNightTheme) {
Dark
} else Light
}
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 cab698fbd..e548f4584 100644
--- a/app/src/main/java/io/legado/app/data/AppDatabase.kt
+++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt
@@ -7,14 +7,18 @@ import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.data.dao.*
import io.legado.app.data.entities.*
+import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.Restore
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
@Database(
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
- RssSource::class, Bookmark::class, RssArticle::class, RssStar::class],
- version = 5,
+ RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
+ RssStar::class, TxtTocRule::class],
+ version = 8,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
@@ -28,7 +32,7 @@ abstract class AppDatabase : RoomDatabase() {
.fallbackToDestructiveMigration()
.addCallback(object : Callback() {
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
- Restore.restore()
+ GlobalScope.launch { Restore.restore(Backup.backupPath) }
}
})
.build()
@@ -47,4 +51,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun rssArticleDao(): RssArticleDao
abstract fun rssStarDao(): RssStarDao
abstract fun cookieDao(): CookieDao
+ abstract fun txtTocRule(): TxtTocRuleDao
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/README.md b/app/src/main/java/io/legado/app/data/README.md
index 18061b12d..9b5f490b7 100644
--- a/app/src/main/java/io/legado/app/data/README.md
+++ b/app/src/main/java/io/legado/app/data/README.md
@@ -1 +1,17 @@
-## 存储数据用
\ No newline at end of file
+## 存储数据用
+* dao 数据操作
+* entities 数据模型
+* \Book 书籍信息
+* \BookChapter 目录信息
+* \BookGroup 书籍分组
+* \Bookmark 书签
+* \BookSource 书源
+* \Cookie http cookie
+* \ReplaceRule 替换规则
+* \RssArticle rss条目
+* \RssReadRecord rss阅读记录
+* \RssSource rss源
+* \RssStar rss收藏
+* \SearchBook 搜索结果
+* \SearchKeyword 搜索关键字
+* \TxtTocRule txt文件目录规则
diff --git a/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt b/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt
index 9a4db0cb1..38cc452f2 100644
--- a/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt
@@ -13,9 +13,15 @@ interface BookChapterDao {
@Query("select * from chapters where bookUrl = :bookUrl")
fun observeByBook(bookUrl: String): LiveData
>
+ @Query("SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%'")
+ fun liveDataSearch(bookUrl: String, key: String): LiveData>
+
@Query("select * from chapters where bookUrl = :bookUrl")
fun getChapterList(bookUrl: String): List
+ @Query("select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end")
+ fun getChapterList(bookUrl: String, start: Int, end: Int): List
+
@Query("select * from chapters where bookUrl = :bookUrl and `index` = :index")
fun getChapter(bookUrl: String, index: Int): BookChapter?
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 68de17a66..b0c07339c 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
@@ -17,15 +17,15 @@ interface BookDao {
@Query("SELECT * FROM books WHERE origin = '${BookType.local}' order by durChapterTime desc")
fun observeLocal(): LiveData>
+ @Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}' order by durChapterTime desc")
+ fun observeLocalUri(): LiveData>
+
@Query("SELECT * FROM books WHERE origin <> '${BookType.local}' and type = 0 order by durChapterTime desc")
fun observeDownload(): LiveData>
- @Query("SELECT * FROM books WHERE `group` = :group")
+ @Query("SELECT * FROM books WHERE (`group` & :group) > 0")
fun observeByGroup(group: Int): LiveData>
- @Query("SELECT bookUrl FROM books WHERE `group` = :group")
- fun observeUrlsByGroup(group: Int): LiveData>
-
@Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'")
fun liveDataSearch(key: String): LiveData>
@@ -42,7 +42,7 @@ interface BookDao {
val hasUpdateBooks: List
@get:Query("SELECT * FROM books")
- val allBooks: List
+ val all: List
@get:Query("SELECT * FROM books where type = 0 ORDER BY durChapterTime DESC limit 1")
val lastReadBook: Book?
@@ -57,11 +57,14 @@ interface BookDao {
fun insert(vararg book: Book)
@Update
- fun update(vararg books: Book)
+ fun update(vararg book: Book)
- @Query("delete from books where bookUrl = :bookUrl")
- fun delete(bookUrl: String)
+ @Delete
+ fun delete(vararg book: Book)
@Query("update books set durChapterPos = :pos where bookUrl = :bookUrl")
fun upProgress(bookUrl: String, pos: Int)
+
+ @Query("update books set `group` = :newGroupId where `group` = :oldGroupId")
+ fun upGroup(oldGroupId: Int, newGroupId: Int)
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt b/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt
index c48bd6e6b..c262d60e1 100644
--- a/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt
@@ -13,11 +13,14 @@ interface BookGroupDao {
@Query("SELECT * FROM book_groups ORDER BY `order`")
fun liveDataAll(): LiveData>
- @get:Query("SELECT MAX(groupId) FROM book_groups")
- val maxId: Int
+ @get:Query("SELECT sum(groupId) FROM book_groups")
+ val idsSum: Int
- @Query("SELECT * FROM book_groups ORDER BY `order`")
- fun all(): List
+ @get:Query("SELECT MAX(`order`) FROM book_groups")
+ val maxOrder: Int
+
+ @get:Query("SELECT * FROM book_groups ORDER BY `order`")
+ val all: List
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg bookGroup: BookGroup)
diff --git a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
index f76ed3d13..0981a94ea 100644
--- a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
@@ -14,10 +14,10 @@ interface BookSourceDao {
@Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey order by customOrder asc")
fun liveDataSearch(searchKey: String = ""): LiveData>
- @Query("select * from book_sources where enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' order by customOrder asc")
+ @Query("select * from book_sources where enabled = 1 and enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' order by customOrder asc")
fun liveExplore(): LiveData>
- @Query("select * from book_sources where enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc")
+ @Query("select * from book_sources where enabled = 1 and enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc")
fun liveExplore(key: String): LiveData>
@Query("select bookSourceGroup from book_sources where bookSourceGroup is not null and bookSourceGroup <> ''")
@@ -26,24 +26,12 @@ interface BookSourceDao {
@Query("select bookSourceGroup from book_sources where enabled = 1 and bookSourceGroup is not null and bookSourceGroup <> ''")
fun liveGroupEnabled(): LiveData>
+ @Query("select bookSourceGroup from book_sources where enabled = 1 and enabledExplore = 1 and exploreUrl is not null and exploreUrl <> '' and bookSourceGroup is not null and bookSourceGroup <> ''")
+ fun liveGroupExplore(): LiveData>
+
@Query("select distinct enabled from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey")
fun searchIsEnable(searchKey: String = ""): List
- @Query("update book_sources set enabled = 1 where bookSourceUrl = :sourceUrl")
- fun enableSection(sourceUrl: String)
-
- @Query("update book_sources set enabled = 0 where bookSourceUrl = :sourceUrl")
- fun disableSection(sourceUrl: String)
-
- @Query("update book_sources set enabledExplore = 1 where bookSourceUrl = :sourceUrl")
- fun enableSectionExplore(sourceUrl: String)
-
- @Query("update book_sources set enabledExplore = 0 where bookSourceUrl = :sourceUrl")
- fun disableSectionExplore(sourceUrl: String)
-
- @Query("delete from book_sources where bookSourceUrl = :sourceUrl")
- fun delSection(sourceUrl: String)
-
@Query("select * from book_sources where enabledExplore = 1 order by customOrder asc")
fun observeFind(): DataSource.Factory
@@ -53,6 +41,9 @@ interface BookSourceDao {
@Query("select * from book_sources where enabled = 1 and bookSourceGroup like '%' || :group || '%'")
fun getEnabledByGroup(group: String): List
+ @get:Query("select * from book_sources where bookUrlPattern is not null || bookUrlPattern <> ''")
+ val hasBookUrlPattern: List
+
@get:Query("select * from book_sources where bookSourceGroup is null or bookSourceGroup = ''")
val noGroup: List
@@ -75,7 +66,7 @@ interface BookSourceDao {
fun update(vararg bookSource: BookSource)
@Delete
- fun delete(bookSource: BookSource)
+ fun delete(vararg bookSource: BookSource)
@Query("delete from book_sources where bookSourceUrl = :key")
fun delete(key: String)
diff --git a/app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt b/app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt
index eb757c547..568714e2f 100644
--- a/app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt
@@ -2,6 +2,8 @@ package io.legado.app.data.dao
import androidx.paging.DataSource
import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
import androidx.room.Query
import io.legado.app.data.entities.Bookmark
@@ -15,4 +17,13 @@ interface BookmarkDao {
@Query("select * from bookmarks where bookUrl = :bookUrl")
fun observeByBook(bookUrl: String): DataSource.Factory
+ @Query("SELECT * FROM bookmarks where bookUrl = :bookUrl and chapterName like '%'||:key||'%' or content like '%'||:key||'%'")
+ fun liveDataSearch(bookUrl: String, key: String): DataSource.Factory
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insert(vararg bookmark: Bookmark)
+
+ @Query("delete from bookmarks where bookUrl = :bookUrl and chapterName like '%'||:chapterName||'%'")
+ fun delByBookmark(bookUrl: String, chapterName: 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 5940272af..0abc76dc8 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
@@ -32,24 +32,15 @@ interface ReplaceRuleDao {
@Query("SELECT * FROM replace_rules WHERE id in (:ids)")
fun findByIds(vararg ids: Long): List
- @Query("update replace_rules set isEnabled = 1 where id in (:ids)")
- fun enableSection(vararg ids: Long)
-
- @Query("update replace_rules set isEnabled = 0 where id in (:ids)")
- fun disableSection(vararg ids: Long)
-
- @Query("delete from replace_rules where id in (:ids)")
- fun delSection(vararg ids: Long)
-
@Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1
- AND (scope LIKE '%' || :scope || '%' or scope = null or scope = '')"""
+ AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')"""
)
fun findEnabledByScope(scope: String): List
@Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1
- AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope = null or scope = '')"""
+ AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')"""
)
fun findEnabledByScope(name: String, origin: String): List
diff --git a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt
index 247a5be6e..fbf3c3611 100644
--- a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt
@@ -3,6 +3,7 @@ package io.legado.app.data.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import io.legado.app.data.entities.RssArticle
+import io.legado.app.data.entities.RssReadRecord
@Dao
interface RssArticleDao {
@@ -10,7 +11,11 @@ interface RssArticleDao {
@Query("select * from rssArticles where origin = :origin and link = :link")
fun get(origin: String, link: String): RssArticle?
- @Query("select * from rssArticles where origin = :origin order by `order` desc")
+ @Query(
+ """select t1.link, t1.origin, t1.`order`, t1.title, t1.content, t1.description, t1.image, t1.pubDate, ifNull(t2.read, 0) as read
+ from rssArticles as t1 left join rssReadRecords as t2
+ on t1.link = t2.record where origin = :origin order by `order` desc"""
+ )
fun liveByOrigin(origin: String): LiveData>
@Insert(onConflict = OnConflictStrategy.REPLACE)
@@ -24,4 +29,9 @@ interface RssArticleDao {
@Query("delete from rssArticles where origin = :origin")
fun delete(origin: String)
+
+ @Insert(onConflict = OnConflictStrategy.IGNORE)
+ fun insertRecord(vararg rssReadRecord: RssReadRecord)
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
index df0bf605c..c1c1e85d9 100644
--- a/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt
@@ -31,21 +31,12 @@ interface RssSourceDao {
@Query("select sourceGroup from rssSources where sourceGroup is not null and sourceGroup <> ''")
fun liveGroup(): LiveData>
- @Query("update rssSources set enabled = 1 where sourceUrl in (:sourceUrls)")
- fun enableSection(vararg sourceUrls: String)
-
- @Query("update rssSources set enabled = 0 where sourceUrl in (:sourceUrls)")
- fun disableSection(vararg sourceUrls: String)
-
@get:Query("select min(customOrder) from rssSources")
val minOrder: Int
@get:Query("select max(customOrder) from rssSources")
val maxOrder: Int
- @Query("delete from rssSources where sourceUrl in (:sourceUrls)")
- fun delSection(vararg sourceUrls: String)
-
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rssSource: RssSource)
diff --git a/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt b/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt
index 87e527f75..faaf85b9f 100644
--- a/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt
@@ -7,6 +7,9 @@ import io.legado.app.data.entities.RssStar
@Dao
interface RssStarDao {
+ @get:Query("select * from rssStars order by starTime desc")
+ val all: List
+
@Query("select * from rssStars where origin = :origin and link = :link")
fun get(origin: String, link: String): RssStar?
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 7e623bfcc..779cfd1e2 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
@@ -19,16 +19,45 @@ interface SearchBookDao {
@Query("select * from searchBooks where bookUrl = :bookUrl")
fun getSearchBook(bookUrl: String): SearchBook?
- @Query("select * from searchBooks where name = :name and author = :author order by originOrder limit 1")
+ @Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources where enabled = 1) order by originOrder limit 1")
fun getFirstByNameAuthor(name: String, author: String): SearchBook?
- @Query("select * from searchBooks where name = :name and author = :author order by originOrder")
- fun getByNameAuthor(name: String, author: String): List
-
- @Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources where enabled = 1) order by originOrder")
+ @Query(
+ """
+ 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
+ order by t2.customOrder
+ """
+ )
fun getByNameAuthorEnable(name: String, author: String): List
+ @Query(
+ """
+ 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||'%'
+ order by t2.customOrder
+ """
+ )
+ fun getChangeSourceSearch(name: String, author: String, key: String): List
+
+ @Query(
+ """
+ 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 <> ''
+ order by t2.customOrder
+ """
+ )
+ fun getEnableHasCover(name: String, author: String): List
+
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg searchBook: SearchBook): List
+ @Query("delete from searchBooks where time < :time")
+ fun clearExpired(time: Long)
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt
new file mode 100644
index 000000000..119daa651
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt
@@ -0,0 +1,20 @@
+package io.legado.app.data.dao
+
+import androidx.room.*
+import io.legado.app.data.entities.TxtTocRule
+
+@Dao
+interface TxtTocRuleDao {
+
+ @get:Query("select * from txtTocRules order by serialNumber")
+ val all: List
+
+ @get:Query("select * from txtTocRules where enable = 1 order by serialNumber")
+ val enabled: List
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insert(vararg rule: TxtTocRule)
+
+ @Delete
+ fun delete(vararg rule: TxtTocRule)
+}
\ No newline at end of file
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 d98f79603..d01919a94 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
@@ -10,6 +10,7 @@ import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize
+import java.nio.charset.Charset
import kotlin.math.max
@Parcelize
@@ -19,15 +20,15 @@ data class Book(
var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径)
var tocUrl: String = "", // 目录页Url (toc=table of Contents)
var origin: String = BookType.local, // 书源URL(默认BookType.local)
- var originName: String = "", //书源名称
- var name: String = "", // 书籍名称(书源获取)
- var author: String = "", // 作者名称(书源获取)
- override var kind: String? = null, // 分类信息(书源获取)
+ var originName: String = "", //书源名称 or 本地书籍文件名
+ var name: String = "", // 书籍名称(书源获取)
+ var author: String = "", // 作者名称(书源获取)
+ override var kind: String? = null, // 分类信息(书源获取)
var customTag: String? = null, // 分类信息(用户修改)
var coverUrl: String? = null, // 封面Url(书源获取)
var customCoverUrl: String? = null, // 封面Url(用户修改)
- var intro: String? = null, // 简介内容(书源获取)
- var customIntro: String? = null, // 简介内容(用户修改)
+ var intro: String? = null, // 简介内容(书源获取)
+ var customIntro: String? = null, // 简介内容(用户修改)
var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍)
var type: Int = 0, // @BookType
var group: Int = 0, // 自定义分组索引号
@@ -48,6 +49,25 @@ data class Book(
var variable: String? = null // 自定义书籍变量信息(用于书源规则检索书籍信息)
) : Parcelable, BaseBook {
+ fun isLocalBook(): Boolean {
+ return origin == BookType.local
+ }
+
+ fun isTxt(): Boolean {
+ return isLocalBook() && originName.endsWith(".txt", true)
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is Book) {
+ return other.bookUrl == bookUrl
+ }
+ return false
+ }
+
+ override fun hashCode(): Int {
+ return bookUrl.hashCode()
+ }
+
@Ignore
@IgnoredOnParcel
override var variableMap: HashMap? = null
@@ -66,6 +86,8 @@ data class Book(
@IgnoredOnParcel
override var tocHtml: String? = null
+ fun getRealAuthor() = author.replace("作者:", "")
+
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
@@ -77,6 +99,10 @@ data class Book(
variable = GSON.toJson(variableMap)
}
+ fun fileCharset(): Charset {
+ return charset(charset ?: "UTF-8")
+ }
+
fun toSearchBook(): SearchBook {
return SearchBook(
name = name,
diff --git a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt
index ba5575cf3..f7a008870 100644
--- a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt
+++ b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt
@@ -33,7 +33,7 @@ data class BookChapter(
var tag: String? = null, //
var start: Long? = null, // 章节起始位置
var end: Long? = null, // 章节终止位置
- var variable: String? = null
+ var variable: String? = null //变量
) : Parcelable {
@Ignore
@@ -52,5 +52,16 @@ data class BookChapter(
variable = GSON.toJson(variableMap)
}
+ override fun hashCode(): Int {
+ return url.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is BookChapter) {
+ return other.url == url
+ }
+ return false
+ }
+
}
diff --git a/app/src/main/java/io/legado/app/data/entities/BookGroup.kt b/app/src/main/java/io/legado/app/data/entities/BookGroup.kt
index 5b3bb0992..9cd74b331 100644
--- a/app/src/main/java/io/legado/app/data/entities/BookGroup.kt
+++ b/app/src/main/java/io/legado/app/data/entities/BookGroup.kt
@@ -8,8 +8,8 @@ import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(tableName = "book_groups")
data class BookGroup(
- @PrimaryKey
- var groupId: Int = 0,
- var groupName: String,
- var order: Int = 0
+ @PrimaryKey
+ val groupId: Int = 0b1,
+ var groupName: String,
+ var order: Int = 0
) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/entities/BookSource.kt b/app/src/main/java/io/legado/app/data/entities/BookSource.kt
index 39b569565..78852a4ec 100644
--- a/app/src/main/java/io/legado/app/data/entities/BookSource.kt
+++ b/app/src/main/java/io/legado/app/data/entities/BookSource.kt
@@ -47,6 +47,18 @@ data class BookSource(
var ruleToc: String? = null, // 目录页规则
var ruleContent: String? = null // 正文页规则
) : Parcelable, JsExtensions {
+
+ override fun hashCode(): Int {
+ return bookSourceUrl.hashCode()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other is BookSource) {
+ return other.bookSourceUrl == bookSourceUrl
+ }
+ return false
+ }
+
@Ignore
@IgnoredOnParcel
private var searchRuleV: SearchRule? = null
@@ -126,6 +138,16 @@ data class BookSource(
return contentRuleV!!
}
+ fun addGroup(group: String) {
+ bookSourceGroup?.let {
+ if (!it.contains(group)) {
+ bookSourceGroup = "$it;$group"
+ }
+ } ?: let {
+ bookSourceGroup = group
+ }
+ }
+
fun getExploreKinds(): ArrayList? {
val exploreKinds = arrayListOf()
exploreUrl?.let {
diff --git a/app/src/main/java/io/legado/app/data/entities/Bookmark.kt b/app/src/main/java/io/legado/app/data/entities/Bookmark.kt
index c1f638137..acd6005a3 100644
--- a/app/src/main/java/io/legado/app/data/entities/Bookmark.kt
+++ b/app/src/main/java/io/legado/app/data/entities/Bookmark.kt
@@ -7,15 +7,15 @@ import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
@Parcelize
-@Entity(tableName = "bookmarks", indices = [(Index(value = ["bookUrl"], unique = true))])
+@Entity(tableName = "bookmarks", indices = [(Index(value = ["time"], unique = true))])
data class Bookmark(
@PrimaryKey
var time: Long = System.currentTimeMillis(),
var bookUrl: String = "",
var bookName: String = "",
- var chapterName: String = "",
var chapterIndex: Int = 0,
var pageIndex: Int = 0,
+ var chapterName: String = "",
var content: String = ""
) : Parcelable
\ 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 269325e5d..8744ce2cb 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
@@ -24,4 +24,17 @@ data class ReplaceRule(
var isRegex: Boolean = true,
@ColumnInfo(name = "sortOrder")
var order: Int = 0
-) : Parcelable
\ No newline at end of file
+) : Parcelable {
+
+
+ override fun equals(other: Any?): Boolean {
+ if (other is ReplaceRule) {
+ return other.id == id
+ }
+ return super.equals(other)
+ }
+
+ override fun hashCode(): Int {
+ return id.hashCode()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt b/app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt
new file mode 100644
index 000000000..edbb4c15d
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt
@@ -0,0 +1,7 @@
+package io.legado.app.data.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "rssReadRecords")
+data class RssReadRecord(@PrimaryKey val record: String, val read: Boolean = true)
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/data/entities/RssSource.kt b/app/src/main/java/io/legado/app/data/entities/RssSource.kt
index 261611caf..999b12523 100644
--- a/app/src/main/java/io/legado/app/data/entities/RssSource.kt
+++ b/app/src/main/java/io/legado/app/data/entities/RssSource.kt
@@ -39,6 +39,17 @@ data class RssSource(
var customOrder: Int = 0
) : Parcelable, JsExtensions {
+ override fun equals(other: Any?): Boolean {
+ if (other is RssSource) {
+ return other.sourceUrl == sourceUrl
+ }
+ return false
+ }
+
+ override fun hashCode(): Int {
+ return sourceUrl.hashCode()
+ }
+
@Throws(Exception::class)
fun getHeaderMap(): Map {
val headerMap = HashMap()
diff --git a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt
index de3466580..2baf259e4 100644
--- a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt
+++ b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt
@@ -90,6 +90,15 @@ data class SearchBook(
origins?.add(origin)
}
+ fun getDisplayLastChapterTitle(): String {
+ latestChapterTitle?.let {
+ if (it.isNotEmpty()) {
+ return it
+ }
+ }
+ return "无最新章节"
+ }
+
fun toBook(): Book {
return Book(
name = name,
diff --git a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt
new file mode 100644
index 000000000..c9cd51d4d
--- /dev/null
+++ b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt
@@ -0,0 +1,14 @@
+package io.legado.app.data.entities
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+
+@Entity(tableName = "txtTocRules")
+data class TxtTocRule(
+ @PrimaryKey
+ var name: String = "",
+ var rule: String = "",
+ var serialNumber: Int,
+ var enable: Boolean = 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 ca7c20f5f..6138c0c7e 100644
--- a/app/src/main/java/io/legado/app/help/ActivityHelp.kt
+++ b/app/src/main/java/io/legado/app/help/ActivityHelp.kt
@@ -19,7 +19,7 @@ object ActivityHelp {
* 判断指定Activity是否存在
*/
fun isExist(activityClass: Class<*>): Boolean {
- for (item in activities) {
+ activities.forEach { item ->
if (item.get()?.javaClass == activityClass) {
return true
}
@@ -63,7 +63,7 @@ object ActivityHelp {
* 关闭指定 activity
*/
fun finishActivity(vararg activities: Activity) {
- for (activity in activities) {
+ activities.forEach { activity ->
activity.finish()
}
}
@@ -81,8 +81,8 @@ object ActivityHelp {
}
}
}
- for (activityWeakReference in waitFinish) {
- activityWeakReference.get()?.finish()
+ waitFinish.forEach {
+ it.get()?.finish()
}
}
diff --git a/app/src/main/java/io/legado/app/help/AdapterDataObserverProxy.kt b/app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt
similarity index 86%
rename from app/src/main/java/io/legado/app/help/AdapterDataObserverProxy.kt
rename to app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt
index 9ef120e15..24f0c0f2b 100644
--- a/app/src/main/java/io/legado/app/help/AdapterDataObserverProxy.kt
+++ b/app/src/main/java/io/legado/app/help/AdapterDataObserverHeader.kt
@@ -2,7 +2,10 @@ package io.legado.app.help
import androidx.recyclerview.widget.RecyclerView
-internal class AdapterDataObserverProxy(var adapterDataObserver: RecyclerView.AdapterDataObserver, var headerCount: Int) : RecyclerView.AdapterDataObserver() {
+internal class AdapterDataObserverHeader(
+ var adapterDataObserver: RecyclerView.AdapterDataObserver,
+ var headerCount: Int
+) : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
adapterDataObserver.onChanged()
}
diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt
new file mode 100644
index 000000000..1f4302fdc
--- /dev/null
+++ b/app/src/main/java/io/legado/app/help/AppConfig.kt
@@ -0,0 +1,89 @@
+package io.legado.app.help
+
+import android.content.Context
+import io.legado.app.App
+import io.legado.app.R
+import io.legado.app.constant.PreferKey
+import io.legado.app.utils.*
+
+object AppConfig {
+
+ fun isNightTheme(context: Context): Boolean {
+ return when (context.getPrefString(PreferKey.themeMode, "0")) {
+ "1" -> false
+ "2" -> true
+ else -> context.sysIsDarkMode()
+ }
+ }
+
+ var isNightTheme: Boolean
+ get() = isNightTheme(App.INSTANCE)
+ set(value) {
+ if (value) {
+ App.INSTANCE.putPrefString(PreferKey.themeMode, "2")
+ } else {
+ App.INSTANCE.putPrefString(PreferKey.themeMode, "1")
+ }
+ }
+
+ var isTransparentStatusBar: Boolean
+ get() = App.INSTANCE.getPrefBoolean("transparentStatusBar")
+ set(value) {
+ App.INSTANCE.putPrefBoolean("transparentStatusBar", value)
+ }
+
+ var backupPath: String?
+ get() = App.INSTANCE.getPrefString(PreferKey.backupPath)
+ set(value) {
+ if (value.isNullOrEmpty()) {
+ App.INSTANCE.removePref(PreferKey.backupPath)
+ } else {
+ App.INSTANCE.putPrefString(PreferKey.backupPath, value)
+ }
+ }
+
+ var isShowRSS: Boolean
+ get() = App.INSTANCE.getPrefBoolean(PreferKey.showRss, true)
+ set(value) {
+ App.INSTANCE.putPrefBoolean(PreferKey.showRss, value)
+ }
+
+ val autoRefreshBook: Boolean
+ get() = App.INSTANCE.getPrefBoolean(App.INSTANCE.getString(R.string.pk_auto_refresh))
+
+ var threadCount: Int
+ get() = App.INSTANCE.getPrefInt(PreferKey.threadCount, 16)
+ set(value) {
+ App.INSTANCE.putPrefInt(PreferKey.threadCount, value)
+ }
+
+ var importBookPath: String?
+ get() = App.INSTANCE.getPrefString("importBookPath")
+ set(value) {
+ if (value == null) {
+ App.INSTANCE.removePref("importBookPath")
+ } else {
+ App.INSTANCE.putPrefString("importBookPath", value)
+ }
+ }
+
+ var ttsSpeechRate: Int
+ get() = App.INSTANCE.getPrefInt(PreferKey.ttsSpeechRate, 5)
+ set(value) {
+ App.INSTANCE.putPrefInt(PreferKey.ttsSpeechRate, value)
+ }
+
+ val ttsSpeechPer: String
+ get() = App.INSTANCE.getPrefString(PreferKey.ttsSpeechPer) ?: "0"
+
+ val isEInkMode: Boolean
+ get() = App.INSTANCE.getPrefBoolean("isEInkMode")
+
+ val clickAllNext: Boolean get() = App.INSTANCE.getPrefBoolean(PreferKey.clickAllNext, false)
+
+ var chineseConverterType: Int
+ get() = App.INSTANCE.getPrefInt(PreferKey.chineseConverterType)
+ set(value) {
+ App.INSTANCE.putPrefInt(PreferKey.chineseConverterType, value)
+ }
+}
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 1ea320505..e474bff96 100644
--- a/app/src/main/java/io/legado/app/help/BookHelp.kt
+++ b/app/src/main/java/io/legado/app/help/BookHelp.kt
@@ -1,101 +1,176 @@
package io.legado.app.help
+import android.net.Uri
+import androidx.documentfile.provider.DocumentFile
+import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap
import io.legado.app.App
+import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule
-import io.legado.app.utils.MD5Utils
-import io.legado.app.utils.getPrefInt
-import io.legado.app.utils.getPrefString
+import io.legado.app.model.localBook.AnalyzeTxtFile
+import io.legado.app.utils.*
import org.apache.commons.text.similarity.JaccardSimilarity
import java.io.File
import kotlin.math.min
object BookHelp {
-
- private var downloadPath: String =
- App.INSTANCE.getPrefString(PreferKey.downloadPath)
+ private const val cacheFolderName = "book_cache"
+ val downloadPath: String
+ get() = App.INSTANCE.getPrefString(PreferKey.downloadPath)
?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath
?: App.INSTANCE.cacheDir.absolutePath
- fun upDownloadPath() {
- downloadPath =
- App.INSTANCE.getPrefString(PreferKey.downloadPath)
- ?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath
- ?: App.INSTANCE.cacheDir.absolutePath
+ private val downloadUri get() = Uri.parse(downloadPath)
+
+ private fun bookFolderName(book: Book): String {
+ return formatFolderName(book.name) + MD5Utils.md5Encode16(book.bookUrl)
}
- private fun getBookCachePath(): String {
- return "$downloadPath${File.separator}book_cache"
+ fun formatChapterName(bookChapter: BookChapter): String {
+ return String.format(
+ "%05d-%s.nb",
+ bookChapter.index,
+ MD5Utils.md5Encode16(bookChapter.title)
+ )
}
fun clearCache() {
- FileHelp.deleteFile(getBookCachePath())
- FileHelp.getFolder(getBookCachePath())
+ if (downloadPath.isContentPath()) {
+ DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)
+ ?.findFile(cacheFolderName)
+ ?.delete()
+ } else {
+ FileUtils.deleteFile(
+ FileUtils.getPath(
+ File(downloadPath),
+ subDirs = *arrayOf(cacheFolderName)
+ )
+ )
+ }
}
@Synchronized
fun saveContent(book: Book, bookChapter: BookChapter, content: String) {
if (content.isEmpty()) return
- FileHelp.getFolder(getBookFolder(book)).listFiles()?.forEach {
- if (it.name.startsWith(String.format("%05d", bookChapter.index))) {
- it.delete()
- return@forEach
+ if (downloadPath.isContentPath()) {
+ DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root ->
+ DocumentUtils.createFileIfNotExist(
+ root,
+ formatChapterName(bookChapter),
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ )?.uri?.writeText(App.INSTANCE, content)
}
+ } else {
+ FileUtils.createFileIfNotExist(
+ File(downloadPath),
+ formatChapterName(bookChapter),
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ ).writeText(content)
}
- val filePath = getChapterPath(book, bookChapter)
- val file = FileHelp.getFile(filePath)
- file.writeText(content)
+ postEvent(EventBus.SAVE_CONTENT, bookChapter)
}
- fun getChapterCount(book: Book): Int {
- return FileHelp.getFolder(getBookFolder(book)).list()?.size ?: 0
+ fun getChapterFiles(book: Book): List {
+ val fileNameList = arrayListOf()
+ if (downloadPath.isContentPath()) {
+ DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root ->
+ DocumentUtils.createFolderIfNotExist(
+ root,
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ )?.let { bookDoc ->
+ DocumentUtils.listFiles(App.INSTANCE, bookDoc.uri).forEach {
+ fileNameList.add(it.name)
+ }
+ }
+ }
+ } else {
+ FileUtils.createFolderIfNotExist(
+ File(downloadPath),
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ ).list()?.let {
+ fileNameList.addAll(it)
+ }
+ }
+ return fileNameList
}
fun hasContent(book: Book, bookChapter: BookChapter): Boolean {
- val filePath = getChapterPath(book, bookChapter)
- runCatching {
- val file = File(filePath)
- if (file.exists()) {
+ when {
+ book.isLocalBook() -> {
return true
}
+ downloadPath.isContentPath() -> {
+ DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root ->
+ return DocumentUtils.exists(
+ root,
+ formatChapterName(bookChapter),
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ )
+ }
+ }
+ else -> {
+ return FileUtils.exists(
+ File(downloadPath),
+ formatChapterName(bookChapter),
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ )
+ }
}
return false
}
fun getContent(book: Book, bookChapter: BookChapter): String? {
- val filePath = getChapterPath(book, bookChapter)
- runCatching {
- val file = File(filePath)
- if (file.exists()) {
- return file.readText()
+ when {
+ book.isLocalBook() -> {
+ return AnalyzeTxtFile.getContent(book, bookChapter)
+ }
+ downloadPath.isContentPath() -> {
+ DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root ->
+ return DocumentUtils.getDirDocument(
+ root,
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ )?.findFile(formatChapterName(bookChapter))
+ ?.uri?.readText(App.INSTANCE)
+ }
+ }
+ else -> {
+ val file = FileUtils.getFile(
+ File(downloadPath),
+ formatChapterName(bookChapter),
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ )
+ if (file.exists()) {
+ return file.readText()
+ }
}
}
return null
}
fun delContent(book: Book, bookChapter: BookChapter) {
- val filePath = getChapterPath(book, bookChapter)
- kotlin.runCatching {
- val file = File(filePath)
- if (file.exists()) {
- file.delete()
+ when {
+ book.isLocalBook() -> return
+ downloadPath.isContentPath() -> {
+ DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root ->
+ DocumentUtils.getDirDocument(
+ root,
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ )?.findFile(formatChapterName(bookChapter))
+ ?.delete()
+ }
+ }
+ else -> {
+ FileUtils.createFileIfNotExist(
+ File(downloadPath),
+ formatChapterName(bookChapter),
+ subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
+ ).delete()
}
}
}
- private fun getBookFolder(book: Book): String {
- val bookFolder = formatFolderName(book.name + book.bookUrl)
- return "${getBookCachePath()}${File.separator}$bookFolder"
- }
-
- private fun getChapterPath(book: Book, bookChapter: BookChapter): String {
- val chapterFile =
- String.format("%05d-%s", bookChapter.index, MD5Utils.md5Encode(bookChapter.title))
- return "${getBookFolder(book)}${File.separator}$chapterFile.nb"
- }
-
private fun formatFolderName(folderName: String): String {
return folderName.replace("[\\\\/:*?\"<>|.]".toRegex(), "")
}
@@ -108,6 +183,9 @@ object BookHelp {
?: ""
}
+ /**
+ * 找到相似度最高的章节
+ */
fun getDurChapterIndexByChapterTitle(
title: String?,
index: Int,
@@ -121,16 +199,16 @@ object BookHelp {
}
var newIndex = 0
- val jaccardSimilarity = JaccardSimilarity()
+ val jSimilarity = JaccardSimilarity()
var similarity = if (chapters.size > index) {
- jaccardSimilarity.apply(title, chapters[index].title)
+ jSimilarity.apply(title, chapters[index].title)
} else 0.0
if (similarity == 1.0) {
return index
} else {
for (i in 1..50) {
if (index - i in chapters.indices) {
- jaccardSimilarity.apply(title, chapters[index - i].title).let {
+ jSimilarity.apply(title, chapters[index - i].title).let {
if (it > similarity) {
similarity = it
newIndex = index - i
@@ -141,7 +219,7 @@ object BookHelp {
}
}
if (index + i in chapters.indices) {
- jaccardSimilarity.apply(title, chapters[index + i].title).let {
+ jSimilarity.apply(title, chapters[index + i].title).let {
if (it > similarity) {
similarity = it
newIndex = index + i
@@ -156,9 +234,11 @@ object BookHelp {
return newIndex
}
- var bookName: String? = null
- var bookOrigin: String? = null
- var replaceRules: List = arrayListOf()
+ private var bookName: String? = null
+ private var bookOrigin: String? = null
+ private var replaceRules: List = arrayListOf()
+ val bodyIndent
+ get() = " ".repeat(App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2))
fun disposeContent(
title: String,
@@ -177,9 +257,6 @@ object BookHelp {
}
}
}
- if (!content.substringBefore("\n").contains(title)) {
- c = title + "\n" + c
- }
for (item in replaceRules) {
item.pattern.let {
if (it.isNotEmpty()) {
@@ -191,7 +268,13 @@ object BookHelp {
}
}
}
- val indent = App.INSTANCE.getPrefInt("textIndent", 2)
- return c.replace("\\s*\\n+\\s*".toRegex(), "\n" + " ".repeat(indent))
+ if (!content.substringBefore("\n").contains(title)) {
+ c = title + "\n" + c
+ }
+ when (AppConfig.chineseConverterType) {
+ 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c)
+ 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c)
+ }
+ return c.replace("\\s*\\n+\\s*".toRegex(), "\n$bodyIndent")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/CrashHandler.kt b/app/src/main/java/io/legado/app/help/CrashHandler.kt
index 9a5c7131a..b59609e18 100644
--- a/app/src/main/java/io/legado/app/help/CrashHandler.kt
+++ b/app/src/main/java/io/legado/app/help/CrashHandler.kt
@@ -9,12 +9,12 @@ import android.os.Looper
import android.util.Log
import android.widget.Toast
import io.legado.app.service.TTSReadAloudService
-import java.io.File
-import java.io.FileOutputStream
+import io.legado.app.utils.FileUtils
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.*
+import java.util.concurrent.TimeUnit
/**
* 异常管理类
@@ -141,14 +141,15 @@ class CrashHandler : Thread.UncaughtExceptionHandler {
val timestamp = System.currentTimeMillis()
val time = format.format(Date())
val fileName = "crash-$time-$timestamp.log"
- val path = mContext?.externalCacheDir?.toString() + "/crash/"
- val dir = File(path)
- if (!dir.exists()) {
- dir.mkdirs()
+ mContext?.externalCacheDir?.let { rootFile ->
+ FileUtils.getDirFile(rootFile, "crash").listFiles()?.forEach {
+ if (it.lastModified() < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)) {
+ it.delete()
+ }
+ }
+ FileUtils.createFileIfNotExist(rootFile, fileName, "crash")
+ .writeText(sb.toString())
}
- val fos = FileOutputStream(path + fileName)
- fos.write(sb.toString().toByteArray())
- fos.close()
}
}
diff --git a/app/src/main/java/io/legado/app/help/FileHelp.kt b/app/src/main/java/io/legado/app/help/FileHelp.kt
deleted file mode 100644
index 5a7678563..000000000
--- a/app/src/main/java/io/legado/app/help/FileHelp.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package io.legado.app.help
-
-import io.legado.app.App
-import java.io.File
-import java.io.IOException
-
-object FileHelp {
-
-
- //获取文件夹
- fun getFolder(filePath: String): File {
- val file = File(filePath)
- //如果文件夹不存在,就创建它
- if (!file.exists()) {
- file.mkdirs()
- }
- return file
- }
-
- //获取文件
- @Synchronized
- fun getFile(filePath: String): File {
- val file = File(filePath)
- try {
- if (!file.exists()) {
- //创建父类文件夹
- getFolder(file.parent)
- //创建文件
- file.createNewFile()
- }
- } catch (e: IOException) {
- e.printStackTrace()
- }
- return file
- }
-
- fun getCachePath(): String {
- return App.INSTANCE.externalCacheDir?.absolutePath
- ?: App.INSTANCE.cacheDir.absolutePath
- }
-
- //递归删除文件夹下的数据
- @Synchronized
- fun deleteFile(filePath: String) {
- val file = File(filePath)
- if (!file.exists()) return
-
- if (file.isDirectory) {
- val files = file.listFiles()
- files?.forEach { subFile ->
- val path = subFile.path
- deleteFile(path)
- }
- }
- //删除文件
- file.delete()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/FirstTopListUpCallback.kt b/app/src/main/java/io/legado/app/help/FirstTopListUpCallback.kt
new file mode 100644
index 000000000..a35f0cd8c
--- /dev/null
+++ b/app/src/main/java/io/legado/app/help/FirstTopListUpCallback.kt
@@ -0,0 +1,34 @@
+package io.legado.app.help
+
+import androidx.recyclerview.widget.ListUpdateCallback
+import androidx.recyclerview.widget.RecyclerView
+import io.legado.app.base.adapter.ItemViewHolder
+
+class FirstTopListUpCallback : ListUpdateCallback {
+ var firstInsert = -1
+ lateinit var adapter: RecyclerView.Adapter
+
+ override fun onChanged(position: Int, count: Int, payload: Any?) {
+ adapter.notifyItemRangeChanged(position, count, payload)
+ }
+
+ override fun onMoved(fromPosition: Int, toPosition: Int) {
+ if (toPosition == 0) {
+ firstInsert = 0
+ }
+ adapter.notifyItemMoved(fromPosition, toPosition)
+ }
+
+ override fun onInserted(position: Int, count: Int) {
+ if (firstInsert == -1 || firstInsert > position) {
+ firstInsert = position
+ }
+ adapter.notifyItemRangeInserted(position, count)
+ }
+
+ override fun onRemoved(position: Int, count: Int) {
+ adapter.notifyItemRangeRemoved(position, count)
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/ImageLoader.kt b/app/src/main/java/io/legado/app/help/ImageLoader.kt
index 26a9063d6..3d4272e9e 100644
--- a/app/src/main/java/io/legado/app/help/ImageLoader.kt
+++ b/app/src/main/java/io/legado/app/help/ImageLoader.kt
@@ -12,13 +12,15 @@ import java.io.File
object ImageLoader {
fun load(context: Context, path: String?): RequestBuilder {
- if (path?.startsWith("http", true) == true) {
- return Glide.with(context).load(path)
+ return when {
+ path.isNullOrEmpty() -> Glide.with(context).load(path)
+ path.startsWith("http", true) -> Glide.with(context).load(path)
+ else -> try {
+ Glide.with(context).load(File(path))
+ } catch (e: Exception) {
+ Glide.with(context).load(path)
+ }
}
- kotlin.runCatching {
- return Glide.with(context).load(File(path))
- }
- return Glide.with(context).load(path)
}
fun load(context: Context, @DrawableRes resId: Int?): RequestBuilder {
diff --git a/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt b/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt
index 9cf1fccf5..0e9a29351 100644
--- a/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt
+++ b/app/src/main/java/io/legado/app/help/ItemTouchCallback.kt
@@ -88,16 +88,13 @@ class ItemTouchCallback : ItemTouchHelper.Callback() {
srcViewHolder: RecyclerView.ViewHolder,
targetViewHolder: RecyclerView.ViewHolder
): Boolean {
- onItemTouchCallbackListener?.let {
- return it.onMove(srcViewHolder.adapterPosition, targetViewHolder.adapterPosition)
- }
- return false
+ return onItemTouchCallbackListener
+ ?.onMove(srcViewHolder.adapterPosition, targetViewHolder.adapterPosition)
+ ?: false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
- onItemTouchCallbackListener?.let {
- return it.onSwiped(viewHolder.adapterPosition)
- }
+ onItemTouchCallbackListener?.onSwiped(viewHolder.adapterPosition)
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
@@ -107,13 +104,21 @@ class ItemTouchCallback : ItemTouchHelper.Callback() {
viewPager?.requestDisallowInterceptTouchEvent(swiping)
}
+ override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
+ super.clearView(recyclerView, viewHolder)
+ onItemTouchCallbackListener?.clearView(recyclerView, viewHolder)
+ }
+
interface OnItemTouchCallbackListener {
+
/**
* 当某个Item被滑动删除的时候
*
* @param adapterPosition item的position
*/
- fun onSwiped(adapterPosition: Int)
+ fun onSwiped(adapterPosition: Int) {
+
+ }
/**
* 当两个Item位置互换的时候被回调
@@ -122,6 +127,13 @@ class ItemTouchCallback : ItemTouchHelper.Callback() {
* @param targetPosition 目的地的Item的position
* @return 开发者处理了操作应该返回true,开发者没有处理就返回false
*/
- fun onMove(srcPosition: Int, targetPosition: Int): Boolean
+ fun onMove(srcPosition: Int, targetPosition: Int): Boolean {
+ return false
+ }
+
+ fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
+
+ }
+
}
}
diff --git a/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt
new file mode 100644
index 000000000..76bcd6116
--- /dev/null
+++ b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt
@@ -0,0 +1,65 @@
+package io.legado.app.help
+
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.os.Build
+import io.legado.app.App
+import io.legado.app.R
+import io.legado.app.ui.welcome.Launcher1
+import io.legado.app.ui.welcome.Launcher2
+import io.legado.app.ui.welcome.Launcher3
+import io.legado.app.ui.welcome.WelcomeActivity
+import org.jetbrains.anko.toast
+
+/**
+ * Created by GKF on 2018/2/27.
+ * 更换图标
+ */
+object LauncherIconHelp {
+ private val packageManager: PackageManager = App.INSTANCE.packageManager
+ private val componentNames = arrayListOf(
+ ComponentName(App.INSTANCE, Launcher1::class.java.name),
+ ComponentName(App.INSTANCE, Launcher2::class.java.name),
+ ComponentName(App.INSTANCE, Launcher3::class.java.name)
+ )
+
+ fun changeIcon(icon: String?) {
+ if (icon.isNullOrEmpty()) return
+ if (Build.VERSION.SDK_INT < 26) {
+ App.INSTANCE.toast(R.string.chage_icon_error)
+ return
+ }
+ var hasEnabled = false
+ componentNames.forEach {
+ if (icon.equals(it.className.substringAfterLast("."), true)) {
+ hasEnabled = true
+ //启用
+ packageManager.setComponentEnabledSetting(
+ it,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ } else {
+ //禁用
+ packageManager.setComponentEnabledSetting(
+ it,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+ }
+ if (hasEnabled) {
+ packageManager.setComponentEnabledSetting(
+ ComponentName(App.INSTANCE, WelcomeActivity::class.java.name),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ } else {
+ packageManager.setComponentEnabledSetting(
+ ComponentName(App.INSTANCE, WelcomeActivity::class.java.name),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+ }
+}
\ No newline at end of file
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 328433c3c..5dc2d4817 100644
--- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt
+++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt
@@ -6,14 +6,10 @@ import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import io.legado.app.App
import io.legado.app.R
+import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.*
-import kotlinx.coroutines.Dispatchers.IO
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import java.io.BufferedWriter
import java.io.File
-import java.io.FileWriter
-import java.io.IOException
/**
* 阅读界面配置
@@ -23,6 +19,12 @@ object ReadBookConfig {
private val configFilePath =
App.INSTANCE.filesDir.absolutePath + File.separator + readConfigFileName
val configList: ArrayList = arrayListOf()
+ private val defaultConfigs by lazy {
+ val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes())
+ GSON.fromJsonArray(json)!!
+ }
+ val durConfig
+ get() = getConfig(styleSelect)
var styleSelect
get() = App.INSTANCE.getPrefInt("readStyleSelect")
@@ -34,28 +36,30 @@ object ReadBookConfig {
}
@Synchronized
- fun getConfig(index: Int = styleSelect): Config {
+ fun getConfig(index: Int): Config {
if (configList.size < 5) {
- reset()
+ resetAll()
}
return configList[index]
}
fun upConfig() {
- val configFile = File(configFilePath)
- val json = if (configFile.exists()) {
- configFile.readText()
- } else {
- String(App.INSTANCE.assets.open(readConfigFileName).readBytes())
+ (getConfigs() ?: defaultConfigs).let {
+ configList.clear()
+ configList.addAll(it)
}
- try {
- GSON.fromJsonArray(json)?.let {
- configList.clear()
- configList.addAll(it)
- } ?: reset()
- } catch (e: Exception) {
- reset()
+ }
+
+ private fun getConfigs(): List? {
+ val configFile = File(configFilePath)
+ if (configFile.exists()) {
+ try {
+ val json = configFile.readText()
+ return GSON.fromJsonArray(json)
+ } catch (e: Exception) {
+ }
}
+ return null
}
fun upBg() {
@@ -63,55 +67,62 @@ object ReadBookConfig {
val dm = resources.displayMetrics
val width = dm.widthPixels
val height = dm.heightPixels
- bg = getConfig().bgDrawable(width, height)
+ bg = durConfig.bgDrawable(width, height)
}
fun save() {
- GlobalScope.launch(IO) {
+ Coroutine.async {
val json = GSON.toJson(configList)
- val configFile = File(configFilePath)
- //获取流并存储
- try {
- BufferedWriter(FileWriter(configFile)).use { writer ->
- writer.write(json)
- writer.flush()
- }
- } catch (e: IOException) {
- e.printStackTrace()
- }
+ FileUtils.createFileIfNotExist(configFilePath).writeText(json)
}
}
- private fun reset() {
- val json = String(App.INSTANCE.assets.open(readConfigFileName).readBytes())
- GSON.fromJsonArray(json)?.let {
+ fun resetDur() {
+ defaultConfigs[styleSelect].let {
+ durConfig.setBg(it.bgType(), it.bgStr())
+ durConfig.setTextColor(it.textColor())
+ upBg()
+ save()
+ }
+ }
+
+ private fun resetAll() {
+ defaultConfigs.let {
configList.clear()
configList.addAll(it)
+ save()
}
- save()
}
data class Config(
- var bgStr: String = "#EEEEEE",
- var bgStrNight: String = "#000000",
- var bgType: Int = 0,
- var bgTypeNight: Int = 0,
- var darkStatusIcon: Boolean = true,
- var darkStatusIconNight: Boolean = false,
- var letterSpacing: Float = 1f,
- var lineSpacingExtra: Int = 12,
- var lineSpacingMultiplier: Float = 1.2f,
- var paddingBottom: Int = 0,
+ var bgStr: String = "#EEEEEE",//白天背景
+ var bgStrNight: String = "#000000",//夜间背景
+ var bgType: Int = 0,//白天背景类型
+ var bgTypeNight: Int = 0,//夜间背景类型
+ var darkStatusIcon: Boolean = true,//白天是否暗色状态栏
+ var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏
+ var textColor: String = "#3E3D3B",//白天文字颜色
+ var textColorNight: String = "#adadad",//夜间文字颜色
+ var textBold: Boolean = false,//是否粗体字
+ var textSize: Int = 15,//文字大小
+ var letterSpacing: Float = 1f,//字间距
+ var lineSpacingExtra: Int = 12,//行间距
+ var paragraphSpacing: Int = 12,
+ var paddingBottom: Int = 6,
var paddingLeft: Int = 16,
var paddingRight: Int = 16,
- var paddingTop: Int = 0,
- var textBold: Boolean = false,
- var textColor: String = "#3E3D3B",
- var textColorNight: String = "#adadad",
- var textSize: Int = 15
+ var paddingTop: Int = 6,
+ var headerPaddingBottom: Int = 0,
+ var headerPaddingLeft: Int = 16,
+ var headerPaddingRight: Int = 16,
+ var headerPaddingTop: Int = 0,
+ var footerPaddingBottom: Int = 6,
+ var footerPaddingLeft: Int = 16,
+ var footerPaddingRight: Int = 16,
+ var footerPaddingTop: Int = 6
) {
fun setBg(bgType: Int, bg: String) {
- if (App.INSTANCE.isNightTheme) {
+ if (AppConfig.isNightTheme) {
bgTypeNight = bgType
bgStrNight = bg
} else {
@@ -121,15 +132,16 @@ object ReadBookConfig {
}
fun setTextColor(color: Int) {
- if (App.INSTANCE.isNightTheme) {
+ if (AppConfig.isNightTheme) {
textColorNight = "#${color.hexString}"
} else {
textColor = "#${color.hexString}"
}
+ ChapterProvider.upStyle(this)
}
fun setStatusIconDark(isDark: Boolean) {
- if (App.INSTANCE.isNightTheme) {
+ if (AppConfig.isNightTheme) {
darkStatusIconNight = isDark
} else {
darkStatusIcon = isDark
@@ -137,7 +149,7 @@ object ReadBookConfig {
}
fun statusIconDark(): Boolean {
- return if (App.INSTANCE.isNightTheme) {
+ return if (AppConfig.isNightTheme) {
darkStatusIconNight
} else {
darkStatusIcon
@@ -145,17 +157,17 @@ object ReadBookConfig {
}
fun textColor(): Int {
- return if (App.INSTANCE.isNightTheme) Color.parseColor(textColorNight)
+ return if (AppConfig.isNightTheme) Color.parseColor(textColorNight)
else Color.parseColor(textColor)
}
fun bgStr(): String {
- return if (App.INSTANCE.isNightTheme) bgStrNight
+ return if (AppConfig.isNightTheme) bgStrNight
else bgStr
}
fun bgType(): Int {
- return if (App.INSTANCE.isNightTheme) bgTypeNight
+ return if (AppConfig.isNightTheme) bgTypeNight
else bgType
}
diff --git a/app/src/main/java/io/legado/app/help/coroutine/CompositeCoroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/CompositeCoroutine.kt
index 3f76acbe6..1eb1102ba 100644
--- a/app/src/main/java/io/legado/app/help/coroutine/CompositeCoroutine.kt
+++ b/app/src/main/java/io/legado/app/help/coroutine/CompositeCoroutine.kt
@@ -76,7 +76,7 @@ class CompositeCoroutine : CoroutineContainer {
resources = null
}
- set?.forEachIndexed { index, coroutine ->
+ set?.forEachIndexed { _, coroutine ->
coroutine.cancel()
}
}
diff --git a/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt b/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt
deleted file mode 100644
index 70391773a..000000000
--- a/app/src/main/java/io/legado/app/help/http/CoroutinesCallAdapterFactory.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package io.legado.app.help.http
-
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.Deferred
-import retrofit2.*
-import java.lang.reflect.ParameterizedType
-import java.lang.reflect.Type
-
-class CoroutinesCallAdapterFactory private constructor() : CallAdapter.Factory() {
- companion object {
- fun create(): CoroutinesCallAdapterFactory {
- return CoroutinesCallAdapterFactory()
- }
- }
-
- override fun get(
- returnType: Type,
- annotations: Array,
- retrofit: Retrofit
- ): CallAdapter<*, *>? {
- if (Deferred::class.java != getRawType(returnType)) {
- return null
- }
- check(returnType is ParameterizedType) { "Deferred return type must be parameterized as Deferred or Deferred" }
- val responseType = getParameterUpperBound(0, returnType)
-
- val rawDeferredType = getRawType(responseType)
- return if (rawDeferredType == Response::class.java) {
- check(responseType is ParameterizedType) { "Response must be parameterized as Response or Response" }
- ResponseCallAdapter(
- getParameterUpperBound(
- 0,
- responseType
- )
- )
- } else {
- BodyCallAdapter(responseType)
- }
- }
-
- private class BodyCallAdapter(
- private val responseType: Type
- ) : CallAdapter> {
-
- override fun responseType() = responseType
-
- override fun adapt(call: Call): Deferred {
- val deferred = CompletableDeferred()
-
- deferred.invokeOnCompletion {
- if (deferred.isCancelled) {
- call.cancel()
- }
- }
-
- call.enqueue(object : Callback {
- override fun onFailure(call: Call, t: Throwable) {
- deferred.completeExceptionally(t)
- }
-
- override fun onResponse(call: Call, response: Response) {
- if (response.isSuccessful) {
- deferred.complete(response.body()!!)
- } else {
- deferred.completeExceptionally(HttpException(response))
- }
- }
- })
-
- return deferred
- }
- }
-
- private class ResponseCallAdapter(
- private val responseType: Type
- ) : CallAdapter>> {
-
- override fun responseType() = responseType
-
- override fun adapt(call: Call): Deferred> {
- val deferred = CompletableDeferred>()
-
- deferred.invokeOnCompletion {
- if (deferred.isCancelled) {
- call.cancel()
- }
- }
-
- call.enqueue(object : Callback {
- override fun onFailure(call: Call, t: Throwable) {
- deferred.completeExceptionally(t)
- }
-
- override fun onResponse(call: Call, response: Response) {
- deferred.complete(response)
- }
- })
-
- return deferred
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/http/EncodeConverter.kt b/app/src/main/java/io/legado/app/help/http/EncodeConverter.kt
index 9e0b5ff1f..0cad990f0 100644
--- a/app/src/main/java/io/legado/app/help/http/EncodeConverter.kt
+++ b/app/src/main/java/io/legado/app/help/http/EncodeConverter.kt
@@ -1,5 +1,6 @@
package io.legado.app.help.http
+import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.UTF8BOMFighter
import okhttp3.ResponseBody
import retrofit2.Converter
@@ -16,20 +17,22 @@ class EncodeConverter(private val encode: String? = null) : Converter.Factory()
): Converter? {
return Converter { value ->
val responseBytes = UTF8BOMFighter.removeUTF8BOM(value.bytes())
- encode?.let { return@Converter String(responseBytes, Charset.forName(encode)) }
+ var charsetName: String? = encode
- var charsetName: String? = null
- val mediaType = value.contentType()
- //根据http头判断
- if (mediaType != null) {
- val charset = mediaType.charset()
- charsetName = charset?.displayName()
+ charsetName?.let {
+ try {
+ return@Converter String(responseBytes, Charset.forName(charsetName))
+ } catch (e: Exception) {
+ }
}
- if (charsetName == null) {
- charsetName = EncodingDetect.getHtmlEncode(responseBytes)
+ //根据http头判断
+ value.contentType()?.charset()?.let {
+ return@Converter String(responseBytes, it)
}
+ //根据内容判断
+ charsetName = EncodingDetect.getHtmlEncode(responseBytes)
String(responseBytes, Charset.forName(charsetName))
}
}
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 da70b17a7..a37e053fb 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
@@ -1,5 +1,7 @@
package io.legado.app.help.http
+import io.legado.app.help.http.api.HttpGetApi
+import io.legado.app.utils.NetworkUtils
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.*
import retrofit2.Retrofit
@@ -7,6 +9,7 @@ import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
+@Suppress("unused")
object HttpHelper {
val client: OkHttpClient by lazy {
@@ -35,11 +38,26 @@ object HttpHelper {
builder.build()
}
- inline fun getApiService(baseUrl: String): T {
- return getRetrofit(baseUrl).create(T::class.java)
+ fun simpleGet(url: String, encode: String? = null): String? {
+ NetworkUtils.getBaseUrl(url)?.let { baseUrl ->
+ val response = getApiService(baseUrl, encode)
+ .get(url, mapOf())
+ .execute()
+ return response.body()
+ }
+ return null
+ }
+
+ suspend fun simpleGetAsync(url: String, encode: String? = null): String? {
+ NetworkUtils.getBaseUrl(url)?.let { baseUrl ->
+ val response = getApiService(baseUrl, encode)
+ .getAsync(url, mapOf())
+ return response.body()
+ }
+ return null
}
- inline fun getApiService(baseUrl: String, encode: String): T {
+ inline fun getApiService(baseUrl: String, encode: String? = null): T {
return getRetrofit(baseUrl, encode).create(T::class.java)
}
@@ -47,8 +65,6 @@ object HttpHelper {
return Retrofit.Builder().baseUrl(baseUrl)
//增加返回值为字符串的支持(以实体类返回)
.addConverterFactory(EncodeConverter(encode))
- //增加返回值为Observable的支持
- .addCallAdapterFactory(CoroutinesCallAdapterFactory.create())
.client(client)
.build()
}
@@ -56,8 +72,6 @@ object HttpHelper {
fun getByteRetrofit(baseUrl: String): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(ByteConverter())
- //增加返回值为Observable的支持
- .addCallAdapterFactory(CoroutinesCallAdapterFactory.create())
.client(client)
.build()
}
diff --git a/app/src/main/java/io/legado/app/help/http/SSLHelper.kt b/app/src/main/java/io/legado/app/help/http/SSLHelper.kt
index ef70ac1b5..800304b62 100644
--- a/app/src/main/java/io/legado/app/help/http/SSLHelper.kt
+++ b/app/src/main/java/io/legado/app/help/http/SSLHelper.kt
@@ -14,11 +14,9 @@ import javax.net.ssl.*
object SSLHelper {
- val sslSocketFactory: SSLParams?
- get() = getSslSocketFactoryBase(null, null, null)
-
/**
- * 为了解决客户端不信任服务器数字证书的问题,网络上大部分的解决方案都是让客户端不对证书做任何检查,
+ * 为了解决客户端不信任服务器数字证书的问题,
+ * 网络上大部分的解决方案都是让客户端不对证书做任何检查,
* 这是一种有很大安全漏洞的办法
*/
val unsafeTrustManager: X509TrustManager = object : X509TrustManager {
@@ -141,7 +139,7 @@ object SSLHelper {
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null)
for ((index, certStream) in certificates.withIndex()) {
- val certificateAlias = Integer.toString(index)
+ val certificateAlias = index.toString()
// 证书工厂根据证书文件的流生成证书 cert
val cert = certificateFactory.generateCertificate(certStream)
// 将 cert 作为可信证书放入到keyStore中
diff --git a/app/src/main/java/io/legado/app/data/api/IHttpGetApi.kt b/app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt
similarity index 66%
rename from app/src/main/java/io/legado/app/data/api/IHttpGetApi.kt
rename to app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt
index e4180fa59..315dc881f 100644
--- a/app/src/main/java/io/legado/app/data/api/IHttpGetApi.kt
+++ b/app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt
@@ -1,6 +1,5 @@
-package io.legado.app.data.api
+package io.legado.app.help.http.api
-import kotlinx.coroutines.Deferred
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.GET
@@ -12,20 +11,20 @@ import retrofit2.http.Url
* Created by GKF on 2018/1/21.
* get web content
*/
-
-interface IHttpGetApi {
+@Suppress("unused")
+interface HttpGetApi {
@GET
- fun getAsync(
+ suspend fun getAsync(
@Url url: String,
@HeaderMap headers: Map
- ): Deferred>
+ ): Response
@GET
- fun getMapAsync(
+ suspend fun getMapAsync(
@Url url: String,
@QueryMap(encoded = true) queryMap: Map,
@HeaderMap headers: Map
- ): Deferred>
+ ): Response
@GET
fun get(
@@ -39,4 +38,11 @@ interface IHttpGetApi {
@QueryMap(encoded = true) queryMap: Map,
@HeaderMap headers: Map
): Call
+
+ @GET
+ suspend fun getMapByteAsync(
+ @Url url: String,
+ @QueryMap(encoded = true) queryMap: Map,
+ @HeaderMap headers: Map
+ ): Response
}
diff --git a/app/src/main/java/io/legado/app/data/api/IHttpPostApi.kt b/app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt
similarity index 78%
rename from app/src/main/java/io/legado/app/data/api/IHttpPostApi.kt
rename to app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt
index 17bf16ee8..9634b5b79 100644
--- a/app/src/main/java/io/legado/app/data/api/IHttpPostApi.kt
+++ b/app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt
@@ -1,6 +1,5 @@
-package io.legado.app.data.api
+package io.legado.app.help.http.api
-import kotlinx.coroutines.Deferred
import okhttp3.RequestBody
import retrofit2.Call
import retrofit2.Response
@@ -10,23 +9,23 @@ import retrofit2.http.*
* Created by GKF on 2018/1/29.
* post
*/
-
-interface IHttpPostApi {
+@Suppress("unused")
+interface HttpPostApi {
@FormUrlEncoded
@POST
- fun postMapAsync(
+ suspend fun postMapAsync(
@Url url: String,
@FieldMap(encoded = true) fieldMap: Map,
@HeaderMap headers: Map
- ): Deferred>
+ ): Response
@POST
- fun postBodyAsync(
+ suspend fun postBodyAsync(
@Url url: String,
@Body body: RequestBody,
@HeaderMap headers: Map
- ): Deferred>
+ ): Response
@FormUrlEncoded
@POST
@@ -45,9 +44,9 @@ interface IHttpPostApi {
@FormUrlEncoded
@POST
- fun postMapByteAsync(
+ suspend fun postMapByteAsync(
@Url url: String,
@FieldMap(encoded = true) fieldMap: Map,
@HeaderMap headers: Map
- ): Deferred>
+ ): Response
}
diff --git a/app/src/main/java/io/legado/app/help/permission/OnPermissionsDeniedCallback.kt b/app/src/main/java/io/legado/app/help/permission/OnPermissionsDeniedCallback.kt
index d6e81a68f..4a51881b0 100644
--- a/app/src/main/java/io/legado/app/help/permission/OnPermissionsDeniedCallback.kt
+++ b/app/src/main/java/io/legado/app/help/permission/OnPermissionsDeniedCallback.kt
@@ -1,5 +1,7 @@
package io.legado.app.help.permission
interface OnPermissionsDeniedCallback {
+
fun onPermissionsDenied(requestCode: Int, deniedPermissions: Array)
+
}
diff --git a/app/src/main/java/io/legado/app/help/permission/Request.kt b/app/src/main/java/io/legado/app/help/permission/Request.kt
index 1fc765bf9..600cbe93c 100644
--- a/app/src/main/java/io/legado/app/help/permission/Request.kt
+++ b/app/src/main/java/io/legado/app/help/permission/Request.kt
@@ -43,7 +43,7 @@ internal class Request : OnRequestPermissionsResultCallback {
}
fun addPermissions(vararg permissions: String) {
- this.permissions?.addAll(Arrays.asList(*permissions))
+ this.permissions?.addAll(listOf(*permissions))
}
fun setRequestCode(requestCode: Int) {
diff --git a/app/src/main/java/io/legado/app/help/permission/RequestManager.kt b/app/src/main/java/io/legado/app/help/permission/RequestManager.kt
index b3fede03f..18bedb800 100644
--- a/app/src/main/java/io/legado/app/help/permission/RequestManager.kt
+++ b/app/src/main/java/io/legado/app/help/permission/RequestManager.kt
@@ -33,6 +33,7 @@ internal object RequestManager : OnPermissionsResultCallback {
if (index >= 0) {
val to = it.size - 1
if (index != to) {
+ @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
Collections.swap(requests, index, to)
}
} else {
diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt
index 529deb629..c7de266ca 100644
--- a/app/src/main/java/io/legado/app/help/storage/Backup.kt
+++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt
@@ -1,22 +1,24 @@
package io.legado.app.help.storage
+import android.content.Context
+import android.net.Uri
+import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
-import io.legado.app.R
-import io.legado.app.help.FileHelp
+import io.legado.app.constant.PreferKey
import io.legado.app.help.ReadBookConfig
-import io.legado.app.utils.FileUtils
-import io.legado.app.utils.GSON
+import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.utils.*
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.withContext
import org.jetbrains.anko.defaultSharedPreferences
-import org.jetbrains.anko.doAsync
-import org.jetbrains.anko.toast
-import org.jetbrains.anko.uiThread
import java.io.File
+import java.util.concurrent.TimeUnit
object Backup {
- val defaultPath by lazy {
- FileUtils.getSdCardPath() + File.separator + "YueDu"
+ val backupPath: String by lazy {
+ FileUtils.getDirFile(App.INSTANCE.filesDir, "backup").absolutePath
}
val legadoPath by lazy {
@@ -27,104 +29,100 @@ object Backup {
legadoPath + File.separator + "Export"
}
- private fun pBackup(path: String = legadoPath) {
- backupBookshelf(path)
- backupBookGroup(path)
- backupBookSource(path)
- backupRssSource(path)
- backupReplaceRule(path)
- backupReadConfig(path)
- backupPreference(path)
- WebDavHelp.backUpWebDav(path)
+ val backupFileNames by lazy {
+ arrayOf(
+ "bookshelf.json", "bookGroup.json", "bookSource.json", "rssSource.json",
+ "rssStar.json", "replaceRule.json", ReadBookConfig.readConfigFileName, "config.xml"
+ )
}
- fun backup() {
- doAsync {
- pBackup()
- uiThread {
- App.INSTANCE.toast(R.string.backup_success)
- }
+ fun autoBack(context: Context) {
+ val lastBackup = context.getPrefLong(PreferKey.lastBackup)
+ if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) {
+ return
}
- }
-
- fun autoBackup() {
- doAsync {
- pBackup()
- }
- }
-
- private fun backupBookshelf(path: String) {
- App.db.bookDao().allBooks.let {
- if (it.isNotEmpty()) {
- val json = GSON.toJson(it)
- val file = FileHelp.getFile(path + File.separator + "bookshelf.json")
- file.writeText(json)
+ Coroutine.async {
+ val backupPath = context.getPrefString(PreferKey.backupPath)
+ if (backupPath.isNullOrEmpty()) {
+ backup(context)
+ } else {
+ backup(context, backupPath)
}
}
}
- private fun backupBookGroup(path: String) {
- App.db.bookGroupDao().all().let {
- if (it.isNotEmpty()) {
- val json = GSON.toJson(it)
- val file = FileHelp.getFile(path + File.separator + "bookGroup.json")
- file.writeText(json)
+ suspend fun backup(context: Context, path: String = legadoPath) {
+ context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis())
+ withContext(IO) {
+ writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath)
+ writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath)
+ writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath)
+ writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath)
+ writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath)
+ writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath)
+ GSON.toJson(ReadBookConfig.configList)?.let {
+ FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName)
+ .writeText(it)
}
- }
- }
-
- private fun backupBookSource(path: String) {
- App.db.bookSourceDao().all.let {
- if (it.isNotEmpty()) {
- val json = GSON.toJson(it)
- val file = FileHelp.getFile(path + File.separator + "bookSource.json")
- file.writeText(json)
+ Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp ->
+ val edit = sp.edit()
+ App.INSTANCE.defaultSharedPreferences.all.map {
+ when (val value = it.value) {
+ is Int -> edit.putInt(it.key, value)
+ is Boolean -> edit.putBoolean(it.key, value)
+ is Long -> edit.putLong(it.key, value)
+ is Float -> edit.putFloat(it.key, value)
+ is String -> edit.putString(it.key, value)
+ else -> Unit
+ }
+ }
+ edit.commit()
}
- }
- }
-
- private fun backupRssSource(path: String) {
- App.db.rssSourceDao().all.let {
- if (it.isNotEmpty()) {
- val json = GSON.toJson(it)
- val file = FileHelp.getFile(path + File.separator + "rssSource.json")
- file.writeText(json)
+ WebDavHelp.backUpWebDav(backupPath)
+ if (path.isContentPath()) {
+ copyBackup(context, Uri.parse(path))
+ } else {
+ copyBackup(File(path))
}
}
}
- private fun backupReplaceRule(path: String) {
- App.db.replaceRuleDao().all.let {
- if (it.isNotEmpty()) {
- val json = GSON.toJson(it)
- val file = FileHelp.getFile(path + File.separator + "replaceRule.json")
- file.writeText(json)
- }
+ private fun writeListToJson(list: List, fileName: String, path: String) {
+ if (list.isNotEmpty()) {
+ val json = GSON.toJson(list)
+ FileUtils.createFileIfNotExist(path + File.separator + fileName).writeText(json)
}
}
- private fun backupReadConfig(path: String) {
- GSON.toJson(ReadBookConfig.configList)?.let {
- FileHelp.getFile(path + File.separator + ReadBookConfig.readConfigFileName)
- .writeText(it)
+ @Throws(java.lang.Exception::class)
+ private fun copyBackup(context: Context, uri: Uri) {
+ DocumentFile.fromTreeUri(context, uri)?.let { treeDoc ->
+ for (fileName in backupFileNames) {
+ val file = File(backupPath + File.separator + fileName)
+ if (file.exists()) {
+ val doc = treeDoc.findFile(fileName) ?: treeDoc.createFile("", fileName)
+ doc?.let {
+ DocumentUtils.writeText(
+ context,
+ file.readText(),
+ doc.uri
+ )
+ }
+ }
+ }
}
}
- private fun backupPreference(path: String) {
- Preferences.getSharedPreferences(App.INSTANCE, path, "config")?.let { sp ->
- val edit = sp.edit()
- App.INSTANCE.defaultSharedPreferences.all.map {
- when (val value = it.value) {
- is Int -> edit.putInt(it.key, value)
- is Boolean -> edit.putBoolean(it.key, value)
- is Long -> edit.putLong(it.key, value)
- is Float -> edit.putFloat(it.key, value)
- is String -> edit.putString(it.key, value)
- else -> Unit
- }
+ @Throws(java.lang.Exception::class)
+ private fun copyBackup(rootFile: File) {
+ for (fileName in backupFileNames) {
+ val file = File(backupPath + File.separator + fileName)
+ if (file.exists()) {
+ file.copyTo(
+ FileUtils.createFileIfNotExist(rootFile, fileName),
+ true
+ )
}
- edit.commit()
}
}
-
}
\ No newline at end of file
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
new file mode 100644
index 000000000..7329705e9
--- /dev/null
+++ b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
@@ -0,0 +1,149 @@
+package io.legado.app.help.storage
+
+import android.content.Context
+import android.net.Uri
+import androidx.documentfile.provider.DocumentFile
+import io.legado.app.App
+import io.legado.app.data.entities.BookSource
+import io.legado.app.data.entities.ReplaceRule
+import io.legado.app.help.coroutine.Coroutine
+import io.legado.app.utils.DocumentUtils
+import io.legado.app.utils.FileUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.jetbrains.anko.toast
+import java.io.File
+
+object ImportOldData {
+ val yueDuPath by lazy {
+ FileUtils.getSdCardPath() + File.separator + "YueDu"
+ }
+
+ fun import(context: Context) {
+ GlobalScope.launch(Dispatchers.IO) {
+ try {// 导入书架
+ val shelfFile =
+ FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookShelf.json")
+ val json = shelfFile.readText()
+ val importCount = importOldBookshelf(json)
+ withContext(Dispatchers.Main) {
+ context.toast("成功导入书籍${importCount}")
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ context.toast("导入书籍失败\n${e.localizedMessage}")
+ }
+ }
+
+ try {// Book source
+ val sourceFile =
+ FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookSource.json")
+ val json = sourceFile.readText()
+ val importCount = importOldSource(json)
+ withContext(Dispatchers.Main) {
+ context.toast("成功导入书源${importCount}")
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ context.toast("导入源失败\n${e.localizedMessage}")
+ }
+ }
+
+ try {// Replace rules
+ val ruleFile =
+ FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookReplaceRule.json")
+ val json = ruleFile.readText()
+ val importCount = importOldReplaceRule(json)
+ withContext(Dispatchers.Main) {
+ context.toast("成功导入替换规则${importCount}")
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ context.toast("导入替换规则失败\n${e.localizedMessage}")
+ }
+ }
+ }
+ }
+
+ fun importUri(uri: Uri) {
+ Coroutine.async {
+ DocumentFile.fromTreeUri(App.INSTANCE, uri)?.listFiles()?.forEach {
+ when (it.name) {
+ "myBookShelf.json" ->
+ try {
+ DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json ->
+ val importCount = importOldBookshelf(json)
+ withContext(Dispatchers.Main) {
+ App.INSTANCE.toast("成功导入书籍${importCount}")
+ }
+ }
+ } catch (e: java.lang.Exception) {
+ withContext(Dispatchers.Main) {
+ App.INSTANCE.toast("导入书籍失败\n${e.localizedMessage}")
+ }
+ }
+ "myBookSource.json" ->
+ try {
+ DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json ->
+ val importCount = importOldSource(json)
+ withContext(Dispatchers.Main) {
+ App.INSTANCE.toast("成功导入书源${importCount}")
+ }
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ App.INSTANCE.toast("导入源失败\n${e.localizedMessage}")
+ }
+ }
+ "myBookReplaceRule.json" ->
+ try {
+ DocumentUtils.readText(App.INSTANCE, it.uri)?.let { json ->
+ val importCount = importOldReplaceRule(json)
+ withContext(Dispatchers.Main) {
+ App.INSTANCE.toast("成功导入替换规则${importCount}")
+ }
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ App.INSTANCE.toast("导入替换规则失败\n${e.localizedMessage}")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun importOldBookshelf(json: String): Int {
+ val books = OldBook.toNewBook(json)
+ App.db.bookDao().insert(*books.toTypedArray())
+ return books.size
+ }
+
+ fun importOldSource(json: String): Int {
+ val bookSources = mutableListOf()
+ val items: List