diff --git a/.github/workflows/legado.yml b/.github/workflows/legado.yml
index 4d9940f4d..57c22b832 100644
--- a/.github/workflows/legado.yml
+++ b/.github/workflows/legado.yml
@@ -28,18 +28,23 @@ jobs:
# 蓝奏云里的文件夹名(需手动在蓝奏云创建)
LANZOU_FOLDER: '阅读3测试版'
# 是否上传到artifact
- UPLOAD_ARTIFACT: 'false'
+ UPLOAD_ARTIFACT: 'true'
steps:
- uses: actions/checkout@v2
- - name: set up JDK 1.8
+ - name: Set Up JDK
uses: actions/setup-java@v1
with:
- java-version: 1.8
- - name: clear 18PlusList.txt
+ java-version: 14
+ - name: Clear 18PlusList.txt
run: |
echo "清空18PlusList.txt"
echo "">$GITHUB_WORKSPACE/app/src/main/assets/18PlusList.txt
- - name: release apk sign
+ - name: App Minify
+ if: ${{ github.event_name == 'release'}}
+ run: |
+ sed '/minifyEnabled/i\ shrinkResources true' $GITHUB_WORKSPACE/app/build.gradle -i
+ sed 's/minifyEnabled false/minifyEnabled true/' $GITHUB_WORKSPACE/app/build.gradle -i
+ - name: Release Apk Sign
run: |
echo "给apk增加签名"
cp $GITHUB_WORKSPACE/.github/workflows/legado.jks $GITHUB_WORKSPACE/app/legado.jks
@@ -47,12 +52,12 @@ jobs:
sed '$a\RELEASE_KEY_ALIAS=legado' $GITHUB_WORKSPACE/gradle.properties -i
sed '$a\RELEASE_STORE_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i
sed '$a\RELEASE_KEY_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i
- - name: apk live together
+ - name: Apk Live Together
run: |
echo "设置apk共存"
sed "s/'.release'/'.releaseA'/" $GITHUB_WORKSPACE/app/build.gradle -i
sed 's/.release/.releaseA/' $GITHUB_WORKSPACE/app/google-services.json -i
- - name: build with gradle
+ - name: Build With Gradle
run: |
echo "开始进行release构建"
chmod +x gradlew
@@ -63,12 +68,12 @@ jobs:
with:
name: legado apk
path: ${{ github.workspace }}/app/build/outputs/apk/app/release/*.apk
- - name: Set up Python 3.8
+ - name: Set Up Python 3
if: ${{ env.LANZOU_PSD }}
uses: actions/setup-python@v2
with:
- python-version: 3.8
- - name: Set up lanzou-api
+ python-version: 3.x
+ - name: Dependence Install
if: ${{ env.LANZOU_PSD }}
run: |
pip install requests
@@ -79,12 +84,12 @@ jobs:
run: |
import requests,os
res = requests.get('https://raw.githubusercontent.com/Celeter/build/master/.github/scripts/API_lanzou.py')
- with open(os.environ["FILEPATH"], 'wb') as f:
+ with open(os.environ["FILE_PATH"], 'wb') as f:
f.write(res.content)
env:
- FILEPATH: ${{ github.workspace }}/.github/lzy.py
+ FILE_PATH: ${{ github.workspace }}/.github/lzy.py
- name: Upload App To Lanzou
if: ${{ env.LANZOU_PSD }}
run: python $GITHUB_WORKSPACE/.github/lzy.py
env:
- UPLOAD_FOLDER: ${{ github.workspace }}/app/build/outputs/apk/app/release/
\ No newline at end of file
+ UPLOAD_FOLDER: ${{ github.workspace }}/app/build/outputs/apk/app/release/
diff --git a/README.md b/README.md
index 82994a452..1327ab258 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,15 @@
-# legado
+
-"cat" => The cat sat on the mat
-
+正则表达式只是我们用于在文本中检索字母和数字的模式。例如正则表达式 `cat`,表示: 字母 `c` 后面跟着一个字母 `a`,再后面跟着一个字母 `t`。"cat" => The cat sat on the mat
正则表达式 `123` 会匹配字符串 "123"。通过将正则表达式中的每个字符逐个与要匹配的字符串中的每个字符进行比较,来完成正则匹配。
-正则表达式通常区分大小写,因此正则表达式 `Cat` 与字符串 "cat" 不匹配。
-
-
-"Cat" => The cat sat on the Cat
-
+正则表达式通常区分大小写,因此正则表达式 `Cat` 与字符串 "cat" 不匹配。"Cat" => The cat sat on the Cat
## 2. 元字符
@@ -66,35 +58,19 @@
## 2.1 英文句号
英文句号 `.` 是元字符的最简单的例子。元字符 `.` 可以匹配任意单个字符。它不会匹配换行符和新行的字符。例如正则表达式 `.ar`,表示: 任意字符后面跟着一个字母 `a`,
-再后面跟着一个字母 `r`。
-
-
-".ar" => The car parked in the garage.
-
+再后面跟着一个字母 `r`。".ar" => The car parked in the garage.
## 2.2 字符集
字符集也称为字符类。方括号被用于指定字符集。使用字符集内的连字符来指定字符范围。方括号内的字符范围的顺序并不重要。
-例如正则表达式 `[Tt]he`,表示: 大写 `T` 或小写 `t` ,后跟字母 `h`,再后跟字母 `e`。
-
-
-"[Tt]he" => The car parked in the garage.
-
+例如正则表达式 `[Tt]he`,表示: 大写 `T` 或小写 `t` ,后跟字母 `h`,再后跟字母 `e`。"[Tt]he" => The car parked in the garage.
-然而,字符集中的英文句号表示它字面的含义。正则表达式 `ar[.]`,表示小写字母 `a`,后面跟着一个字母 `r`,再后面跟着一个英文句号 `.` 字符。
-
-
-"ar[.]" => A garage is a good place to park a car.
-
+然而,字符集中的英文句号表示它字面的含义。正则表达式 `ar[.]`,表示小写字母 `a`,后面跟着一个字母 `r`,再后面跟着一个英文句号 `.` 字符。"ar[.]" => A garage is a good place to park a car.
### 2.2.1 否定字符集
一般来说插入字符 `^` 表示一个字符串的开始,但是当它在方括号内出现时,它会取消字符集。例如正则表达式 `[^c]ar`,表示: 除了字母 `c` 以外的任意字符,后面跟着字符 `a`,
-再后面跟着一个字母 `r`。
-
-
-"[^c]ar" => The car parked in the garage.
-
+再后面跟着一个字母 `r`。"[^c]ar" => The car parked in the garage.
## 2.3 重复
@@ -103,85 +79,42 @@
### 2.3.1 星号
该符号 `*` 表示匹配上一个匹配规则的零次或多次。正则表达式 `a*` 表示小写字母 `a` 可以重复零次或者多次。但是它如果出现在字符集或者字符类之后,它表示整个字符集的重复。
-例如正则表达式 `[a-z]*`,表示: 一行中可以包含任意数量的小写字母。
-
-
-"[a-z]*" => The car parked in the garage #21.
-
+例如正则表达式 `[a-z]*`,表示: 一行中可以包含任意数量的小写字母。"[a-z]*" => The car parked in the garage #21.
该 `*` 符号可以与元符号 `.` 用在一起,用来匹配任意字符串 `.*`。该 `*` 符号可以与空格符 `\s` 一起使用,用来匹配一串空格字符。
-例如正则表达式 `\s*cat\s*`,表示: 零个或多个空格,后面跟小写字母 `c`,再后面跟小写字母 `a`,再再后面跟小写字母 `t`,后面再跟零个或多个空格。
-
-
-"\s*cat\s*" => The fat cat sat on the cat.
-
+例如正则表达式 `\s*cat\s*`,表示: 零个或多个空格,后面跟小写字母 `c`,再后面跟小写字母 `a`,再再后面跟小写字母 `t`,后面再跟零个或多个空格。"\s*cat\s*" => The fat cat sat on the cat.
### 2.3.2 加号
-该符号 `+` 匹配上一个字符的一次或多次。例如正则表达式 `c.+t`,表示: 一个小写字母 `c`,后跟任意数量的字符,后跟小写字母 `t`。
-
-
-"c.+t" => The fat cat sat on the mat.
-
+该符号 `+` 匹配上一个字符的一次或多次。例如正则表达式 `c.+t`,表示: 一个小写字母 `c`,后跟任意数量的字符,后跟小写字母 `t`。"c.+t" => The fat cat sat on the mat.
### 2.3.3 问号
在正则表达式中,元字符 `?` 用来表示前一个字符是可选的。该符号匹配前一个字符的零次或一次。
-例如正则表达式 `[T]?he`,表示: 可选的大写字母 `T`,后面跟小写字母 `h`,后跟小写字母 `e`。
-
-
-"[T]he" => The car is parked in the garage.
-
-
-"[T]?he" => The car is parked in the garage.
-
+例如正则表达式 `[T]?he`,表示: 可选的大写字母 `T`,后面跟小写字母 `h`,后跟小写字母 `e`。"[T]he" => The car is parked in the garage.
"[T]?he" => The car is parked in the garage.
## 2.4 花括号
-在正则表达式中花括号(也被称为量词 ?)用于指定字符或一组字符可以重复的次数。例如正则表达式 `[0-9]{2,3}`,表示: 匹配至少2位数字但不超过3位(0到9范围内的字符)。
-
-
-"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.
-
-
-我们可以省略第二个数字。例如正则表达式 `[0-9]{2,}`,表示: 匹配2个或更多个数字。如果我们也删除逗号,则正则表达式 `[0-9]{2}`,表示: 匹配正好为2位数的数字。
+在正则表达式中花括号(也被称为量词 ?)用于指定字符或一组字符可以重复的次数。例如正则表达式 `[0-9]{2,3}`,表示: 匹配至少2位数字但不超过3位(0到9范围内的字符)。"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.
-
-"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.
-
-
-
-"[0-9]{2}" => The number was 9.9997 but we rounded it off to 10.0.
-
+我们可以省略第二个数字。例如正则表达式 `[0-9]{2,}`,表示: 匹配2个或更多个数字。如果我们也删除逗号,则正则表达式 `[0-9]{2}`,表示: 匹配正好为2位数的数字。"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.
"[0-9]{2}" => The number was 9.9997 but we rounded it off to 10.0.
## 2.5 字符组
字符组是一组写在圆括号内的子模式 `(...)`。正如我们在正则表达式中讨论的那样,如果我们把一个量词放在一个字符之后,它会重复前一个字符。
但是,如果我们把量词放在一个字符组之后,它会重复整个字符组。
-例如正则表达式 `(ab)*` 表示匹配零个或多个的字符串 "ab"。我们还可以在字符组中使用元字符 `|`。例如正则表达式 `(c|g|p)ar`,表示: 小写字母 `c`、`g` 或 `p` 后面跟字母 `a`,后跟字母 `r`。
-
-
-"(c|g|p)ar" => The car is parked in the garage.
-
+例如正则表达式 `(ab)*` 表示匹配零个或多个的字符串 "ab"。我们还可以在字符组中使用元字符 `|`。例如正则表达式 `(c|g|p)ar`,表示: 小写字母 `c`、`g` 或 `p` 后面跟字母 `a`,后跟字母 `r`。"(c|g|p)ar" => The car is parked in the garage.
## 2.6 分支结构
在正则表达式中垂直条 `|` 用来定义分支结构,分支结构就像多个表达式之间的条件。现在你可能认为这个字符集和分支机构的工作方式一样。
但是字符集和分支结构巨大的区别是字符集只在字符级别上有作用,然而分支结构在表达式级别上依然可以使用。
-例如正则表达式 `(T|t)he|car`,表示: 大写字母 `T` 或小写字母 `t`,后面跟小写字母 `h`,后跟小写字母 `e` 或小写字母 `c`,后跟小写字母 `a`,后跟小写字母 `r`。
-
-
-"(T|t)he|car" => The car is parked in the garage.
-
+例如正则表达式 `(T|t)he|car`,表示: 大写字母 `T` 或小写字母 `t`,后面跟小写字母 `h`,后跟小写字母 `e` 或小写字母 `c`,后跟小写字母 `a`,后跟小写字母 `r`。"(T|t)he|car" => The car is parked in the garage.
## 2.7 转义特殊字符
正则表达式中使用反斜杠 `\` 来转义下一个字符。这将允许你使用保留字符来作为匹配字符 `{ } [ ] / \ + * . $ ^ | ?`。在特殊字符前面加 `\`,就可以使用它来做匹配字符。
-例如正则表达式 `.` 是用来匹配除了换行符以外的任意字符。现在要在输入字符串中匹配 `.` 字符,正则表达式 `(f|c|m)at\.?`,表示: 小写字母 `f`、`c` 或者 `m` 后跟小写字母 `a`,后跟小写字母 `t`,后跟可选的 `.` 字符。
-
-
-"(f|c|m)at\.?" => The fat cat sat on the mat.
-
+例如正则表达式 `.` 是用来匹配除了换行符以外的任意字符。现在要在输入字符串中匹配 `.` 字符,正则表达式 `(f|c|m)at\.?`,表示: 小写字母 `f`、`c` 或者 `m` 后跟小写字母 `a`,后跟小写字母 `t`,后跟可选的 `.` 字符。"(f|c|m)at\.?" => The fat cat sat on the mat.
## 2.8 定位符
@@ -192,27 +125,11 @@
插入符号 `^` 符号用于检查匹配字符是否是输入字符串的第一个字符。如果我们使用正则表达式 `^a` (如果a是起始符号)匹配字符串 `abc`,它会匹配到 `a`。
但是如果我们使用正则表达式 `^b`,它是匹配不到任何东西的,因为在字符串 `abc` 中 "b" 不是起始字符。
-让我们来看看另一个正则表达式 `^(T|t)he`,这表示: 大写字母 `T` 或小写字母 `t` 是输入字符串的起始符号,后面跟着小写字母 `h`,后跟小写字母 `e`。
-
-
-"(T|t)he" => The car is parked in the garage.
-
-
-
-"^(T|t)he" => The car is parked in the garage.
-
+让我们来看看另一个正则表达式 `^(T|t)he`,这表示: 大写字母 `T` 或小写字母 `t` 是输入字符串的起始符号,后面跟着小写字母 `h`,后跟小写字母 `e`。"(T|t)he" => The car is parked in the garage.
"^(T|t)he" => The car is parked in the garage.
### 2.8.2 美元符号
-美元 `$` 符号用于检查匹配字符是否是输入字符串的最后一个字符。例如正则表达式 `(at\.)$`,表示: 小写字母 `a`,后跟小写字母 `t`,后跟一个 `.` 字符,且这个匹配器必须是字符串的结尾。
-
-
-"(at\.)" => The fat cat. sat. on the mat.
-
-
-
-"(at\.)$" => The fat cat sat on the mat.
-
+美元 `$` 符号用于检查匹配字符是否是输入字符串的最后一个字符。例如正则表达式 `(at\.)$`,表示: 小写字母 `a`,后跟小写字母 `t`,后跟一个 `.` 字符,且这个匹配器必须是字符串的结尾。"(at\.)" => The fat cat. sat. on the mat.
"(at\.)$" => The fat cat sat on the mat.
## 3. 简写字符集
@@ -246,37 +163,21 @@
正向先行断言认为第一部分的表达式必须是先行断言表达式。返回的匹配结果仅包含与第一部分表达式匹配的文本。
要在一个括号内定义一个正向先行断言,在括号中问号和等号是这样使用的 `(?=...)`。先行断言表达式写在括号中的等号后面。
例如正则表达式 `(T|t)he(?=\sfat)`,表示: 匹配大写字母 `T` 或小写字母 `t`,后面跟字母 `h`,后跟字母 `e`。
-在括号中,我们定义了正向先行断言,它会引导正则表达式引擎匹配 `The` 或 `the` 后面跟着 `fat`。
-
-
-"(T|t)he(?=\sfat)" => The fat cat sat on the mat.
-
+在括号中,我们定义了正向先行断言,它会引导正则表达式引擎匹配 `The` 或 `the` 后面跟着 `fat`。"(T|t)he(?=\sfat)" => The fat cat sat on the mat.
### 4.2 负向先行断言
当我们需要从输入字符串中获取不匹配表达式的内容时,使用负向先行断言。负向先行断言的定义跟我们定义的正向先行断言一样,
唯一的区别是不是等号 `=`,我们使用否定符号 `!`,例如 `(?!...)`。
-我们来看看下面的正则表达式 `(T|t)he(?!\sfat)`,表示: 从输入字符串中获取全部 `The` 或者 `the` 且不匹配 `fat` 前面加上一个空格字符。
-
-
-"(T|t)he(?!\sfat)" => The fat cat sat on the mat.
-
+我们来看看下面的正则表达式 `(T|t)he(?!\sfat)`,表示: 从输入字符串中获取全部 `The` 或者 `the` 且不匹配 `fat` 前面加上一个空格字符。"(T|t)he(?!\sfat)" => The fat cat sat on the mat.
### 4.3 正向后行断言
-正向后行断言是用于获取在特定模式之前的所有匹配内容。正向后行断言表示为 `(?<=...)`。例如正则表达式 `(?<=(T|t)he\s)(fat|mat)`,表示: 从输入字符串中获取在单词 `The` 或 `the` 之后的所有 `fat` 和 `mat` 单词。
-
-
-"(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat.
-
+正向后行断言是用于获取在特定模式之前的所有匹配内容。正向后行断言表示为 `(?<=...)`。例如正则表达式 `(?<=(T|t)he\s)(fat|mat)`,表示: 从输入字符串中获取在单词 `The` 或 `the` 之后的所有 `fat` 和 `mat` 单词。"(?<=(T|t)he\s)(fat|mat)" => The fat cat sat on the mat.
### 4.4 负向后行断言
-负向后行断言是用于获取不在特定模式之前的所有匹配的内容。负向后行断言表示为 `(?
-"(?<!(T|t)he\s)(cat)" => The cat sat on cat.
-
+负向后行断言是用于获取不在特定模式之前的所有匹配的内容。负向后行断言表示为 `(?"(?<!(T|t)he\s)(cat)" => The cat sat on cat.
## 5. 标记
@@ -291,46 +192,22 @@
### 5.1 不区分大小写
`i` 修饰符用于执行不区分大小写匹配。例如正则表达式 `/The/gi`,表示: 大写字母 `T`,后跟小写字母 `h`,后跟字母 `e`。
-但是在正则匹配结束时 `i` 标记会告诉正则表达式引擎忽略这种情况。正如你所看到的,我们还使用了 `g` 标记,因为我们要在整个输入字符串中搜索匹配。
-
-
-"The" => The fat cat sat on the mat.
-
-
-
-"/The/gi" => The fat cat sat on the mat.
-
+但是在正则匹配结束时 `i` 标记会告诉正则表达式引擎忽略这种情况。正如你所看到的,我们还使用了 `g` 标记,因为我们要在整个输入字符串中搜索匹配。"The" => The fat cat sat on the mat.
"/The/gi" => The fat cat sat on the mat.
### 5.2 全局搜索
`g` 修饰符用于执行全局匹配 (会查找所有匹配,不会在查找到第一个匹配时就停止)。
例如正则表达式 `/.(at)/g`,表示: 除换行符之外的任意字符,后跟小写字母 `a`,后跟小写字母 `t`。
-因为我们在正则表达式的末尾使用了 `g` 标记,它会从整个输入字符串中找到每个匹配项。
-
-
-".(at)" => The fat cat sat on the mat.
-
-
-
-"/.(at)/g" => The fat cat sat on the mat.
-
+因为我们在正则表达式的末尾使用了 `g` 标记,它会从整个输入字符串中找到每个匹配项。".(at)" => The fat cat sat on the mat.
"/.(at)/g" => The fat cat sat on the mat.
### 5.3 多行匹配
`m` 修饰符被用来执行多行的匹配。正如我们前面讨论过的 `(^, $)`,使用定位符来检查匹配字符是输入字符串开始或者结束。但是我们希望每一行都使用定位符,所以我们就使用 `m` 修饰符。
-例如正则表达式 `/at(.)?$/gm`,表示: 小写字母 `a`,后跟小写字母 `t`,匹配除了换行符以外任意字符零次或一次。而且因为 `m` 标记,现在正则表达式引擎匹配字符串中每一行的末尾。
-
-
-"/.at(.)?$/" => The fat
+例如正则表达式 `/at(.)?$/gm`,表示: 小写字母 `a`,后跟小写字母 `t`,匹配除了换行符以外任意字符零次或一次。而且因为 `m` 标记,现在正则表达式引擎匹配字符串中每一行的末尾。"/.at(.)?$/" => The fat
cat sat
- on the mat.
-
-
-
-"/.at(.)?$/gm" => The fat
+ on the mat.
"/.at(.)?$/gm" => The fat
cat sat
- on the mat.
-
+ on the mat.
## 常用正则表达式
diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md
index f89732b07..f3d953e59 100644
--- a/app/src/main/assets/updateLog.md
+++ b/app/src/main/assets/updateLog.md
@@ -3,6 +3,25 @@
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
+**2021/01/04**
+* 修复bug
+
+**2021/01/03**
+* 导出书单只保留书名与作者,导入时自动查找可用源
+* 添加预加载设置
+* 选择分组时只搜索分组
+
+**2020/12/30**
+* 解决文件下载异常,在线语音可正常播放 by [Celeter](https://github.com/Celeter)
+* 更新默认在线朗读库, 默认id小于0方便下次更新时删除旧数据, 有重复的自己删除
+* 导入导出书单
+* 其它一些优化
+
+**2020/12/27**
+* 订阅添加搜索和分组
+* 修复部分手机状态栏bug
+* 单url订阅支持内容规则和样式
+
**2020/12/19**
* 书签转移到文本菜单里,会记录选择的文本和位置
* 订阅源添加单url选项,直接打开url
diff --git a/app/src/main/java/io/legado/app/api/ReaderProvider.kt b/app/src/main/java/io/legado/app/api/ReaderProvider.kt
index b45d2f5c0..897b0d757 100644
--- a/app/src/main/java/io/legado/app/api/ReaderProvider.kt
+++ b/app/src/main/java/io/legado/app/api/ReaderProvider.kt
@@ -12,7 +12,6 @@ import android.net.Uri
import com.google.gson.Gson
import io.legado.app.api.controller.BookshelfController
import io.legado.app.api.controller.SourceController
-import io.legado.app.web.utils.ReturnData
import java.util.*
/**
diff --git a/app/src/main/java/io/legado/app/web/utils/ReturnData.kt b/app/src/main/java/io/legado/app/api/ReturnData.kt
similarity index 63%
rename from app/src/main/java/io/legado/app/web/utils/ReturnData.kt
rename to app/src/main/java/io/legado/app/api/ReturnData.kt
index 7c460e5e2..f6d6aefdc 100644
--- a/app/src/main/java/io/legado/app/web/utils/ReturnData.kt
+++ b/app/src/main/java/io/legado/app/api/ReturnData.kt
@@ -1,4 +1,4 @@
-package io.legado.app.web.utils
+package io.legado.app.api
class ReturnData {
@@ -6,18 +6,13 @@ class ReturnData {
var isSuccess: Boolean = false
private set
- var errorMsg: String? = null
+ var errorMsg: String = "未知错误,请联系开发者!"
private set
var data: Any? = null
private set
- init {
- this.isSuccess = false
- this.errorMsg = "未知错误,请联系开发者!"
- }
-
- fun setErrorMsg(errorMsg: String?): ReturnData {
+ fun setErrorMsg(errorMsg: String): ReturnData {
this.isSuccess = false
this.errorMsg = errorMsg
return this
diff --git a/app/src/main/java/io/legado/app/api/controller/BookshelfController.kt b/app/src/main/java/io/legado/app/api/controller/BookshelfController.kt
index 55410156e..c8e878f0f 100644
--- a/app/src/main/java/io/legado/app/api/controller/BookshelfController.kt
+++ b/app/src/main/java/io/legado/app/api/controller/BookshelfController.kt
@@ -1,6 +1,7 @@
package io.legado.app.api.controller
import io.legado.app.App
+import io.legado.app.api.ReturnData
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book
import io.legado.app.help.BookHelp
@@ -10,7 +11,6 @@ import io.legado.app.utils.GSON
import io.legado.app.utils.cnCompare
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.getPrefInt
-import io.legado.app.web.utils.ReturnData
import kotlinx.coroutines.runBlocking
object BookshelfController {
@@ -66,7 +66,7 @@ object BookshelfController {
} else {
App.db.bookSourceDao.getBookSource(book.origin)?.let { source ->
runBlocking {
- WebBook(source).getContentSuspend(book, chapter)
+ WebBook(source).getContentAwait(this, book, chapter)
}.let {
saveBookReadIndex(book, index)
returnData.setData(it)
diff --git a/app/src/main/java/io/legado/app/api/controller/SourceController.kt b/app/src/main/java/io/legado/app/api/controller/SourceController.kt
index 69d609931..6dc142804 100644
--- a/app/src/main/java/io/legado/app/api/controller/SourceController.kt
+++ b/app/src/main/java/io/legado/app/api/controller/SourceController.kt
@@ -3,11 +3,12 @@ package io.legado.app.api.controller
import android.text.TextUtils
import io.legado.app.App
+import io.legado.app.api.ReturnData
import io.legado.app.data.entities.BookSource
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
import io.legado.app.utils.fromJsonObject
-import io.legado.app.web.utils.ReturnData
+import io.legado.app.utils.msg
object SourceController {
@@ -22,7 +23,7 @@ object SourceController {
fun saveSource(postData: String?): ReturnData {
val returnData = ReturnData()
- try {
+ kotlin.runCatching {
val bookSource = GSON.fromJsonObject(postData)
if (bookSource != null) {
if (TextUtils.isEmpty(bookSource.bookSourceName) || TextUtils.isEmpty(bookSource.bookSourceUrl)) {
@@ -34,8 +35,8 @@ object SourceController {
} else {
returnData.setErrorMsg("转换书源失败")
}
- } catch (e: Exception) {
- returnData.setErrorMsg(e.localizedMessage)
+ }.onFailure {
+ returnData.setErrorMsg(it.msg)
}
return returnData
}
diff --git a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
index 6b2ca45ba..468dd4c95 100644
--- a/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
+++ b/app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
@@ -32,13 +32,10 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun show(manager: FragmentManager, tag: String?) {
- try {
+ kotlin.runCatching {
//在每个add事务前增加一个remove事务,防止连续的add
manager.beginTransaction().remove(this).commit()
super.show(manager, tag)
- } catch (e: Exception) {
- //同一实例使用不同的tag会异常,这里捕获一下
- e.printStackTrace()
}
}
diff --git a/app/src/main/java/io/legado/app/base/adapter/DiffRecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/DiffRecyclerAdapter.kt
index 258f93c87..60d3600ed 100644
--- a/app/src/main/java/io/legado/app/base/adapter/DiffRecyclerAdapter.kt
+++ b/app/src/main/java/io/legado/app/base/adapter/DiffRecyclerAdapter.kt
@@ -26,8 +26,6 @@ abstract class DiffRecyclerAdapter- (protected val context
}
}
- private val lock = Object()
-
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null
@@ -47,39 +45,46 @@ abstract class DiffRecyclerAdapter
- (protected val context
recyclerView.adapter = this
}
+ @Synchronized
fun setItems(items: List
- ?) {
- synchronized(lock) {
+ kotlin.runCatching {
asyncListDiffer.submitList(items)
}
}
+ @Synchronized
fun setItem(position: Int, item: ITEM) {
- synchronized(lock) {
+ kotlin.runCatching {
val list = ArrayList(asyncListDiffer.currentList)
list[position] = item
asyncListDiffer.submitList(list)
}
}
- fun updateItem(item: ITEM) =
- synchronized(lock) {
+ @Synchronized
+ fun updateItem(item: ITEM) {
+ kotlin.runCatching {
val index = asyncListDiffer.currentList.indexOf(item)
if (index >= 0) {
asyncListDiffer.currentList[index] = item
notifyItemChanged(index)
}
}
+ }
- fun updateItem(position: Int, payload: Any) =
- synchronized(lock) {
+ @Synchronized
+ fun updateItem(position: Int, payload: Any) {
+ kotlin.runCatching {
val size = itemCount
if (position in 0 until size) {
notifyItemChanged(position, payload)
}
}
+ }
- fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) =
- synchronized(lock) {
+ @Synchronized
+ fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) {
+ kotlin.runCatching {
val size = itemCount
if (fromPosition in 0 until size && toPosition in 0 until size) {
notifyItemRangeChanged(
@@ -89,6 +94,7 @@ abstract class DiffRecyclerAdapter
- (protected val context
)
}
}
+ }
fun isEmpty() = asyncListDiffer.currentList.isEmpty()
diff --git a/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt b/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt
index 7ddbaf581..0d6e3b668 100644
--- a/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt
+++ b/app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt
@@ -26,8 +26,6 @@ abstract class RecyclerAdapter
- (protected val context: Co
private val items: MutableList
- = mutableListOf()
- private val lock = Object()
-
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null
@@ -45,42 +43,49 @@ abstract class RecyclerAdapter
- (protected val context: Co
recyclerView.adapter = this
}
+ @Synchronized
fun addHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {
- synchronized(lock) {
+ kotlin.runCatching {
val index = headerItems.size()
headerItems.put(TYPE_HEADER_VIEW + headerItems.size(), header)
notifyItemInserted(index)
}
}
- fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) =
- synchronized(lock) {
+ @Synchronized
+ fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
+ kotlin.runCatching {
val index = getActualItemCount() + footerItems.size()
footerItems.put(TYPE_FOOTER_VIEW + footerItems.size(), footer)
notifyItemInserted(index)
}
+ }
-
- fun removeHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) =
- synchronized(lock) {
+ @Synchronized
+ fun removeHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {
+ kotlin.runCatching {
val index = headerItems.indexOfValue(header)
if (index >= 0) {
headerItems.remove(index)
notifyItemRemoved(index)
}
}
+ }
- fun removeFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) =
- synchronized(lock) {
+ @Synchronized
+ fun removeFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {
+ kotlin.runCatching {
val index = footerItems.indexOfValue(footer)
if (index >= 0) {
footerItems.remove(index)
notifyItemRemoved(getActualItemCount() + index - 2)
}
}
+ }
+ @Synchronized
fun setItems(items: List
- ?) {
- synchronized(lock) {
+ kotlin.runCatching {
if (this.items.isNotEmpty()) {
this.items.clear()
}
@@ -92,8 +97,46 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
- fun setItems(items: List
- ?, diffResult: DiffUtil.DiffResult) {
- synchronized(lock) {
+ @Synchronized
+ fun setItems(items: List
- ?, itemCallback: DiffUtil.ItemCallback
- ) {
+ kotlin.runCatching {
+ val callback = object : DiffUtil.Callback() {
+ override fun getOldListSize(): Int {
+ return itemCount
+ }
+
+ override fun getNewListSize(): Int {
+ return (items?.size ?: 0) + getHeaderCount() + getFooterCount()
+ }
+
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+ val oldItem = getItem(oldItemPosition - getHeaderCount())
+ ?: return true
+ val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
+ ?: return true
+ return itemCallback.areItemsTheSame(oldItem, newItem)
+ }
+
+ override fun areContentsTheSame(
+ oldItemPosition: Int,
+ newItemPosition: Int
+ ): Boolean {
+ val oldItem = getItem(oldItemPosition - getHeaderCount())
+ ?: return true
+ val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
+ ?: return true
+ return itemCallback.areContentsTheSame(oldItem, newItem)
+ }
+
+ override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
+ val oldItem = getItem(oldItemPosition - getHeaderCount())
+ ?: return null
+ val newItem = items?.getOrNull(newItemPosition - getHeaderCount())
+ ?: return null
+ return itemCallback.getChangePayload(oldItem, newItem)
+ }
+ }
+ val diffResult = DiffUtil.calculateDiff(callback)
if (this.items.isNotEmpty()) {
this.items.clear()
}
@@ -105,8 +148,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun setItem(position: Int, item: ITEM) {
- synchronized(lock) {
+ kotlin.runCatching {
val oldSize = getActualItemCount()
if (position in 0 until oldSize) {
this.items[position] = item
@@ -116,8 +160,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun addItem(item: ITEM) {
- synchronized(lock) {
+ kotlin.runCatching {
val oldSize = getActualItemCount()
if (this.items.add(item)) {
notifyItemInserted(oldSize + getHeaderCount())
@@ -126,8 +171,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun addItems(position: Int, newItems: List
- ) {
- synchronized(lock) {
+ kotlin.runCatching {
if (this.items.addAll(position, newItems)) {
notifyItemRangeInserted(position + getHeaderCount(), newItems.size)
}
@@ -135,8 +181,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun addItems(newItems: List
- ) {
- synchronized(lock) {
+ kotlin.runCatching {
val oldSize = getActualItemCount()
if (this.items.addAll(newItems)) {
if (oldSize == 0 && getHeaderCount() == 0) {
@@ -149,8 +196,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun removeItem(position: Int) {
- synchronized(lock) {
+ kotlin.runCatching {
if (this.items.removeAt(position) != null) {
notifyItemRemoved(position + getHeaderCount())
}
@@ -158,8 +206,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun removeItem(item: ITEM) {
- synchronized(lock) {
+ kotlin.runCatching {
if (this.items.remove(item)) {
notifyItemRemoved(this.items.indexOf(item) + getHeaderCount())
}
@@ -167,8 +216,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun removeItems(items: List
- ) {
- synchronized(lock) {
+ kotlin.runCatching {
if (this.items.removeAll(items)) {
notifyDataSetChanged()
}
@@ -176,8 +226,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
+ @Synchronized
fun swapItem(oldPosition: Int, newPosition: Int) {
- synchronized(lock) {
+ kotlin.runCatching {
val size = getActualItemCount()
if (oldPosition in 0 until size && newPosition in 0 until size) {
val srcPosition = oldPosition + getHeaderCount()
@@ -189,8 +240,9 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
}
- fun updateItem(item: ITEM) =
- synchronized(lock) {
+ @Synchronized
+ fun updateItem(item: ITEM) {
+ kotlin.runCatching {
val index = this.items.indexOf(item)
if (index >= 0) {
this.items[index] = item
@@ -198,17 +250,21 @@ abstract class RecyclerAdapter
- (protected val context: Co
}
onCurrentListChanged()
}
+ }
- fun updateItem(position: Int, payload: Any) =
- synchronized(lock) {
+ @Synchronized
+ fun updateItem(position: Int, payload: Any) {
+ kotlin.runCatching {
val size = getActualItemCount()
if (position in 0 until size) {
notifyItemChanged(position + getHeaderCount(), payload)
}
}
+ }
- fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) =
- synchronized(lock) {
+ @Synchronized
+ fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) {
+ kotlin.runCatching {
val size = getActualItemCount()
if (fromPosition in 0 until size && toPosition in 0 until size) {
notifyItemRangeChanged(
@@ -218,11 +274,15 @@ abstract class RecyclerAdapter
- (protected val context: Co
)
}
}
+ }
- fun clearItems() = synchronized(lock) {
- this.items.clear()
- notifyDataSetChanged()
- onCurrentListChanged()
+ @Synchronized
+ fun clearItems() {
+ kotlin.runCatching {
+ this.items.clear()
+ notifyDataSetChanged()
+ onCurrentListChanged()
+ }
}
fun isEmpty() = items.isEmpty()
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 de28caddc..f4e902ba0 100644
--- a/app/src/main/java/io/legado/app/constant/PreferKey.kt
+++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt
@@ -64,6 +64,7 @@ object PreferKey {
const val importKeepName = "importKeepName"
const val screenOrientation = "screenOrientation"
const val syncBookProgress = "syncBookProgress"
+ const val preDownload = "preDownload"
const val cPrimary = "colorPrimary"
const val cAccent = "colorAccent"
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 e7ffc7fe0..3655e0117 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,6 +14,9 @@ interface BookSourceDao {
@Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey or bookSourceComment like :searchKey order by customOrder asc")
fun liveDataSearch(searchKey: String = ""): LiveData
>
+ @Query("select * from book_sources where bookSourceGroup like :searchKey order by customOrder asc")
+ fun liveDataGroupSearch(searchKey: String = ""): LiveData>
+
@Query("select * from book_sources where enabled = 1 order by customOrder asc")
fun liveDataEnabled(): LiveData>
@@ -26,6 +29,9 @@ interface BookSourceDao {
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc")
fun liveExplore(key: String): LiveData>
+ @Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and (bookSourceGroup like :key) order by customOrder asc")
+ fun liveGroupExplore(key: String): LiveData>
+
@Query("select bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''")
fun liveGroup(): LiveData>
@@ -33,7 +39,7 @@ interface BookSourceDao {
fun liveGroupEnabled(): LiveData>
@Query("select bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''")
- fun liveGroupExplore(): LiveData>
+ fun liveExploreGroup(): LiveData>
@Query("select distinct enabled from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey")
fun searchIsEnable(searchKey: String = ""): List
diff --git a/app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt b/app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt
index f71169bb1..f97c17af1 100644
--- a/app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt
+++ b/app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt
@@ -28,4 +28,6 @@ interface HttpTTSDao {
@Update
fun update(vararg httpTTS: HttpTTS)
+ @Query("delete from httpTTS where id < 0")
+ fun deleteDefault()
}
\ 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 ad374f5c9..2a0e1149c 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
@@ -14,6 +14,9 @@ interface ReplaceRuleDao {
@Query("SELECT * FROM replace_rules where `group` like :key or name like :key ORDER BY sortOrder ASC")
fun liveDataSearch(key: String): LiveData>
+ @Query("SELECT * FROM replace_rules where `group` like :key ORDER BY sortOrder ASC")
+ fun liveDataGroupSearch(key: String): LiveData>
+
@get:Query("SELECT MIN(sortOrder) FROM replace_rules")
val minOrder: Int
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 c2d1b80ee..ad2b25706 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
@@ -25,6 +25,9 @@ interface RssSourceDao {
@Query("SELECT * FROM rssSources where sourceName like :key or sourceUrl like :key or sourceGroup like :key order by customOrder")
fun liveSearch(key: String): LiveData>
+ @Query("SELECT * FROM rssSources where sourceGroup like :key order by customOrder")
+ fun liveGroupSearch(key: String): LiveData>
+
@Query("SELECT * FROM rssSources where enabled = 1 and (sourceName like '%'||:searchKey||'%' or sourceGroup like '%'||:searchKey||'%' or sourceUrl like '%'||:searchKey||'%') order by customOrder")
fun liveEnabled(searchKey: String): LiveData>
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 5604cec7c..b439fb367 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
@@ -46,14 +46,15 @@ data class BookSource(
var ruleBookInfo: BookInfoRule? = null, // 书籍信息页规则
var ruleToc: TocRule? = null, // 目录页规则
var ruleContent: ContentRule? = null // 正文页规则
-): Parcelable, JsExtensions {
-
+) : Parcelable, JsExtensions {
+
override fun hashCode(): Int {
return bookSourceUrl.hashCode()
}
-
- override fun equals(other: Any?) = if (other is BookSource) other.bookSourceUrl == bookSourceUrl else false
-
+
+ override fun equals(other: Any?) =
+ if (other is BookSource) other.bookSourceUrl == bookSourceUrl else false
+
@Throws(Exception::class)
fun getHeaderMap() = (HashMap().apply {
this[AppConst.UA_NAME] = AppConfig.userAgent
@@ -71,17 +72,17 @@ data class BookSource(
}
}
}) as Map
-
+
fun getSearchRule() = ruleSearch ?: SearchRule()
-
+
fun getExploreRule() = ruleExplore ?: ExploreRule()
-
+
fun getBookInfoRule() = ruleBookInfo ?: BookInfoRule()
-
+
fun getTocRule() = ruleToc ?: TocRule()
-
+
fun getContentRule() = ruleContent ?: ContentRule()
-
+
fun addGroup(group: String) {
bookSourceGroup?.let {
if (!it.contains(group)) {
@@ -91,20 +92,20 @@ data class BookSource(
bookSourceGroup = group
}
}
-
+
fun removeGroup(group: String) {
bookSourceGroup?.splitNotBlank("[,;,;]".toRegex())?.toHashSet()?.let {
it.remove(group)
bookSourceGroup = TextUtils.join(",", it)
}
}
-
+
fun getExploreKinds() = arrayListOf().apply {
- exploreUrl?.let {
- var a = it
+ exploreUrl?.let { urlRule ->
+ var a = urlRule
if (a.isNotBlank()) {
- try {
- if (it.startsWith("", false)) {
+ kotlin.runCatching {
+ if (urlRule.startsWith("", false)) {
val aCache = ACache.get(App.INSTANCE, "explore")
a = aCache.getAsString(bookSourceUrl) ?: ""
if (a.isBlank()) {
@@ -114,7 +115,7 @@ data class BookSource(
bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager
a = AppConst.SCRIPT_ENGINE.eval(
- it.substring(4, it.lastIndexOf("<")),
+ urlRule.substring(4, urlRule.lastIndexOf("<")),
bindings
).toString()
aCache.put(bookSourceUrl, a)
@@ -126,13 +127,13 @@ data class BookSource(
if (d.size > 1)
add(ExploreKind(d[0], d[1]))
}
- } catch (e: Exception) {
- add(ExploreKind(e.localizedMessage ?: ""))
+ }.onFailure {
+ add(ExploreKind(it.localizedMessage ?: ""))
}
}
}
}
-
+
/**
* 执行JS
*/
@@ -147,60 +148,60 @@ data class BookSource(
fun equal(source: BookSource) =
equal(bookSourceName, source.bookSourceName)
- && equal(bookSourceUrl, source.bookSourceUrl)
- && equal(bookSourceGroup, source.bookSourceGroup)
- && bookSourceType == source.bookSourceType
- && equal(bookUrlPattern, source.bookUrlPattern)
- && equal(bookSourceComment, source.bookSourceComment)
- && enabled == source.enabled
- && enabledExplore == source.enabledExplore
- && equal(header, source.header)
- && equal(loginUrl, source.loginUrl)
- && equal(exploreUrl, source.exploreUrl)
- && equal(searchUrl, source.searchUrl)
- && getSearchRule() == source.getSearchRule()
- && getExploreRule() == source.getExploreRule()
- && getBookInfoRule() == source.getBookInfoRule()
- && getTocRule() == source.getTocRule()
- && getContentRule() == source.getContentRule()
-
+ && equal(bookSourceUrl, source.bookSourceUrl)
+ && equal(bookSourceGroup, source.bookSourceGroup)
+ && bookSourceType == source.bookSourceType
+ && equal(bookUrlPattern, source.bookUrlPattern)
+ && equal(bookSourceComment, source.bookSourceComment)
+ && enabled == source.enabled
+ && enabledExplore == source.enabledExplore
+ && equal(header, source.header)
+ && equal(loginUrl, source.loginUrl)
+ && equal(exploreUrl, source.exploreUrl)
+ && equal(searchUrl, source.searchUrl)
+ && getSearchRule() == source.getSearchRule()
+ && getExploreRule() == source.getExploreRule()
+ && getBookInfoRule() == source.getBookInfoRule()
+ && getTocRule() == source.getTocRule()
+ && getContentRule() == source.getContentRule()
+
private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())
data class ExploreKind(
var title: String,
var url: String? = null
)
-
+
class Converters {
@TypeConverter
fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule)
-
+
@TypeConverter
fun stringToExploreRule(json: String?) = GSON.fromJsonObject(json)
@TypeConverter
fun searchRuleToString(searchRule: SearchRule?): String = GSON.toJson(searchRule)
-
+
@TypeConverter
fun stringToSearchRule(json: String?) = GSON.fromJsonObject(json)
@TypeConverter
fun bookInfoRuleToString(bookInfoRule: BookInfoRule?): String = GSON.toJson(bookInfoRule)
-
+
@TypeConverter
fun stringToBookInfoRule(json: String?) = GSON.fromJsonObject(json)
@TypeConverter
fun tocRuleToString(tocRule: TocRule?): String = GSON.toJson(tocRule)
-
+
@TypeConverter
fun stringToTocRule(json: String?) = GSON.fromJsonObject(json)
-
+
@TypeConverter
fun contentRuleToString(contentRule: ContentRule?): String = GSON.toJson(contentRule)
-
+
@TypeConverter
fun stringToContentRule(json: String?) = GSON.fromJsonObject(json)
-
+
}
}
\ 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 1b5f7f8f0..d805d9dd0 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
@@ -49,8 +49,7 @@ data class ReplaceRule(
if (isRegex){
try {
Pattern.compile(pattern)
- }
- catch (ex: PatternSyntaxException){
+ } catch (ex: PatternSyntaxException) {
return false
}
}
diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt
index 076bbfdc4..1c5c10a72 100644
--- a/app/src/main/java/io/legado/app/help/AppConfig.kt
+++ b/app/src/main/java/io/legado/app/help/AppConfig.kt
@@ -149,6 +149,8 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
val syncBookProgress get() = context.getPrefBoolean(PreferKey.syncBookProgress, true)
+ val preDownload get() = context.getPrefBoolean(PreferKey.preDownload, true)
+
private fun getPrefUserAgent(): String {
val ua = context.getPrefString(PreferKey.userAgent)
if (ua.isNullOrBlank()) {
diff --git a/app/src/main/java/io/legado/app/help/DefaultData.kt b/app/src/main/java/io/legado/app/help/DefaultData.kt
index db14e1f29..d2bab07f1 100644
--- a/app/src/main/java/io/legado/app/help/DefaultData.kt
+++ b/app/src/main/java/io/legado/app/help/DefaultData.kt
@@ -45,6 +45,13 @@ object DefaultData {
GSON.fromJsonArray(json)!!
}
+ fun importDefaultHttpTTS() {
+ App.db.httpTTSDao.deleteDefault()
+ httpTTS.let {
+ App.db.httpTTSDao.insert(*it.toTypedArray())
+ }
+ }
+
fun importDefaultTocRules() {
App.db.txtTocRule.deleteDefault()
txtTocRules.let {
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 2184d03d3..cc702ff0f 100644
--- a/app/src/main/java/io/legado/app/help/ImageLoader.kt
+++ b/app/src/main/java/io/legado/app/help/ImageLoader.kt
@@ -22,9 +22,9 @@ object ImageLoader {
path.isNullOrEmpty() -> Glide.with(context).load(path)
path.isAbsUrl() -> Glide.with(context).load(AnalyzeUrl(path).getGlideUrl())
path.isContentScheme() -> Glide.with(context).load(Uri.parse(path))
- else -> try {
+ else -> kotlin.runCatching {
Glide.with(context).load(File(path))
- } catch (e: Exception) {
+ }.getOrElse {
Glide.with(context).load(path)
}
}
diff --git a/app/src/main/java/io/legado/app/help/IntentHelp.kt b/app/src/main/java/io/legado/app/help/IntentHelp.kt
index 05b580272..7524bed78 100644
--- a/app/src/main/java/io/legado/app/help/IntentHelp.kt
+++ b/app/src/main/java/io/legado/app/help/IntentHelp.kt
@@ -13,23 +13,23 @@ object IntentHelp {
fun toTTSSetting(context: Context) {
//跳转到文字转语音设置界面
- try {
+ kotlin.runCatching {
val intent = Intent()
intent.action = "com.android.settings.TTS_SETTINGS"
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
- } catch (ignored: Exception) {
+ }.onFailure {
context.toast(R.string.tip_cannot_jump_setting_page)
}
}
fun toInstallUnknown(context: Context) {
- try {
+ kotlin.runCatching {
val intent = Intent()
intent.action = "android.settings.MANAGE_UNKNOWN_APP_SOURCES"
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
- } catch (ignored: Exception) {
+ }.onFailure {
context.toast("无法打开设置")
}
}
diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt
index aa6865aed..7a38e2a33 100644
--- a/app/src/main/java/io/legado/app/help/JsExtensions.kt
+++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt
@@ -29,13 +29,15 @@ interface JsExtensions {
* 访问网络,返回String
*/
fun ajax(urlStr: String): String? {
- return try {
- val analyzeUrl = AnalyzeUrl(urlStr)
- runBlocking {
+ return runBlocking {
+ kotlin.runCatching {
+ val analyzeUrl = AnalyzeUrl(urlStr)
analyzeUrl.getStrResponse(urlStr).body
+ }.onFailure {
+ it.printStackTrace()
+ }.getOrElse {
+ it.msg
}
- } catch (e: Exception) {
- e.msg
}
}
@@ -43,13 +45,15 @@ interface JsExtensions {
* 访问网络,返回Response
*/
fun connect(urlStr: String): Any {
- return try {
- val analyzeUrl = AnalyzeUrl(urlStr)
- runBlocking {
+ return runBlocking {
+ kotlin.runCatching {
+ val analyzeUrl = AnalyzeUrl(urlStr)
analyzeUrl.getStrResponse(urlStr)
+ }.onFailure {
+ it.printStackTrace()
+ }.getOrElse {
+ it.msg
}
- } catch (e: Exception) {
- e.msg
}
}
@@ -65,7 +69,7 @@ interface JsExtensions {
FileUtils.deleteFile(zipPath)
val zipFile = FileUtils.createFileIfNotExist(zipPath)
StringUtils.hexStringToByte(content).let {
- if (it != null) {
+ if (it.isNotEmpty()) {
zipFile.writeBytes(it)
}
}
diff --git a/app/src/main/java/io/legado/app/help/LocalConfig.kt b/app/src/main/java/io/legado/app/help/LocalConfig.kt
index 084113293..ac0d3034d 100644
--- a/app/src/main/java/io/legado/app/help/LocalConfig.kt
+++ b/app/src/main/java/io/legado/app/help/LocalConfig.kt
@@ -63,4 +63,10 @@ object LocalConfig {
val ruleHelpVersionIsLast: Boolean
get() = isLastVersion(1, "ruleHelpVersion")
+
+ val hasUpHttpTTS: Boolean
+ get() = isLastVersion(1, "httpTtsVersion")
+
+ val hasUpTxtTocRule: Boolean
+ get() = isLastVersion(1, "txtTocRuleVersion")
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/help/ThemeConfig.kt b/app/src/main/java/io/legado/app/help/ThemeConfig.kt
index b4875ef8b..9de5ceade 100644
--- a/app/src/main/java/io/legado/app/help/ThemeConfig.kt
+++ b/app/src/main/java/io/legado/app/help/ThemeConfig.kt
@@ -61,11 +61,11 @@ object ThemeConfig {
private fun getConfigs(): List? {
val configFile = File(configFilePath)
if (configFile.exists()) {
- try {
+ kotlin.runCatching {
val json = configFile.readText()
return GSON.fromJsonArray(json)
- } catch (e: Exception) {
- e.printStackTrace()
+ }.onFailure {
+ it.printStackTrace()
}
}
return null
diff --git a/app/src/main/java/io/legado/app/help/http/parser/TextParser.kt b/app/src/main/java/io/legado/app/help/http/parser/TextParser.kt
index 09891944d..031ad9d33 100644
--- a/app/src/main/java/io/legado/app/help/http/parser/TextParser.kt
+++ b/app/src/main/java/io/legado/app/help/http/parser/TextParser.kt
@@ -8,7 +8,7 @@ import rxhttp.wrapper.exception.HttpStatusCodeException
import java.nio.charset.Charset
@Parser(name = "Text")
-class TextParser(val encode: String? = null) : rxhttp.wrapper.parse.Parser {
+class TextParser(private val encode: String? = null) : rxhttp.wrapper.parse.Parser {
override fun onParse(response: Response): String {
diff --git a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
index 33827f829..178f8369a 100644
--- a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
+++ b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
@@ -15,61 +15,61 @@ object ImportOldData {
fun importUri(context: Context, uri: Uri) {
if (uri.isContentScheme()) {
- DocumentFile.fromTreeUri(context, uri)?.listFiles()?.forEach {
- when (it.name) {
+ DocumentFile.fromTreeUri(context, uri)?.listFiles()?.forEach { doc ->
+ when (doc.name) {
"myBookShelf.json" ->
- try {
- DocumentUtils.readText(context, it.uri)?.let { json ->
+ kotlin.runCatching {
+ DocumentUtils.readText(context, doc.uri)?.let { json ->
val importCount = importOldBookshelf(json)
context.toast("成功导入书籍${importCount}")
}
- } catch (e: java.lang.Exception) {
- context.toast("导入书籍失败\n${e.localizedMessage}")
+ }.onFailure {
+ context.toast("导入书籍失败\n${it.localizedMessage}")
}
"myBookSource.json" ->
- try {
- DocumentUtils.readText(context, it.uri)?.let { json ->
+ kotlin.runCatching {
+ DocumentUtils.readText(context, doc.uri)?.let { json ->
val importCount = importOldSource(json)
context.toast("成功导入书源${importCount}")
}
- } catch (e: Exception) {
- context.toast("导入源失败\n${e.localizedMessage}")
+ }.onFailure {
+ context.toast("导入源失败\n${it.localizedMessage}")
}
"myBookReplaceRule.json" ->
- try {
- DocumentUtils.readText(context, it.uri)?.let { json ->
+ kotlin.runCatching {
+ DocumentUtils.readText(context, doc.uri)?.let { json ->
val importCount = importOldReplaceRule(json)
context.toast("成功导入替换规则${importCount}")
}
- } catch (e: Exception) {
- context.toast("导入替换规则失败\n${e.localizedMessage}")
+ }.onFailure {
+ context.toast("导入替换规则失败\n${it.localizedMessage}")
}
}
}
} else {
- uri.path?.let {
- val file = File(it)
- try {// 导入书架
+ uri.path?.let { path ->
+ val file = File(path)
+ kotlin.runCatching {// 导入书架
val shelfFile =
FileUtils.createFileIfNotExist(file, "myBookShelf.json")
val json = shelfFile.readText()
val importCount = importOldBookshelf(json)
context.toast("成功导入书籍${importCount}")
- } catch (e: Exception) {
- context.toast("导入书籍失败\n${e.localizedMessage}")
+ }.onFailure {
+ context.toast("导入书籍失败\n${it.localizedMessage}")
}
- try {// Book source
+ kotlin.runCatching {// Book source
val sourceFile =
FileUtils.getFile(file, "myBookSource.json")
val json = sourceFile.readText()
val importCount = importOldSource(json)
context.toast("成功导入书源${importCount}")
- } catch (e: Exception) {
- context.toast("导入源失败\n${e.localizedMessage}")
+ }.onFailure {
+ context.toast("导入源失败\n${it.localizedMessage}")
}
- try {// Replace rules
+ kotlin.runCatching {// Replace rules
val ruleFile = FileUtils.getFile(file, "myBookReplaceRule.json")
if (ruleFile.exists()) {
val json = ruleFile.readText()
@@ -78,8 +78,8 @@ object ImportOldData {
} else {
context.toast("未找到替换规则")
}
- } catch (e: Exception) {
- context.toast("导入替换规则失败\n${e.localizedMessage}")
+ }.onFailure {
+ context.toast("导入替换规则失败\n${it.localizedMessage}")
}
}
}
diff --git a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
index cfcf094ad..dafedc128 100644
--- a/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
+++ b/app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
@@ -12,7 +12,6 @@ import rxhttp.wrapper.param.toInputStream
import java.io.File
import java.io.IOException
import java.io.InputStream
-import java.io.UnsupportedEncodingException
import java.net.MalformedURLException
import java.net.URL
import java.net.URLEncoder
@@ -39,15 +38,12 @@ class WebDav(urlStr: String) {
private val url: URL = URL(urlStr)
private val httpUrl: String? by lazy {
val raw = url.toString().replace("davs://", "https://").replace("dav://", "http://")
- try {
- return@lazy URLEncoder.encode(raw, "UTF-8")
+ return@lazy kotlin.runCatching {
+ URLEncoder.encode(raw, "UTF-8")
.replace("\\+".toRegex(), "%20")
.replace("%3A".toRegex(), ":")
.replace("%2F".toRegex(), "/")
- } catch (e: UnsupportedEncodingException) {
- e.printStackTrace()
- return@lazy null
- }
+ }.getOrNull()
}
val host: String? get() = url.host
val path get() = url.toString()
diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt
index d9a9fa49c..ec3d31097 100644
--- a/app/src/main/java/io/legado/app/model/Debug.kt
+++ b/app/src/main/java/io/legado/app/model/Debug.kt
@@ -11,6 +11,7 @@ import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.htmlFormat
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.msg
+import kotlinx.coroutines.CoroutineScope
import java.text.SimpleDateFormat
import java.util.*
@@ -101,7 +102,7 @@ object Debug {
}
}
- fun startDebug(webBook: WebBook, key: String) {
+ fun startDebug(scope: CoroutineScope, webBook: WebBook, key: String) {
cancelDebug()
debugSource = webBook.sourceUrl
startTime = System.currentTimeMillis()
@@ -111,12 +112,12 @@ object Debug {
book.origin = webBook.sourceUrl
book.bookUrl = key
log(webBook.sourceUrl, "⇒开始访问详情页:$key")
- infoDebug(webBook, book)
+ infoDebug(scope, webBook, book)
}
key.contains("::") -> {
val url = key.substring(key.indexOf("::") + 2)
log(webBook.sourceUrl, "⇒开始访问发现页:$url")
- exploreDebug(webBook, url)
+ exploreDebug(scope, webBook, url)
}
key.startsWith("++")-> {
val url = key.substring(2)
@@ -124,7 +125,7 @@ object Debug {
book.origin = webBook.sourceUrl
book.tocUrl = url
log(webBook.sourceUrl, "⇒开始访目录页:$url")
- tocDebug(webBook, book)
+ tocDebug(scope, webBook, book)
}
key.startsWith("--")-> {
val url = key.substring(2)
@@ -134,23 +135,23 @@ object Debug {
val chapter = BookChapter()
chapter.title = "调试"
chapter.url = url
- contentDebug(webBook, book, chapter, null)
+ contentDebug(scope, webBook, book, chapter, null)
}
else -> {
log(webBook.sourceUrl, "⇒开始搜索关键字:$key")
- searchDebug(webBook, key)
+ searchDebug(scope, webBook, key)
}
}
}
- private fun exploreDebug(webBook: WebBook, url: String) {
+ private fun exploreDebug(scope: CoroutineScope, webBook: WebBook, url: String) {
log(debugSource, "︾开始解析发现页")
- val explore = webBook.exploreBook(url, 1)
+ val explore = webBook.exploreBook(scope, url, 1)
.onSuccess { exploreBooks ->
if (exploreBooks.isNotEmpty()) {
log(debugSource, "︽发现页解析完成")
log(debugSource, showTime = false)
- infoDebug(webBook, exploreBooks[0].toBook())
+ infoDebug(scope, webBook, exploreBooks[0].toBook())
} else {
log(debugSource, "︽未获取到书籍", state = -1)
}
@@ -161,14 +162,14 @@ object Debug {
tasks.add(explore)
}
- private fun searchDebug(webBook: WebBook, key: String) {
+ private fun searchDebug(scope: CoroutineScope, webBook: WebBook, key: String) {
log(debugSource, "︾开始解析搜索页")
- val search = webBook.searchBook(key, 1)
+ val search = webBook.searchBook(scope, key, 1)
.onSuccess { searchBooks ->
if (searchBooks.isNotEmpty()) {
log(debugSource, "︽搜索页解析完成")
log(debugSource, showTime = false)
- infoDebug(webBook, searchBooks[0].toBook())
+ infoDebug(scope, webBook, searchBooks[0].toBook())
} else {
log(debugSource, "︽未获取到书籍", state = -1)
}
@@ -179,13 +180,13 @@ object Debug {
tasks.add(search)
}
- private fun infoDebug(webBook: WebBook, book: Book) {
+ private fun infoDebug(scope: CoroutineScope, webBook: WebBook, book: Book) {
log(debugSource, "︾开始解析详情页")
- val info = webBook.getBookInfo(book)
+ val info = webBook.getBookInfo(scope, book)
.onSuccess {
log(debugSource, "︽详情页解析完成")
log(debugSource, showTime = false)
- tocDebug(webBook, book)
+ tocDebug(scope, webBook, book)
}
.onError {
log(debugSource, it.msg, state = -1)
@@ -193,15 +194,15 @@ object Debug {
tasks.add(info)
}
- private fun tocDebug(webBook: WebBook, book: Book) {
+ private fun tocDebug(scope: CoroutineScope, webBook: WebBook, book: Book) {
log(debugSource, "︾开始解析目录页")
- val chapterList = webBook.getChapterList(book)
+ val chapterList = webBook.getChapterList(scope, book)
.onSuccess {
if (it.isNotEmpty()) {
log(debugSource, "︽目录页解析完成")
log(debugSource, showTime = false)
val nextChapterUrl = if (it.size > 1) it[1].url else null
- contentDebug(webBook, book, it[0], nextChapterUrl)
+ contentDebug(scope, webBook, book, it[0], nextChapterUrl)
} else {
log(debugSource, "︽目录列表为空", state = -1)
}
@@ -213,13 +214,14 @@ object Debug {
}
private fun contentDebug(
+ scope: CoroutineScope,
webBook: WebBook,
book: Book,
bookChapter: BookChapter,
nextChapterUrl: String?
) {
log(debugSource, "︾开始解析正文页")
- val content = webBook.getContent(book, bookChapter, nextChapterUrl)
+ val content = webBook.getContent(scope, book, bookChapter, nextChapterUrl)
.onSuccess {
log(debugSource, "︽正文页解析完成", state = 1000)
}
diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
index 7a778472e..5445884bf 100644
--- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
+++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
@@ -62,10 +62,10 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
fun setBaseUrl(baseUrl: String?): AnalyzeRule {
baseUrl?.let {
this.baseUrl = baseUrl
- try {
+ kotlin.runCatching {
baseURL = URL(baseUrl.substringBefore(","))
- } catch (e: Exception) {
- e.printStackTrace()
+ }.onFailure {
+ it.printStackTrace()
}
}
return this
@@ -229,9 +229,9 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
}
}
if (result == null) result = ""
- val str = try {
+ val str = kotlin.runCatching {
Entities.unescape(result.toString())
- } catch (e: Exception) {
+ }.getOrElse {
result.toString()
}
if (isUrl) {
@@ -653,13 +653,15 @@ class AnalyzeRule(val ruleData: RuleDataInterface) : JsExtensions {
* js实现跨域访问,不能删
*/
override fun ajax(urlStr: String): String? {
- return try {
- val analyzeUrl = AnalyzeUrl(urlStr, book = book)
- runBlocking {
+ return runBlocking {
+ kotlin.runCatching {
+ val analyzeUrl = AnalyzeUrl(urlStr, book = book)
analyzeUrl.getStrResponse(urlStr).body
+ }.onFailure {
+ it.printStackTrace()
+ }.getOrElse {
+ it.msg
}
- } catch (e: Exception) {
- e.localizedMessage
}
}
diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
index c5895a0c0..0a0146874 100644
--- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
+++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
@@ -358,6 +358,7 @@ class AnalyzeUrl(
.setAssemblyEnabled(false)
.setOkClient(HttpHelper.getProxyClient(proxy))
.addAllEncoded(fieldMap)
+ .addAllHeader(headerMap)
.toByteArray().await()
}
}
diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
index 94ba0baf7..1bba73ec1 100644
--- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
@@ -12,6 +12,7 @@ import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -19,13 +20,13 @@ import kotlin.coroutines.resumeWithException
object BookChapterList {
suspend fun analyzeChapterList(
- coroutineScope: CoroutineScope,
+ scope: CoroutineScope,
book: Book,
body: String?,
bookSource: BookSource,
baseUrl: String
): List = suspendCancellableCoroutine { block ->
- try {
+ kotlin.runCatching {
val chapterList = ArrayList()
body ?: throw Exception(
App.INSTANCE.getString(R.string.error_get_web_content, baseUrl)
@@ -45,7 +46,7 @@ object BookChapterList {
}
var chapterData =
analyzeChapterList(
- book, baseUrl, body, tocRule, listRule, bookSource, log = true
+ scope, book, baseUrl, body, tocRule, listRule, bookSource, log = true
)
chapterData.chapterList?.let {
chapterList.addAll(it)
@@ -55,7 +56,7 @@ object BookChapterList {
block.resume(finish(book, chapterList, reverse))
}
1 -> {
- Coroutine.async(scope = coroutineScope) {
+ Coroutine.async(scope = scope) {
var nextUrl = chapterData.nextUrl[0]
while (nextUrl.isNotEmpty() && !nextUrlList.contains(nextUrl)) {
nextUrlList.add(nextUrl)
@@ -63,18 +64,15 @@ object BookChapterList {
ruleUrl = nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
- ).getStrResponse(bookSource.bookSourceUrl)
- .body?.let { nextBody ->
- chapterData = analyzeChapterList(
- book, nextUrl, nextBody, tocRule, listRule, bookSource
- )
- nextUrl = if (chapterData.nextUrl.isNotEmpty()) {
- chapterData.nextUrl[0]
- } else ""
- chapterData.chapterList?.let {
- chapterList.addAll(it)
- }
+ ).getStrResponse(bookSource.bookSourceUrl).body?.let { nextBody ->
+ chapterData = analyzeChapterList(
+ this, book, nextUrl, nextBody, tocRule, listRule, bookSource
+ )
+ nextUrl = chapterData.nextUrl.firstOrNull() ?: ""
+ chapterData.chapterList?.let {
+ chapterList.addAll(it)
}
+ }
}
Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}")
block.resume(finish(book, chapterList, reverse))
@@ -94,29 +92,21 @@ object BookChapterList {
Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}")
for (item in chapterDataList) {
downloadToc(
- coroutineScope,
- item,
- book,
- bookSource,
- tocRule,
- listRule,
- chapterList,
- chapterDataList,
- {
- block.resume(finish(book, chapterList, reverse))
- }, {
- block.cancel(it)
- })
+ scope, item, book, bookSource,
+ tocRule, listRule, chapterList, chapterDataList
+ ) {
+ block.resume(finish(book, chapterList, reverse))
+ }
}
}
}
- } catch (e: Exception) {
- block.resumeWithException(e)
+ }.onFailure {
+ block.resumeWithException(it)
}
}
private fun downloadToc(
- coroutineScope: CoroutineScope,
+ scope: CoroutineScope,
chapterData: ChapterData,
book: Book,
bookSource: BookSource,
@@ -124,10 +114,9 @@ object BookChapterList {
listRule: String,
chapterList: ArrayList,
chapterDataList: ArrayList>,
- onFinish: () -> Unit,
- onError: (e: Throwable) -> Unit
+ onFinish: () -> Unit
) {
- Coroutine.async(scope = coroutineScope) {
+ Coroutine.async(scope = scope) {
val nextBody = AnalyzeUrl(
ruleUrl = chapterData.nextUrl,
book = book,
@@ -135,8 +124,7 @@ object BookChapterList {
).getStrResponse(bookSource.bookSourceUrl).body
?: throw Exception("${chapterData.nextUrl}, 下载失败")
val nextChapterData = analyzeChapterList(
- book, chapterData.nextUrl, nextBody, tocRule, listRule, bookSource,
- false
+ this, book, chapterData.nextUrl, nextBody, tocRule, listRule, bookSource, false
)
synchronized(chapterDataList) {
val isFinished = addChapterListIsFinish(
@@ -154,7 +142,7 @@ object BookChapterList {
}
}
}.onError {
- onError(it)
+ throw it
}
}
@@ -200,6 +188,7 @@ object BookChapterList {
}
private fun analyzeChapterList(
+ scope: CoroutineScope,
book: Book,
baseUrl: String,
body: String,
@@ -232,6 +221,7 @@ object BookChapterList {
Debug.log(bookSource.bookSourceUrl, "┌获取目录列表", log)
val elements = analyzeRule.getElements(listRule)
Debug.log(bookSource.bookSourceUrl, "└列表大小:${elements.size}", log)
+ scope.ensureActive()
if (elements.isNotEmpty()) {
val nameRule = analyzeRule.splitSourceRule(tocRule.chapterName)
val urlRule = analyzeRule.splitSourceRule(tocRule.chapterUrl)
@@ -239,6 +229,7 @@ object BookChapterList {
val update = analyzeRule.splitSourceRule(tocRule.updateTime)
var isVip: String?
for (item in elements) {
+ scope.ensureActive()
analyzeRule.setContent(item)
val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = baseUrl)
analyzeRule.chapter = bookChapter
diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt
index f1bb8d405..25fcfa512 100644
--- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt
@@ -19,7 +19,7 @@ object BookContent {
@Throws(Exception::class)
suspend fun analyzeContent(
- coroutineScope: CoroutineScope,
+ scope: CoroutineScope,
body: String?,
book: Book,
bookChapter: BookChapter,
@@ -73,7 +73,7 @@ object BookContent {
contentDataList.add(ContentData(nextUrl = item))
}
for (item in contentDataList) {
- withContext(coroutineScope.coroutineContext) {
+ withContext(scope.coroutineContext) {
AnalyzeUrl(
ruleUrl = item.nextUrl,
book = book,
diff --git a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
index 877081cbc..d4eb8b9d9 100644
--- a/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
@@ -10,11 +10,14 @@ import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat
import io.legado.app.utils.htmlFormat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ensureActive
object BookInfo {
@Throws(Exception::class)
fun analyzeBookInfo(
+ scope: CoroutineScope,
book: Book,
body: String?,
bookSource: BookSource,
@@ -30,11 +33,13 @@ object BookInfo {
analyzeRule.setContent(body).setBaseUrl(baseUrl)
infoRule.init?.let {
if (it.isNotBlank()) {
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it))
}
}
val mCanReName = canReName && !infoRule.canReName.isNullOrBlank()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名")
BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let {
if (it.isNotEmpty() && (mCanReName || book.name.isEmpty())) {
@@ -42,6 +47,7 @@ object BookInfo {
}
Debug.log(bookSource.bookSourceUrl, "└${it}")
}
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者")
BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let {
if (it.isNotEmpty() && (mCanReName || book.author.isEmpty())) {
@@ -49,6 +55,7 @@ object BookInfo {
}
Debug.log(bookSource.bookSourceUrl, "└${it}")
}
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类")
analyzeRule.getStringList(infoRule.kind)
?.joinToString(",")
@@ -56,27 +63,31 @@ object BookInfo {
if (it.isNotEmpty()) book.kind = it
}
Debug.log(bookSource.bookSourceUrl, "└${book.kind}")
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数")
wordCountFormat(analyzeRule.getString(infoRule.wordCount)).let {
if (it.isNotEmpty()) book.wordCount = it
}
Debug.log(bookSource.bookSourceUrl, "└${book.wordCount}")
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节")
analyzeRule.getString(infoRule.lastChapter).let {
if (it.isNotEmpty()) book.latestChapterTitle = it
}
Debug.log(bookSource.bookSourceUrl, "└${book.latestChapterTitle}")
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介")
analyzeRule.getString(infoRule.intro).let {
if (it.isNotEmpty()) book.intro = it.htmlFormat()
}
Debug.log(bookSource.bookSourceUrl, "└${book.intro}")
-
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
analyzeRule.getString(infoRule.coverUrl).let {
if (it.isNotEmpty()) book.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)
}
Debug.log(bookSource.bookSourceUrl, "└${book.coverUrl}")
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, true)
if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl
diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt
index 464580854..25ab215e6 100644
--- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt
@@ -12,9 +12,8 @@ import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat
import io.legado.app.utils.htmlFormat
-import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.isActive
+import kotlinx.coroutines.ensureActive
object BookList {
@@ -36,10 +35,10 @@ object BookList {
)
)
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}")
- if (!scope.isActive) throw CancellationException()
val analyzeRule = AnalyzeRule(variableBook)
analyzeRule.setContent(body).setBaseUrl(baseUrl)
bookSource.bookUrlPattern?.let {
+ scope.ensureActive()
if (baseUrl.matches(it.toRegex())) {
Debug.log(bookSource.bookSourceUrl, "≡链接为详情页")
getInfoItem(scope, analyzeRule, bookSource, baseUrl, variableBook.variable)
@@ -67,6 +66,7 @@ object BookList {
}
Debug.log(bookSource.bookSourceUrl, "┌获取书籍列表")
collections = analyzeRule.getElements(ruleList)
+ scope.ensureActive()
if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) {
Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析")
getInfoItem(scope, analyzeRule, bookSource, baseUrl, variableBook.variable)
@@ -85,7 +85,6 @@ object BookList {
val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount)
Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}")
for ((index, item) in collections.withIndex()) {
- if (!scope.isActive) throw CancellationException()
getSearchItem(
scope,
item,
@@ -134,37 +133,37 @@ object BookList {
with(bookSource.getBookInfoRule()) {
init?.let {
if (it.isNotEmpty()) {
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it))
}
}
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名")
searchBook.name = BookHelp.formatBookName(analyzeRule.getString(name))
Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}")
if (searchBook.name.isNotEmpty()) {
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者")
searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(author))
Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}")
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类")
searchBook.kind = analyzeRule.getStringList(kind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}")
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数")
searchBook.wordCount = wordCountFormat(analyzeRule.getString(wordCount))
Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}")
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节")
searchBook.latestChapterTitle = analyzeRule.getString(lastChapter)
Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}")
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介")
searchBook.intro = analyzeRule.getString(intro).htmlFormat()
Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}")
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
searchBook.coverUrl = analyzeRule.getString(coverUrl, true)
Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}")
@@ -199,38 +198,38 @@ object BookList {
searchBook.originOrder = bookSource.customOrder
analyzeRule.book = searchBook
analyzeRule.setContent(item)
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取书名", log)
searchBook.name = BookHelp.formatBookName(analyzeRule.getString(ruleName))
Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}", log)
if (searchBook.name.isNotEmpty()) {
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取作者", log)
searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor))
Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}", log)
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类", log)
searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",")
Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}", log)
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取字数", log)
searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount))
Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}", log)
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log)
searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter)
Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}", log)
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log)
searchBook.intro = analyzeRule.getString(ruleIntro).htmlFormat()
Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", log)
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log)
analyzeRule.getString(ruleCoverUrl).let {
if (it.isNotEmpty()) searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)
}
Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}", log)
- if (!scope.isActive) throw CancellationException()
+ scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log)
searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, true)
if (searchBook.bookUrl.isEmpty()) {
diff --git a/app/src/main/java/io/legado/app/model/webBook/PreciseSearch.kt b/app/src/main/java/io/legado/app/model/webBook/PreciseSearch.kt
new file mode 100644
index 000000000..31d08314c
--- /dev/null
+++ b/app/src/main/java/io/legado/app/model/webBook/PreciseSearch.kt
@@ -0,0 +1,38 @@
+package io.legado.app.model.webBook
+
+import io.legado.app.data.entities.Book
+import io.legado.app.data.entities.BookSource
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.isActive
+
+/**
+ * 精准搜索
+ */
+object PreciseSearch {
+
+ suspend fun searchFirstBook(
+ scope: CoroutineScope,
+ bookSources: List,
+ name: String,
+ author: String
+ ): Book? {
+ bookSources.forEach { bookSource ->
+ val webBook = WebBook(bookSource)
+ kotlin.runCatching {
+ if (!scope.isActive) return null
+ webBook.searchBookAwait(scope, name).firstOrNull {
+ it.name == name && it.author == author
+ }?.let {
+ return if (it.tocUrl.isBlank()) {
+ if (!scope.isActive) return null
+ webBook.getBookInfoAwait(scope, it.toBook())
+ } else {
+ it.toBook()
+ }
+ }
+ }
+ }
+ return null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt
index d94c4df10..95aa3a95e 100644
--- a/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt
@@ -68,9 +68,9 @@ class SearchBookModel(private val scope: CoroutineScope, private val callBack: C
searchIndex++
val source = bookSourceList[searchIndex]
val task = WebBook(source).searchBook(
+ scope,
searchKey,
searchPage,
- scope = scope,
context = searchPool!!
).timeout(30000L)
.onSuccess(IO) {
diff --git a/app/src/main/java/io/legado/app/model/webBook/WebBook.kt b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt
index 952ec1993..bafc0ca0f 100644
--- a/app/src/main/java/io/legado/app/model/webBook/WebBook.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt
@@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
+@Suppress("MemberVisibilityCanBePrivate")
class WebBook(val bookSource: BookSource) {
val sourceUrl: String
@@ -20,17 +21,17 @@ class WebBook(val bookSource: BookSource) {
* 搜索
*/
fun searchBook(
+ scope: CoroutineScope,
key: String,
page: Int? = 1,
- scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO,
): Coroutine> {
return Coroutine.async(scope, context) {
- searchBookSuspend(scope, key, page)
+ searchBookAwait(scope, key, page)
}
}
- suspend fun searchBookSuspend(
+ suspend fun searchBookAwait(
scope: CoroutineScope,
key: String,
page: Int? = 1,
@@ -63,94 +64,111 @@ class WebBook(val bookSource: BookSource) {
* 发现
*/
fun exploreBook(
+ scope: CoroutineScope,
url: String,
page: Int? = 1,
- scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO,
): Coroutine> {
- val variableBook = SearchBook()
return Coroutine.async(scope, context) {
- val analyzeUrl = AnalyzeUrl(
- ruleUrl = url,
- page = page,
- baseUrl = sourceUrl,
- book = variableBook,
- headerMapF = bookSource.getHeaderMap()
- )
- val res = analyzeUrl.getStrResponse(bookSource.bookSourceUrl)
- BookList.analyzeBookList(
- scope,
- res.body,
- bookSource,
- analyzeUrl,
- res.url,
- variableBook,
- false
- )
+ exploreBookAwait(scope, url, page)
}
}
+ suspend fun exploreBookAwait(
+ scope: CoroutineScope = Coroutine.DEFAULT,
+ url: String,
+ page: Int? = 1,
+ ): ArrayList {
+ val variableBook = SearchBook()
+ val analyzeUrl = AnalyzeUrl(
+ ruleUrl = url,
+ page = page,
+ baseUrl = sourceUrl,
+ book = variableBook,
+ headerMapF = bookSource.getHeaderMap()
+ )
+ val res = analyzeUrl.getStrResponse(bookSource.bookSourceUrl)
+ return BookList.analyzeBookList(
+ scope,
+ res.body,
+ bookSource,
+ analyzeUrl,
+ res.url,
+ variableBook,
+ false
+ )
+ }
+
/**
* 书籍信息
*/
fun getBookInfo(
+ scope: CoroutineScope,
book: Book,
- scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO,
canReName: Boolean = true,
): Coroutine {
return Coroutine.async(scope, context) {
- book.type = bookSource.bookSourceType
- if (!book.infoHtml.isNullOrEmpty()) {
- book.infoHtml
- BookInfo.analyzeBookInfo(book, book.infoHtml, bookSource, book.bookUrl, canReName)
- } else {
- val res = AnalyzeUrl(
- ruleUrl = book.bookUrl,
- baseUrl = sourceUrl,
- headerMapF = bookSource.getHeaderMap(),
- book = book
- ).getStrResponse(bookSource.bookSourceUrl)
- BookInfo.analyzeBookInfo(book, res.body, bookSource, book.bookUrl, canReName)
- }
- book
+ getBookInfoAwait(scope, book, canReName)
+ }
+ }
+
+ suspend fun getBookInfoAwait(
+ scope: CoroutineScope = Coroutine.DEFAULT,
+ book: Book,
+ canReName: Boolean = true,
+ ): Book {
+ book.type = bookSource.bookSourceType
+ if (!book.infoHtml.isNullOrEmpty()) {
+ book.infoHtml
+ BookInfo.analyzeBookInfo(
+ scope,
+ book,
+ book.infoHtml,
+ bookSource,
+ book.bookUrl,
+ canReName
+ )
+ } else {
+ val res = AnalyzeUrl(
+ ruleUrl = book.bookUrl,
+ baseUrl = sourceUrl,
+ headerMapF = bookSource.getHeaderMap(),
+ book = book
+ ).getStrResponse(bookSource.bookSourceUrl)
+ BookInfo.analyzeBookInfo(scope, book, res.body, bookSource, book.bookUrl, canReName)
}
+ return book
}
/**
* 目录
*/
fun getChapterList(
+ scope: CoroutineScope,
book: Book,
- scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO
): Coroutine> {
return Coroutine.async(scope, context) {
- book.type = bookSource.bookSourceType
- if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {
- BookChapterList.analyzeChapterList(
- this,
- book,
- book.tocHtml,
- bookSource,
- book.tocUrl
- )
- } else {
- val res = AnalyzeUrl(
- book = book,
- ruleUrl = book.tocUrl,
- baseUrl = book.bookUrl,
- headerMapF = bookSource.getHeaderMap()
- ).getStrResponse(bookSource.bookSourceUrl)
- BookChapterList.analyzeChapterList(
- this,
- book,
- res.body,
- bookSource,
- book.tocUrl
- )
- }
+ getChapterListAwait(scope, book)
+ }
+ }
+ suspend fun getChapterListAwait(
+ scope: CoroutineScope = Coroutine.DEFAULT,
+ book: Book,
+ ): List {
+ book.type = bookSource.bookSourceType
+ return if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {
+ BookChapterList.analyzeChapterList(scope, book, book.tocHtml, bookSource, book.tocUrl)
+ } else {
+ val res = AnalyzeUrl(
+ book = book,
+ ruleUrl = book.tocUrl,
+ baseUrl = book.bookUrl,
+ headerMapF = bookSource.getHeaderMap()
+ ).getStrResponse(bookSource.bookSourceUrl)
+ BookChapterList.analyzeChapterList(scope, book, res.body, bookSource, book.tocUrl)
}
}
@@ -158,27 +176,22 @@ class WebBook(val bookSource: BookSource) {
* 章节内容
*/
fun getContent(
+ scope: CoroutineScope,
book: Book,
bookChapter: BookChapter,
nextChapterUrl: String? = null,
- scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO
): Coroutine {
return Coroutine.async(scope, context) {
- getContentSuspend(
- book, bookChapter, nextChapterUrl, scope
- )
+ getContentAwait(scope, book, bookChapter, nextChapterUrl)
}
}
- /**
- * 章节内容
- */
- suspend fun getContentSuspend(
+ suspend fun getContentAwait(
+ scope: CoroutineScope,
book: Book,
bookChapter: BookChapter,
nextChapterUrl: String? = null,
- scope: CoroutineScope = Coroutine.DEFAULT
): String {
if (bookSource.getContentRule().content.isNullOrEmpty()) {
Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}")
diff --git a/app/src/main/java/io/legado/app/service/AudioPlayService.kt b/app/src/main/java/io/legado/app/service/AudioPlayService.kt
index b268adea1..805fdfc14 100644
--- a/app/src/main/java/io/legado/app/service/AudioPlayService.kt
+++ b/app/src/main/java/io/legado/app/service/AudioPlayService.kt
@@ -120,7 +120,7 @@ class AudioPlayService : BaseService(),
private fun play() {
upNotification()
if (requestFocus()) {
- try {
+ kotlin.runCatching {
AudioPlay.status = Status.STOP
postEvent(EventBus.AUDIO_STATE, Status.STOP)
mediaPlayer.reset()
@@ -130,10 +130,10 @@ class AudioPlayService : BaseService(),
mediaPlayer.setDataSource(this, uri, analyzeUrl.headerMap)
mediaPlayer.prepareAsync()
handler.removeCallbacks(mpRunnable)
- } catch (e: Exception) {
- e.printStackTrace()
+ }.onFailure {
+ it.printStackTrace()
launch {
- toast("$url ${e.localizedMessage}")
+ toast("$url ${it.localizedMessage}")
stopSelf()
}
}
@@ -270,7 +270,7 @@ class AudioPlayService : BaseService(),
val book = AudioPlay.book
val webBook = AudioPlay.webBook
if (book != null && webBook != null) {
- webBook.getContent(book, chapter, scope = this@AudioPlayService)
+ webBook.getContent(this@AudioPlayService, book, chapter)
.onSuccess { content ->
if (content.isEmpty()) {
withContext(Main) {
diff --git a/app/src/main/java/io/legado/app/service/CacheBookService.kt b/app/src/main/java/io/legado/app/service/CacheBookService.kt
index 181985df3..06a6dea35 100644
--- a/app/src/main/java/io/legado/app/service/CacheBookService.kt
+++ b/app/src/main/java/io/legado/app/service/CacheBookService.kt
@@ -184,12 +184,8 @@ class CacheBookService : BaseService() {
return@async
}
if (!BookHelp.hasContent(book, bookChapter)) {
- webBook.getContent(
- book,
- bookChapter,
- scope = this,
- context = searchPool
- ).timeout(60000L)
+ webBook.getContent(this, book, bookChapter, context = searchPool)
+ .timeout(60000L)
.onError {
synchronized(this) {
downloadingList.remove(bookChapter.url)
diff --git a/app/src/main/java/io/legado/app/service/help/CacheBook.kt b/app/src/main/java/io/legado/app/service/help/CacheBook.kt
index 1c69ac09c..782ab3bf8 100644
--- a/app/src/main/java/io/legado/app/service/help/CacheBook.kt
+++ b/app/src/main/java/io/legado/app/service/help/CacheBook.kt
@@ -10,6 +10,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.CacheBookService
import io.legado.app.utils.msg
+import kotlinx.coroutines.CoroutineScope
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
@@ -61,6 +62,7 @@ object CacheBook {
}
fun download(
+ scope: CoroutineScope,
webBook: WebBook,
book: Book,
chapter: BookChapter,
@@ -73,7 +75,7 @@ object CacheBook {
downloadMap[book.bookUrl] = CopyOnWriteArraySet()
}
downloadMap[book.bookUrl]?.add(chapter.index)
- webBook.getContent(book, chapter)
+ webBook.getContent(scope, book, chapter)
.onSuccess { content ->
if (ReadBook.book?.bookUrl == book.bookUrl) {
ReadBook.contentLoadFinish(
diff --git a/app/src/main/java/io/legado/app/service/help/CheckSource.kt b/app/src/main/java/io/legado/app/service/help/CheckSource.kt
index 88d1dbcca..596662429 100644
--- a/app/src/main/java/io/legado/app/service/help/CheckSource.kt
+++ b/app/src/main/java/io/legado/app/service/help/CheckSource.kt
@@ -50,7 +50,7 @@ class CheckSource(val source: BookSource) {
): Coroutine<*> {
val webBook = WebBook(source)
return webBook
- .searchBook(keyword, scope = scope, context = context)
+ .searchBook(scope, keyword, context = context)
.timeout(60000L)
.onError(Dispatchers.IO) {
source.addGroup("失效")
diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt
index c4fb40204..c76386bd6 100644
--- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt
+++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt
@@ -133,10 +133,12 @@ object ReadBook {
callBack?.upContent()
}
loadContent(durChapterIndex.plus(1), upContent, false)
- GlobalScope.launch(Dispatchers.IO) {
- for (i in 2..10) {
- delay(100)
- download(durChapterIndex + i)
+ if (AppConfig.preDownload) {
+ GlobalScope.launch(Dispatchers.IO) {
+ for (i in 2..9) {
+ delay(1000)
+ download(durChapterIndex + i)
+ }
}
}
}
@@ -163,10 +165,12 @@ object ReadBook {
callBack?.upContent()
}
loadContent(durChapterIndex.minus(1), upContent, false)
- GlobalScope.launch(Dispatchers.IO) {
- for (i in -5..-2) {
- delay(100)
- download(durChapterIndex + i)
+ if (AppConfig.preDownload) {
+ GlobalScope.launch(Dispatchers.IO) {
+ for (i in 2..9) {
+ delay(1000)
+ download(durChapterIndex - i)
+ }
}
}
}
@@ -282,7 +286,7 @@ object ReadBook {
val book = book
val webBook = webBook
if (book != null && webBook != null) {
- CacheBook.download(webBook, book, chapter)
+ CacheBook.download(Coroutine.DEFAULT, webBook, book, chapter)
} else if (book != null) {
contentLoadFinish(
book, chapter, "没有书源", resetPageOffset = resetPageOffset
diff --git a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt
index abfa31b03..f6c9088de 100644
--- a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt
@@ -30,7 +30,7 @@ class AboutActivity : BaseActivity() {
.replace(R.id.fl_fragment, aboutFragment, fTag)
.commit()
binding.tvAppSummary.post {
- try {
+ kotlin.runCatching {
val span = ForegroundColorSpan(accentColor)
val spannableString = SpannableString(binding.tvAppSummary.text)
val gzh = getString(R.string.legado_gzh)
@@ -40,8 +40,6 @@ class AboutActivity : BaseActivity() {
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
binding.tvAppSummary.text = spannableString
- } catch (e: Exception) {
- e.printStackTrace()
}
}
}
diff --git a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
index 68ff8d0fa..ca805475b 100644
--- a/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
+++ b/app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
@@ -14,6 +14,7 @@ import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.openUrl
import io.legado.app.utils.sendMail
import io.legado.app.utils.sendToClip
+import io.legado.app.utils.toast
class AboutFragment : PreferenceFragmentCompat() {
@@ -93,12 +94,13 @@ class AboutFragment : PreferenceFragmentCompat() {
Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D$key")
// 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- return try {
+ kotlin.runCatching {
startActivity(intent)
- true
- } catch (e: java.lang.Exception) {
- false
+ return true
+ }.onFailure {
+ toast("添加失败,请手动添加")
}
+ return false
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
index 793c8b2d6..184663f3d 100644
--- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
@@ -53,7 +53,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
changeDruChapterIndex: ((chapters: List) -> Unit)? = null
) {
execute {
- AudioPlay.webBook?.getBookInfo(book, this)
+ AudioPlay.webBook?.getBookInfo(this, book)
?.onSuccess {
loadChapterList(book, changeDruChapterIndex)
}
@@ -65,7 +65,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
changeDruChapterIndex: ((chapters: List) -> Unit)? = null
) {
execute {
- AudioPlay.webBook?.getChapterList(book, this)
+ AudioPlay.webBook?.getChapterList(this, book)
?.onSuccess(Dispatchers.IO) { cList ->
if (cList.isNotEmpty()) {
if (changeDruChapterIndex == null) {
diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
index efad70917..1e417a1a0 100644
--- a/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
@@ -82,7 +82,7 @@ class CacheActivity : VMBaseActivity()
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
- R.id.menu_download -> launch(IO) {
+ R.id.menu_download -> {
if (adapter.downloadMap.isNullOrEmpty()) {
adapter.getItems().forEach { book ->
CacheBook.start(
diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
index 305126b36..2bfcffdaa 100644
--- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
@@ -98,7 +98,7 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
searchIndex++
val source = bookSourceList[searchIndex]
val task = WebBook(source)
- .searchBook(name, scope = this, context = searchPool!!)
+ .searchBook(this, name, context = searchPool!!)
.timeout(60000L)
.onSuccess(Dispatchers.IO) {
if (it.isNotEmpty()) {
diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
index 7c61b8d62..279cf7d79 100644
--- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
@@ -123,7 +123,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
val source = bookSourceList[searchIndex]
val webBook = WebBook(source)
val task = webBook
- .searchBook(name, scope = this, context = searchPool!!)
+ .searchBook(this, name, context = searchPool!!)
.timeout(60000L)
.onSuccess(IO) {
it.forEach { searchBook ->
@@ -159,7 +159,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
}
private fun loadBookInfo(webBook: WebBook, book: Book) {
- webBook.getBookInfo(book, this)
+ webBook.getBookInfo(this, book)
.onSuccess {
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
loadBookToc(webBook, book)
@@ -175,7 +175,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
}
private fun loadBookToc(webBook: WebBook, book: Book) {
- webBook.getChapterList(book, this)
+ webBook.getChapterList(this, book)
.onSuccess(IO) { chapters ->
if (chapters.isNotEmpty()) {
book.latestChapterTitle = chapters.last().title
diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
index 5781eb555..502fdefa5 100644
--- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
@@ -34,7 +34,7 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application
val source = bookSource
val url = exploreUrl
if (source != null && url != null) {
- WebBook(source).exploreBook(url, page, this)
+ WebBook(source).exploreBook(this, url, page)
.timeout(30000L)
.onSuccess(IO) { searchBooks ->
booksData.postValue(searchBooks)
diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
index ae0be9479..21f2dd157 100644
--- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
@@ -57,7 +57,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
loadChapter(book, changeDruChapterIndex)
} else {
App.db.bookSourceDao.getBookSource(book.origin)?.let { bookSource ->
- WebBook(bookSource).getBookInfo(book, this, canReName = canReName)
+ WebBook(bookSource).getBookInfo(this, book, canReName = canReName)
.onSuccess(IO) {
bookData.postValue(book)
if (inBookshelf) {
@@ -88,7 +88,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
}
} else {
App.db.bookSourceDao.getBookSource(book.origin)?.let { bookSource ->
- WebBook(bookSource).getChapterList(book, this)
+ WebBook(bookSource).getChapterList(this, book)
.onSuccess(IO) {
if (it.isNotEmpty()) {
if (inBookshelf) {
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
index f2cd19baa..02040e26d 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
@@ -21,6 +21,7 @@ import io.legado.app.constant.Status
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookProgress
+import io.legado.app.data.entities.Bookmark
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ReadTipConfig
import io.legado.app.help.storage.Backup
@@ -204,6 +205,21 @@ class ReadBookActivity : ReadBookBaseActivity(),
}
}
R.id.menu_download -> showDownloadDialog()
+ R.id.menu_add_bookmark -> {
+ val book = ReadBook.book
+ val page = ReadBook.curTextChapter?.page(ReadBook.durPageIndex())
+ if (book != null && page != null) {
+ val bookmark = Bookmark(
+ bookUrl = book.bookUrl,
+ bookName = book.name,
+ chapterIndex = ReadBook.durChapterIndex,
+ chapterPos = ReadBook.durChapterPos,
+ chapterName = page.title,
+ bookText = page.text.trim()
+ )
+ showBookMark(bookmark)
+ }
+ }
R.id.menu_copy_text ->
TextDialog.show(supportFragmentManager, ReadBook.curTextChapter?.getContent())
R.id.menu_update_toc -> ReadBook.book?.let {
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookBaseActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookBaseActivity.kt
index 56165f6ee..6a305f928 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookBaseActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookBaseActivity.kt
@@ -13,6 +13,7 @@ import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Bookmark
import io.legado.app.databinding.ActivityBookReadBinding
+import io.legado.app.databinding.DialogBookmarkBinding
import io.legado.app.databinding.DialogDownloadChoiceBinding
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.help.AppConfig
@@ -200,18 +201,18 @@ abstract class ReadBookBaseActivity :
@SuppressLint("InflateParams")
fun showBookMark(bookmark: Bookmark) {
- val book = ReadBook.book ?: return
- val textChapter = ReadBook.curTextChapter ?: return
alert(title = getString(R.string.bookmark_add)) {
- val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
- editView.setHint(R.string.note_content)
+ message = bookmark.chapterName
+ val alertBinding = DialogBookmarkBinding.inflate(layoutInflater).apply {
+ editBookText.setText(bookmark.bookText)
+ editView.setText(bookmark.content)
}
- message = bookmark.bookText
customView = alertBinding.root
yesButton {
- alertBinding.editView.text?.toString()?.let { editContent ->
+ alertBinding.apply {
Coroutine.async {
- bookmark.content = editContent
+ bookmark.bookText = editBookText.text.toString()
+ bookmark.content = editView.text.toString()
App.db.bookmarkDao.insert(bookmark)
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
index 7f0962663..de9c080d7 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
@@ -13,10 +13,12 @@ import io.legado.app.help.BookHelp
import io.legado.app.help.IntentDataHelp
import io.legado.app.help.storage.BookWebDav
import io.legado.app.model.localBook.LocalBook
+import io.legado.app.model.webBook.PreciseSearch
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
+import io.legado.app.utils.msg
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
@@ -109,7 +111,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
if (book.isLocalBook()) {
loadChapterList(book, changeDruChapterIndex)
} else {
- ReadBook.webBook?.getBookInfo(book, this, canReName = false)
+ ReadBook.webBook?.getBookInfo(this, book, canReName = false)
?.onSuccess {
loadChapterList(book, changeDruChapterIndex)
}
@@ -138,7 +140,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
ReadBook.upMsg("LoadTocError:${it.localizedMessage}")
}
} else {
- ReadBook.webBook?.getChapterList(book, this)
+ ReadBook.webBook?.getChapterList(this, book)
?.onSuccess(IO) { cList ->
if (cList.isNotEmpty()) {
if (changeDruChapterIndex == null) {
@@ -213,24 +215,18 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
private fun autoChangeSource(name: String, author: String) {
if (!AppConfig.autoChangeSource) return
execute {
- App.db.bookSourceDao.allTextEnabled.forEach { source ->
- try {
- WebBook(source)
- .searchBookSuspend(this, name)
- .getOrNull(0)?.let {
- if (it.name == name && (it.author == author || author == "")) {
- val book = it.toBook()
- book.upInfoFromOld(ReadBook.book)
- changeTo(book)
- return@execute
- }
- }
- } catch (e: Exception) {
- //nothing
- }
+ val sources = App.db.bookSourceDao.allTextEnabled
+ val book = PreciseSearch.searchFirstBook(this, sources, name, author)
+ if (book != null) {
+ book.upInfoFromOld(ReadBook.book)
+ changeTo(book)
+ } else {
+ throw Exception("自动换源失败")
}
}.onStart {
ReadBook.upMsg(context.getString(R.string.source_auto_changing))
+ }.onError {
+ toast(it.msg)
}.onFinally {
ReadBook.upMsg(null)
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt
index 6ec4d86fe..4a75e05c0 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt
@@ -130,7 +130,7 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
readAloud(callBack.selectedText)
}
R.id.menu_browser -> {
- try {
+ kotlin.runCatching {
val intent = if (callBack.selectedText.isAbsUrl()) {
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(callBack.selectedText)
@@ -141,9 +141,9 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
}
}
context.startActivity(intent)
- } catch (e: Exception) {
- e.printStackTrace()
- context.toast(e.localizedMessage ?: "ERROR")
+ }.onFailure {
+ it.printStackTrace()
+ context.toast(it.localizedMessage ?: "ERROR")
}
}
else -> item.intent?.let {
@@ -210,7 +210,7 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
*/
@RequiresApi(Build.VERSION_CODES.M)
private fun onInitializeMenu(menu: Menu) {
- try {
+ kotlin.runCatching {
var menuItemOrder = 100
for (resolveInfo in getSupportedActivities()) {
menu.add(
@@ -218,8 +218,8 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
menuItemOrder++, resolveInfo.loadLabel(context.packageManager)
).intent = createProcessTextIntentForResolveInfo(resolveInfo)
}
- } catch (e: Exception) {
- context.toast("获取文字操作菜单出错:${e.localizedMessage}")
+ }.onFailure {
+ context.toast("获取文字操作菜单出错:${it.localizedMessage}")
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
index 542853559..2c807a279 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
@@ -21,6 +21,7 @@ import io.legado.app.databinding.DialogHttpTtsEditBinding
import io.legado.app.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemHttpTtsBinding
import io.legado.app.lib.dialogs.alert
+import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryColor
import io.legado.app.service.help.ReadAloud
import io.legado.app.ui.widget.dialog.TextDialog
@@ -59,6 +60,7 @@ class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
private fun initView() = with(binding) {
toolBar.setBackgroundColor(primaryColor)
toolBar.setTitle(R.string.speak_engine)
+ ATH.applyEdgeEffectColor(recyclerView)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
adapter = Adapter(requireContext())
recyclerView.adapter = adapter
diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt
index b72f65343..bfcb8ae2b 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt
@@ -14,9 +14,7 @@ class SpeakEngineViewModel(application: Application) : BaseViewModel(application
fun importDefault() {
execute {
- DefaultData.httpTTS.let {
- App.db.httpTTSDao.insert(*it.toTypedArray())
- }
+ DefaultData.importDefaultHttpTTS()
}
}
diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
index 25803ac2a..8bc72438b 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
@@ -338,10 +338,10 @@ class ReadView(context: Context, attrs: AttributeSet) :
}
end -= 1
}
- try {
+ kotlin.runCatching {
curPage.selectStartMoveIndex(firstRelativePage, lineStart, start)
curPage.selectEndMoveIndex(firstRelativePage, lineEnd, end)
- } catch (e: Exception) {
+ }.onFailure {
print(
"""
curPage.selectStartMoveIndex($firstRelativePage, $lineStart, $start)
diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
index ef4e8a0db..af7f8ecd8 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
@@ -13,6 +13,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
+import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextChar
import io.legado.app.ui.book.read.page.entities.TextLine
@@ -118,7 +119,7 @@ object ChapterProvider {
return TextChapter(
bookChapter.index, bookChapter.title,
- bookChapter.getAbsoluteURL().substringBefore(","),
+ bookChapter.getAbsoluteURL().split(AnalyzeUrl.splitUrlRegex)[0],
textPages, chapterSize
)
}
@@ -351,7 +352,7 @@ object ChapterProvider {
}
private fun getTypeface(fontPath: String): Typeface {
- return try {
+ return kotlin.runCatching {
when {
fontPath.isContentScheme() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
val fd = App.INSTANCE.contentResolver
@@ -369,7 +370,7 @@ object ChapterProvider {
else -> Typeface.SANS_SERIF
}
}
- } catch (e: Exception) {
+ }.getOrElse {
ReadBookConfig.textFont = ""
ReadBookConfig.save()
Typeface.SANS_SERIF
diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
index 7aa11f1c5..3f1d377bb 100644
--- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
@@ -160,6 +160,13 @@ class SearchActivity : VMBaseActivity Unit)? = null, error: (() -> Unit)? = null) {
- webBook?.let {
+ execute {
+ Debug.callback = this@BookSourceDebugModel
+ Debug.startDebug(this, webBook!!, key)
+ }.onStart {
start?.invoke()
- Debug.callback = this
- Debug.startDebug(it, key)
- } ?: error?.invoke()
+ }.onError {
+ error?.invoke()
+ }
}
override fun printLog(state: Int, msg: String) {
diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
index c14809aaf..0aeceded0 100644
--- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
@@ -12,7 +12,6 @@ import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.recyclerview.widget.ItemTouchHelper
-import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import io.legado.app.App
import io.legado.app.R
@@ -140,14 +139,13 @@ class BookSourceActivity : VMBaseActivity showHelp()
}
if (item.groupId == R.id.source_group) {
- searchView.setQuery(item.title, true)
+ searchView.setQuery("group:${item.title}", true)
}
return super.onCompatOptionsItemSelected(item)
}
private fun initRecyclerView() {
ATH.applyEdgeEffectColor(binding.recyclerView)
- binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.addItemDecoration(VerticalDivider(this))
adapter = BookSourceAdapter(this, this)
binding.recyclerView.adapter = adapter
@@ -182,6 +180,10 @@ class BookSourceActivity : VMBaseActivity {
App.db.bookSourceDao.liveDataDisabled()
}
+ searchKey.startsWith("group:") -> {
+ val key = searchKey.substringAfter("group:")
+ App.db.bookSourceDao.liveDataGroupSearch("%$key%")
+ }
else -> {
App.db.bookSourceDao.liveDataSearch("%$searchKey%")
}
@@ -206,7 +208,7 @@ class BookSourceActivity : VMBaseActivity data.sortedBy { it.lastUpdateTime }
else -> data.reversed()
}
- adapter.setItems(sourceList)
+ adapter.setItems(sourceList, adapter.diffItemCallback)
})
}
diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
index 55f179023..1545a3108 100644
--- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
+++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
@@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.PopupMenu
import androidx.core.os.bundleOf
+import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
@@ -27,6 +28,51 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
private val selected = linkedSetOf()
+ val diffItemCallback: DiffUtil.ItemCallback
+ get() = object : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(oldItem: BookSource, newItem: BookSource): Boolean {
+ return oldItem.bookSourceUrl == newItem.bookSourceUrl
+ }
+
+ override fun areContentsTheSame(oldItem: BookSource, newItem: BookSource): Boolean {
+ if (oldItem.bookSourceName != newItem.bookSourceName)
+ return false
+ if (oldItem.bookSourceGroup != newItem.bookSourceGroup)
+ return false
+ if (oldItem.enabled != newItem.enabled)
+ return false
+ if (oldItem.enabledExplore != newItem.enabledExplore
+ || oldItem.exploreUrl != newItem.exploreUrl
+ ) {
+ return false
+ }
+ return true
+ }
+
+ override fun getChangePayload(oldItem: BookSource, newItem: BookSource): Any? {
+ val payload = Bundle()
+ if (oldItem.bookSourceName != newItem.bookSourceName) {
+ payload.putString("name", newItem.bookSourceName)
+ }
+ if (oldItem.bookSourceGroup != newItem.bookSourceGroup) {
+ payload.putString("group", newItem.bookSourceGroup)
+ }
+ if (oldItem.enabled != newItem.enabled) {
+ payload.putBoolean("enabled", newItem.enabled)
+ }
+ if (oldItem.enabledExplore != newItem.enabledExplore
+ || oldItem.exploreUrl != newItem.exploreUrl
+ ) {
+ payload.putBoolean("showExplore", true)
+ }
+ if (payload.isEmpty) {
+ return null
+ }
+ return payload
+ }
+ }
+
override fun getViewBinding(parent: ViewGroup): ItemBookSourceBinding {
return ItemBookSourceBinding.inflate(inflater, parent, false)
}
diff --git a/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt b/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt
index 2fb5a4085..5c0524302 100644
--- a/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt
+++ b/app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt
@@ -13,8 +13,9 @@ import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseFragment
import io.legado.app.data.entities.Bookmark
-import io.legado.app.databinding.DialogEditTextBinding
+import io.legado.app.databinding.DialogBookmarkBinding
import io.legado.app.databinding.FragmentBookmarkBinding
+import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.ATH
import io.legado.app.ui.widget.recycler.VerticalDivider
@@ -84,18 +85,19 @@ class BookmarkFragment : VMBaseFragment(R.layout.fragment_
@SuppressLint("InflateParams")
override fun onLongClick(bookmark: Bookmark) {
requireContext().alert(R.string.bookmark) {
- val alertBinding = DialogEditTextBinding.inflate(layoutInflater)
- message = bookmark.bookText
- customView {
- alertBinding.apply {
- editView.setHint(R.string.note_content)
- editView.setText(bookmark.content)
- }.root
+ message = bookmark.chapterName
+ val alertBinding = DialogBookmarkBinding.inflate(layoutInflater).apply {
+ editBookText.setText(bookmark.bookText)
+ editView.setText(bookmark.content)
}
+ customView = alertBinding.root
yesButton {
- alertBinding.editView.text?.toString()?.let { editContent ->
- bookmark.content = editContent
- App.db.bookmarkDao.update(bookmark)
+ alertBinding.apply {
+ Coroutine.async {
+ bookmark.bookText = editBookText.text.toString()
+ bookmark.content = editView.text.toString()
+ App.db.bookmarkDao.insert(bookmark)
+ }
}
}
noButton()
diff --git a/app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt b/app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt
index 631b2da66..b464c1657 100644
--- a/app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt
+++ b/app/src/main/java/io/legado/app/ui/filepicker/FilePicker.kt
@@ -30,11 +30,10 @@ object FilePicker {
items(selectList) { _, index ->
when (index) {
0 -> {
- try {
+ kotlin.runCatching {
val intent = createSelectDirIntent()
activity.startActivityForResult(intent, requestCode)
- } catch (e: java.lang.Exception) {
- e.printStackTrace()
+ }.onFailure {
checkPermissions(activity) {
FilePickerDialog.show(
activity.supportFragmentManager,
@@ -81,11 +80,10 @@ object FilePicker {
items(selectList) { _, index ->
when (index) {
0 -> {
- try {
+ kotlin.runCatching {
val intent = createSelectDirIntent()
fragment.startActivityForResult(intent, requestCode)
- } catch (e: java.lang.Exception) {
- e.printStackTrace()
+ }.onFailure {
checkPermissions(fragment) {
FilePickerDialog.show(
fragment.childFragmentManager,
@@ -133,15 +131,14 @@ object FilePicker {
items(selectList) { _, index ->
when (index) {
0 -> {
- try {
+ kotlin.runCatching {
val intent = createSelectFileIntent()
intent.putExtra(
Intent.EXTRA_MIME_TYPES,
typesOfExtensions(allowExtensions)
)
activity.startActivityForResult(intent, requestCode)
- } catch (e: java.lang.Exception) {
- e.printStackTrace()
+ }.onFailure {
checkPermissions(activity) {
FilePickerDialog.show(
activity.supportFragmentManager,
@@ -191,15 +188,14 @@ object FilePicker {
items(selectList) { _, index ->
when (index) {
0 -> {
- try {
+ kotlin.runCatching {
val intent = createSelectFileIntent()
intent.putExtra(
Intent.EXTRA_MIME_TYPES,
typesOfExtensions(allowExtensions)
)
fragment.startActivityForResult(intent, requestCode)
- } catch (e: java.lang.Exception) {
- e.printStackTrace()
+ }.onFailure {
checkPermissions(fragment) {
FilePickerDialog.show(
fragment.childFragmentManager,
diff --git a/app/src/main/java/io/legado/app/ui/filepicker/entity/JavaBean.kt b/app/src/main/java/io/legado/app/ui/filepicker/entity/JavaBean.kt
index c23a827fb..a6fb0c2bc 100644
--- a/app/src/main/java/io/legado/app/ui/filepicker/entity/JavaBean.kt
+++ b/app/src/main/java/io/legado/app/ui/filepicker/entity/JavaBean.kt
@@ -34,15 +34,13 @@ open class JavaBean : Serializable {
val fields = list.toTypedArray()
for (field in fields) {
val fieldName = field.name
- try {
+ kotlin.runCatching {
val obj = field.get(this)
sb.append(fieldName)
sb.append("=")
sb.append(obj)
sb.append("\n")
- } catch (ignored: IllegalAccessException) {
}
-
}
return sb.toString()
}
diff --git a/app/src/main/java/io/legado/app/ui/filepicker/utils/ConvertUtils.kt b/app/src/main/java/io/legado/app/ui/filepicker/utils/ConvertUtils.kt
index 1af75ebe8..b4f0f0b7e 100644
--- a/app/src/main/java/io/legado/app/ui/filepicker/utils/ConvertUtils.kt
+++ b/app/src/main/java/io/legado/app/ui/filepicker/utils/ConvertUtils.kt
@@ -6,7 +6,6 @@ import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import java.io.BufferedReader
-import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.text.DecimalFormat
@@ -24,11 +23,9 @@ object ConvertUtils {
const val KB: Long = 1024
fun toInt(obj: Any): Int {
- return try {
+ return kotlin.runCatching {
Integer.parseInt(obj.toString())
- } catch (e: NumberFormatException) {
- -1
- }
+ }.getOrDefault(-1)
}
fun toInt(bytes: ByteArray): Int {
@@ -42,11 +39,9 @@ object ConvertUtils {
}
fun toFloat(obj: Any): Float {
- return try {
+ return kotlin.runCatching {
java.lang.Float.parseFloat(obj.toString())
- } catch (e: NumberFormatException) {
- -1f
- }
+ }.getOrDefault(-1f)
}
fun toString(objects: Array, tag: String): String {
@@ -62,7 +57,7 @@ object ConvertUtils {
fun toBitmap(bytes: ByteArray, width: Int = -1, height: Int = -1): Bitmap? {
var bitmap: Bitmap? = null
if (bytes.isNotEmpty()) {
- try {
+ kotlin.runCatching {
val options = BitmapFactory.Options()
// 设置让解码器以最佳方式解码
options.inPreferredConfig = null
@@ -72,7 +67,6 @@ object ConvertUtils {
}
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
bitmap!!.density = 96// 96 dpi
- } catch (e: Exception) {
}
}
return bitmap
@@ -101,7 +95,7 @@ object ConvertUtils {
@JvmOverloads
fun toString(`is`: InputStream, charset: String = "utf-8"): String {
val sb = StringBuilder()
- try {
+ kotlin.runCatching {
val reader = BufferedReader(InputStreamReader(`is`, charset))
while (true) {
val line = reader.readLine()
@@ -113,9 +107,7 @@ object ConvertUtils {
}
reader.close()
`is`.close()
- } catch (e: IOException) {
}
-
return sb.toString()
}
diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
index 239b0c9ff..88969fe5d 100644
--- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
@@ -9,6 +9,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.DefaultData
+import io.legado.app.help.LocalConfig
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.CacheBook
import io.legado.app.utils.FileUtils
@@ -74,13 +75,15 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
}
App.db.bookSourceDao.getBookSource(book.origin)?.let { bookSource ->
val webBook = WebBook(bookSource)
- webBook.getChapterList(book, context = upTocPool)
+ webBook.getChapterList(this, book, context = upTocPool)
.timeout(300000)
.onSuccess(IO) {
App.db.bookDao.update(book)
App.db.bookChapterDao.delByBook(book.bookUrl)
App.db.bookChapterDao.insert(*it.toTypedArray())
- cacheBook(webBook, book)
+ if (AppConfig.preDownload) {
+ cacheBook(webBook, book)
+ }
}
.onError {
it.printStackTrace()
@@ -114,7 +117,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
var addToCache = false
while (!addToCache) {
if (CacheBook.downloadCount() < 5) {
- CacheBook.download(webBook, book, chapter)
+ CacheBook.download(this, webBook, book, chapter)
addToCache = true
} else {
delay(1000)
@@ -148,7 +151,12 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
fun upVersion() {
execute {
- DefaultData.importDefaultTocRules()
+ if (LocalConfig.hasUpHttpTTS) {
+ DefaultData.importDefaultHttpTTS()
+ }
+ if (LocalConfig.hasUpTxtTocRule) {
+ DefaultData.importDefaultTocRules()
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
index fd4f39ee4..300ba646d 100644
--- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
+++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
@@ -1,6 +1,8 @@
package io.legado.app.ui.main.bookshelf
import android.annotation.SuppressLint
+import android.app.Activity.RESULT_OK
+import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@@ -29,17 +31,23 @@ import io.legado.app.ui.book.cache.CacheActivity
import io.legado.app.ui.book.group.GroupManageDialog
import io.legado.app.ui.book.local.ImportBookActivity
import io.legado.app.ui.book.search.SearchActivity
+import io.legado.app.ui.filepicker.FilePicker
+import io.legado.app.ui.filepicker.FilePickerDialog
import io.legado.app.ui.main.MainViewModel
import io.legado.app.ui.main.bookshelf.books.BooksFragment
import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
+import org.jetbrains.anko.share
/**
* 书架界面
*/
class BookshelfFragment : VMBaseFragment(R.layout.fragment_bookshelf),
TabLayout.OnTabSelectedListener,
+ FilePickerDialog.CallBack,
SearchView.OnQueryTextListener {
+
+ private val requestCodeImportBookshelf = 312
private val binding by viewBinding(FragmentBookshelfBinding::bind)
override val viewModel: BookshelfViewModel
get() = getViewModel(BookshelfViewModel::class.java)
@@ -67,8 +75,7 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b
when (item.itemId) {
R.id.menu_search -> startActivity()
R.id.menu_update_toc -> {
- val group = bookGroups[tabLayout.selectedTabPosition]
- val fragment = fragmentMap[group.groupId]
+ val fragment = fragmentMap[selectedGroup.groupId]
fragment?.getBooks()?.let {
activityViewModel.upToc(it)
}
@@ -79,18 +86,25 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b
R.id.menu_add_local -> startActivity()
R.id.menu_add_url -> addBookByUrl()
R.id.menu_arrange_bookshelf -> startActivity(
- Pair("groupId", selectedGroup?.groupId ?: 0),
- Pair("groupName", selectedGroup?.groupName ?: 0)
+ Pair("groupId", selectedGroup.groupId ?: 0),
+ Pair("groupName", selectedGroup.groupName ?: 0)
)
R.id.menu_download -> startActivity(
- Pair("groupId", selectedGroup?.groupId ?: 0),
- Pair("groupName", selectedGroup?.groupName ?: 0)
+ Pair("groupId", selectedGroup.groupId ?: 0),
+ Pair("groupName", selectedGroup.groupName ?: 0)
)
+ R.id.menu_export_bookshelf -> {
+ val fragment = fragmentMap[selectedGroup.groupId]
+ viewModel.exportBookshelf(fragment?.getBooks()) {
+ activity?.share(it)
+ }
+ }
+ R.id.menu_import_bookshelf -> importBookshelfAlert()
}
}
- private val selectedGroup: BookGroup?
- get() = bookGroups.getOrNull(binding.viewPagerBookshelf.currentItem)
+ private val selectedGroup: BookGroup
+ get() = bookGroups[tabLayout.selectedTabPosition]
private fun initView() {
ATH.applyEdgeEffectColor(binding.viewPagerBookshelf)
@@ -190,8 +204,8 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b
}
override fun onTabReselected(tab: TabLayout.Tab) {
- fragmentMap[selectedGroup?.groupId]?.let {
- toast("${selectedGroup?.groupName}(${it.getBooksCount()})")
+ fragmentMap[selectedGroup.groupId]?.let {
+ toast("${selectedGroup.groupName}(${it.getBooksCount()})")
}
}
@@ -202,7 +216,42 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b
}
fun gotoTop() {
- fragmentMap[selectedGroup?.groupId]?.gotoTop()
+ fragmentMap[selectedGroup.groupId]?.gotoTop()
+ }
+
+ private fun importBookshelfAlert() {
+ alert(titleResource = R.string.import_bookshelf) {
+ val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
+ editView.hint = "url/json"
+ }
+ customView = alertBinding.root
+ okButton {
+ alertBinding.editView.text?.toString()?.let {
+ viewModel.importBookshelf(it, selectedGroup.groupId)
+ }
+ }
+ noButton()
+ neutralButton(R.string.select_file) {
+ FilePicker.selectFile(
+ this@BookshelfFragment,
+ requestCodeImportBookshelf,
+ allowExtensions = arrayOf("txt", "json")
+ )
+ }
+ }.show()
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ requestCodeImportBookshelf -> if (resultCode == RESULT_OK) {
+ data?.data?.let { uri ->
+ uri.readText(requireContext())?.let {
+ viewModel.importBookshelf(it, selectedGroup.groupId)
+ }
+ }
+ }
+ }
}
private inner class TabFragmentPageAdapter(fm: FragmentManager) :
diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt
index 0e159277c..e2e1b50e3 100644
--- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt
@@ -7,9 +7,13 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup
import io.legado.app.data.entities.BookSource
+import io.legado.app.model.webBook.PreciseSearch
import io.legado.app.model.webBook.WebBook
-import io.legado.app.utils.NetworkUtils
+import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.isActive
+import rxhttp.wrapper.param.RxHttp
+import rxhttp.wrapper.param.toText
class BookshelfViewModel(application: Application) : BaseViewModel(application) {
@@ -41,7 +45,7 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
origin = bookSource.bookSourceUrl,
originName = bookSource.bookSourceName
)
- WebBook(bookSource).getBookInfo(book, this)
+ WebBook(bookSource).getBookInfo(this, book)
.onSuccess(IO) {
it.order = App.db.bookDao.maxOrder + 1
App.db.bookDao.insert(it)
@@ -62,6 +66,65 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
}
}
+ fun exportBookshelf(books: List?, success: (json: String) -> Unit) {
+ execute {
+ val exportList = arrayListOf