支持DIY书源

pull/5/head
fengyuecanzhu 4 years ago
parent be25d5569b
commit b7fc0b98f4
  1. 4
      .idea/assetWizardSettings.xml
  2. BIN
      .idea/caches/build_file_checksums.ser
  3. 2
      .idea/inspectionProfiles/Project_Default.xml
  4. 180
      README.md
  5. 20
      app/build.gradle
  6. 20
      app/src/androidTest/java/xyz/fycz/myreader/ExampleInstrumentedTest.java
  7. 8
      app/src/androidTest/java/xyz/fycz/myreader/TestAudioPlay.java
  8. 7
      app/src/main/AndroidManifest.xml
  9. 471
      app/src/main/assets/highlightjs/highlight.pack.js
  10. 85
      app/src/main/assets/highlightjs/rainbow.css
  11. 3
      app/src/main/assets/webdavhelp.fy
  12. 29
      app/src/main/java/xyz/fycz/myreader/application/App.java
  13. 26
      app/src/main/java/xyz/fycz/myreader/application/SysManager.java
  14. 4
      app/src/main/java/xyz/fycz/myreader/base/BaseActivity.java
  15. 13
      app/src/main/java/xyz/fycz/myreader/common/APPCONST.java
  16. 1
      app/src/main/java/xyz/fycz/myreader/entity/SearchBookBean.java
  17. 30
      app/src/main/java/xyz/fycz/myreader/entity/sourcedebug/DebugBook.kt
  18. 13
      app/src/main/java/xyz/fycz/myreader/entity/sourcedebug/DebugChapter.kt
  19. 100
      app/src/main/java/xyz/fycz/myreader/entity/sourcedebug/DebugEntity.java
  20. 10
      app/src/main/java/xyz/fycz/myreader/entity/sourcedebug/ListResult.kt
  21. 12
      app/src/main/java/xyz/fycz/myreader/entity/sourceedit/EditEntity.kt
  22. 181
      app/src/main/java/xyz/fycz/myreader/entity/sourceedit/EditEntityUtil.kt
  23. 80
      app/src/main/java/xyz/fycz/myreader/enums/BookSource.java
  24. 80
      app/src/main/java/xyz/fycz/myreader/enums/LocalBookSource.java
  25. 4
      app/src/main/java/xyz/fycz/myreader/greendao/GreenDaoManager.java
  26. 42
      app/src/main/java/xyz/fycz/myreader/greendao/entity/Book.java
  27. 373
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/BookSource.java
  28. 93
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/ContentRule.java
  29. 185
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/FindRule.java
  30. 175
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/InfoRule.java
  31. 236
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/SearchRule.java
  32. 116
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/TocRule.java
  33. 22
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/convert/ContentRuleConvert.java
  34. 24
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/convert/FindRuleConvert.java
  35. 23
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/convert/InfoRuleConvert.java
  36. 23
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/convert/SearchRuleConvert.java
  37. 23
      app/src/main/java/xyz/fycz/myreader/greendao/entity/rule/convert/TocRuleConvert.java
  38. 6
      app/src/main/java/xyz/fycz/myreader/greendao/service/BookGroupService.java
  39. 10
      app/src/main/java/xyz/fycz/myreader/greendao/service/BookService.java
  40. 4
      app/src/main/java/xyz/fycz/myreader/greendao/util/MySQLiteOpenHelper.java
  41. 34
      app/src/main/java/xyz/fycz/myreader/model/ReplaceRuleManager.java
  42. 6
      app/src/main/java/xyz/fycz/myreader/model/SearchEngine.java
  43. 7
      app/src/main/java/xyz/fycz/myreader/model/audio/ReadAloudService.java
  44. 20
      app/src/main/java/xyz/fycz/myreader/model/backup/UserService.java
  45. 247
      app/src/main/java/xyz/fycz/myreader/model/source/BaseAnalyzer.java
  46. 375
      app/src/main/java/xyz/fycz/myreader/model/source/BookSourceManager.java
  47. 84
      app/src/main/java/xyz/fycz/myreader/model/source/JsonPathAnalyzer.java
  48. 319
      app/src/main/java/xyz/fycz/myreader/model/source/MatcherAnalyzer.java
  49. 117
      app/src/main/java/xyz/fycz/myreader/model/source/XpathAnalyzer.java
  50. 9
      app/src/main/java/xyz/fycz/myreader/model/storage/Backup.kt
  51. 8
      app/src/main/java/xyz/fycz/myreader/model/storage/Restore.kt
  52. 8
      app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
  53. 34
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookDetailedActivity.java
  54. 145
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookSourceActivity.java
  55. 18
      app/src/main/java/xyz/fycz/myreader/ui/activity/BookstoreActivity.java
  56. 6
      app/src/main/java/xyz/fycz/myreader/ui/activity/FeedbackActivity.java
  57. 47
      app/src/main/java/xyz/fycz/myreader/ui/activity/FileSystemActivity.java
  58. 13
      app/src/main/java/xyz/fycz/myreader/ui/activity/LoginActivity.java
  59. 16
      app/src/main/java/xyz/fycz/myreader/ui/activity/MainActivity.java
  60. 71
      app/src/main/java/xyz/fycz/myreader/ui/activity/MoreSettingActivity.java
  61. 50
      app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
  62. 12
      app/src/main/java/xyz/fycz/myreader/ui/activity/RegisterActivity.java
  63. 312
      app/src/main/java/xyz/fycz/myreader/ui/activity/ReplaceRuleActivity.java
  64. 258
      app/src/main/java/xyz/fycz/myreader/ui/activity/RuleActivity.java
  65. 178
      app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
  66. 273
      app/src/main/java/xyz/fycz/myreader/ui/activity/SourceDebugActivity.java
  67. 280
      app/src/main/java/xyz/fycz/myreader/ui/activity/SourceEditActivity.java
  68. 6
      app/src/main/java/xyz/fycz/myreader/ui/activity/SplashActivity.java
  69. 129
      app/src/main/java/xyz/fycz/myreader/ui/adapter/BaseSourceAdapter.java
  70. 98
      app/src/main/java/xyz/fycz/myreader/ui/adapter/BookSourceAdapter.java
  71. 10
      app/src/main/java/xyz/fycz/myreader/ui/adapter/BookStoreBookAdapter.java
  72. 11
      app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseAdapter.java
  73. 5
      app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDetailedAdapter.java
  74. 15
      app/src/main/java/xyz/fycz/myreader/ui/adapter/BookcaseDragAdapter.java
  75. 4
      app/src/main/java/xyz/fycz/myreader/ui/adapter/FontsAdapter.java
  76. 61
      app/src/main/java/xyz/fycz/myreader/ui/adapter/LocalSourceAdapter.java
  77. 85
      app/src/main/java/xyz/fycz/myreader/ui/adapter/ReplaceRuleAdapter.java
  78. 24
      app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchBookAdapter.java
  79. 4
      app/src/main/java/xyz/fycz/myreader/ui/adapter/SearchHistoryAdapter.java
  80. 17
      app/src/main/java/xyz/fycz/myreader/ui/adapter/SourceEditAdapter.java
  81. 160
      app/src/main/java/xyz/fycz/myreader/ui/adapter/helper/ItemTouchCallback.java
  82. 185
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookSourceHolder.java
  83. 17
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/BookStoreBookHolder.java
  84. 67
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/LocalSourceHolder.java
  85. 28
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/ReplaceRuleHolder.java
  86. 63
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SearchBookHolder.java
  87. 79
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SourceEditHolder.kt
  88. 4
      app/src/main/java/xyz/fycz/myreader/ui/adapter/holder/SourceExchangeHolder.java
  89. 7
      app/src/main/java/xyz/fycz/myreader/ui/dialog/APPDownloadTip.java
  90. 4
      app/src/main/java/xyz/fycz/myreader/ui/dialog/AudioPlayerDialog.java
  91. 5
      app/src/main/java/xyz/fycz/myreader/ui/dialog/ChangeSourceDialog.java
  92. 29
      app/src/main/java/xyz/fycz/myreader/ui/dialog/DialogCreator.java
  93. 17
      app/src/main/java/xyz/fycz/myreader/ui/dialog/MyAlertDialog.java
  94. 42
      app/src/main/java/xyz/fycz/myreader/ui/dialog/ReplaceDialog.java
  95. 2
      app/src/main/java/xyz/fycz/myreader/ui/dialog/SourceExchangeDialog.java
  96. 432
      app/src/main/java/xyz/fycz/myreader/ui/fragment/DIYSourceFragment.java
  97. 125
      app/src/main/java/xyz/fycz/myreader/ui/fragment/LocalSourceFragment.java
  98. 36
      app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
  99. 4
      app/src/main/java/xyz/fycz/myreader/ui/fragment/PrivateBooksFragment.java
  100. 40
      app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
  101. Some files were not shown because too many files have changed in this diff Show More

@ -19,8 +19,8 @@
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="ic_delete" />
<entry key="sourceFile" value="F:\SVG图标\删 除.svg" />
<entry key="outputName" value="ic_debug" />
<entry key="sourceFile" value="F:\SVG图标\debug.svg" />
</map>
</option>
</PersistentState>

@ -30,7 +30,7 @@
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
<option name="myAdditionalJavadocTags" value="date,r(oldStr," />
</inspection_tool>
</profile>
</component>

@ -20,181 +20,11 @@
三七中文、新笔趣阁、斋书苑
* 4个实体书书源:超星图书·实体、作品集·实体、99藏书·实体、100本·实体
* 如何自行制作并添加书源.
* 基于面向接口开发的思想,对于书源我设计了如下接口:
* ```java
// 这个接口位于xyz.fycz.myreader.webapi.crawler.base包下
public interface ReadCrawler {
String getSearchLink(); // 书源的搜索url
String getCharset(); // 书源的字符编码
String getSearchCharset(); // 书源搜索关键字的字符编码,和书源的字符编码就行
String getNameSpace(); // 书源主页地址
Boolean isPost(); // 是否以post请求搜索
String getContentFormHtml(String html); // 获取书籍内容规则
ArrayList<Chapter> getChaptersFromHtml(String html); // 获取书籍章节列表规则
ConcurrentMultiValueMap<SearchBookBean, Book> getBooksFromSearchHtml(String html); // 搜索书籍规则
}
```
* 了解上述接口的方法,我们就可以开始创建书源了
* 第一步:创建一个书源类实现上述接口,下面以笔趣阁44为例进行说明
* ```java
// 注意:如果搜索书籍页没有图片、最新章节、书籍简介等信息,可以通过实现BookInfoCrawler接口,从书籍详情页获取
public class BiQuGe44ReadCrawler implements ReadCrawler, BookInfoCrawler {
//网站主页地址
public static final String NAME_SPACE = "https://www.wqge.cc";
/*
搜索url,搜索关键词以{key}进行占位
如果是post请求,以“,”分隔url,“,”前是搜索地址,“,”后是请求体,搜索关键词同样以{key}占位
例如:"https://www.9txs.com/search.html,searchkey={key}"
*/
public static final String NOVEL_SEARCH = "https://www.wqge.cc/modules/article/search.php?searchkey={key}";
// 书源字符编码
public static final String CHARSET = "GBK";
// 书源搜索关键词编码
public static final String SEARCH_CHARSET = "utf-8";
@Override
public String getSearchLink() {
return NOVEL_SEARCH;
}
@Override
public String getCharset() {
return CHARSET;
}
@Override
public String getNameSpace() {
return NAME_SPACE;
}
@Override
public Boolean isPost() {
return false;
}
@Override
public String getSearchCharset() {
return SEARCH_CHARSET;
}
/**
* 从html中获取章节正文
* @param html
* @return
*/
public String getContentFormHtml(String html) {
Document doc = Jsoup.parse(html);
Element divContent = doc.getElementById("content");
if (divContent != null) {
String content = Html.fromHtml(divContent.html()).toString();
char c = 160;
String spaec = "" + c;
content = content.replace(spaec, " ");
return content;
} else {
return "";
}
}
/**
* 从html中获取章节列表
*
* @param html
* @return
*/
public ArrayList<Chapter> getChaptersFromHtml(String html) {
ArrayList<Chapter> chapters = new ArrayList<>();
Document doc = Jsoup.parse(html);
String readUrl = doc.select("meta[property=og:novel:read_url]").attr("content");
Element divList = doc.getElementById("list");
String lastTile = null;
int i = 0;
Elements elementsByTag = divList.getElementsByTag("dd");
for (int j = 9; j < elementsByTag.size(); j++) {
Element dd = elementsByTag.get(j);
Elements as = dd.getElementsByTag("a");
if (as.size() > 0) {
Element a = as.get(0);
String title = a.text() ;
if (!StringHelper.isEmpty(lastTile) && title.equals(lastTile)) {
continue;
}
Chapter chapter = new Chapter();
chapter.setNumber(i++);
chapter.setTitle(title);
String url = readUrl + a.attr("href");
chapter.setUrl(url);
chapters.add(chapter);
lastTile = title;
}
}
return chapters;
}
/**
* 从搜索html中得到书列表
* @param html
* @return
*/
public ConcurrentMultiValueMap<SearchBookBean, Book> getBooksFromSearchHtml(String html) {
ConcurrentMultiValueMap<SearchBookBean, Book> books = new ConcurrentMultiValueMap<>();
Document doc = Jsoup.parse(html);
Elements divs = doc.getElementsByTag("table");
Element div = divs.get(0);
Elements elementsByTag = div.getElementsByTag("tr");
for (int i = 1; i < elementsByTag.size(); i++) {
Element element = elementsByTag.get(i);
Book book = new Book();
Elements info = element.getElementsByTag("td");
book.setName(info.get(0).text());
book.setChapterUrl(NAME_SPACE + info.get(0).getElementsByTag("a").attr("href"));
book.setAuthor(info.get(2).text());
book.setNewestChapterTitle(info.get(1).text());
book.setSource(BookSource.biquge44.toString());
// SearchBookBean用于合并相同书籍
SearchBookBean sbb = new SearchBookBean(book.getName(), book.getAuthor());
books.add(sbb, book);
}
return books;
}
/**
* 获取书籍详细信息
* @param book
*/
public Book getBookInfo(String html, Book book){
Document doc = Jsoup.parse(html);
Element img = doc.getElementById("fmimg");
book.setImgUrl(img.getElementsByTag("img").get(0).attr("src"));
Element desc = doc.getElementById("intro");
book.setDesc(desc.getElementsByTag("p").get(0).text());
Element type = doc.getElementsByClass("con_top").get(0);
book.setType(type.getElementsByTag("a").get(2).text());
return book;
}
}
```
* 第二步:注册书源信息。有两个地方需要注册:
* 1)在xyz.fycz.myreader.enums.BookSource类(这是个枚举类型)中添加你的书源的命名以及书源名称,例如:
* ```java
biquge44("笔趣阁44") // biquge44是书源的命名,笔趣阁44是书源名称
```
* 2)在app/src/main/resources/crawler.properties配置文件中添加书源类信息,例如:
* ```java
// biquge44书源的命名,与BookSource中的命名一致,xyz.fycz.myreader.webapi.crawler.read.BiQuGe44ReadCrawler是书源类的完整路径
biquge44=xyz.fycz.myreader.webapi.crawler.read.BiQuGe44ReadCrawler
```
* 第三步:启用书源(新增的书源默认禁用)。只需在软件内-我的-设置-禁用书源中取消该书源的禁用即可。
#### 一、关于书源
* 软件中有两种书源,内置书源和DIY书源
* 内置书源为java类,需要编译
* DIY书源为用户自定义书源,可在软件内以特定规则书写的书源
* ![内置书源说明](./source/LocalSource.md) ![DIY书源说明](./source/DIYSource.md)
#### 二、关于发现界面

@ -99,6 +99,7 @@ android {
}
productFlavors {
}
}
@ -136,7 +137,10 @@ dependencies {
implementation 'org.greenrobot:greendao:3.3.0'
implementation 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1'
//JSoup
implementation 'org.jsoup:jsoup:1.11.3'
implementation 'cn.wanghaomiao:JsoupXpath:2.4.3'
implementation 'com.jayway.jsonpath:json-path:2.5.0'
//SmartRefreshLayout
implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.2'
@ -184,24 +188,10 @@ dependencies {
//
implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
/*//email
implementation 'com.sun.mail:android-mail:1.6.2'
implementation 'com.sun.mail:android-activation:1.6.2'*/
//Material Dialogs
/*def dialog_version = '3.3.0'
implementation "com.afollestad.material-dialogs:core:$dialog_version"
implementation "com.afollestad.material-dialogs:input:$dialog_version"
implementation "com.afollestad.material-dialogs:files:$dialog_version"
implementation "com.afollestad.material-dialogs:bottomsheets:$dialog_version"*/
//kongzueDialog
//implementation 'com.kongzue.dialog_v3:dialog:3.2.5'
}
greendao {
schemaVersion 18
schemaVersion 23
daoPackage 'xyz.fycz.myreader.greendao.gen'
// targetGenDir 'src/main/java'
}

@ -9,6 +9,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import xyz.fycz.myreader.model.source.MatcherAnalyzer;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
import static org.junit.Assert.assertEquals;
/**
@ -27,9 +32,18 @@ public class ExampleInstrumentedTest {
@Test
public void test() {
//ReadService readService = new ReadService();
//readService.initSynthesizer();
//readService.startSynthesizer("科幻电影是我们从小就爱看的电影题材,我们也都梦想过生活在那样的科幻世界里。科幻片,顾名思义即“科学幻想片”,是“以科学幻想为内容的故事片,其基本特点是从今天已知的科学原理和科学成就出发,对未来的世界或遥远的过去的情景作幻想式的描述。”");
try {
String str = OkHttpUtils.getHtml("https://www.xiaobiquge.com/29/29787/101469886.html", "gbk");
MatcherAnalyzer analyzer = new MatcherAnalyzer();
String rule = "<div class=\"logo\"><text></div>##@e(<text>,小笔趣阁){@a(<text> + 风月读书)}";
/*List<String> list = analyzer.matcherInnerString(rule, str);
for (String s : list) {
System.out.println(s);
}*/
System.out.println(analyzer.getInnerText(rule, str));
} catch (IOException e) {
e.printStackTrace();
}
}
}

@ -4,13 +4,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.runner.RunWith;
import java.io.UnsupportedEncodingException;
@RunWith(AndroidJUnit4.class)
public class TestAudioPlay {
public void test() {
//ReadService readService = new ReadService();
//readService.initSynthesizer();
//readService.startSynthesizer("科幻电影是我们从小就爱看的电影题材,我们也都梦想过生活在那样的科幻世界里。科幻片,顾名思义即“科学幻想片”,是“以科学幻想为内容的故事片,其基本特点是从今天已知的科学原理和科学成就出发,对未来的世界或遥远的过去的情景作幻想式的描述。”");
public void test() throws UnsupportedEncodingException {
}
}

@ -37,7 +37,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name=".application.MyApplication"
android:name=".application.App"
android:allowBackup="true"
android:alwaysRetainTaskState="true"
android:icon="@mipmap/ic_launcher"
@ -101,11 +101,14 @@
<activity android:name=".ui.activity.FileSystemActivity"/>
<activity android:name=".ui.activity.FeedbackActivity"/>
<activity android:name=".ui.activity.RuleActivity"/>
<activity android:name=".ui.activity.ReplaceRuleActivity"/>
<activity android:name=".ui.activity.MoreSettingActivity"/>
<activity android:name=".ui.activity.BookstoreActivity"/>
<activity android:name=".ui.activity.QRCodeScanActivity"/>
<activity android:name=".ui.activity.BookSourceActivity"/>
<activity android:name=".ui.activity.SourceEditActivity"/>
<activity android:name=".ui.activity.SourceDebugActivity"/>
<receiver android:name=".util.notification.NotificationClickReceiver"/>
<receiver android:name=".ui.presenter.BookcasePresenter$cancelDownloadReceiver"/>

@ -0,0 +1,471 @@
/*
Highlight.js 10.5.0 (af20048d)
License: BSD-3-Clause
Copyright (c) 2006-2020, Ivan Sagalaev
*/
var hljs=function(){"use strict";function e(t){
return t instanceof Map?t.clear=t.delete=t.set=()=>{
throw Error("map is read-only")}:t instanceof Set&&(t.add=t.clear=t.delete=()=>{
throw Error("set is read-only")
}),Object.freeze(t),Object.getOwnPropertyNames(t).forEach((n=>{var s=t[n]
;"object"!=typeof s||Object.isFrozen(s)||e(s)})),t}var t=e,n=e;t.default=n
;class s{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}
ignoreMatch(){this.ignore=!0}}function r(e){
return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")
}function a(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t]
;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const i=e=>!!e.kind
;class o{constructor(e,t){
this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){
this.buffer+=r(e)}openNode(e){if(!i(e))return;let t=e.kind
;e.sublanguage||(t=`${this.classPrefix}${t}`),this.span(t)}closeNode(e){
i(e)&&(this.buffer+="</span>")}value(){return this.buffer}span(e){
this.buffer+=`<span class="${e}">`}}class l{constructor(){this.rootNode={
children:[]},this.stack=[this.rootNode]}get top(){
return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){
this.top.children.push(e)}openNode(e){const t={kind:e,children:[]}
;this.add(t),this.stack.push(t)}closeNode(){
if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){
for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}
walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){
return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),
t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){
"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{
l._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e}
addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}
addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root
;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){
return new o(this,this.options).value()}finalize(){return!0}}function u(e){
return e?"string"==typeof e?e:e.source:null}
const g="[a-zA-Z]\\w*",d="[a-zA-Z_]\\w*",h="\\b\\d+(\\.\\d+)?",f="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",p="\\b(0b[01]+)",m={
begin:"\\\\[\\s\\S]",relevance:0},b={className:"string",begin:"'",end:"'",
illegal:"\\n",contains:[m]},x={className:"string",begin:'"',end:'"',
illegal:"\\n",contains:[m]},E={
begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/
},v=(e,t,n={})=>{const s=a({className:"comment",begin:e,end:t,contains:[]},n)
;return s.contains.push(E),s.contains.push({className:"doctag",
begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),s
},N=v("//","$"),w=v("/\\*","\\*/"),R=v("#","$");var y=Object.freeze({
__proto__:null,IDENT_RE:g,UNDERSCORE_IDENT_RE:d,NUMBER_RE:h,C_NUMBER_RE:f,
BINARY_NUMBER_RE:p,
RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",
SHEBANG:(e={})=>{const t=/^#![ ]*\//
;return e.binary&&(e.begin=((...e)=>e.map((e=>u(e))).join(""))(t,/.*\b/,e.binary,/\b.*/)),
a({className:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{
0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:m,APOS_STRING_MODE:b,
QUOTE_STRING_MODE:x,PHRASAL_WORDS_MODE:E,COMMENT:v,C_LINE_COMMENT_MODE:N,
C_BLOCK_COMMENT_MODE:w,HASH_COMMENT_MODE:R,NUMBER_MODE:{className:"number",
begin:h,relevance:0},C_NUMBER_MODE:{className:"number",begin:f,relevance:0},
BINARY_NUMBER_MODE:{className:"number",begin:p,relevance:0},CSS_NUMBER_MODE:{
className:"number",
begin:h+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",
relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",
begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[m,{begin:/\[/,end:/\]/,
relevance:0,contains:[m]}]}]},TITLE_MODE:{className:"title",begin:g,relevance:0
},UNDERSCORE_TITLE_MODE:{className:"title",begin:d,relevance:0},METHOD_GUARD:{
begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{
"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{
t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function _(e,t){
"."===e.input[e.index-1]&&t.ignoreMatch()}function k(e,t){
t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",
e.__beforeBegin=_,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords)
}function M(e,t){
Array.isArray(e.illegal)&&(e.illegal=((...e)=>"("+e.map((e=>u(e))).join("|")+")")(...e.illegal))
}function O(e,t){if(e.match){
if(e.begin||e.end)throw Error("begin & end are not supported with match")
;e.begin=e.match,delete e.match}}function A(e,t){
void 0===e.relevance&&(e.relevance=1)}
const L=["of","and","for","in","not","or","if","then","parent","list","value"]
;function B(e,t){return t?Number(t):(e=>L.includes(e.toLowerCase()))(e)?0:1}
function I(e,{plugins:t}){function n(t,n){
return RegExp(u(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class s{
constructor(){
this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}
addRule(e,t){
t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),
this.matchAt+=(e=>RegExp(e.toString()+"|").exec("").length-1)(e)+1}compile(){
0===this.regexes.length&&(this.exec=()=>null)
;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(((e,t="|")=>{
const n=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;let s=0,r=""
;for(let a=0;a<e.length;a++){s+=1;const i=s;let o=u(e[a])
;for(a>0&&(r+=t),r+="(";o.length>0;){const e=n.exec(o);if(null==e){r+=o;break}
r+=o.substring(0,e.index),
o=o.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+i):(r+=e[0],
"("===e[0]&&s++)}r+=")"}return r})(e),!0),this.lastIndex=0}exec(e){
this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e)
;if(!t)return null
;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),s=this.matchIndexes[n]
;return t.splice(0,n),Object.assign(t,s)}}class r{constructor(){
this.rules=[],this.multiRegexes=[],
this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){
if(this.multiRegexes[e])return this.multiRegexes[e];const t=new s
;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),
t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){
return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){
this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){
const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex
;let n=t.exec(e)
;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{
const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}
return n&&(this.regexIndex+=n.position+1,
this.regexIndex===this.count&&this.considerAll()),n}}
if(e.compilerExtensions||(e.compilerExtensions=[]),
e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.")
;return e.classNameAliases=a(e.classNameAliases||{}),function t(s,i){const o=s
;if(s.compiled)return o
;[O].forEach((e=>e(s,i))),e.compilerExtensions.forEach((e=>e(s,i))),
s.__beforeBegin=null,[k,M,A].forEach((e=>e(s,i))),s.compiled=!0;let l=null
;if("object"==typeof s.keywords&&(l=s.keywords.$pattern,
delete s.keywords.$pattern),s.keywords&&(s.keywords=((e,t)=>{const n={}
;return"string"==typeof e?s("keyword",e):Object.keys(e).forEach((t=>{s(t,e[t])
})),n;function s(e,s){t&&(s=s.toLowerCase()),s.split(" ").forEach((t=>{
const s=t.split("|");n[s[0]]=[e,B(s[0],s[1])]}))}
})(s.keywords,e.case_insensitive)),
s.lexemes&&l)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ")
;return l=l||s.lexemes||/\w+/,
o.keywordPatternRe=n(l,!0),i&&(s.begin||(s.begin=/\B|\b/),
o.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),
s.end||s.endsWithParent||(s.end=/\B|\b/),
s.end&&(o.endRe=n(s.end)),o.terminatorEnd=u(s.end)||"",
s.endsWithParent&&i.terminatorEnd&&(o.terminatorEnd+=(s.end?"|":"")+i.terminatorEnd)),
s.illegal&&(o.illegalRe=n(s.illegal)),
s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>a(e,{
variants:null},t)))),e.cachedVariants?e.cachedVariants:T(e)?a(e,{
starts:e.starts?a(e.starts):null
}):Object.isFrozen(e)?a(e):e))("self"===e?s:e)))),s.contains.forEach((e=>{t(e,o)
})),s.starts&&t(s.starts,i),o.matcher=(e=>{const t=new r
;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"
}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"
}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(o),o}(e)}function T(e){
return!!e&&(e.endsWithParent||T(e.starts))}function j(e){const t={
props:["language","code","autodetect"],data:()=>({detectedLanguage:"",
unknownLanguage:!1}),computed:{className(){
return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){
if(!this.autoDetect&&!e.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),
this.unknownLanguage=!0,r(this.code);let t={}
;return this.autoDetect?(t=e.highlightAuto(this.code),
this.detectedLanguage=t.language):(t=e.highlight(this.language,this.code,this.ignoreIllegals),
this.detectedLanguage=this.language),t.value},autoDetect(){
return!(this.language&&(e=this.autodetect,!e&&""!==e));var e},
ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{
class:this.className,domProps:{innerHTML:this.highlighted}})])}};return{
Component:t,VuePlugin:{install(e){e.component("highlightjs",t)}}}}const S={
"after:highlightBlock":({block:e,result:t,text:n})=>{const s=D(e)
;if(!s.length)return;const a=document.createElement("div")
;a.innerHTML=t.value,t.value=((e,t,n)=>{let s=0,a="";const i=[];function o(){
return e.length&&t.length?e[0].offset!==t[0].offset?e[0].offset<t[0].offset?e:t:"start"===t[0].event?e:t:e.length?e:t
}function l(e){a+="<"+P(e)+[].map.call(e.attributes,(function(e){
return" "+e.nodeName+'="'+r(e.value)+'"'})).join("")+">"}function c(e){
a+="</"+P(e)+">"}function u(e){("start"===e.event?l:c)(e.node)}
for(;e.length||t.length;){let t=o()
;if(a+=r(n.substring(s,t[0].offset)),s=t[0].offset,t===e){i.reverse().forEach(c)
;do{u(t.splice(0,1)[0]),t=o()}while(t===e&&t.length&&t[0].offset===s)
;i.reverse().forEach(l)
}else"start"===t[0].event?i.push(t[0].node):i.pop(),u(t.splice(0,1)[0])}
return a+r(n.substr(s))})(s,D(a),n)}};function P(e){
return e.nodeName.toLowerCase()}function D(e){const t=[];return function e(n,s){
for(let r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?s+=r.nodeValue.length:1===r.nodeType&&(t.push({
event:"start",offset:s,node:r}),s=e(r,s),P(r).match(/br|hr|img|input/)||t.push({
event:"stop",offset:s,node:r}));return s}(e,0),t}const C=e=>{console.error(e)
},H=(e,...t)=>{console.log("WARN: "+e,...t)},$=(e,t)=>{
console.log(`Deprecated as of ${e}. ${t}`)},U=r,z=a,K=Symbol("nomatch")
;return(e=>{const n=Object.create(null),r=Object.create(null),a=[];let i=!0
;const o=/(^(<[^>]+>|\t|)+|\n)/gm,l="Could not find the language '{}', did you forget to load/include a language module?",u={
disableAutodetect:!0,name:"Plain text",contains:[]};let g={
noHighlightRe:/^(no-?highlight)$/i,
languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",
tabReplace:null,useBR:!1,languages:null,__emitter:c};function d(e){
return g.noHighlightRe.test(e)}function h(e,t,n,s){const r={code:t,language:e}
;_("before:highlight",r);const a=r.result?r.result:f(r.language,r.code,n,s)
;return a.code=r.code,_("after:highlight",a),a}function f(e,t,r,o){const c=t
;function u(e,t){const n=w.case_insensitive?t[0].toLowerCase():t[0]
;return Object.prototype.hasOwnProperty.call(e.keywords,n)&&e.keywords[n]}
function d(){null!=_.subLanguage?(()=>{if(""===O)return;let e=null
;if("string"==typeof _.subLanguage){
if(!n[_.subLanguage])return void M.addText(O)
;e=f(_.subLanguage,O,!0,k[_.subLanguage]),k[_.subLanguage]=e.top
}else e=p(O,_.subLanguage.length?_.subLanguage:null)
;_.relevance>0&&(A+=e.relevance),M.addSublanguage(e.emitter,e.language)
})():(()=>{if(!_.keywords)return void M.addText(O);let e=0
;_.keywordPatternRe.lastIndex=0;let t=_.keywordPatternRe.exec(O),n="";for(;t;){
n+=O.substring(e,t.index);const s=u(_,t);if(s){const[e,r]=s
;M.addText(n),n="",A+=r;const a=w.classNameAliases[e]||e;M.addKeyword(t[0],a)
}else n+=t[0];e=_.keywordPatternRe.lastIndex,t=_.keywordPatternRe.exec(O)}
n+=O.substr(e),M.addText(n)})(),O=""}function h(e){
return e.className&&M.openNode(w.classNameAliases[e.className]||e.className),
_=Object.create(e,{parent:{value:_}}),_}function m(e,t,n){let r=((e,t)=>{
const n=e&&e.exec(t);return n&&0===n.index})(e.endRe,n);if(r){if(e["on:end"]){
const n=new s(e);e["on:end"](t,n),n.ignore&&(r=!1)}if(r){
for(;e.endsParent&&e.parent;)e=e.parent;return e}}
if(e.endsWithParent)return m(e.parent,t,n)}function b(e){
return 0===_.matcher.regexIndex?(O+=e[0],1):(T=!0,0)}function x(e){
const t=e[0],n=c.substr(e.index),s=m(_,e,n);if(!s)return K;const r=_
;r.skip?O+=t:(r.returnEnd||r.excludeEnd||(O+=t),d(),r.excludeEnd&&(O=t));do{
_.className&&M.closeNode(),_.skip||_.subLanguage||(A+=_.relevance),_=_.parent
}while(_!==s.parent)
;return s.starts&&(s.endSameAsBegin&&(s.starts.endRe=s.endRe),
h(s.starts)),r.returnEnd?0:t.length}let E={};function v(t,n){const a=n&&n[0]
;if(O+=t,null==a)return d(),0
;if("begin"===E.type&&"end"===n.type&&E.index===n.index&&""===a){
if(O+=c.slice(n.index,n.index+1),!i){const t=Error("0 width match regex")
;throw t.languageName=e,t.badRule=E.rule,t}return 1}
if(E=n,"begin"===n.type)return function(e){
const t=e[0],n=e.rule,r=new s(n),a=[n.__beforeBegin,n["on:begin"]]
;for(const n of a)if(n&&(n(e,r),r.ignore))return b(t)
;return n&&n.endSameAsBegin&&(n.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),
n.skip?O+=t:(n.excludeBegin&&(O+=t),
d(),n.returnBegin||n.excludeBegin||(O=t)),h(n),n.returnBegin?0:t.length}(n)
;if("illegal"===n.type&&!r){
const e=Error('Illegal lexeme "'+a+'" for mode "'+(_.className||"<unnamed>")+'"')
;throw e.mode=_,e}if("end"===n.type){const e=x(n);if(e!==K)return e}
if("illegal"===n.type&&""===a)return 1
;if(B>1e5&&B>3*n.index)throw Error("potential infinite loop, way more iterations than matches")
;return O+=a,a.length}const w=N(e)
;if(!w)throw C(l.replace("{}",e)),Error('Unknown language: "'+e+'"')
;const R=I(w,{plugins:a});let y="",_=o||R;const k={},M=new g.__emitter(g);(()=>{
const e=[];for(let t=_;t!==w;t=t.parent)t.className&&e.unshift(t.className)
;e.forEach((e=>M.openNode(e)))})();let O="",A=0,L=0,B=0,T=!1;try{
for(_.matcher.considerAll();;){
B++,T?T=!1:_.matcher.considerAll(),_.matcher.lastIndex=L
;const e=_.matcher.exec(c);if(!e)break;const t=v(c.substring(L,e.index),e)
;L=e.index+t}return v(c.substr(L)),M.closeAllNodes(),M.finalize(),y=M.toHTML(),{
relevance:A,value:y,language:e,illegal:!1,emitter:M,top:_}}catch(t){
if(t.message&&t.message.includes("Illegal"))return{illegal:!0,illegalBy:{
msg:t.message,context:c.slice(L-100,L+100),mode:t.mode},sofar:y,relevance:0,
value:U(c),emitter:M};if(i)return{illegal:!1,relevance:0,value:U(c),emitter:M,
language:e,top:_,errorRaised:t};throw t}}function p(e,t){
t=t||g.languages||Object.keys(n);const s=(e=>{const t={relevance:0,
emitter:new g.__emitter(g),value:U(e),illegal:!1,top:u}
;return t.emitter.addText(e),t})(e),r=t.filter(N).filter(R).map((t=>f(t,e,!1)))
;r.unshift(s);const a=r.sort(((e,t)=>{
if(e.relevance!==t.relevance)return t.relevance-e.relevance
;if(e.language&&t.language){if(N(e.language).supersetOf===t.language)return 1
;if(N(t.language).supersetOf===e.language)return-1}return 0})),[i,o]=a,l=i
;return l.second_best=o,l}const m={"before:highlightBlock":({block:e})=>{
g.useBR&&(e.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n"))
},"after:highlightBlock":({result:e})=>{
g.useBR&&(e.value=e.value.replace(/\n/g,"<br>"))}},b=/^(<[^>]+>|\t)+/gm,x={
"after:highlightBlock":({result:e})=>{
g.tabReplace&&(e.value=e.value.replace(b,(e=>e.replace(/\t/g,g.tabReplace))))}}
;function E(e){let t=null;const n=(e=>{let t=e.className+" "
;t+=e.parentNode?e.parentNode.className:"";const n=g.languageDetectRe.exec(t)
;if(n){const t=N(n[1])
;return t||(H(l.replace("{}",n[1])),H("Falling back to no-highlight mode for this block.",e)),
t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>d(e)||N(e)))})(e)
;if(d(n))return;_("before:highlightBlock",{block:e,language:n}),t=e
;const s=t.textContent,a=n?h(n,s,!0):p(s);_("after:highlightBlock",{block:e,
result:a,text:s}),e.innerHTML=a.value,((e,t,n)=>{const s=t?r[t]:n
;e.classList.add("hljs"),s&&e.classList.add(s)})(e,n,a.language),e.result={
language:a.language,re:a.relevance,relavance:a.relevance
},a.second_best&&(e.second_best={language:a.second_best.language,
re:a.second_best.relevance,relavance:a.second_best.relevance})}const v=()=>{
v.called||(v.called=!0,document.querySelectorAll("pre code").forEach(E))}
;function N(e){return e=(e||"").toLowerCase(),n[e]||n[r[e]]}
function w(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{r[e]=t
}))}function R(e){const t=N(e);return t&&!t.disableAutodetect}function _(e,t){
const n=e;a.forEach((e=>{e[n]&&e[n](t)}))}Object.assign(e,{highlight:h,
highlightAuto:p,fixMarkup:e=>{
return $("10.2.0","fixMarkup will be removed entirely in v11.0"),
$("10.2.0","Please see https://github.com/highlightjs/highlight.js/issues/2534"),
t=e,
g.tabReplace||g.useBR?t.replace(o,(e=>"\n"===e?g.useBR?"<br>":e:g.tabReplace?e.replace(/\t/g,g.tabReplace):e)):t
;var t},highlightBlock:E,configure:e=>{
e.useBR&&($("10.3.0","'useBR' will be removed entirely in v11.0"),
$("10.3.0","Please see https://github.com/highlightjs/highlight.js/issues/2559")),
g=z(g,e)},initHighlighting:v,initHighlightingOnLoad:()=>{
window.addEventListener("DOMContentLoaded",v,!1)},registerLanguage:(t,s)=>{
let r=null;try{r=s(e)}catch(e){
if(C("Language definition for '{}' could not be registered.".replace("{}",t)),
!i)throw e;C(e),r=u}
r.name||(r.name=t),n[t]=r,r.rawDefinition=s.bind(null,e),r.aliases&&w(r.aliases,{
languageName:t})},listLanguages:()=>Object.keys(n),getLanguage:N,
registerAliases:w,requireLanguage:e=>{
$("10.4.0","requireLanguage will be removed entirely in v11."),
$("10.4.0","Please see https://github.com/highlightjs/highlight.js/pull/2844")
;const t=N(e);if(t)return t
;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},
autoDetection:R,inherit:z,addPlugin:e=>{a.push(e)},vuePlugin:j(e).VuePlugin
}),e.debugMode=()=>{i=!1},e.safeMode=()=>{i=!0},e.versionString="10.5.0"
;for(const e in y)"object"==typeof y[e]&&t(y[e])
;return Object.assign(e,y),e.addPlugin(m),e.addPlugin(S),e.addPlugin(x),e})({})
}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("xml",(()=>{"use strict";function e(e){
return e?"string"==typeof e?e:e.source:null}function n(e){return a("(?=",e,")")}
function a(...n){return n.map((n=>e(n))).join("")}function s(...n){
return"("+n.map((n=>e(n))).join("|")+")"}return e=>{
const t=a(/[A-Z_]/,a("(",/[A-Z0-9_.-]+:/,")?"),/[A-Z0-9_.-]*/),i={
className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},r={begin:/\s/,
contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]
},c=e.inherit(r,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{
className:"meta-string"}),g=e.inherit(e.QUOTE_STRING_MODE,{
className:"meta-string"}),m={endsWithParent:!0,illegal:/</,relevance:0,
contains:[{className:"attr",begin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\s*/,
relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,
end:/"/,contains:[i]},{begin:/'/,end:/'/,contains:[i]},{begin:/[^\s"'=<>`]+/}]}]
}]};return{name:"HTML, XML",
aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],
case_insensitive:!0,contains:[{className:"meta",begin:/<![a-z]/,end:/>/,
relevance:10,contains:[r,g,l,c,{begin:/\[/,end:/\]/,contains:[{className:"meta",
begin:/<![a-z]/,end:/>/,contains:[r,c,g,l]}]}]},e.COMMENT(/<!--/,/-->/,{
relevance:10}),{begin:/<!\[CDATA\[/,end:/\]\]>/,relevance:10},i,{
className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",
begin:/<style(?=\s|>)/,end:/>/,keywords:{name:"style"},contains:[m],starts:{
end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",
begin:/<script(?=\s|>)/,end:/>/,keywords:{name:"script"},contains:[m],starts:{
end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{
className:"tag",begin:/<>|<\/>/},{className:"tag",
begin:a(/</,n(a(t,s(/\/>/,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",
begin:t,relevance:0,starts:m}]},{className:"tag",begin:a(/<\//,n(a(t,/>/))),
contains:[{className:"name",begin:t,relevance:0},{begin:/>/,relevance:0}]}]}}
})());hljs.registerLanguage("css",(()=>{"use strict";return e=>{
var n="[a-zA-Z-][a-zA-Z0-9_-]*",a={
begin:/([*]\s?)?(?:[A-Z_.\-\\]+|--[a-zA-Z0-9_-]+)\s*(\/\*\*\/)?:/,
returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",
begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,
contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",
begin:/[\w-]+/},{begin:/\(/,end:/\)/,
contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]
},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{
className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]
}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,
contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",
begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:"\\."+n},{
className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",
contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",
begin:/:(:)?[a-zA-Z0-9_+()"'.-]+/},{begin:"@(page|font-face)",
lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",
illegal:/:/,returnBegin:!0,contains:[{className:"keyword",
begin:/@-?\w[\w]*(-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,
relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,
className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]
}]},{className:"selector-tag",begin:n,relevance:0},{begin:/\{/,end:/\}/,
illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,{begin:/;/},a]}]}}})());hljs.registerLanguage("java",(()=>{"use strict"
;var e="\\.([0-9](_*[0-9])*)",n="[0-9a-fA-F](_*[0-9a-fA-F])*",a={
className:"number",variants:[{
begin:`(\\b([0-9](_*[0-9])*)((${e})|\\.)?|(${e}))[eE][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
},{begin:`\\b([0-9](_*[0-9])*)((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{
begin:`(${e})[fFdD]?\\b`},{begin:"\\b([0-9](_*[0-9])*)[fFdD]\\b"},{
begin:`\\b0[xX]((${n})\\.?|(${n})?\\.(${n}))[pP][+-]?([0-9](_*[0-9])*)[fFdD]?\\b`
},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${n})[lL]?\\b`},{
begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],
relevance:0};return e=>{
var n="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s={
className:"meta",begin:"@[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*",
contains:[{begin:/\(/,end:/\)/,contains:["self"]}]};const r=a;return{
name:"Java",aliases:["jsp"],keywords:n,illegal:/<\/|#/,
contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,
relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{
begin:/import java\.[a-z]+\./,keywords:"import",relevance:2
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{
className:"class",beginKeywords:"class interface enum",end:/[{;=]/,
excludeEnd:!0,keywords:"class interface enum",illegal:/[:"\[\]]/,contains:[{
beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{
beginKeywords:"new throw return else",relevance:0},{className:"class",
begin:"record\\s+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,excludeEnd:!0,
end:/[{;=]/,keywords:n,contains:[{beginKeywords:"record"},{
begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,
keywords:n,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE]
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"function",
begin:"([\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(<[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*(\\s*,\\s*[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",
returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:n,contains:[{
begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,
keywords:n,relevance:0,
contains:[s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,r,e.C_BLOCK_COMMENT_MODE]
},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},r,s]}}})());hljs.registerLanguage("markdown",(()=>{"use strict";function n(...n){
return n.map((n=>{return(e=n)?"string"==typeof e?e:e.source:null;var e
})).join("")}return e=>{const a={begin:/<\/?[A-Za-z_]/,end:">",
subLanguage:"xml",relevance:0},i={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0
},{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,
relevance:2},{begin:n(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/),
relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{
begin:/\[.+?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{
className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,
returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",
excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",
end:"\\]",excludeBegin:!0,excludeEnd:!0}]},s={className:"strong",contains:[],
variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},c={
className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{
begin:/_(?!_)/,end:/_/,relevance:0}]};s.contains.push(c),c.contains.push(s)
;let t=[a,i]
;return s.contains=s.contains.concat(t),c.contains=c.contains.concat(t),
t=t.concat(s,c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{
className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:t},{
begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",
contains:t}]}]},a,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",
end:"\\s+",excludeEnd:!0},s,c,{className:"quote",begin:"^>\\s+",contains:t,
end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{
begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{
begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",
contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{
begin:"^[-\\*]{3,}",end:"$"},i,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{
className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{
className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}})());hljs.registerLanguage("json",(()=>{"use strict";return n=>{const e={
literal:"true false null"
},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],a=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],l={
end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:e},t={begin:/\{/,
end:/\}/,contains:[{className:"attr",begin:/"/,end:/"/,
contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(l,{begin:/:/
})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(l)],
illegal:"\\S"};return a.push(t,s),i.forEach((n=>{a.push(n)})),{name:"JSON",
contains:a,keywords:e,illegal:"\\S"}}})());hljs.registerLanguage("javascript",(()=>{"use strict"
;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],s=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"])
;function r(e){return i("(?=",e,")")}function i(...e){return e.map((e=>{
return(n=e)?"string"==typeof n?n:n.source:null;var n})).join("")}return t=>{
const c=e,o={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,
isTrulyOpeningTag:(e,n)=>{const a=e[0].length+e.index,s=e.input[a]
;"<"!==s?">"===s&&(((e,{after:n})=>{const a="</"+e[0].slice(1)
;return-1!==e.input.indexOf(a,n)})(e,{after:a
})||n.ignoreMatch()):n.ignoreMatch()}},l={$pattern:e,keyword:n.join(" "),
literal:a.join(" "),built_in:s.join(" ")
},b="\\.([0-9](_?[0-9])*)",g="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",d={
className:"number",variants:[{
begin:`(\\b(${g})((${b})|\\.)?|(${b}))[eE][+-]?([0-9](_?[0-9])*)\\b`},{
begin:`\\b(${g})\\b((${b})\\b|\\.)?|(${b})\\b`},{
begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{
begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{
begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{
begin:"\\b0[0-7]+n?\\b"}],relevance:0},E={className:"subst",begin:"\\$\\{",
end:"\\}",keywords:l,contains:[]},u={begin:"html`",end:"",starts:{end:"`",
returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},_={
begin:"css`",end:"",starts:{end:"`",returnEnd:!1,
contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},m={className:"string",
begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]},N={className:"comment",
variants:[t.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{
className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",
end:"\\}",relevance:0},{className:"variable",begin:c+"(?=\\s*(-)|$)",
endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]
}),t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]
},y=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,u,_,m,d,t.REGEXP_MODE]
;E.contains=y.concat({begin:/\{/,end:/\}/,keywords:l,contains:["self"].concat(y)
});const f=[].concat(N,E.contains),A=f.concat([{begin:/\(/,end:/\)/,keywords:l,
contains:["self"].concat(f)}]),p={className:"params",begin:/\(/,end:/\)/,
excludeBegin:!0,excludeEnd:!0,keywords:l,contains:A};return{name:"Javascript",
aliases:["js","jsx","mjs","cjs"],keywords:l,exports:{PARAMS_CONTAINS:A},
illegal:/#(?![$_A-z])/,contains:[t.SHEBANG({label:"shebang",binary:"node",
relevance:5}),{label:"use_strict",className:"meta",relevance:10,
begin:/^\s*['"]use (strict|asm)['"]/
},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,u,_,m,N,d,{
begin:i(/[{,\n]\s*/,r(i(/(((\/\/.*$)|(\/\*(\*[^/]|[^*])*\*\/))\s*)*/,c+"\\s*:"))),
relevance:0,contains:[{className:"attr",begin:c+r("\\s*:"),relevance:0}]},{
begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",
keywords:"return throw case",contains:[N,t.REGEXP_MODE,{className:"function",
begin:"(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",
returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{
begin:t.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0
},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:l,contains:A}]}]
},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{
variants:[{begin:"<>",end:"</>"},{begin:o.begin,"on:begin":o.isTrulyOpeningTag,
end:o.end}],subLanguage:"xml",contains:[{begin:o.begin,end:o.end,skip:!0,
contains:["self"]}]}],relevance:0},{className:"function",
beginKeywords:"function",end:/[{;]/,excludeEnd:!0,keywords:l,
contains:["self",t.inherit(t.TITLE_MODE,{begin:c}),p],illegal:/%/},{
beginKeywords:"while if switch catch for"},{className:"function",
begin:t.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
returnBegin:!0,contains:[p,t.inherit(t.TITLE_MODE,{begin:c})]},{variants:[{
begin:"\\."+c},{begin:"\\$"+c}],relevance:0},{className:"class",
beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"[\]]/,contains:[{
beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{begin:/\b(?=constructor)/,
end:/[{;]/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:c}),"self",p]
},{begin:"(get|set)\\s+(?="+c+"\\()",end:/\{/,keywords:"get set",
contains:[t.inherit(t.TITLE_MODE,{begin:c}),{begin:/\(\)/},p]},{begin:/\$[(.]/}]
}}})());

@ -0,0 +1,85 @@
/*
Style with support for rainbow parens
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #474949;
color: #d1d9e1;
}
.hljs-comment,
.hljs-quote {
color: #969896;
font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-literal,
.hljs-type,
.hljs-addition {
color: #cc99cc;
}
.hljs-number,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #f99157;
}
.hljs-string,
.hljs-doctag,
.hljs-regexp {
color: #8abeb7;
}
.hljs-title,
.hljs-name,
.hljs-section,
.hljs-built_in {
color: #b5bd68;
}
.hljs-variable,
.hljs-template-variable,
.hljs-selector-id,
.hljs-class .hljs-title {
color: #ffcc66;
}
.hljs-section,
.hljs-name,
.hljs-strong {
font-weight: bold;
}
.hljs-symbol,
.hljs-bullet,
.hljs-subst,
.hljs-meta,
.hljs-link {
color: #f99157;
}
.hljs-deletion {
color: #dc322f;
}
.hljs-formula {
background: #eee8d5;
}
.hljs-attr,
.hljs-attribute {
color: #81a2be;
}
.hljs-emphasis {
font-style: italic;
}

@ -1,3 +0,0 @@
1、 正确填写WebDAV 服务器地址、WebDAV 账号、WebDAV 密码;(要获得这三项的信息,需要注册一个坚果云账号,如果直接在手机上注册,坚果云会让你下载app,过程比较麻烦,为了一步到位,最好是在电脑上打开这个注册链接:https://www.jianguoyun.com/d/signup;注册后,进入坚果云;点击右上角账户名处选择 “账户信息”,然后选择“安全选项”;在“安全选项” 中找到“第三方应用管理”,并选择“添加应用”,输入名称如“阅读”后,会生成密码,选择完成;其中https://dav.jianguoyun.com/dav/就是填入“WebDAV 服务器地址”的内容,“使用情况”后面的邮箱地址就是你的“WebDAV 账号”,点击显示密码后得到的密码就是你的“WebDAV 密码”。)
2、 无需操作,APP默认每天自动云备份一次。

@ -13,7 +13,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
@ -45,6 +44,7 @@ import xyz.fycz.myreader.R;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.ui.activity.MainActivity;
import xyz.fycz.myreader.ui.dialog.APPDownloadTip;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
@ -58,11 +58,11 @@ import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
public class MyApplication extends Application {
public class App extends Application {
public static final String TAG = MyApplication.class.getSimpleName();
private static Handler handler = new Handler();
private static MyApplication application;
public static final String TAG = App.class.getSimpleName();
private static final Handler handler = new Handler();
private static App application;
private ExecutorService mFixedThreadPool;
@ -70,6 +70,7 @@ public class MyApplication extends Application {
public void onCreate() {
super.onCreate();
application = this;
firstInit();
HttpUtil.trustAllHosts();//信任所有证书
RxJavaPlugins.setErrorHandler(Functions.emptyConsumer());
// handleSSLHandshake();
@ -81,6 +82,14 @@ public class MyApplication extends Application {
// LLog.init(APPCONST.LOG_DIR);
}
private void firstInit() {
SharedPreUtils sru = SharedPreUtils.getInstance();
if (!sru.getBoolean("firstInit")){
BookSourceManager.initDefaultSources();
sru.putBoolean("firstInit", true);
}
}
public void initNightTheme() {
if (isNightFS()){
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
@ -110,7 +119,7 @@ public class MyApplication extends Application {
Setting setting = SysManager.getSetting();
setting.setDayStyle(!isNightMode);
SysManager.saveSetting(setting);
MyApplication.getApplication().initNightTheme();
App.getApplication().initNightTheme();
}
@SuppressLint("TrulyRandom")
@ -194,7 +203,7 @@ public class MyApplication extends Application {
return handler;
}
public static MyApplication getApplication() {
public static App getApplication() {
return application;
}
@ -259,7 +268,7 @@ public class MyApplication extends Application {
*/
public static void checkVersionByServer(final AppCompatActivity activity, final boolean isManualCheck,
final BookcaseFragment mBookcaseFragment) {
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
try {
String url = "https://shimo.im/docs/cqkgjPRRydYYhQKt/read";
if (isApkInDebug(getmContext())) {
@ -313,7 +322,7 @@ public class MyApplication extends Application {
}
Log.i("检查更新,最新版本", newestVersion + "");
if (newestVersion > versionCode) {
MyApplication m = new MyApplication();
App m = new App();
Setting setting = SysManager.getSetting();
if (isManualCheck || setting.getNewestVersionCode() < newestVersion || isForceUpdate) {
setting.setNewestVersionCode(newestVersion);
@ -326,7 +335,7 @@ public class MyApplication extends Application {
}
} catch (Exception e) {
e.printStackTrace();
Log.e("检查更新失败!", e.getLocalizedMessage());
Log.e("检查更新失败!", "" + e.getLocalizedMessage());
if (isManualCheck || NetworkUtils.isNetWorkAvailable()) {
ToastUtils.showError("检查更新失败!");
}

@ -2,14 +2,16 @@ package xyz.fycz.myreader.application;
import android.util.Log;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.enums.BookcaseStyle;
import xyz.fycz.myreader.enums.LocalBookSource;
import xyz.fycz.myreader.util.CacheHelper;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import static xyz.fycz.myreader.application.MyApplication.getVersionCode;
import static xyz.fycz.myreader.application.App.getVersionCode;
public class SysManager {
@ -109,28 +111,32 @@ public class SysManager {
switch (setting.getSourceVersion()) {
case 0:
default:
ReadCrawlerUtil.addReadCrawler(BookSource.miaobi, BookSource.dstq, BookSource.xs7, BookSource.du1du, BookSource.paiotian);
ReadCrawlerUtil.addReadCrawler(LocalBookSource.miaobi, LocalBookSource.dstq, LocalBookSource.xs7, LocalBookSource.du1du, LocalBookSource.paiotian);
ReadCrawlerUtil.removeReadCrawler("cangshu99");
Log.d("SourceVersion", "" + 0);
case 1:
ReadCrawlerUtil.addReadCrawler(BookSource.laoyao, BookSource.xingxing, BookSource.shiguang, BookSource.xiagu, BookSource.hongchen);
ReadCrawlerUtil.addReadCrawler(LocalBookSource.laoyao, LocalBookSource.xingxing, LocalBookSource.shiguang, LocalBookSource.xiagu, LocalBookSource.hongchen);
Log.d("SourceVersion", "" + 1);
case 2:
//ReadCrawlerUtil.addReadCrawler(BookSource.rexue, BookSource.chuanqi);
ReadCrawlerUtil.addReadCrawler(BookSource.chuanqi);
//ReadCrawlerUtil.addReadCrawler(LocalBookSource.rexue, LocalBookSource.chuanqi);
ReadCrawlerUtil.addReadCrawler(LocalBookSource.chuanqi);
Log.d("SourceVersion", "" + 2);
case 3:
ReadCrawlerUtil.resetReadCrawlers();
Log.d("SourceVersion", "" + 3);
case 4:
ReadCrawlerUtil.removeReadCrawler("qiqi", "rexue", "pinshu");
ReadCrawlerUtil.addReadCrawler(BookSource.bijian, BookSource.yanqinglou, BookSource.wolong);
ReadCrawlerUtil.addReadCrawler(LocalBookSource.bijian, LocalBookSource.yanqinglou, LocalBookSource.wolong);
Log.d("SourceVersion", "" + 4);
case 5:
ReadCrawlerUtil.addReadCrawler(BookSource.ewenxue, BookSource.shuhaige,
BookSource.luoqiu, BookSource.zw37, BookSource.xbiquge,
BookSource.zaishuyuan);
ReadCrawlerUtil.addReadCrawler(LocalBookSource.ewenxue, LocalBookSource.shuhaige,
LocalBookSource.luoqiu, LocalBookSource.zw37, LocalBookSource.xbiquge,
LocalBookSource.zaishuyuan);
Log.d("SourceVersion", "" + 5);
case 6:
SharedPreUtils.getInstance().putString(App.getmContext().getString(R.string.searchSource), "");
Log.d("SourceVersion", "" + 5);
break;
}
setting.setSourceVersion(APPCONST.SOURCE_VERSION);
saveSetting(setting);

@ -21,7 +21,7 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import xyz.fycz.myreader.ActivityManage;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.util.StatusBarUtil;
@ -91,7 +91,7 @@ public abstract class BaseActivity extends AppCompatActivity {
protected void setNightTheme(boolean isNightMode) {
Setting setting = SysManager.getSetting();
setting.setDayStyle(!isNightMode);
MyApplication.getApplication().initNightTheme();
App.getApplication().initNightTheme();
}

@ -33,6 +33,10 @@ public class APPCONST {
public static long exitTime;
public static final int exitConfirmTime = 2000;
public static final String MATCHER = "Matcher";
public static final String XPATH = "Xpath";
public static final String JSON_PATH = "JsonPath";
public static final String BOOK = "book";
public static final String TITLE = "title";
public static final String FIND_CRAWLER = "findCrawler";
@ -41,12 +45,12 @@ public class APPCONST {
public static final String SEARCH_BOOK_BEAN = "SearchBookBean";
public static final String SOURCE_INDEX = "sourceIndex";
public static final String FONT = "font";
public static final String BOOK_SOURCE = "bookSource";
public static final String RESULT_NEED_REFRESH = "result_need_refresh";
public static final String RESULT_IS_COLLECTED = "result_is_collected";
public static final String RESULT_LAST_READ_POSITION = "result_last_read_position";
public static final String RESULT_HISTORY_CHAPTER = "result_history_chapter";
public static final String RESULT_UP_MENU = "result_up_meu";
public static final String RESULT_REPLACE_RULE = "result_up_meu";
public static final String RESULT_UP_MENU = "result_up_menu";
public static final String[] READ_STYLE_NIGHT = {"#94928c", "#393431"};//黑夜
@ -71,6 +75,9 @@ public class APPCONST {
public static final int REQUEST_IMPORT_LAYOUT = 1006;
public static final int REQUEST_QR_SCAN = 1007;
public static final int REQUEST_IMPORT_REPLACE_RULE = 1008;
public static final int REQUEST_IMPORT_BOOK_SOURCE = 1009;
public static final int REQUEST_EDIT_BOOK_SOURCE = 1010;
public static final int REQUEST_BOOK_SOURCE = 1011;
public static final int REQUEST_READ = 1;
@ -83,7 +90,7 @@ public class APPCONST {
//设置版本号
public static final int SETTING_VERSION = 11;
public static final int SOURCE_VERSION = 6;
public static final int SOURCE_VERSION = 7;
public static final String FORMAT_FILE_DATE = "yyyy-MM-dd";

@ -51,6 +51,7 @@ public class SearchBookBean {
if (author == null){
return name.equals(that.name);
}
if (name == null) return false;
return name.equals(that.name) &&
author.equals(that.author);
}

@ -0,0 +1,30 @@
package xyz.fycz.myreader.entity.sourcedebug
/**
* @author fengyue
* @date 2021/2/12 21:11
*/
data class DebugBook(
/*var name: String? = null,
var author: String? = null,
var type: String? = null,
var desc: String? = null,
var wordCount: String? = null,
var status: String? = null,
var lastChapter: String? = null,
var updateTime: String? = null,
var imgUrl: String? = null,
var tocUrl: String? = null,
var infoUrl: String? = null,*/
var 书名: String? = null,
var 作者: String? = null,
var 分类: String? = null,
var 简介: String? = null,
var 字数: String? = null,
var 连载状态: String? = null,
var 最新章节: String? = null,
var 更新时间: String? = null,
var 封面链接: String? = null,
var 目录链接: String? = null,
var 详情链接: String? = null,
)

@ -0,0 +1,13 @@
package xyz.fycz.myreader.entity.sourcedebug
/**
* @author fengyue
* @date 2021/2/12 21:21
*/
data class DebugChapter(
/*var title: String? = null,
var url: String? = null,
var content: String? = null*/
var 章节名称: String? = null,
var 章节链接: String? = null,
)

@ -0,0 +1,100 @@
package xyz.fycz.myreader.entity.sourcedebug;
import android.os.Parcel;
import android.os.Parcelable;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
/**
* @author fengyue
* @date 2021/2/12 19:35
*/
public class DebugEntity implements Parcelable {
public static final int SEARCH = 0;
public static final int INFO = 1;
public static final int TOC = 2;
public static final int CONTENT = 3;
private int debugMode;
private BookSource bookSource;
private String url;
private String parseResult;
private String html;
public DebugEntity() {
}
protected DebugEntity(Parcel in) {
debugMode = in.readInt();
bookSource = in.readParcelable(BookSource.class.getClassLoader());
url = in.readString();
parseResult = in.readString();
html = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(debugMode);
dest.writeParcelable(bookSource, flags);
dest.writeString(url);
dest.writeString(parseResult);
dest.writeString(html);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<DebugEntity> CREATOR = new Creator<DebugEntity>() {
@Override
public DebugEntity createFromParcel(Parcel in) {
return new DebugEntity(in);
}
@Override
public DebugEntity[] newArray(int size) {
return new DebugEntity[size];
}
};
public int getDebugMode() {
return debugMode;
}
public void setDebugMode(int debugMode) {
this.debugMode = debugMode;
}
public BookSource getBookSource() {
return bookSource;
}
public void setBookSource(BookSource bookSource) {
this.bookSource = bookSource;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getParseResult() {
return parseResult;
}
public void setParseResult(String parseResult) {
this.parseResult = parseResult;
}
public String getHtml() {
return html;
}
public void setHtml(String html) {
this.html = html;
}
}

@ -0,0 +1,10 @@
package xyz.fycz.myreader.entity.sourcedebug
/**
* @author fengyue
* @date 2021/2/15 22:37
*/
data class ListResult(
var 信息: String? = null,
var 结果: Any? = null,
)

@ -0,0 +1,12 @@
package xyz.fycz.myreader.entity.sourceedit
/**
* @author fengyue
* @date 2021/2/9 10:15
*/
data class EditEntity(
var key: String,
var value: String? = "",
var hint: Int,
var tip: String? = "规则后可接##加@r/@a/@c/@nc函数"
)

@ -0,0 +1,181 @@
package xyz.fycz.myreader.entity.sourceedit
import xyz.fycz.myreader.R
import xyz.fycz.myreader.greendao.entity.rule.*
import java.util.*
/**
* @author fengyue
* @date 2021/2/9 12:03
*/
object EditEntityUtil {
fun getSourceEntities(source: BookSource? = BookSource()): List<EditEntity> {
val sourceEntities: ArrayList<EditEntity> = ArrayList()
sourceEntities.apply {
add(EditEntity("sourceUrl", source?.sourceUrl, R.string.source_url, "不能为空且唯一"))
add(EditEntity("sourceName", source?.sourceName, R.string.source_name, "不能为空"))
add(EditEntity("sourceGroup", source?.sourceGroup, R.string.source_group, "不同分组以;/,隔开"))
add(EditEntity("sourceCharset", source?.sourceCharset, R.string.source_charset, "默认UTF-8"))
add(EditEntity("sourceComment", source?.sourceComment, R.string.comment, "这是您留给使用者的说明"))
}
return sourceEntities
}
fun getSearchEntities(searchRule: SearchRule? = SearchRule()): List<EditEntity> {
val searchEntities: ArrayList<EditEntity> = ArrayList()
searchEntities.apply {
add(EditEntity("searchUrl", searchRule?.searchUrl, R.string.r_search_url,
"搜索关键词以{key}进行占位;post请求以“,”分隔url,“,”前是搜索地址,“,”后是请求体"))
add(EditEntity("charset", searchRule?.charset, R.string.r_search_charset, "默认使用书源字符编码"))
add(EditEntity("list", searchRule?.list, R.string.r_book_list,
"对于Matcher解析器:此处填写书籍列表所在区间,支持普通函数;" +
"\n对于Xpath/JsonPath解析器:此处填写书籍列表规则,不支持普通函数,规则后接##!加数字可以跳过列表前几个"))
add(EditEntity("name", searchRule?.name, R.string.r_book_name))
add(EditEntity("author", searchRule?.author, R.string.r_author))
add(EditEntity("type", searchRule?.type, R.string.rule_book_type))
add(EditEntity("wordCount", searchRule?.wordCount, R.string.rule_word_count))
add(EditEntity("status", searchRule?.status, R.string.rule_status))
add(EditEntity("desc", searchRule?.desc, R.string.rule_book_desc))
add(EditEntity("lastChapter", searchRule?.lastChapter, R.string.rule_last_chapter))
add(EditEntity("updateTime", searchRule?.updateTime, R.string.rule_update_time))
add(EditEntity("imgUrl", searchRule?.imgUrl, R.string.rule_img_url))
add(EditEntity("tocUrl", searchRule?.tocUrl, R.string.rule_toc_url))
add(EditEntity("infoUrl", searchRule?.infoUrl, R.string.r_info_url, "为空时使用目录URL"))
add(EditEntity("relatedWithInfo", searchRule?.isRelatedWithInfo?.toString(), R.string.r_search_related_with_info,
"搜索时是否关联书籍详情页,填true/t表示关联,false/f表示不关联,测试时不生效"))
}
return searchEntities
}
fun getInfoEntities(infoRule: InfoRule? = InfoRule()): List<EditEntity> {
val infoEntities: ArrayList<EditEntity> = ArrayList()
infoEntities.apply {
add(EditEntity("name", infoRule?.name, R.string.r_book_name))
add(EditEntity("author", infoRule?.author, R.string.r_author))
add(EditEntity("type", infoRule?.type, R.string.rule_book_type))
add(EditEntity("desc", infoRule?.desc, R.string.rule_book_desc))
add(EditEntity("status", infoRule?.status, R.string.rule_status))
add(EditEntity("wordCount", infoRule?.wordCount, R.string.rule_word_count))
add(EditEntity("lastChapter", infoRule?.lastChapter, R.string.rule_last_chapter))
add(EditEntity("updateTime", infoRule?.updateTime, R.string.rule_update_time))
add(EditEntity("imgUrl", infoRule?.imgUrl, R.string.rule_img_url))
add(EditEntity("tocUrl", infoRule?.tocUrl, R.string.rule_toc_url))
}
return infoEntities
}
fun getTocEntities(tocRule: TocRule? = TocRule()): List<EditEntity> {
val tocEntities: ArrayList<EditEntity> = ArrayList()
tocEntities.apply {
add(EditEntity("chapterBaseUrl", tocRule?.chapterBaseUrl, R.string.rule_chapter_base_url,
"如果章节URL(一般为相对路径)无法定位章节,可填写此规则获取,默认为书源URL"))
add(EditEntity("chapterList", tocRule?.chapterList, R.string.rule_chapter_list,
"对于Mathcer解析器:此处填写书籍列表所在区间,支持普通函数;" +
"\n对于Xpath/JsonPath解析器:此处填写书籍列表规则,不支持普通函数,规则后接##!加数字可以跳过列表前几个"))
add(EditEntity("chapterName", tocRule?.chapterName, R.string.rule_chapter_name,
"对于Mathcer解析器:此处填写章节名称和URL规则,其中章节名称以<title>占位,章节URL以<link>占位,不支持普通函数,规则后接##!加数字可以跳过列表前几个\n" +
"对于Xpath/JsonPath解析器:此处填写章节名称,支持普通函数"))
add(EditEntity("chapterUrl", tocRule?.chapterUrl, R.string.rule_chapter_url,
"对于Mathcer解析器:此处不用填写\n" +
"对于Xpath/JsonPath解析器:此处填写章节URL规则"))
add(EditEntity("tocUrlNext", tocRule?.tocUrlNext, R.string.rule_next_toc_url,
"填写后获取目录时将会不断地从目录下一页获取章节,直至下一页URL为空时停止,注意:千万不要获取恒存在的URL,否则将出现死循环甚至崩溃"))
}
return tocEntities
}
fun getContentEntities(contentRule: ContentRule? = ContentRule()): List<EditEntity> {
val contentEntities: ArrayList<EditEntity> = ArrayList()
contentEntities.apply {
add(EditEntity("content", contentRule?.content, R.string.rule_book_content))
add(EditEntity("contentBaseUrl", contentRule?.contentBaseUrl, R.string.rule_base_url_content,
"如果下一页URL(一般为相对路径)无法定位下一页,可填写此规则获取,默认为书源URL"))
add(EditEntity("contentUrlNext", contentRule?.contentUrlNext, R.string.rule_next_content,
"填写后正文时将会不断地从下一页获取内容,直至下一页URL为空时停止,注意:千万不要获取恒存在的URL,否则将出现死循环甚至崩溃"))
}
return contentEntities
}
fun getSource(bookSource: BookSource, sourceEntities: List<EditEntity>): BookSource {
val source = bookSource.clone() as BookSource
sourceEntities.forEach {
when (it.key) {
"sourceUrl" -> source.sourceUrl = it.value
"sourceName" -> source.sourceName = it.value
"sourceGroup" -> source.sourceGroup = it.value
"sourceCharset" -> source.sourceCharset = it.value
"sourceComment" -> source.sourceComment = it.value
}
}
return source
}
fun getSearchRule(searchEntities: List<EditEntity>): SearchRule {
val searchRule = SearchRule()
searchEntities.forEach {
when (it.key) {
"searchUrl" -> searchRule.searchUrl = it.value
"charset" -> searchRule.charset = it.value
"list" -> searchRule.list = it.value
"name" -> searchRule.name = it.value
"author" -> searchRule.author = it.value
"type" -> searchRule.type = it.value
"desc" -> searchRule.desc = it.value
"wordCount" -> searchRule.wordCount = it.value
"status" -> searchRule.status = it.value
"lastChapter" -> searchRule.lastChapter = it.value
"updateTime" -> searchRule.updateTime = it.value
"imgUrl" -> searchRule.imgUrl = it.value
"tocUrl" -> searchRule.tocUrl = it.value
"infoUrl" -> searchRule.infoUrl = it.value
"relatedWithInfo" -> searchRule.isRelatedWithInfo = it.value?.contains("t") == true
}
}
return searchRule
}
fun getInfoRule(infoRuleEntities: List<EditEntity>): InfoRule {
val infoRule = InfoRule()
infoRuleEntities.forEach {
when (it.key) {
"name" -> infoRule.name = it.value
"author" -> infoRule.author = it.value
"type" -> infoRule.type = it.value
"desc" -> infoRule.desc = it.value
"status" -> infoRule.status = it.value
"wordCount" -> infoRule.wordCount = it.value
"lastChapter" -> infoRule.lastChapter = it.value
"updateTime" -> infoRule.updateTime = it.value
"imgUrl" -> infoRule.imgUrl = it.value
"tocUrl" -> infoRule.tocUrl = it.value
}
}
return infoRule
}
fun getTocRule(infoRuleEntities: List<EditEntity>): TocRule {
val tocRule = TocRule()
infoRuleEntities.forEach {
when (it.key) {
"chapterList" -> tocRule.chapterList = it.value
"chapterBaseUrl" -> tocRule.chapterBaseUrl = it.value
"chapterName" -> tocRule.chapterName = it.value
"chapterUrl" -> tocRule.chapterUrl = it.value
"tocUrlNext" -> tocRule.tocUrlNext = it.value
}
}
return tocRule
}
fun getContentRule(infoRuleEntities: List<EditEntity>): ContentRule {
val contentRule = ContentRule()
infoRuleEntities.forEach {
when (it.key) {
"content" -> contentRule.content = it.value
"contentBaseUrl" -> contentRule.contentBaseUrl = it.value
"contentUrlNext" -> contentRule.contentUrlNext = it.value
}
}
return contentRule
}
}

@ -1,80 +0,0 @@
package xyz.fycz.myreader.enums;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.util.ToastUtils;
/**
* 小说源
*/
public enum BookSource {
local("本地书籍"),
fynovel("风月小说"),
tianlai(MyApplication.getApplication().getString(R.string.read_tianlai)),
biquge44(MyApplication.getApplication().getString(R.string.read_biquge44)),
//pinshu(MyApplication.getApplication().getString(R.string.read_pinshu)),
biquge(MyApplication.getApplication().getString(R.string.read_biquge)),
qb5(MyApplication.getApplication().getString(R.string.read_qb5)),
miqu(MyApplication.getApplication().getString(R.string.read_miqu)),
jiutao(MyApplication.getApplication().getString(R.string.read_jiutao)),
miaobi(MyApplication.getApplication().getString(R.string.read_miaobi)),
dstq(MyApplication.getApplication().getString(R.string.read_dstq)),
yunzhong(MyApplication.getApplication().getString(R.string.read_yunzhong)),
sonovel(MyApplication.getApplication().getString(R.string.read_sonovel)),
quannovel(MyApplication.getApplication().getString(R.string.read_quannovel)),
//qiqi(MyApplication.getApplication().getString(R.string.read_qiqi)),
xs7(MyApplication.getApplication().getString(R.string.read_xs7)),
du1du(MyApplication.getApplication().getString(R.string.read_du1du)),
paiotian(MyApplication.getApplication().getString(R.string.read_paiotian)),
laoyao(MyApplication.getApplication().getString(R.string.read_laoyao)),
xingxing(MyApplication.getApplication().getString(R.string.read_xingxing)),
shiguang(MyApplication.getApplication().getString(R.string.read_shiguang)),
//rexue(MyApplication.getApplication().getString(R.string.read_rexue)),
chuanqi(MyApplication.getApplication().getString(R.string.read_chuanqi)),
xiagu(MyApplication.getApplication().getString(R.string.read_xiagu)),
hongchen(MyApplication.getApplication().getString(R.string.read_hongchen)),
bijian(MyApplication.getApplication().getString(R.string.read_bijian)),
yanqinglou(MyApplication.getApplication().getString(R.string.read_yanqinglou)),
wolong(MyApplication.getApplication().getString(R.string.read_wolong)),
ewenxue(MyApplication.getApplication().getString(R.string.read_ewenxue)),
shuhaige(MyApplication.getApplication().getString(R.string.read_shuhaige)),
luoqiu(MyApplication.getApplication().getString(R.string.read_luoqiu)),
zw37(MyApplication.getApplication().getString(R.string.read_zw37)),
xbiquge(MyApplication.getApplication().getString(R.string.read_xbiquge)),
zaishuyuan(MyApplication.getApplication().getString(R.string.read_zaishuyuan)),
chaoxing(MyApplication.getApplication().getString(R.string.read_chaoxing)),
zuopin(MyApplication.getApplication().getString(R.string.read_zuopin)),
cangshu99(MyApplication.getApplication().getString(R.string.read_cangshu99)),
ben100(MyApplication.getApplication().getString(R.string.read_ben100));
//liulangcat("流浪猫·实体"),
public String text;
BookSource(String text) {
this.text = text;
}
public static String getFromName(String name){
for (BookSource bookSource : BookSource.values()){
if (bookSource.text.equals(name)){
return bookSource.toString();
}
}
return "";
}
public static BookSource get(int var0) {
return values()[var0];
}
public static BookSource fromString(String string) {
try {
return valueOf(string);
} catch (Exception e) {
ToastUtils.showError(e.getLocalizedMessage());
return fynovel;
}
}
}

@ -0,0 +1,80 @@
package xyz.fycz.myreader.enums;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.util.ToastUtils;
/**
* 小说源
*/
public enum LocalBookSource {
local("本地书籍"),
fynovel("风月小说"),
tianlai(App.getApplication().getString(R.string.read_tianlai)),
biquge44(App.getApplication().getString(R.string.read_biquge44)),
//pinshu(App.getApplication().getString(R.string.read_pinshu)),
biquge(App.getApplication().getString(R.string.read_biquge)),
qb5(App.getApplication().getString(R.string.read_qb5)),
miqu(App.getApplication().getString(R.string.read_miqu)),
jiutao(App.getApplication().getString(R.string.read_jiutao)),
miaobi(App.getApplication().getString(R.string.read_miaobi)),
dstq(App.getApplication().getString(R.string.read_dstq)),
yunzhong(App.getApplication().getString(R.string.read_yunzhong)),
sonovel(App.getApplication().getString(R.string.read_sonovel)),
quannovel(App.getApplication().getString(R.string.read_quannovel)),
//qiqi(App.getApplication().getString(R.string.read_qiqi)),
xs7(App.getApplication().getString(R.string.read_xs7)),
du1du(App.getApplication().getString(R.string.read_du1du)),
paiotian(App.getApplication().getString(R.string.read_paiotian)),
laoyao(App.getApplication().getString(R.string.read_laoyao)),
xingxing(App.getApplication().getString(R.string.read_xingxing)),
shiguang(App.getApplication().getString(R.string.read_shiguang)),
//rexue(App.getApplication().getString(R.string.read_rexue)),
chuanqi(App.getApplication().getString(R.string.read_chuanqi)),
xiagu(App.getApplication().getString(R.string.read_xiagu)),
hongchen(App.getApplication().getString(R.string.read_hongchen)),
bijian(App.getApplication().getString(R.string.read_bijian)),
yanqinglou(App.getApplication().getString(R.string.read_yanqinglou)),
wolong(App.getApplication().getString(R.string.read_wolong)),
ewenxue(App.getApplication().getString(R.string.read_ewenxue)),
shuhaige(App.getApplication().getString(R.string.read_shuhaige)),
luoqiu(App.getApplication().getString(R.string.read_luoqiu)),
zw37(App.getApplication().getString(R.string.read_zw37)),
xbiquge(App.getApplication().getString(R.string.read_xbiquge)),
zaishuyuan(App.getApplication().getString(R.string.read_zaishuyuan)),
chaoxing(App.getApplication().getString(R.string.read_chaoxing)),
zuopin(App.getApplication().getString(R.string.read_zuopin)),
cangshu99(App.getApplication().getString(R.string.read_cangshu99)),
ben100(App.getApplication().getString(R.string.read_ben100));
//liulangcat("流浪猫·实体"),
public String text;
LocalBookSource(String text) {
this.text = text;
}
public static String getFromName(String name){
for (LocalBookSource bookSource : LocalBookSource.values()){
if (bookSource.text.equals(name)){
return bookSource.toString();
}
}
return "";
}
public static LocalBookSource get(int var0) {
return values()[var0];
}
public static LocalBookSource fromString(String string) {
try {
return valueOf(string);
} catch (Exception e) {
ToastUtils.showError(e.getLocalizedMessage());
return fynovel;
}
}
}

@ -1,7 +1,7 @@
package xyz.fycz.myreader.greendao;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.greendao.gen.DaoMaster;
import xyz.fycz.myreader.greendao.gen.DaoSession;
import xyz.fycz.myreader.greendao.util.MySQLiteOpenHelper;
@ -23,7 +23,7 @@ public class GreenDaoManager {
}
public GreenDaoManager(){
mySQLiteOpenHelper = new MySQLiteOpenHelper(MyApplication.getmContext(), "read" , null);
mySQLiteOpenHelper = new MySQLiteOpenHelper(App.getmContext(), "read" , null);
daoMaster = new DaoMaster(mySQLiteOpenHelper.getWritableDatabase());
mDaoSession = daoMaster.newSession();
}

@ -34,18 +34,14 @@ public class Book implements Serializable {
private String imgUrl;//封面图片url
private String desc;//简介
private String author;//作者
@Nullable
private String type;//类型(本地书籍为:本地书籍)
private String updateDate;//更新时间
@Nullable
private String wordCount;
private String status;
private String newestChapterId;//最新章节id
@Nullable
private String newestChapterTitle;//最新章节标题
@Nullable
private String newestChapterUrl;//最新章节url
@Nullable
private String historyChapterId;//上次关闭时的章节ID
@Nullable
private int histtoryChapterNum;//上次关闭时的章节数
private int sortCode;//排序编码
@ -74,13 +70,13 @@ public class Book implements Serializable {
private long lastReadTime;
@Generated(hash = 1269102537)
@Generated(hash = 1963023743)
public Book(String id, String name, String chapterUrl, String infoUrl, String imgUrl, String desc,
String author, String type, String updateDate, String newestChapterId, String newestChapterTitle,
String newestChapterUrl, String historyChapterId, int histtoryChapterNum, int sortCode, int noReadNum,
int chapterTotalNum, int lastReadPosition, String source, boolean isCloseUpdate,
boolean isDownLoadAll, String groupId, int groupSort, String tag, Boolean replaceEnable,
long lastReadTime) {
String author, String type, String updateDate, String wordCount, String status,
String newestChapterId, String newestChapterTitle, String newestChapterUrl, String historyChapterId,
int histtoryChapterNum, int sortCode, int noReadNum, int chapterTotalNum, int lastReadPosition,
String source, boolean isCloseUpdate, boolean isDownLoadAll, String groupId, int groupSort,
String tag, Boolean replaceEnable, long lastReadTime) {
this.id = id;
this.name = name;
this.chapterUrl = chapterUrl;
@ -90,6 +86,8 @@ public class Book implements Serializable {
this.author = author;
this.type = type;
this.updateDate = updateDate;
this.wordCount = wordCount;
this.status = status;
this.newestChapterId = newestChapterId;
this.newestChapterTitle = newestChapterTitle;
this.newestChapterUrl = newestChapterUrl;
@ -324,4 +322,24 @@ public class Book implements Serializable {
}
public String getWordCount() {
return this.wordCount;
}
public void setWordCount(String wordCount) {
this.wordCount = wordCount;
}
public String getStatus() {
return this.status;
}
public void setStatus(String status) {
this.status = status;
}
}

@ -0,0 +1,373 @@
package xyz.fycz.myreader.greendao.entity.rule;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import org.greenrobot.greendao.annotation.Convert;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.OrderBy;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Transient;
import org.greenrobot.greendao.annotation.Unique;
import java.util.ArrayList;
import java.util.Objects;
import xyz.fycz.myreader.greendao.entity.rule.convert.ContentRuleConvert;
import xyz.fycz.myreader.greendao.entity.rule.convert.FindRuleConvert;
import xyz.fycz.myreader.greendao.entity.rule.convert.InfoRuleConvert;
import xyz.fycz.myreader.greendao.entity.rule.convert.SearchRuleConvert;
import xyz.fycz.myreader.greendao.entity.rule.convert.TocRuleConvert;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/**
* @author fengyue
* @date 2021/2/8 17:37
*/
@Entity
public class BookSource implements Parcelable, Cloneable {
//基本信息
@Id
private String sourceUrl;
private String sourceEName;//内置书源标识
private String sourceName;
private String sourceGroup;
private String sourceCharset;
private String sourceType;
private String sourceComment;
private Long lastUpdateTime;
@OrderBy
private int orderNum;
@OrderBy
@NotNull
private int weight;
private boolean enable;
@Transient
private transient ArrayList<String> groupList;
//搜索规则
@Convert(converter = SearchRuleConvert.class, columnType = String.class)
private SearchRule searchRule;
//详情规则
@Convert(converter = InfoRuleConvert.class, columnType = String.class)
private InfoRule infoRule;
//目录规则
@Convert(converter = TocRuleConvert.class, columnType = String.class)
private TocRule tocRule;
//正文页规则
@Convert(converter = ContentRuleConvert.class, columnType = String.class)
private ContentRule contentRule;
//发现页规则
@Convert(converter = FindRuleConvert.class, columnType = String.class)
private FindRule findRule;
@Generated(hash = 441434718)
public BookSource(String sourceUrl, String sourceEName, String sourceName, String sourceGroup,
String sourceCharset, String sourceType, String sourceComment, Long lastUpdateTime,
int orderNum, int weight, boolean enable, SearchRule searchRule, InfoRule infoRule,
TocRule tocRule, ContentRule contentRule, FindRule findRule) {
this.sourceUrl = sourceUrl;
this.sourceEName = sourceEName;
this.sourceName = sourceName;
this.sourceGroup = sourceGroup;
this.sourceCharset = sourceCharset;
this.sourceType = sourceType;
this.sourceComment = sourceComment;
this.lastUpdateTime = lastUpdateTime;
this.orderNum = orderNum;
this.weight = weight;
this.enable = enable;
this.searchRule = searchRule;
this.infoRule = infoRule;
this.tocRule = tocRule;
this.contentRule = contentRule;
this.findRule = findRule;
}
@Generated(hash = 2045691642)
public BookSource() {
}
protected BookSource(Parcel in) {
sourceUrl = in.readString();
sourceEName = in.readString();
sourceName = in.readString();
sourceGroup = in.readString();
sourceCharset = in.readString();
sourceType = in.readString();
sourceComment = in.readString();
if (in.readByte() == 0) {
lastUpdateTime = null;
} else {
lastUpdateTime = in.readLong();
}
orderNum = in.readInt();
weight = in.readInt();
enable = in.readByte() != 0;
searchRule = in.readParcelable(SearchRule.class.getClassLoader());
infoRule = in.readParcelable(InfoRule.class.getClassLoader());
tocRule = in.readParcelable(TocRule.class.getClassLoader());
contentRule = in.readParcelable(ContentRule.class.getClassLoader());
findRule = in.readParcelable(FindRule.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(sourceUrl);
dest.writeString(sourceEName);
dest.writeString(sourceName);
dest.writeString(sourceGroup);
dest.writeString(sourceCharset);
dest.writeString(sourceType);
dest.writeString(sourceComment);
if (lastUpdateTime == null) {
dest.writeByte((byte) 0);
} else {
dest.writeByte((byte) 1);
dest.writeLong(lastUpdateTime);
}
dest.writeInt(orderNum);
dest.writeInt(weight);
dest.writeByte((byte) (enable ? 1 : 0));
dest.writeParcelable(searchRule, flags);
dest.writeParcelable(infoRule, flags);
dest.writeParcelable(tocRule, flags);
dest.writeParcelable(contentRule, flags);
dest.writeParcelable(findRule, flags);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<BookSource> CREATOR = new Creator<BookSource>() {
@Override
public BookSource createFromParcel(Parcel in) {
return new BookSource(in);
}
@Override
public BookSource[] newArray(int size) {
return new BookSource[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BookSource source = (BookSource) o;
return enable == source.enable &&
stringEquals(sourceUrl, source.sourceUrl) &&
stringEquals(sourceEName, source.sourceEName) &&
stringEquals(sourceName, source.sourceName) &&
stringEquals(sourceGroup, source.sourceGroup) &&
stringEquals(sourceCharset, source.sourceCharset) &&
stringEquals(sourceType, source.sourceType) &&
stringEquals(sourceComment, source.sourceComment) &&
Objects.equals(searchRule, source.searchRule) &&
Objects.equals(infoRule, source.infoRule) &&
Objects.equals(tocRule, source.tocRule) &&
Objects.equals(contentRule, source.contentRule) &&
Objects.equals(findRule, source.findRule);
}
@NonNull
@Override
public Object clone() {
try {
Gson gson = new Gson();
String json = gson.toJson(this);
return gson.fromJson(json, BookSource.class);
} catch (Exception ignored) {
}
return this;
}
public String getSourceName() {
return this.sourceName;
}
public void setSourceName(String sourceName) {
this.sourceName = sourceName;
}
public String getSourceGroup() {
return this.sourceGroup;
}
public void setSourceGroup(String sourceGroup) {
this.sourceGroup = sourceGroup;
upGroupList();
this.sourceGroup = TextUtils.join("; ", groupList);
}
public String getSourceUrl() {
return this.sourceUrl;
}
public void setSourceUrl(String sourceUrl) {
this.sourceUrl = sourceUrl;
}
public String getSourceCharset() {
return this.sourceCharset;
}
public void setSourceCharset(String sourceCharset) {
this.sourceCharset = sourceCharset;
}
public int getOrderNum() {
return this.orderNum;
}
public void setOrderNum(int orderNum) {
this.orderNum = orderNum;
}
public int getWeight() {
return this.weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public boolean getEnable() {
return this.enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public SearchRule getSearchRule() {
return this.searchRule;
}
public void setSearchRule(SearchRule searchRule) {
this.searchRule = searchRule;
}
public InfoRule getInfoRule() {
return this.infoRule;
}
public void setInfoRule(InfoRule infoRule) {
this.infoRule = infoRule;
}
public TocRule getTocRule() {
return this.tocRule;
}
public void setTocRule(TocRule tocRule) {
this.tocRule = tocRule;
}
public ContentRule getContentRule() {
return this.contentRule;
}
public void setContentRule(ContentRule contentRule) {
this.contentRule = contentRule;
}
public String getSourceEName() {
return this.sourceEName;
}
public void setSourceEName(String sourceEName) {
this.sourceEName = sourceEName;
}
private void upGroupList() {
if (groupList == null)
groupList = new ArrayList<>();
else
groupList.clear();
if (!TextUtils.isEmpty(sourceGroup)) {
for (String group : sourceGroup.split("\\s*[,;,;]\\s*")) {
group = group.trim();
if (TextUtils.isEmpty(group) || groupList.contains(group)) continue;
groupList.add(group);
}
}
}
public void addGroup(String group) {
if (groupList == null)
upGroupList();
if (!groupList.contains(group)) {
groupList.add(group);
updateModTime();
sourceGroup = TextUtils.join("; ", groupList);
}
}
public void removeGroup(String group) {
if (groupList == null)
upGroupList();
if (groupList.contains(group)) {
groupList.remove(group);
updateModTime();
sourceGroup = TextUtils.join("; ", groupList);
}
}
public boolean containsGroup(String group) {
if (groupList == null) {
upGroupList();
}
return groupList.contains(group);
}
public Long getLastUpdateTime() {
return this.lastUpdateTime;
}
public void setLastUpdateTime(Long lastUpdateTime) {
this.lastUpdateTime = lastUpdateTime;
}
public void updateModTime() {
this.lastUpdateTime = System.currentTimeMillis();
}
public String getSourceComment() {
return this.sourceComment;
}
public void setSourceComment(String sourceComment) {
this.sourceComment = sourceComment;
}
public FindRule getFindRule() {
return this.findRule;
}
public void setFindRule(FindRule findRule) {
this.findRule = findRule;
}
public String getSourceType() {
return this.sourceType;
}
public void setSourceType(String sourceType) {
this.sourceType = sourceType;
}
}

@ -0,0 +1,93 @@
package xyz.fycz.myreader.greendao.entity.rule;
import android.os.Parcel;
import android.os.Parcelable;
import org.greenrobot.greendao.converter.PropertyConverter;
import java.util.Objects;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/**
* @author fengyue
* @date 2021/2/8 18:01
*/
public class ContentRule implements Parcelable {
private String content;
private String contentBaseUrl;
private String contentUrlNext;
protected ContentRule(Parcel in) {
content = in.readString();
contentBaseUrl = in.readString();
contentUrlNext = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(content);
dest.writeString(contentBaseUrl);
dest.writeString(contentUrlNext);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<ContentRule> CREATOR = new Creator<ContentRule>() {
@Override
public ContentRule createFromParcel(Parcel in) {
return new ContentRule(in);
}
@Override
public ContentRule[] newArray(int size) {
return new ContentRule[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) o = new ContentRule();
if (getClass() != o.getClass()) return false;
ContentRule that = (ContentRule) o;
return stringEquals(content, that.content) &&
stringEquals(contentBaseUrl, that.contentBaseUrl) &&
stringEquals(contentUrlNext, that.contentUrlNext);
}
public ContentRule() {
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getContentBaseUrl() {
return contentBaseUrl;
}
public void setContentBaseUrl(String contentBaseUrl) {
this.contentBaseUrl = contentBaseUrl;
}
public String getContentUrlNext() {
return contentUrlNext;
}
public void setContentUrlNext(String contentUrlNext) {
this.contentUrlNext = contentUrlNext;
}
}

@ -0,0 +1,185 @@
package xyz.fycz.myreader.greendao.entity.rule;
import android.os.Parcel;
import android.os.Parcelable;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/**
* @author fengyue
* @date 2021/2/10 8:57
*/
public class FindRule implements Parcelable {
private String bookList;
private String name;
private String author;
private String type;
private String desc;
private String wordCount;
private String status;
private String lastChapter;
private String updateTime;
private String imgUrl;
private String tocUrl;
public FindRule() {
}
protected FindRule(Parcel in) {
bookList = in.readString();
name = in.readString();
author = in.readString();
type = in.readString();
desc = in.readString();
wordCount = in.readString();
status = in.readString();
lastChapter = in.readString();
updateTime = in.readString();
imgUrl = in.readString();
tocUrl = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(bookList);
dest.writeString(name);
dest.writeString(author);
dest.writeString(type);
dest.writeString(desc);
dest.writeString(wordCount);
dest.writeString(status);
dest.writeString(lastChapter);
dest.writeString(updateTime);
dest.writeString(imgUrl);
dest.writeString(tocUrl);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<FindRule> CREATOR = new Creator<FindRule>() {
@Override
public FindRule createFromParcel(Parcel in) {
return new FindRule(in);
}
@Override
public FindRule[] newArray(int size) {
return new FindRule[size];
}
};
public String getBookList() {
return bookList;
}
public void setBookList(String bookList) {
this.bookList = bookList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getWordCount() {
return wordCount;
}
public void setWordCount(String wordCount) {
this.wordCount = wordCount;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getLastChapter() {
return lastChapter;
}
public void setLastChapter(String lastChapter) {
this.lastChapter = lastChapter;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getTocUrl() {
return tocUrl;
}
public void setTocUrl(String tocUrl) {
this.tocUrl = tocUrl;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) o = new FindRule();
if (getClass() != o.getClass()) return false;
FindRule findRule = (FindRule) o;
return stringEquals(bookList, findRule.bookList) &&
stringEquals(name, findRule.name) &&
stringEquals(author, findRule.author) &&
stringEquals(type, findRule.type) &&
stringEquals(desc, findRule.desc) &&
stringEquals(wordCount, findRule.wordCount) &&
stringEquals(status, findRule.status) &&
stringEquals(lastChapter, findRule.lastChapter) &&
stringEquals(updateTime, findRule.updateTime) &&
stringEquals(imgUrl, findRule.imgUrl) &&
stringEquals(tocUrl, findRule.tocUrl);
}
}

@ -0,0 +1,175 @@
package xyz.fycz.myreader.greendao.entity.rule;
import android.os.Parcel;
import android.os.Parcelable;
import org.greenrobot.greendao.converter.PropertyConverter;
import java.util.Objects;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/**
* @author fengyue
* @date 2021/2/8 17:53
*/
public class InfoRule implements Parcelable {
private String name;
private String author;
private String type;
private String desc;
private String status;
private String wordCount;
private String lastChapter;
private String updateTime;
private String imgUrl;
private String tocUrl;
public InfoRule() {
}
protected InfoRule(Parcel in) {
name = in.readString();
author = in.readString();
type = in.readString();
desc = in.readString();
status = in.readString();
wordCount = in.readString();
lastChapter = in.readString();
updateTime = in.readString();
imgUrl = in.readString();
tocUrl = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(author);
dest.writeString(type);
dest.writeString(desc);
dest.writeString(status);
dest.writeString(wordCount);
dest.writeString(lastChapter);
dest.writeString(updateTime);
dest.writeString(imgUrl);
dest.writeString(tocUrl);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<InfoRule> CREATOR = new Creator<InfoRule>() {
@Override
public InfoRule createFromParcel(Parcel in) {
return new InfoRule(in);
}
@Override
public InfoRule[] newArray(int size) {
return new InfoRule[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) o = new InfoRule();
if (getClass() != o.getClass()) return false;
InfoRule infoRule = (InfoRule) o;
return stringEquals(name, infoRule.name) &&
stringEquals(author, infoRule.author) &&
stringEquals(type, infoRule.type) &&
stringEquals(desc, infoRule.desc) &&
stringEquals(status, infoRule.status) &&
stringEquals(wordCount, infoRule.wordCount) &&
stringEquals(lastChapter, infoRule.lastChapter) &&
stringEquals(updateTime, infoRule.updateTime) &&
stringEquals(imgUrl, infoRule.imgUrl) &&
stringEquals(tocUrl, infoRule.tocUrl);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getWordCount() {
return wordCount;
}
public void setWordCount(String wordCount) {
this.wordCount = wordCount;
}
public String getLastChapter() {
return lastChapter;
}
public void setLastChapter(String lastChapter) {
this.lastChapter = lastChapter;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getTocUrl() {
return tocUrl;
}
public void setTocUrl(String tocUrl) {
this.tocUrl = tocUrl;
}
}

@ -0,0 +1,236 @@
package xyz.fycz.myreader.greendao.entity.rule;
import android.os.Parcel;
import android.os.Parcelable;
import org.greenrobot.greendao.converter.PropertyConverter;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import xyz.fycz.myreader.util.utils.GsonUtils;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/**
* @author fengyue
* @date 2021/2/8 17:48
*/
public class SearchRule implements Parcelable {
private String searchUrl;
private String charset;
private String list;
private String name;
private String author;
private String type;
private String desc;
private String wordCount;
private String status;
private String lastChapter;
private String updateTime;
private String imgUrl;
private String tocUrl;
private String infoUrl;
private boolean relatedWithInfo;
public SearchRule() {
}
protected SearchRule(Parcel in) {
searchUrl = in.readString();
charset = in.readString();
list = in.readString();
name = in.readString();
author = in.readString();
type = in.readString();
desc = in.readString();
wordCount = in.readString();
status = in.readString();
lastChapter = in.readString();
updateTime = in.readString();
imgUrl = in.readString();
tocUrl = in.readString();
infoUrl = in.readString();
relatedWithInfo = in.readByte() != 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(searchUrl);
dest.writeString(charset);
dest.writeString(list);
dest.writeString(name);
dest.writeString(author);
dest.writeString(type);
dest.writeString(desc);
dest.writeString(wordCount);
dest.writeString(status);
dest.writeString(lastChapter);
dest.writeString(updateTime);
dest.writeString(imgUrl);
dest.writeString(tocUrl);
dest.writeString(infoUrl);
dest.writeByte((byte) (relatedWithInfo ? 1 : 0));
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<SearchRule> CREATOR = new Creator<SearchRule>() {
@Override
public SearchRule createFromParcel(Parcel in) {
return new SearchRule(in);
}
@Override
public SearchRule[] newArray(int size) {
return new SearchRule[size];
}
};
public String getSearchUrl() {
return searchUrl;
}
public void setSearchUrl(String searchUrl) {
this.searchUrl = searchUrl;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getList() {
return list;
}
public void setList(String list) {
this.list = list;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getLastChapter() {
return lastChapter;
}
public void setLastChapter(String lastChapter) {
this.lastChapter = lastChapter;
}
public String getUpdateTime() {
return updateTime;
}
public void setUpdateTime(String updateTime) {
this.updateTime = updateTime;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getTocUrl() {
return tocUrl;
}
public void setTocUrl(String tocUrl) {
this.tocUrl = tocUrl;
}
public String getWordCount() {
return wordCount;
}
public void setWordCount(String wordCount) {
this.wordCount = wordCount;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getInfoUrl() {
return infoUrl;
}
public void setInfoUrl(String infoUrl) {
this.infoUrl = infoUrl;
}
public boolean isRelatedWithInfo() {
return relatedWithInfo;
}
public void setRelatedWithInfo(boolean relatedWithInfo) {
this.relatedWithInfo = relatedWithInfo;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SearchRule that = (SearchRule) o;
return relatedWithInfo == that.relatedWithInfo &&
stringEquals(searchUrl, that.searchUrl) &&
stringEquals(charset, that.charset) &&
stringEquals(list, that.list) &&
stringEquals(name, that.name) &&
stringEquals(author, that.author) &&
stringEquals(type, that.type) &&
stringEquals(desc, that.desc) &&
stringEquals(wordCount, that.wordCount) &&
stringEquals(status, that.status) &&
stringEquals(lastChapter, that.lastChapter) &&
stringEquals(updateTime, that.updateTime) &&
stringEquals(imgUrl, that.imgUrl) &&
stringEquals(tocUrl, that.tocUrl) &&
stringEquals(infoUrl, that.infoUrl);
}
}

@ -0,0 +1,116 @@
package xyz.fycz.myreader.greendao.entity.rule;
import android.os.Parcel;
import android.os.Parcelable;
import org.greenrobot.greendao.converter.PropertyConverter;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import static xyz.fycz.myreader.util.utils.StringUtils.stringEquals;
/**
* @author fengyue
* @date 2021/2/8 17:59
*/
public class TocRule implements Parcelable {
private String chapterList;
private String chapterBaseUrl;
private String chapterName;
private String chapterUrl;
private String tocUrlNext;
protected TocRule(Parcel in) {
chapterList = in.readString();
chapterBaseUrl = in.readString();
chapterName = in.readString();
chapterUrl = in.readString();
tocUrlNext = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(chapterList);
dest.writeString(chapterBaseUrl);
dest.writeString(chapterName);
dest.writeString(chapterUrl);
dest.writeString(tocUrlNext);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<TocRule> CREATOR = new Creator<TocRule>() {
@Override
public TocRule createFromParcel(Parcel in) {
return new TocRule(in);
}
@Override
public TocRule[] newArray(int size) {
return new TocRule[size];
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) o = new TocRule();
if (getClass() != o.getClass()) return false;
TocRule tocRule = (TocRule) o;
return stringEquals(chapterList, tocRule.chapterList) &&
stringEquals(chapterBaseUrl, tocRule.chapterBaseUrl) &&
stringEquals(chapterName, tocRule.chapterName) &&
stringEquals(chapterUrl, tocRule.chapterUrl) &&
stringEquals(tocUrlNext, tocRule.tocUrlNext);
}
public String getChapterList() {
return chapterList;
}
public void setChapterList(String chapterList) {
this.chapterList = chapterList;
}
public String getChapterBaseUrl() {
return chapterBaseUrl;
}
public void setChapterBaseUrl(String chapterBaseUrl) {
this.chapterBaseUrl = chapterBaseUrl;
}
public String getChapterName() {
return chapterName;
}
public void setChapterName(String chapterName) {
this.chapterName = chapterName;
}
public String getChapterUrl() {
return chapterUrl;
}
public void setChapterUrl(String chapterUrl) {
this.chapterUrl = chapterUrl;
}
public String getTocUrlNext() {
return tocUrlNext;
}
public void setTocUrlNext(String tocUrlNext) {
this.tocUrlNext = tocUrlNext;
}
public TocRule() {
}
}

@ -0,0 +1,22 @@
package xyz.fycz.myreader.greendao.entity.rule.convert;
import org.greenrobot.greendao.converter.PropertyConverter;
import xyz.fycz.myreader.greendao.entity.rule.ContentRule;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
/**
* @author fengyue
* @date 2021/2/8 18:27
*/
public class ContentRuleConvert implements PropertyConverter<ContentRule, String> {
@Override
public ContentRule convertToEntityProperty(String databaseValue) {
return GsonExtensionsKt.getGSON().fromJson(databaseValue, ContentRule.class);
}
@Override
public String convertToDatabaseValue(ContentRule entityProperty) {
return GsonExtensionsKt.getGSON().toJson(entityProperty);
}
}

@ -0,0 +1,24 @@
package xyz.fycz.myreader.greendao.entity.rule.convert;
import org.greenrobot.greendao.converter.PropertyConverter;
import xyz.fycz.myreader.greendao.entity.rule.FindRule;
import xyz.fycz.myreader.greendao.entity.rule.TocRule;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
/**
* @author fengyue
* @date 2021/2/8 18:28
*/
public class FindRuleConvert implements PropertyConverter<FindRule, String> {
@Override
public FindRule convertToEntityProperty(String databaseValue) {
return GsonExtensionsKt.getGSON().fromJson(databaseValue, FindRule.class);
}
@Override
public String convertToDatabaseValue(FindRule entityProperty) {
return GsonExtensionsKt.getGSON().toJson(entityProperty);
}
}

@ -0,0 +1,23 @@
package xyz.fycz.myreader.greendao.entity.rule.convert;
import org.greenrobot.greendao.converter.PropertyConverter;
import xyz.fycz.myreader.greendao.entity.rule.InfoRule;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
/**
* @author fengyue
* @date 2021/2/8 18:28
*/
public class InfoRuleConvert implements PropertyConverter<InfoRule, String> {
@Override
public InfoRule convertToEntityProperty(String databaseValue) {
return GsonExtensionsKt.getGSON().fromJson(databaseValue, InfoRule.class);
}
@Override
public String convertToDatabaseValue(InfoRule entityProperty) {
return GsonExtensionsKt.getGSON().toJson(entityProperty);
}
}

@ -0,0 +1,23 @@
package xyz.fycz.myreader.greendao.entity.rule.convert;
import org.greenrobot.greendao.converter.PropertyConverter;
import xyz.fycz.myreader.greendao.entity.rule.SearchRule;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
/**
* @author fengyue
* @date 2021/2/8 18:29
*/
public class SearchRuleConvert implements PropertyConverter<SearchRule, String> {
@Override
public SearchRule convertToEntityProperty(String databaseValue) {
return GsonExtensionsKt.getGSON().fromJson(databaseValue, SearchRule.class);
}
@Override
public String convertToDatabaseValue(SearchRule entityProperty) {
return GsonExtensionsKt.getGSON().toJson(entityProperty);
}
}

@ -0,0 +1,23 @@
package xyz.fycz.myreader.greendao.entity.rule.convert;
import org.greenrobot.greendao.converter.PropertyConverter;
import xyz.fycz.myreader.greendao.entity.rule.TocRule;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
/**
* @author fengyue
* @date 2021/2/8 18:28
*/
public class TocRuleConvert implements PropertyConverter<TocRule, String> {
@Override
public TocRule convertToEntityProperty(String databaseValue) {
return GsonExtensionsKt.getGSON().fromJson(databaseValue, TocRule.class);
}
@Override
public String convertToDatabaseValue(TocRule entityProperty) {
return GsonExtensionsKt.getGSON().toJson(entityProperty);
}
}

@ -1,12 +1,10 @@
package xyz.fycz.myreader.greendao.service;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.greendao.GreenDaoManager;
import xyz.fycz.myreader.greendao.entity.BookGroup;
import xyz.fycz.myreader.greendao.gen.BookGroupDao;
import xyz.fycz.myreader.greendao.gen.BookMarkDao;
import xyz.fycz.myreader.ui.activity.SplashActivity;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.StringHelper;
@ -93,7 +91,7 @@ public class BookGroupService extends BaseService{
* @return
*/
public boolean curGroupIsPrivate(){
String curBookGroupId = SharedPreUtils.getInstance().getString(MyApplication.getmContext().getString(R.string.curBookGroupId), "");
String curBookGroupId = SharedPreUtils.getInstance().getString(App.getmContext().getString(R.string.curBookGroupId), "");
String privateGroupId = SharedPreUtils.getInstance().getString("privateGroupId");
return !StringHelper.isEmpty(curBookGroupId) && curBookGroupId.equals(privateGroupId);
}

@ -7,23 +7,19 @@ import net.ricecode.similarity.JaroWinklerStrategy;
import net.ricecode.similarity.StringSimilarityService;
import net.ricecode.similarity.StringSimilarityServiceImpl;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.gen.BookDao;
import xyz.fycz.myreader.greendao.gen.ChapterDao;
import xyz.fycz.myreader.util.*;
import xyz.fycz.myreader.greendao.GreenDaoManager;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -116,7 +112,7 @@ public class BookService extends BaseService {
public void addBook(Book book) {
book.setSortCode(0);
book.setGroupSort(0);
book.setGroupId(SharedPreUtils.getInstance().getString(MyApplication.getmContext().getString(R.string.curBookGroupId), ""));
book.setGroupId(SharedPreUtils.getInstance().getString(App.getmContext().getString(R.string.curBookGroupId), ""));
if (StringHelper.isEmpty(book.getId())) {
book.setId(StringHelper.getStringRandom(25));
}
@ -333,7 +329,7 @@ public class BookService extends BaseService {
public boolean matchHistoryChapterPos(Book book, ArrayList<Chapter> mChapters) {
float matchSui = SysManager.getSetting().getMatchChapterSuitability();
int index = getDurChapter(book.getHisttoryChapterNum(), book.getChapterTotalNum(), book.getHistoryChapterId(), mChapters);
assert book.getHistoryChapterId() != null;
if (book.getHistoryChapterId() == null) return false;
String oldName = StringUtils.deleteWhitespace(book.getHistoryChapterId());
String newName = StringUtils.deleteWhitespace(mChapters.get(index).getTitle());
/*int i1 = getChapterNum(oldName);

@ -36,7 +36,9 @@ public class MySQLiteOpenHelper extends DaoMaster.OpenHelper {
DaoMaster.dropAllTables(db, ifExists);
}
},
BookDao.class, ChapterDao.class, SearchHistoryDao.class, BookMarkDao.class, BookGroupDao.class, ReplaceRuleBeanDao.class
BookDao.class, ChapterDao.class, SearchHistoryDao.class,
BookMarkDao.class, BookGroupDao.class, ReplaceRuleBeanDao.class,
BookSourceDao.class
);
}

@ -8,13 +8,18 @@ import java.util.Collections;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.annotations.NonNull;
import xyz.fycz.myreader.greendao.GreenDaoManager;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.gen.ReplaceRuleBeanDao;
import xyz.fycz.myreader.util.utils.GsonUtils;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
@ -162,12 +167,24 @@ public class ReplaceRuleManager {
}
public static void delDataS(List<ReplaceRuleBean> replaceRuleBeans) {
for (ReplaceRuleBean replaceRuleBean : replaceRuleBeans) {
GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().delete(replaceRuleBean);
}
if (replaceRuleBeans == null) return;
GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().deleteInTx(replaceRuleBeans);
refreshDataS();
}
public static Single<Boolean> toTop(ReplaceRuleBean bean) {
return Single.create((SingleOnSubscribe<Boolean>) e -> {
List<ReplaceRuleBean> beans = getAllRules();
for (int i = 0; i < beans.size(); i++) {
beans.get(i).setSerialNumber(i + 1);
}
bean.setSerialNumber(0);
GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().insertOrReplaceInTx(beans);
GreenDaoManager.getDaoSession().getReplaceRuleBeanDao().insertOrReplace(bean);
e.onSuccess(true);
}).compose(RxUtils::toSimpleSingle);
}
private static void refreshDataS() {
replaceRuleBeansEnabled = GreenDaoManager.getDaoSession()
.getReplaceRuleBeanDao().queryBuilder()
@ -184,13 +201,12 @@ public class ReplaceRuleManager {
return importReplaceRuleO(text)
.compose(RxUtils::toSimpleSingle);
}
/*if (NetworkUtils.isUrl(text)) {
return BaseModelImpl.getInstance().getRetrofitString(StringUtils.getBaseUrl(text), "utf-8")
.create(IHttpGetApi.class)
.get(text, AnalyzeHeaders.getMap(null))
.flatMap(rsp -> importReplaceRuleO(rsp.body()))
if (NetworkUtils.isUrl(text)) {
String finalText = text;
return Observable.create((ObservableOnSubscribe<String>) emitter -> emitter.onNext(OkHttpUtils.getHtml(finalText)))
.flatMap(ReplaceRuleManager::importReplaceRuleO)
.compose(RxUtils::toSimpleSingle);
}*/
}
return Observable.error(new Exception("不是Json或Url格式"));
}

@ -7,14 +7,12 @@ import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.service.BookMarkService;
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.ToastUtils;
@ -50,7 +48,7 @@ public class SearchEngine {
private OnSearchListener searchListener;
public SearchEngine() {
threadsNum = SharedPreUtils.getInstance().getInt(MyApplication.getmContext().getString(R.string.threadNum), 8);
threadsNum = SharedPreUtils.getInstance().getInt(App.getmContext().getString(R.string.threadNum), 8);
}
public void setOnSearchListener(OnSearchListener searchListener) {

@ -19,7 +19,6 @@ import android.speech.tts.UtteranceProgressListener;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
@ -30,7 +29,7 @@ import java.util.List;
import java.util.Locale;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.ui.activity.ReadActivity;
import xyz.fycz.myreader.util.SharedPreUtils;
@ -287,9 +286,9 @@ public class ReadAloudService extends Service {
private void initTTS() {
if (textToSpeech == null)
textToSpeech = new TextToSpeech(MyApplication.getmContext(), new TTSListener());
textToSpeech = new TextToSpeech(App.getmContext(), new TTSListener());
if (textToSpeech_ui == null)
textToSpeech_ui = new TextToSpeech(MyApplication.getmContext(), new TTSUIListener());
textToSpeech_ui = new TextToSpeech(App.getmContext(), new TTSUIListener());
if (mParams == null) {
mParams = new HashMap();
mParams.put(TextToSpeech.Engine.KEY_PARAM_STREAM, "3");

@ -1,7 +1,7 @@
package xyz.fycz.myreader.model.backup;
import io.reactivex.annotations.NonNull;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.model.storage.Backup;
import xyz.fycz.myreader.model.storage.Restore;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
@ -27,7 +27,7 @@ public class UserService {
* @return 是否成功登录
*/
public static void login(final Map<String, String> userLoginInfo, final ResultCallback resultCallback) {
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
HttpURLConnection conn = null;
try {
URL url = new URL(URLCONST.APP_WEB_URL + "login");
@ -66,7 +66,7 @@ public class UserService {
}
public static void register(final Map<String, String> userRegisterInfo, final ResultCallback resultCallback) {
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
HttpURLConnection conn = null;
try {
URL url = new URL(URLCONST.APP_WEB_URL + "reg");
@ -111,7 +111,7 @@ public class UserService {
public static boolean writeConfig(Map<String,String> userLoginInfo){
FileOutputStream fos = null;
try {
fos = MyApplication.getApplication().openFileOutput("userConfig.fy", MyApplication.getApplication().MODE_PRIVATE);
fos = App.getApplication().openFileOutput("userConfig.fy", App.getApplication().MODE_PRIVATE);
String userInfo = "username='" + userLoginInfo.get("loginName") + "',\npassword='" + userLoginInfo.get("loginPwd") + "'";
byte[] bs = userInfo.getBytes();
fos.write(bs);
@ -131,7 +131,7 @@ public class UserService {
* @return
*/
public static Map<String,String> readConfig(){
File file = MyApplication.getApplication().getFileStreamPath("userConfig.fy");
File file = App.getApplication().getFileStreamPath("userConfig.fy");
if (!file.exists()){
return null;
}
@ -198,10 +198,10 @@ public class UserService {
if (userInfo == null){
rc.onFinish(false, 0);
}
Backup.INSTANCE.backup(MyApplication.getmContext(), APPCONST.FILE_DIR + "webBackup/", new Backup.CallBack() {
Backup.INSTANCE.backup(App.getmContext(), APPCONST.FILE_DIR + "webBackup/", new Backup.CallBack() {
@Override
public void backupSuccess() {
MyApplication.getApplication().newThread(() ->{
App.getApplication().newThread(() ->{
File inputFile = FileUtils.getFile(APPCONST.FILE_DIR + "webBackup");
if (!inputFile.exists()) {
rc.onFinish(false, 0);
@ -318,8 +318,8 @@ public class UserService {
private static String makeSignalParam(){
return "&signal=" + AppInfoUtils.getSingInfo(MyApplication.getmContext(),
MyApplication.getApplication().getPackageName(), AppInfoUtils.SHA1);
return "&signal=" + AppInfoUtils.getSingInfo(App.getmContext(),
App.getApplication().getPackageName(), AppInfoUtils.SHA1);
}
/**
@ -327,7 +327,7 @@ public class UserService {
* @return
*/
public static boolean isLogin(){
File file = MyApplication.getApplication().getFileStreamPath("userConfig.fy");
File file = App.getApplication().getFileStreamPath("userConfig.fy");
return file.exists();
}
}

@ -0,0 +1,247 @@
package xyz.fycz.myreader.model.source;
import android.util.Log;
import java.util.List;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.utils.StringUtils;
/**
* @author fengyue
* @date 2021/2/14 18:51
*/
public abstract class BaseAnalyzer {
public abstract List<String> getStringList(String rule, Object obj, boolean first);
public List<String> getStringList(String rule, Object obj) {
return getStringList(rule, obj, false);
}
public String getString(String rule, Object obj) {
if (StringHelper.isEmpty(rule)) return "";
List<String> list = getStringList(rule, obj, true);
if (list.size() == 0) return "";
return list.get(0);
}
/**
* 执行函数
* 1@r/@replace(oldStr, newStr)
* 2@a/@append(str1+str2+str3)
* 3@e/equal(str1,str2) @e(<text>, novel)
* 4@c/contains(str) 必须包含
* 5@nc/notContains(str) 必须不包含
*
* @param funs
* @param content
* @return
*/
protected String evalFunction(String funs, String content) {
boolean hasSemicolon = funs.contains("\\;");
if (hasSemicolon) funs = funs.replace("\\;", "\\;");
if (funs.endsWith(";")) funs = funs.substring(0, funs.length() - 1);
String[] funArr = funs.split(";");
for (String fun : funArr) {
if (hasSemicolon) fun = fun.replace("\\;", ";");
if (fun.contains("@e")) {
content = String.valueOf(evalEqual(fun, content));
} else if (fun.contains("@r")) {
content = evalReplace(fun, content);
} else if (fun.contains("@a")) {
content = evalAppend(fun, content);
} else if (fun.contains("@c")) {
content = evalContains(fun, content);
} else if (fun.contains("@nc")) {
content = evalNotContains(fun, content);
}
}
return content;
}
/**
* 执行替换函数
*
* @param rule
* @param content
* @return
*/
protected String evalReplace(String rule, String content) {
rule = rule.trim();
if (!rule.matches("@(r|replace)\\(.+,.*\\)")) {
Log.d("evalReplace", "格式错误");
return content;
}
rule = rule.replace("(*)", ".*");
rule = rule.replace("\\,", "\\,");
String oldStr = null;
String newStr = null;
try {
oldStr = StringUtils.getSubString(rule, "(", ",")
.replace("\\,", ",");
newStr = StringUtils.getSubString(rule, ",", ")")
.replace("\\,", ",");
Log.d("evalReplace", "执行成功");
} catch (Exception e) {
e.printStackTrace();
return content;
}
return content.replaceAll(oldStr, newStr);
}
/**
* 执行添加函数
*
* @param rule
* @param content
* @return
*/
protected String evalAppend(String rule, String content) {
rule = rule.trim();
if (!rule.matches("@(a|append)\\(.+\\)")) {
Log.d("evalAppend", "格式错误");
return content;
}
StringBuilder reVal = null;
try {
rule = StringUtils.getMSubString(rule, "(", ")");
rule = rule.replace("\\+", "\\plus");
String[] strs = rule.split("\\+");
reVal = new StringBuilder();
for (String str : strs) {
str = str.replace("\\plus", "+");
if (str.matches("<(html|text)>")) {
reVal.append(content);
} else {
reVal.append(str);
}
}
Log.d("evalAppend", "执行成功");
} catch (Exception e) {
e.printStackTrace();
return content;
}
return reVal.toString();
}
/**
* 执行判断是否相等
*
* @param rule
* @return
*/
protected boolean evalEqual(String rule, String content) {
rule = rule.trim();
if (!rule.matches("@(e|equal)\\(.*,.*\\)")) {
Log.d("evalEqual", "格式错误");
return false;
}
String lStr = null;
String rStr = null;
try {
lStr = StringUtils.getSubString(rule, "(", ",");
rStr = StringUtils.getSubString(rule, ",", ")");
if (lStr.matches("<(html|text)>")) {
lStr = content;
}
if (rStr.matches("<(html|text)>")) {
rStr = content;
}
Log.d("evalEqual", "执行成功");
} catch (Exception e) {
e.printStackTrace();
return false;
}
return lStr != null && lStr.equals(rStr);
}
/**
* 执行是否包含函数
*
* @param rule
* @param content
* @return
*/
protected String evalContains(String rule, String content) {
rule = rule.trim();
if (!rule.matches("@(c|contains)\\(.+\\)")) {
Log.d("evalContains", "格式错误");
return content;
}
try {
rule = StringUtils.getMSubString(rule, "(", ")");
if (!content.contains(rule)) {
content = "";
}
Log.d("evalContains", "执行成功");
} catch (Exception e) {
e.printStackTrace();
return content;
}
return content;
}
/**
* 执行是否不包含函数
*
* @param rule
* @param content
* @return
*/
protected String evalNotContains(String rule, String content) {
rule = rule.trim();
if (!rule.matches("@(nc|notContains)\\(.+\\)")) {
Log.d("evalNotContains", "格式错误");
return content;
}
try {
rule = StringUtils.getMSubString(rule, "(", ")");
if (content.contains(rule)) {
content = "";
}
} catch (Exception e) {
e.printStackTrace();
return content;
}
Log.d("evalNotContains", "执行成功");
return content;
}
/**
* 执行跳过函数
*
* @param rule
* @param content
* @return
*/
private String evalSkip(String rule, String content, int index) {
rule = rule.trim();
if (!rule.matches("@(s|skip)\\([0-9]+\\)") && !rule.matches("!([0-9]+)")) {
Log.d("evalSkip", "格式错误");
return content;
}
if (rule.matches("@(s|skip)\\([0-9]+\\)")) {
rule = StringUtils.getMSubString(rule, "(", ")");
} else if (rule.matches("!([0-9]+)")) {
rule = rule.substring(1);
}
int skip = 0;
try {
skip = Integer.parseInt(rule);
Log.d("evalSkip", "执行成功");
if (index < skip) return "";
} catch (NumberFormatException e) {
e.printStackTrace();
}
return content;
}
public static String getCharset(String charset) {
if (StringHelper.isEmpty(charset)) {
charset = "UTF-8";
}
return charset;
}
}

@ -0,0 +1,375 @@
package xyz.fycz.myreader.model.source;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.enums.LocalBookSource;
import xyz.fycz.myreader.greendao.GreenDaoManager;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.gen.BookSourceDao;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.utils.GsonUtils;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
public class BookSourceManager {
public static List<BookSource> getEnabledBookSource() {
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.Enable.eq(true))
.orderRaw(BookSourceDao.Properties.Weight.columnName + " DESC")
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
}
public static List<BookSource> getAllBookSource() {
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.orderRaw(getBookSourceSort())
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
}
public static List<BookSource> getEnabledBookSourceByOrderNum() {
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.Enable.eq(true))
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
}
public static List<BookSource> getAllBookSourceByOrderNum() {
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
}
public static List<BookSource> getEnableSourceByGroup(String group) {
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.Enable.eq(true))
.where(BookSourceDao.Properties.SourceGroup.like("%" + group + "%"))
.orderRaw(BookSourceDao.Properties.Weight.columnName + " DESC")
.list();
}
/**
* 通过book.getSource()获取书源
*
* @param str
* @return
*/
public static BookSource getBookSourceByStr(String str) {
if (NetworkUtils.isUrl(str)) {
return getBookSourceByUrl(str);
} else {
return getBookSourceByEName(str);
}
}
/**
* 通过url获取书源
*
* @param url
* @return
*/
public static BookSource getBookSourceByUrl(String url) {
if (url == null) return getDefaultSource();
BookSource source = GreenDaoManager.getDaoSession().getBookSourceDao().load(url);
if (source == null) return getDefaultSource();
return source;
}
/**
* 内置书源获取
*
* @param ename
* @return
*/
@Nullable
public static BookSource getBookSourceByEName(String ename) {
if (ename == null) return getDefaultSource();
if ("local".equals(ename)) return getLocalSource();
return GreenDaoManager.getDaoSession().getBookSourceDao().
queryBuilder()
.where(BookSourceDao.Properties.SourceEName.eq(ename))
.unique();
}
/**
* 通过book.getSource()获取书源名
*
* @param str
* @return
*/
public static String getSourceNameByStr(String str) {
return getBookSourceByStr(str).getSourceName();
}
/**
* 获取默认书源
*
* @return
*/
private static BookSource getDefaultSource() {
BookSource bookSource = new BookSource();
bookSource.setSourceUrl("xyz.fycz.myreader.webapi.crawler.read.FYReadCrawler");
bookSource.setSourceName("风月小说");
bookSource.setSourceEName("fynovel");
bookSource.setSourceGroup("内置书源");
return bookSource;
}
/**
* 获取本地书籍
*
* @return
*/
private static BookSource getLocalSource() {
BookSource bookSource = new BookSource();
bookSource.setSourceEName("local");
bookSource.setSourceName("本地书籍");
return bookSource;
}
/**
* 获取所有内置书源
*
* @return
*/
public static List<BookSource> getAllLocalSource() {
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.SourceEName.isNotNull())
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
}
/**
* 获取所有导入书源
*
* @return
*/
public static List<BookSource> getAllNoLocalSource() {
return GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.SourceEName.isNull())
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
}
/**
* 删除书源
*
* @param source
*/
public static void removeBookSource(BookSource source) {
if (source == null) return;
GreenDaoManager.getDaoSession().getBookSourceDao().delete(source);
}
public static void removeBookSources(List<BookSource> sources) {
if (sources == null) return;
GreenDaoManager.getDaoSession().getBookSourceDao().deleteInTx(sources);
}
public static String getBookSourceSort() {
switch (SharedPreUtils.getInstance().getInt("SourceSort", 0)) {
case 1:
return BookSourceDao.Properties.Weight.columnName + " DESC";
case 2:
return BookSourceDao.Properties.SourceName.columnName + " COLLATE LOCALIZED ASC";
default:
return BookSourceDao.Properties.OrderNum.columnName + " ASC";
}
}
public static void addBookSource(List<BookSource> bookSources) {
for (BookSource bookSource : bookSources) {
addBookSource(bookSource);
}
}
public static boolean addBookSource(BookSource bookSource) {
if (TextUtils.isEmpty(bookSource.getSourceName()) || TextUtils.isEmpty(bookSource.getSourceUrl()))
return false;
if (bookSource.getSourceUrl().endsWith("/")) {
bookSource.setSourceUrl(bookSource.getSourceUrl().replaceAll("/+$", ""));
}
BookSource temp = GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.SourceUrl.eq(bookSource.getSourceUrl())).unique();
if (temp != null) {
bookSource.setOrderNum(temp.getOrderNum());
} else {
bookSource.setOrderNum((int) (GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder().count() + 1));
}
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplace(bookSource);
return true;
}
public static Single<Boolean> saveData(BookSource bookSource) {
return Single.create((SingleOnSubscribe<Boolean>) emitter -> {
if (bookSource.getOrderNum() == 0) {
bookSource.setOrderNum((int) (GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder().count() + 1));
}
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplace(bookSource);
emitter.onSuccess(true);
}).compose(RxUtils::toSimpleSingle);
}
public static Single<Boolean> saveDatas(List<BookSource> sources) {
return Single.create((SingleOnSubscribe<Boolean>) emitter -> {
for (BookSource source : sources) {
if (source.getOrderNum() == 0) {
source.setOrderNum((int) (GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder().count() + 1));
}
}
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplaceInTx(sources);
emitter.onSuccess(true);
}).compose(RxUtils::toSimpleSingle);
}
public static Single<Boolean> toTop(BookSource source) {
return Single.create((SingleOnSubscribe<Boolean>) e -> {
List<BookSource> List = getAllBookSourceByOrderNum();
for (int i = 0; i < List.size(); i++) {
List.get(i).setOrderNum(i + 1);
}
source.setOrderNum(0);
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplaceInTx(List);
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplace(source);
e.onSuccess(true);
}).compose(RxUtils::toSimpleSingle);
}
public static List<String> getEnableNoLocalGroupList() {
List<String> groupList = new ArrayList<>();
String sql = "SELECT DISTINCT "
+ BookSourceDao.Properties.SourceGroup.columnName
+ " FROM " + BookSourceDao.TABLENAME
+ " WHERE " + BookSourceDao.Properties.Enable.name + " = 1";
Cursor cursor = GreenDaoManager.getDaoSession().getDatabase().rawQuery(sql, null);
if (!cursor.moveToFirst()) return groupList;
do {
String group = cursor.getString(0);
if (TextUtils.isEmpty(group) || TextUtils.isEmpty(group.trim())) continue;
for (String item : group.split("\\s*[,;,;]\\s*")) {
if (TextUtils.isEmpty(item) || groupList.contains(item)|| item.equals("内置书源")) continue;
groupList.add(item);
}
} while (cursor.moveToNext());
Collections.sort(groupList);
return groupList;
}
public static List<String> getNoLocalGroupList() {
List<String> groupList = new ArrayList<>();
String sql = "SELECT DISTINCT " + BookSourceDao.Properties.SourceGroup.columnName + " FROM " + BookSourceDao.TABLENAME;
Cursor cursor = GreenDaoManager.getDaoSession().getDatabase().rawQuery(sql, null);
if (!cursor.moveToFirst()) return groupList;
do {
String group = cursor.getString(0);
if (TextUtils.isEmpty(group) || TextUtils.isEmpty(group.trim())) continue;
for (String item : group.split("\\s*[,;,;]\\s*")) {
if (TextUtils.isEmpty(item) || groupList.contains(item) || item.equals("内置书源")) continue;
groupList.add(item);
}
} while (cursor.moveToNext());
Collections.sort(groupList);
return groupList;
}
public static Observable<List<BookSource>> importSource(String string) {
if (StringHelper.isEmpty(string)) return null;
string = string.trim();
if (NetworkUtils.isIPv4Address(string)) {
string = String.format("http://%s:65501", string);
}
if (StringUtils.isJsonType(string)) {
return importBookSourceFromJson(string.trim())
.compose(RxUtils::toSimpleSingle);
} else if (StringUtils.isCompressJsonType(string)) {
return importBookSourceFromJson(StringUtils.unCompressJson(string))
.compose(RxUtils::toSimpleSingle);
}
if (NetworkUtils.isUrl(string)) {
String finalString = string;
return Observable.create((ObservableEmitter<String> e) -> e.onNext(OkHttpUtils.getHtml(finalString)))
.flatMap(BookSourceManager::importBookSourceFromJson)
.compose(RxUtils::toSimpleSingle);
}
return Observable.error(new Exception("不是Json或Url格式"));
}
private static Observable<List<BookSource>> importBookSourceFromJson(String json) {
return Observable.create(e -> {
List<BookSource> successImportSources = new ArrayList<>();
if (StringUtils.isJsonArray(json)) {
try {
List<BookSource> bookSources = GsonUtils.parseJArray(json, BookSource.class);
for (BookSource bookSource : bookSources) {
if (bookSource.containsGroup("删除")) {
GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.SourceUrl.eq(bookSource.getSourceUrl()))
.buildDelete().executeDeleteWithoutDetachingEntities();
} else {
if (addBookSource(bookSource)){
successImportSources.add(bookSource);
}
}
}
e.onNext(successImportSources);
e.onComplete();
return;
} catch (Exception ignored) {
}
}
if (StringUtils.isJsonObject(json)) {
try {
BookSource bookSource = GsonUtils.parseJObject(json, BookSource.class);
if (addBookSource(bookSource))
successImportSources.add(bookSource);
e.onNext(successImportSources);
e.onComplete();
return;
} catch (Exception ignored) {
}
}
e.onError(new Throwable("格式不对"));
});
}
public static void initDefaultSources() {
Log.d("initDefaultSources", "execute");
GreenDaoManager.getDaoSession().getBookSourceDao().deleteAll();
String searchSource = SharedPreUtils.getInstance().getString(App.getmContext().getString(R.string.searchSource));
boolean isEmpty = StringHelper.isEmpty(searchSource);
for (LocalBookSource source : LocalBookSource.values()) {
if (source == LocalBookSource.local || source == LocalBookSource.fynovel) continue;
BookSource source1 = new BookSource();
source1.setSourceEName(source.toString());
source1.setSourceName(source.text);
source1.setSourceGroup("内置书源");
source1.setEnable(isEmpty || searchSource.contains(source.toString()));
source1.setSourceUrl(ReadCrawlerUtil.getReadCrawlerClz(source.toString()));
BookSourceManager.addBookSource(source1);
}
BookSourceManager.addBookSource(MatcherAnalyzer.getTestSource());
BookSourceManager.addBookSource(MatcherAnalyzer.getTestSource2());
}
}

@ -0,0 +1,84 @@
package xyz.fycz.myreader.model.source;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import com.jayway.jsonpath.ParseContext;
import com.jayway.jsonpath.ReadContext;
import com.jayway.jsonpath.spi.json.GsonJsonProvider;
import java.util.ArrayList;
import java.util.List;
import xyz.fycz.myreader.util.StringHelper;
/**
* @author fengyue
* @date 2021/2/15 9:08
*/
public class JsonPathAnalyzer extends BaseAnalyzer {
private final ParseContext parseContext = JsonPath.using(Configuration.builder()
.jsonProvider(new GsonJsonProvider())
.options(Option.ALWAYS_RETURN_LIST)
.build());
@Override
public List<String> getStringList(String rule, Object obj, boolean isFirst) {
return getStringList(rule, getReadContext(obj), isFirst);
}
public List<String> getStringList(String rule, ReadContext rc, boolean isFirst) {
List<String> list = new ArrayList<>();
if (StringHelper.isEmpty(rule)) return list;
boolean hasFunction = rule.contains("##");
String funs = "";
if (hasFunction) {
funs = rule.substring(rule.indexOf("##") + 2);
rule = rule.substring(0, rule.indexOf("##"));
}
JsonArray temp = rc.read(rule);
for (JsonElement element : temp) {
String str = element.toString();
if (str.startsWith("\"")) str = str.substring(1, str.length() - 1);
if (hasFunction) str = evalFunction(funs, str);
if (StringHelper.isEmpty(str)) continue;
list.add(str);
if (isFirst) break;
}
return list;
}
/**
* @param rule ##!int:跳过前几个节点
* @param rc
* @return
*/
public List<ReadContext> getReadContextList(String rule, ReadContext rc) {
List<ReadContext> list = new ArrayList<>();
int skip = 0;
if (rule.contains("##!")) {
try {
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3));
} catch (Exception ignored) {
}
rule = rule.split("##")[0];
}
if (StringHelper.isEmpty(rule)) return list;
JsonArray temp = rc.read(rule);
for (JsonElement element : temp) {
String str = element.toString();
if (str.startsWith("\"")) str = str.substring(1, str.length() - 1);
list.add(getReadContext(str));
}
return list.subList(skip, list.size());
}
public ReadContext getReadContext(Object obj) {
if (obj instanceof ReadContext) return (ReadContext) obj;
if (obj instanceof String) return parseContext.parse((String) obj);
return parseContext.parse(obj);
}
}

@ -0,0 +1,319 @@
package xyz.fycz.myreader.model.source;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.entity.rule.ContentRule;
import xyz.fycz.myreader.greendao.entity.rule.InfoRule;
import xyz.fycz.myreader.greendao.entity.rule.SearchRule;
import xyz.fycz.myreader.greendao.entity.rule.TocRule;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
/**
* @author fengyue
* @date 2021/2/7 12:01
*/
public class MatcherAnalyzer extends BaseAnalyzer{
private enum RegexMod {
None, Both, Left, Right
}
/**
* 匹配中间文本
*
* @param rule <text|html>用于占位正则使用(*)
* @param obj
* @return
*/
public List<String> getStringList(String rule, Object obj, boolean first) {
String html = (String) obj;
List<String> innerStrList = new ArrayList<>();
if (StringHelper.isEmpty(rule)) return innerStrList;
boolean isHtml = rule.contains("<html>");
boolean hasFunction = rule.contains("##");
String funs = "";
if (hasFunction) {
funs = rule.substring(rule.indexOf("##") + 2);
rule = rule.substring(0, rule.indexOf("##"));
}
String[] rules = rule.split("<(html|text)>", 2);
if (rules.length < 2) {
return innerStrList;
}
RegexMod regexMod = getRegexMod(rules);
int lIndex = 0;
int rIndex = 0;
Matcher lMatcher;
Matcher rMatcher = null;
switch (regexMod) {
case None:
while ((lIndex = html.indexOf(rules[0], lIndex)) != -1) {
lIndex += rules[0].length();
rIndex = html.indexOf(rules[1], lIndex);
if (rIndex < lIndex) break;
String str = html.substring(lIndex, rIndex);
if (hasFunction) str = evalFunction(funs, str);
if (isHtml) str = StringUtils.fromHtml(str);
if (StringHelper.isEmpty(str)) continue;
innerStrList.add(str);
if (first) break;
}
break;
case Both:
rules[0] = rules[0].replace("(*)", ".*");
rules[1] = rules[1].replace("(*)", ".*");
lMatcher = Pattern.compile(rules[0]).matcher(html);
rMatcher = Pattern.compile(rules[1]).matcher(html);
while (lMatcher.find()) {
lIndex = lMatcher.end();
while (rIndex < lIndex && rMatcher.find()) {
rIndex = rMatcher.start();
}
if (rIndex < lIndex) break;
String str = html.substring(lIndex, rIndex);
if (hasFunction) str = evalFunction(funs, str);
if (isHtml) str = StringUtils.fromHtml(str);
if (StringHelper.isEmpty(str)) continue;
innerStrList.add(str);
if (first) break;
}
break;
case Left:
rules[0] = rules[0].replace("(*)", ".*");
lMatcher = Pattern.compile(rules[0]).matcher(html);
while (lMatcher.find()) {
lIndex = lMatcher.end();
rIndex = html.indexOf(rules[1], lIndex);
if (rIndex < lIndex) break;
String str = html.substring(lIndex, rIndex);
if (hasFunction) str = evalFunction(funs, str);
if (isHtml) str = StringUtils.fromHtml(str);
if (StringHelper.isEmpty(str)) continue;
innerStrList.add(str);
if (first) break;
}
break;
case Right:
rules[1] = rules[1].replace("(*)", ".*");
rMatcher = Pattern.compile(rules[1]).matcher(html);
while ((lIndex = html.indexOf(rules[0], lIndex)) != -1) {
while (rIndex < lIndex && rMatcher.find()) {
rIndex = rMatcher.start();
}
if (rIndex < lIndex) break;
String str = html.substring(lIndex, rIndex);
if (hasFunction) str = evalFunction(funs, str);
if (isHtml) str = StringUtils.fromHtml(str);
if (StringHelper.isEmpty(str)) continue;
innerStrList.add(str);
if (first) break;
}
break;
}
return innerStrList;
}
/**
* 使用正则匹配章节列表
*
* @param rule <link><title> ##!int:跳过前几个章节
* @param html
* @return
*/
public ArrayList<Chapter> matchChapters(String rule, String html, String baseUrl) {
ArrayList<Chapter> chapters = new ArrayList<>();
if (StringHelper.isEmpty(rule)) return chapters;
if (!rule.contains("<link>") || !rule.contains("<title>")) return chapters;
rule = rule.replace("(*)", ".*");
int skip = 0;
if (rule.contains("##!")) {
try {
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3));
} catch (Exception ignored) {
}
rule = rule.split("##")[0];
}
int linkI = 1;
int titleI = 2;
if (rule.indexOf("<link>") > rule.indexOf("<title>")) {
linkI = 2;
titleI = 1;
}
rule = rule.replaceAll("[()]", "");
rule = rule.replace("<link>", "(.*?)");
rule = rule.replace("<title>", "(.*?)");
Matcher matcher = Pattern.compile(rule).matcher(html);
String lastTile = null;
int i = 0;
int j = 0;
while (matcher.find()) {
if (i++ < skip) continue;
String title = matcher.group(titleI);
if (!StringHelper.isEmpty(lastTile) && lastTile.equals(title)) continue;
Chapter chapter = new Chapter();
chapter.setNumber(j++);
chapter.setTitle(title);
chapter.setUrl(NetworkUtils.getAbsoluteURL(baseUrl, matcher.group(linkI)));
chapters.add(chapter);
lastTile = title;
}
return chapters;
}
/**
* 不使用正则匹配章节列表
*
* @param source ##!int:跳过前几个章节
* @param html
* @param baseUrl
* @return
*/
public ArrayList<Chapter> matchChapters(BookSource source, String html, String baseUrl) {
int skip = 0;
String rule = source.getTocRule().getChapterList();
if (rule.contains("##!")) {
try {
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3));
} catch (Exception ignored) {
}
rule = rule.split("##")[0];
}
ArrayList<Chapter> chapters = new ArrayList<>();
List<String> chapterStrList = getStringList(rule, html);
String lastTile = null;
int j = 0;
for (int i = 0; i < chapterStrList.size(); i++) {
if (i++ < skip) continue;
String chapterStr = chapterStrList.get(i);
String title = getString(source.getTocRule().getChapterName(), chapterStr);
String url = getString(source.getTocRule().getChapterUrl(), chapterStr);
if (!StringHelper.isEmpty(lastTile) && title.equals(lastTile)) continue;
Chapter chapter = new Chapter();
chapter.setNumber(j++);
chapter.setTitle(title);
chapter.setUrl(NetworkUtils.getAbsoluteURL(baseUrl, url));
chapters.add(chapter);
lastTile = title;
}
return chapters;
}
private RegexMod getRegexMod(String[] rules) {
RegexMod regexMod;
if (rules[0].contains("(*)") && rules[1].contains("(*)")) {
regexMod = RegexMod.Both;
} else if (rules[0].contains("(*)")) {
regexMod = RegexMod.Left;
} else if (rules[1].contains("(*)")) {
regexMod = RegexMod.Right;
} else {
regexMod = RegexMod.None;
}
return regexMod;
}
/**
* <dl>
* <dt><a href="https://www.bxwxorg.com/read/126774/"><img src="https://img.bxwxorg.com/126774/1104941.jpg" alt="都市大主宰" height="150" width="120"></a></dt>
* <dd><h3><a href="https://www.bxwxorg.com/read/126774/">都市大主宰</a></h3></dd>
* <dd class="book_other">作者<span>赌霸</span>状态<span>连载中</span>分类<span>都市生活</span>字数<span>327万字</span></dd>
* <dd class="book_des">都市大主宰乡村的孩子也可以在都市修练仙道并且自在逍遥一个奇遇改变了他的屌丝人生从此他慢慢地变得很强直至最强狂暴直爽的性格注定他一生的不平凡都市红尘世俗世事都市修真这一切是多么的美好因修炼金刚淬体术他的人生变得很精彩终于有一...</dd>
* <dd class="book_other">最新章节<a href="https://www.bxwxorg.com/read/126774/4046182.html">第1468章  生龙活虎</a> 更新时间<span>2021-02-07 11:24</span></dd>
* </dl>
*
* @return
*/
public static BookSource getTestSource() {
BookSource source = new BookSource();
source.setSourceName("笔下文学");
source.setSourceCharset("utf-8");
source.setSourceUrl("https://www.bxwxorg.com/");
source.setEnable(true);
SearchRule searchRule = new SearchRule();
searchRule.setSearchUrl("/search.html,searchtype=all&searchkey={key}");
searchRule.setList("<div id=\"sitembox\"><text><div class=\"update_title\">");
searchRule.setName("<h3><a href=\"(*)\"><text></a>");
searchRule.setAuthor("作者:<span><text></span>");
searchRule.setType("分类:<span><text></span>");
searchRule.setDesc("<dd class=\"book_des\"><text></dd>");
searchRule.setLastChapter("最新章节:<a href=\"(*)\"><text></a>");
searchRule.setImgUrl("<img src=\"<text>\"");
searchRule.setTocUrl("<dt><a href=\"<text>\">");
searchRule.setRelatedWithInfo(false);
source.setSearchRule(searchRule);
TocRule tocRule = new TocRule();
tocRule.setChapterList("<div id=\"list\"><text></div>");
tocRule.setChapterName("<dd><a href=\"<link>\"><title></a></dd>##!12");
source.setTocRule(tocRule);
ContentRule contentRule = new ContentRule();
contentRule.setContent("<div id=\"content\"><html></div>##@r(<p>因某些原因.*回家的路!,);@r(<p>喜欢.*最快。,)");
source.setContentRule(contentRule);
return source;
}
/**
* <tr id="nr">
* <td class="odd"><a href="/du/172/172477/">万界大主宰</a></td>
* <td class="even"><a href="/du/172/172477/47331417.html" target="_blank"> 第一百六十章 死亡禁地</a></td>
* <td class="odd">九掌柜</td>
* <td class="even">1386K</td>
* <td class="odd" align="center">20-09-20</td>
* <td class="even" align="center">连载</td>
* </tr>
*
* @return
*/
public static BookSource getTestSource2() {
BookSource source = new BookSource();
source.setSourceName("顶点小说");
source.setSourceCharset("gbk");
source.setSourceUrl("https://www.23wx.cc");
source.setEnable(true);
SearchRule searchRule = new SearchRule();
searchRule.setSearchUrl("/modules/article/search.php?searchkey={key}");
searchRule.setCharset("gbk");
searchRule.setList("<table class=\"grid\" width=\"100%\" align=\"center\"><text></table>");
searchRule.setName("<td class=\"odd\"><a href=\"(*)\"><text></a></td>");
searchRule.setAuthor("<td class=\"odd\"><text></td>##@nc(<a)");
searchRule.setLastChapter("<a href=\"(*)\" target=\"_blank\"><text></a>");
searchRule.setTocUrl("<td class=\"odd\"><a href=\"<text>\">");
searchRule.setRelatedWithInfo(true);
searchRule.setWordCount("<td class=\"even\"><text></td>##@nc(<a)");
source.setSearchRule(searchRule);
InfoRule infoRule = new InfoRule();
infoRule.setName("<meta property=\"og:title\" content=\"<text>\" />");
infoRule.setAuthor("<meta name=\"og:novel:author\" content=\"<text>\" />");
infoRule.setLastChapter("<meta name=\"og:novel:latest_chapter_name\" content=\"<text>\" />");
infoRule.setType("<meta name=\"og:novel:category\" content=\"<text>\" />");
infoRule.setDesc("<div id=\"intro\"><html></div>");
// source.setRuleImgUrl("<div id=\"fmimg\"><img src=\"<text>\"");
infoRule.setImgUrl("<meta property=\"og:image\" content=\"<text>\" />");
infoRule.setTocUrl("<meta name=\"og:novel:read_url\" content=\"<text>\" />");
source.setInfoRule(infoRule);
TocRule tocRule = new TocRule();
tocRule.setChapterBaseUrl("<meta name=\"og:novel:read_url\" content=\"<text>\" />");
tocRule.setChapterList("<div id=\"list\"><text></div>");
tocRule.setChapterName("<dd><a href=\"<link>\"><title></a></dd>");
source.setTocRule(tocRule);
ContentRule contentRule = new ContentRule();
contentRule.setContent("<div id=\"content\"><html></div>");
source.setContentRule(contentRule);
return source;
}
}

@ -0,0 +1,117 @@
package xyz.fycz.myreader.model.source;
import org.seimicrawler.xpath.JXDocument;
import org.seimicrawler.xpath.JXNode;
import java.util.ArrayList;
import java.util.List;
import xyz.fycz.myreader.util.StringHelper;
/**
* @author fengyue
* @date 2021/2/14 19:03
*/
public class XpathAnalyzer extends BaseAnalyzer {
@Override
public List<String> getStringList(String rule, Object obj, boolean first) {
if (obj instanceof JXDocument) {
JXDocument jxDoc = (JXDocument) obj;
return getStringList(rule, jxDoc, first);
} else if (obj instanceof JXNode) {
JXNode jxNode = (JXNode) obj;
return getStringList(rule, jxNode, first);
} else {
return new ArrayList<>();
}
}
public List<String> getStringList(String rule, JXDocument jxDoc, boolean isFirst) {
List<String> list = new ArrayList<>();
if (StringHelper.isEmpty(rule)) return list;
boolean hasFunction = rule.contains("##");
String funs = "";
if (hasFunction) {
funs = rule.substring(rule.indexOf("##") + 2);
rule = rule.substring(0, rule.indexOf("##"));
}
for (JXNode jxNode : jxDoc.selN(rule)) {
String str = jxNode.toString();
if (hasFunction) str = evalFunction(funs, str);
if (StringHelper.isEmpty(str)) continue;
list.add(str);
if (isFirst) break;
}
return list;
}
public List<String> getStringList(String rule, JXNode jxNode, boolean isFirst) {
List<String> list = new ArrayList<>();
if (StringHelper.isEmpty(rule)) return list;
boolean hasFunction = rule.contains("##");
String funs = "";
if (hasFunction) {
funs = rule.substring(rule.indexOf("##") + 2);
rule = rule.substring(0, rule.indexOf("##"));
}
for (JXNode jxNode1 : jxNode.sel(rule)) {
String str = jxNode1.toString();
if (hasFunction) str = evalFunction(funs, str);
if (StringHelper.isEmpty(str)) continue;
list.add(str);
if (isFirst) break;
}
return list;
}
/**
* @param rule ##!int:跳过前几个节点
* @param obj
* @return
*/
public List<JXNode> getJXNodeList(String rule, Object obj) {
if (obj instanceof JXDocument) {
JXDocument jxDoc = (JXDocument) obj;
return getJXNodeList(rule, jxDoc);
} else if (obj instanceof JXNode) {
JXNode jxNode = (JXNode) obj;
return getJXNodeList(rule, jxNode);
} else {
return new ArrayList<>();
}
}
public List<JXNode> getJXNodeList(String rule, JXDocument JXDoc) {
List<JXNode> list = new ArrayList<>();
int skip = 0;
if (rule.contains("##!")) {
try {
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3));
} catch (Exception ignored) {
}
rule = rule.split("##")[0];
}
if (StringHelper.isEmpty(rule)) return list;
List<JXNode> selN = JXDoc.selN(rule);
list = selN.subList(skip, selN.size());
return list;
}
public List<JXNode> getJXNodeList(String rule, JXNode jxNode) {
List<JXNode> list = new ArrayList<>();
if (StringHelper.isEmpty(rule)) return list;
int skip = 0;
if (rule.contains("##!")) {
try {
skip = Integer.parseInt(rule.substring(rule.indexOf("##!") + 3));
} catch (Exception ignored) {
}
rule = rule.split("##")[0];
}
List<JXNode> selN = jxNode.sel(rule);
list = selN.subList(skip, selN.size());
return list;
}
}

@ -8,12 +8,11 @@ import io.reactivex.Single
import io.reactivex.SingleOnSubscribe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import xyz.fycz.myreader.application.MyApplication
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.application.SysManager
import xyz.fycz.myreader.base.observer.MySingleObserver
import xyz.fycz.myreader.common.APPCONST
import xyz.fycz.myreader.greendao.GreenDaoManager
import xyz.fycz.myreader.greendao.service.BookMarkService
import xyz.fycz.myreader.greendao.service.BookService
import xyz.fycz.myreader.greendao.service.SearchHistoryService
import xyz.fycz.myreader.util.SharedPreUtils
@ -26,7 +25,7 @@ import java.util.concurrent.TimeUnit
object Backup {
val backupPath = MyApplication.getApplication().filesDir.absolutePath + File.separator + "backup"
val backupPath = App.getApplication().filesDir.absolutePath + File.separator + "backup"
val defaultPath by lazy {
APPCONST.BACKUP_FILE_DIR
@ -52,9 +51,9 @@ object Backup {
}
val path = SharedPreUtils.getInstance().getString("backupPath", defaultPath)
if (path == null) {
backup(MyApplication.getmContext(), defaultPath, null, true)
backup(App.getmContext(), defaultPath, null, true)
} else {
backup(MyApplication.getmContext(), path, null, true)
backup(App.getmContext(), path, null, true)
}
}

@ -7,7 +7,7 @@ import io.reactivex.Single
import io.reactivex.SingleOnSubscribe
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import xyz.fycz.myreader.application.MyApplication
import xyz.fycz.myreader.application.App
import xyz.fycz.myreader.application.SysManager
import xyz.fycz.myreader.base.observer.MySingleObserver
import xyz.fycz.myreader.entity.ReadStyle
@ -114,7 +114,7 @@ object Restore {
} catch (e: Exception) {
e.printStackTrace()
}
Preferences.getSharedPreferences(MyApplication.getmContext(), path, "config")?.all?.map {
Preferences.getSharedPreferences(App.getmContext(), path, "config")?.all?.map {
val edit = SharedPreUtils.getInstance()
when (val value = it.value) {
is Int -> edit.putInt(it.key, value)
@ -124,14 +124,14 @@ object Restore {
is String -> edit.putString(it.key, value)
else -> Unit
}
edit.putInt("versionCode", MyApplication.getVersionCode())
edit.putInt("versionCode", App.getVersionCode())
}
e.onSuccess(true)
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : MySingleObserver<Boolean>() {
override fun onSuccess(t: Boolean) {
MyApplication.getApplication().initNightTheme()
App.getApplication().initNightTheme()
callBack?.restoreSuccess()
}

@ -9,7 +9,7 @@ import android.net.Uri;
import androidx.appcompat.widget.Toolbar;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.databinding.ActivityAboutBinding;
@ -42,7 +42,7 @@ public class AboutActivity extends BaseActivity {
@Override
protected void initWidget() {
super.initWidget();
binding.il.tvVersionName.setText("风月读书v" + MyApplication.getStrVersionName());
binding.il.tvVersionName.setText("风月读书v" + App.getStrVersionName());
}
@Override
@ -59,10 +59,10 @@ public class AboutActivity extends BaseActivity {
});
binding.il.vwShare.setOnClickListener(v -> ShareUtils.share(this, getString(R.string.share_text) +
SharedPreUtils.getInstance().getString(getString(R.string.downloadLink), URLCONST.LAN_ZOUS_URL)));
binding.il.vwUpdate.setOnClickListener(v -> MyApplication.checkVersionByServer(this, true, null));
binding.il.vwUpdate.setOnClickListener(v -> App.checkVersionByServer(this, true, null));
binding.il.vwUpdateLog.setOnClickListener(v -> DialogCreator.createAssetTipDialog(this, "更新日志", "updatelog.fy"));
binding.il.vwQq.setOnClickListener(v -> {
if (!MyApplication.joinQQGroup(this,"8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn")){
if (!App.joinQQGroup(this,"8PIOnHFuH6A38hgxvD_Rp2Bu-Ke1ToBn")){
//数据
ClipData mClipData = ClipData.newPlainText("Label", "1085028304");
//把数据设置到剪切板上

@ -19,7 +19,6 @@ import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.bumptech.glide.Glide;
@ -38,9 +37,8 @@ import cn.bingoogolapple.qrcode.zxing.QRCodeEncoder;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.annotations.NonNull;
import xyz.fycz.myreader.BuildConfig;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.base.observer.MySingleObserver;
@ -48,10 +46,11 @@ import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.databinding.ActivityBookDetailBinding;
import xyz.fycz.myreader.entity.SharedBook;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.greendao.service.ChapterService;
import xyz.fycz.myreader.ui.adapter.DetailCatalogAdapter;
import xyz.fycz.myreader.ui.dialog.BookGroupDialog;
@ -148,6 +147,7 @@ public class BookDetailedActivity extends BaseActivity {
mChapters = (ArrayList<Chapter>) mChapterService.findBookAllChapterByBookId(mBook.getId());
}
mBookGroupDia = new BookGroupDialog(this);
mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
}
@Override
@ -251,6 +251,7 @@ public class BookDetailedActivity extends BaseActivity {
mBookService.updateBook(mBook, bookTem);
}
mBook = bookTem;
mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
mHandler.sendMessage(mHandler.obtainMessage(1));
if (isCollected) {
String tip = null;
@ -311,24 +312,22 @@ public class BookDetailedActivity extends BaseActivity {
if (StringHelper.isEmpty(mBook.getImgUrl())) {
mBook.setImgUrl("");
}
assert mBook.getNewestChapterTitle() != null;
binding.ic.bookDetailTvDesc.setText("");
if (mBook.getType() != null) {
binding.ih.bookDetailTvType.setText(mBook.getType());
} else {
binding.ih.bookDetailTvType.setText("");
}
if (!"null".equals(mBook.getSource())) {
binding.ih.bookDetailSource.setText("书源:" + BookSource.fromString(mBook.getSource()).text);
}
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
BookSource source = BookSourceManager.getBookSourceByStr(mBook.getSource());
binding.ih.bookDetailSource.setText(String.format("书源:%s", source.getSourceName()));
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source);
if (rc instanceof BookInfoCrawler && StringHelper.isEmpty(mBook.getImgUrl())) {
binding.pbLoading.setVisibility(View.VISIBLE);
BookInfoCrawler bic = (BookInfoCrawler) rc;
CommonApi.getBookInfo(mBook, bic, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
if (!MyApplication.isDestroy(BookDetailedActivity.this)) {
if (!App.isDestroy(BookDetailedActivity.this)) {
mHandler.sendMessage(mHandler.obtainMessage(4));
}
}
@ -349,7 +348,7 @@ public class BookDetailedActivity extends BaseActivity {
private void initOtherInfo() {
binding.ic.bookDetailTvDesc.setText("\t\t\t\t" + mBook.getDesc());
binding.ih.bookDetailTvType.setText(mBook.getType());
if (!MyApplication.isDestroy(this)) {
if (!App.isDestroy(this)) {
binding.ih.bookDetailIvCover.load(mBook.getImgUrl(), mBook.getName(), mBook.getAuthor());
/*Glide.with(this)
.load(mBook.getImgUrl())
@ -379,9 +378,9 @@ public class BookDetailedActivity extends BaseActivity {
CharSequence[] sources = new CharSequence[aBooks.size()];
int checkedItem = 0;
for (int i = 0; i < sources.length; i++) {
sources[i] = BookSource.fromString(aBooks.get(i).getSource()).text
sources[i] = LocalBookSource.fromString(aBooks.get(i).getSource()).text
+ "\n" + aBooks.get(i).getNewestChapterTitle();
if (sources[i].equals(BookSource.fromString(mBook.getSource()).text
if (sources[i].equals(LocalBookSource.fromString(mBook.getSource()).text
+ "\n" + aBooks.get(i).getNewestChapterTitle())) {
checkedItem = i;
}
@ -427,7 +426,6 @@ public class BookDetailedActivity extends BaseActivity {
*/
private void initChapters(boolean isChangeSource) {
if (mChapters.size() == 0 && !"本地书籍".equals(mBook.getType())) {
mReadCrawler = ReadCrawlerUtil.getReadCrawler(mBook.getSource());
if (isCollected) {
mChapters = (ArrayList<Chapter>) mChapterService.findBookAllChapterByBookId(mBook.getId());
}
@ -454,7 +452,7 @@ public class BookDetailedActivity extends BaseActivity {
for (int i = mChapters.size() - 1; i >= end; i--) {
mNewestChapters.add(mChapters.get(i));
}
MyApplication.runOnUiThread(() -> mCatalogAdapter.refreshItems(mNewestChapters));
App.runOnUiThread(() -> mCatalogAdapter.refreshItems(mNewestChapters));
}
@Override
@ -567,7 +565,7 @@ public class BookDetailedActivity extends BaseActivity {
mBookService.updateEntity(mBook);
break;
case R.id.action_open_link: //打开链接
Uri uri = Uri.parse(mBook.getChapterUrl());
Uri uri = Uri.parse(NetworkUtils.getAbsoluteURL(mReadCrawler.getNameSpace(), mBook.getChapterUrl()));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
break;
@ -643,7 +641,7 @@ public class BookDetailedActivity extends BaseActivity {
private void shareBook() {
if ("本地书籍".equals(mBook.getType())) {
File file = new File(mBook.getChapterUrl());
if (!file.exists()){
if (!file.exists()) {
ToastUtils.showWarring("书籍源文件不存在,无法分享!");
return;
}
@ -734,7 +732,7 @@ public class BookDetailedActivity extends BaseActivity {
textPaint.setColor(Color.BLACK);
cv.drawText(mBook.getType() == null ? "" : mBook.getType(), margin + marginTop + img.getWidth(), margin + marginTop * 8, textPaint);
assert mBook.getSource() != null;
cv.drawText("书源:" + BookSource.fromString(mBook.getSource()).text, margin + marginTop + img.getWidth(), margin + marginTop * 10, textPaint);
cv.drawText("书源:" + BookSourceManager.getSourceNameByStr(mBook.getSource()), margin + marginTop + img.getWidth(), margin + marginTop * 10, textPaint);
int textSize = 35;
int textInterval = textSize / 2;

@ -0,0 +1,145 @@
package xyz.fycz.myreader.ui.activity;
import android.app.Activity;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import com.google.android.material.tabs.TabLayout;
import java.util.Arrays;
import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BaseTabActivity;
import xyz.fycz.myreader.databinding.ActivityBooksourceBinding;
import xyz.fycz.myreader.ui.fragment.DIYSourceFragment;
import xyz.fycz.myreader.ui.fragment.LocalSourceFragment;
/**
* @author fengyue
* @date 2021/2/10 9:14
*/
public class BookSourceActivity extends BaseTabActivity {
private ActivityBooksourceBinding binding;
private SearchView searchView;
@Override
protected void bindView() {
binding = ActivityBooksourceBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
super.bindView();
}
@Override
protected List<Fragment> createTabFragments() {
return Arrays.asList(new LocalSourceFragment(this),
new DIYSourceFragment(this));
}
@Override
protected List<String> createTabTitles() {
return Arrays.asList("内置书源", "DIY书源");
}
@Override
protected void setUpToolbar(Toolbar toolbar) {
super.setUpToolbar(toolbar);
setStatusBarColor(R.color.colorPrimary, true);
}
@Override
protected void initWidget() {
super.initWidget();
binding.tabTlIndicator.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
if (!TextUtils.isEmpty(searchView.getQuery())) {
App.getHandler().postDelayed(() ->{
List<Fragment> fragments = getSupportFragmentManager().getFragments();
switch (tab.getPosition()) {
case 0:
((LocalSourceFragment) fragments.get(0)).startSearch("");
break;
case 1:
((DIYSourceFragment) fragments.get(1)).startSearch("");
break;
}
}, 300);
}
searchView.onActionViewCollapsed();
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_view_search, menu);
MenuItem search = menu.findItem(R.id.action_search);
searchView = (SearchView) search.getActionView();
TextView textView = searchView.findViewById(R.id.search_src_text);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
searchView.setQueryHint("搜索书源");
searchView.setMaxWidth(getResources().getDisplayMetrics().widthPixels);
searchView.onActionViewCollapsed();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
List<Fragment> fragments = getSupportFragmentManager().getFragments();
switch (binding.tabVp.getCurrentItem()) {
case 0:
((LocalSourceFragment) fragments.get(0)).startSearch(newText);
break;
case 1:
((DIYSourceFragment) fragments.get(1)).startSearch(newText);
break;
}
return false;
}
});
return super.onCreateOptionsMenu(menu);
}
@Override
public void finish() {
setResult(Activity.RESULT_OK);
super.finish();
}
@Override
public void onBackPressed() {
if (!"".contentEquals(searchView.getQuery())) {
searchView.onActionViewCollapsed();
} else {
super.onBackPressed();
}
}
public SearchView getSearchView() {
return searchView;
}
}

@ -17,7 +17,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActiityBookstoreBinding;
@ -212,7 +212,7 @@ public class BookstoreActivity extends BaseActivity {
((QiDianMobileRank) findCrawler).initCookie(this, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
spu.putString(getString(R.string.qdCookie), (String) o);
mBookTypes = findCrawler.getBookTypes();
initBooks();
@ -220,7 +220,7 @@ public class BookstoreActivity extends BaseActivity {
@Override
public void onError(Exception e) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
binding.refreshLayout.showError();
e.printStackTrace();
}
@ -235,14 +235,14 @@ public class BookstoreActivity extends BaseActivity {
BookStoreApi.getBookTypeList(findCrawler, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
mBookTypes = (ArrayList<BookType>) o;
initBooks();
}
@Override
public void onError(Exception e) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
e.printStackTrace();
mHandler.sendEmptyMessage(5);
}
@ -271,7 +271,7 @@ public class BookstoreActivity extends BaseActivity {
((QiDianMobileRank) findCrawler).getRankBooks(curType, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
List<Book> books = new ArrayList<>();
for (QDBook rb : (List<QDBook>) o) {
Book book = new Book();
@ -297,7 +297,7 @@ public class BookstoreActivity extends BaseActivity {
@Override
public void onError(Exception e) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
mHandler.sendMessage(mHandler.obtainMessage(4));
ToastUtils.showError("数据加载失败!\n" + e.getMessage());
e.printStackTrace();
@ -307,13 +307,13 @@ public class BookstoreActivity extends BaseActivity {
BookStoreApi.getBookRankList(curType, findCrawler, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
mHandler.sendMessage(mHandler.obtainMessage(2, o));
}
@Override
public void onError(Exception e) {
if (MyApplication.isDestroy(BookstoreActivity.this)) return;
if (App.isDestroy(BookstoreActivity.this)) return;
mHandler.sendMessage(mHandler.obtainMessage(4));
ToastUtils.showError("数据加载失败!\n" + e.getMessage());
e.printStackTrace();

@ -11,7 +11,7 @@ import android.webkit.WebViewClient;
import androidx.appcompat.widget.Toolbar;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.databinding.ActivityFeedbackBinding;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
@ -66,14 +66,14 @@ public class FeedbackActivity extends BaseActivity {
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
super.onReceivedError(view, request, error);
if (!MyApplication.isDestroy(FeedbackActivity.this))
if (!App.isDestroy(FeedbackActivity.this))
binding.refreshLayout.showError();
}
});
binding.wvFeedback.setWebChromeClient(new WebChromeClient(){
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100 && !MyApplication.isDestroy(FeedbackActivity.this)){
if (newProgress == 100 && !App.isDestroy(FeedbackActivity.this)){
binding.refreshLayout.showFinish();
}
}});

@ -13,7 +13,7 @@ import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseTabActivity;
import xyz.fycz.myreader.databinding.ActivityFileSystemBinding;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.enums.LocalBookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
@ -65,12 +65,12 @@ public class FileSystemActivity extends BaseTabActivity {
protected List<Fragment> createTabFragments() {
mLocalFragment = new LocalBookFragment();
mCategoryFragment = new FileCategoryFragment();
return Arrays.asList(mLocalFragment,mCategoryFragment);
return Arrays.asList(mLocalFragment, mCategoryFragment);
}
@Override
protected List<String> createTabTitles() {
return Arrays.asList("智能导入","手机目录");
return Arrays.asList("智能导入", "手机目录");
}
@Override
@ -101,10 +101,9 @@ public class FileSystemActivity extends BaseTabActivity {
@Override
public void onPageSelected(int position) {
if (position == 0){
if (position == 0) {
mCurFragment = mLocalFragment;
}
else {
} else {
mCurFragment = mCategoryFragment;
}
//改变菜单状态
@ -169,7 +168,7 @@ public class FileSystemActivity extends BaseTabActivity {
@Override
public void onBackPressed() {
if (mCurFragment == mCategoryFragment){
if (mCurFragment == mCategoryFragment) {
if (mCategoryFragment.backLast()) return;
}
super.onBackPressed();
@ -177,12 +176,13 @@ public class FileSystemActivity extends BaseTabActivity {
/**
* 将文件转换成CollBook
*
* @param files:需要加载的文件列表
* @return
*/
private List<Book> convertBook(List<File> files){
private List<Book> convertBook(List<File> files) {
List<Book> books = new ArrayList<>(files.size());
for(File file : files){
for (File file : files) {
//判断文件是否存在
if (!file.exists()) continue;
@ -193,7 +193,7 @@ public class FileSystemActivity extends BaseTabActivity {
book.setHistoryChapterId("未开始阅读");
book.setNewestChapterTitle("未拆分章节");
book.setAuthor("本地书籍");
book.setSource(BookSource.local.toString());
book.setSource(LocalBookSource.local.toString());
book.setDesc("无");
book.setIsCloseUpdate(true);
books.add(book);
@ -204,50 +204,48 @@ public class FileSystemActivity extends BaseTabActivity {
/**
* 改变底部选择栏的状态
*/
private void changeMenuStatus(){
private void changeMenuStatus() {
//点击、删除状态的设置
if (mCurFragment.getCheckedCount() == 0){
if (mCurFragment.getCheckedCount() == 0) {
binding.fileSystemBtnAddBook.setText(getString(R.string.file_add_shelf));
//设置某些按钮的是否可点击
setMenuClickable(false);
if (binding.fileSystemCbSelectedAll.isChecked()){
if (binding.fileSystemCbSelectedAll.isChecked()) {
mCurFragment.setChecked(false);
binding.fileSystemCbSelectedAll.setChecked(mCurFragment.isCheckedAll());
}
}
else {
} else {
binding.fileSystemBtnAddBook.setText(getString(R.string.file_add_shelves, mCurFragment.getCheckedCount()));
setMenuClickable(true);
//全选状态的设置
//如果选中的全部的数据,则判断为全选
if (mCurFragment.getCheckedCount() == mCurFragment.getCheckableCount()){
if (mCurFragment.getCheckedCount() == mCurFragment.getCheckableCount()) {
//设置为全选
mCurFragment.setChecked(true);
binding.fileSystemCbSelectedAll.setChecked(mCurFragment.isCheckedAll());
}
//如果曾今是全选则替换
else if (mCurFragment.isCheckedAll()){
else if (mCurFragment.isCheckedAll()) {
mCurFragment.setChecked(false);
binding.fileSystemCbSelectedAll.setChecked(mCurFragment.isCheckedAll());
}
}
//重置全选的文字
if (mCurFragment.isCheckedAll()){
if (mCurFragment.isCheckedAll()) {
binding.fileSystemCbSelectedAll.setText("取消");
}
else {
} else {
binding.fileSystemCbSelectedAll.setText("全选");
}
}
private void setMenuClickable(boolean isClickable){
private void setMenuClickable(boolean isClickable) {
//设置是否可删除
binding.fileSystemBtnDelete.setEnabled(isClickable);
@ -261,16 +259,15 @@ public class FileSystemActivity extends BaseTabActivity {
/**
* 改变全选按钮的状态
*/
private void changeCheckedAllStatus(){
private void changeCheckedAllStatus() {
//获取可选择的文件数量
int count = mCurFragment.getCheckableCount();
//设置是否能够全选
if (count > 0){
if (count > 0) {
binding.fileSystemCbSelectedAll.setClickable(true);
binding.fileSystemCbSelectedAll.setEnabled(true);
}
else {
} else {
binding.fileSystemCbSelectedAll.setClickable(false);
binding.fileSystemCbSelectedAll.setEnabled(false);
}

@ -29,6 +29,7 @@ import xyz.fycz.myreader.util.CodeUtil;
import xyz.fycz.myreader.util.CyptoUtils;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.util.utils.ProgressUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
@ -104,7 +105,7 @@ public class LoginActivity extends BaseActivity implements TextWatcher {
ToastUtils.showError("无网络连接!");
return;
}
ProgressDialog dialog = DialogCreator.createProgressDialog(this, null, "正在登陆...");
ProgressUtils.show(this, "正在登陆...");
binding.btLogin.setEnabled(false);
final String loginName = binding.etUser.getEditText().getText().toString().trim();
String loginPwd = binding.etPassword.getEditText().getText().toString();
@ -130,7 +131,7 @@ public class LoginActivity extends BaseActivity implements TextWatcher {
ToastUtils.showSuccess(resultName);
} else {
mHandler.sendMessage(mHandler.obtainMessage(1));
dialog.dismiss();
ProgressUtils.dismiss();
ToastUtils.showWarring(resultName);
}
@ -140,7 +141,7 @@ public class LoginActivity extends BaseActivity implements TextWatcher {
public void onError(Exception e) {
ToastUtils.showError("登录失败\n" + e.getLocalizedMessage());
mHandler.sendMessage(mHandler.obtainMessage(1));
dialog.dismiss();
ProgressUtils.dismiss();
}
});
@ -217,4 +218,10 @@ public class LoginActivity extends BaseActivity implements TextWatcher {
binding.btLogin.setEnabled(false);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
ProgressUtils.dismiss();
}
}

@ -28,7 +28,7 @@ import java.util.ArrayList;
import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.APPCONST;
@ -290,10 +290,12 @@ public class MainActivity extends BaseActivity {
} else if (itemId == R.id.action_refresh) {
mBookcaseFragment.getmBookcasePresenter().initNoReadNum();
} else if (itemId == R.id.action_edit) {
invalidateOptionsMenu();
initMenuAnim();
binding.bottomNavigationView.setVisibility(View.GONE);
binding.bottomNavigationView.startAnimation(mBottomOutAnim);
if (mBookcaseFragment.getmBookcasePresenter().canEditBookcase()) {
invalidateOptionsMenu();
initMenuAnim();
binding.bottomNavigationView.setVisibility(View.GONE);
binding.bottomNavigationView.startAnimation(mBottomOutAnim);
}
} else if (itemId == R.id.action_qr_scan) {
Intent intent = new Intent(this, QRCodeScanActivity.class);
startActivityForResult(intent, APPCONST.REQUEST_QR_SCAN);
@ -328,7 +330,7 @@ public class MainActivity extends BaseActivity {
if (binding.viewPagerMain.getCurrentItem() == 0) {
getSupportActionBar().setSubtitle(groupName);
}
// MyApplication.checkVersionByServer(this);
// App.checkVersionByServer(this);
}
@ -399,7 +401,7 @@ public class MainActivity extends BaseActivity {
@Override
protected void onDestroy() {
MyApplication.getApplication().shutdownThreadPool();
App.getApplication().shutdownThreadPool();
super.onDestroy();
}

@ -19,20 +19,22 @@ import androidx.fragment.app.FragmentTransaction;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.base.BaseFragment;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActivityMoreSettingBinding;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
@ -41,7 +43,6 @@ import xyz.fycz.myreader.ui.fragment.WebDavFragment;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import static xyz.fycz.myreader.common.APPCONST.BOOK_CACHE_PATH;
@ -127,7 +128,7 @@ public class MoreSettingActivity extends BaseActivity {
} else if (curFragment == mWebDavFragment) {
getSupportActionBar().setTitle(getString(R.string.webdav_setting));
invalidateOptionsMenu();
}else if (curFragment == mPrivateBooksFragment){
} else if (curFragment == mPrivateBooksFragment) {
getSupportActionBar().setTitle(getString(R.string.private_bookcase));
invalidateOptionsMenu();
}
@ -170,8 +171,8 @@ public class MoreSettingActivity extends BaseActivity {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_tip) {
if (curFragment == mWebDavFragment) {
DialogCreator.createAssetTipDialog(this, "如何使用WebDav进行云备份?", "webdavhelp.fy");
}else if(curFragment == mPrivateBooksFragment){
MyAlertDialog.showTipDialogWithLink(this, "如何使用WebDav进行云备份?", R.string.webdav_tip);
} else if (curFragment == mPrivateBooksFragment) {
DialogCreator.createTipDialog(this, "关于私密书架", getString(R.string.private_bookcase_tip));
}
}
@ -256,7 +257,7 @@ public class MoreSettingActivity extends BaseActivity {
SysManager.saveSetting(mSetting);
}
);
binding.rlContentReplace.setOnClickListener(v -> startActivity(new Intent(this, RuleActivity.class)));
binding.rlContentReplace.setOnClickListener(v -> startActivity(new Intent(this, ReplaceRuleActivity.class)));
binding.rlReadAloudVolumeTurnPage.setOnClickListener(
(v) -> {
if (readAloudVolumeTurnPage) {
@ -325,7 +326,7 @@ public class MoreSettingActivity extends BaseActivity {
);
binding.llCloseRefresh.setOnClickListener(v -> {
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
if (mCloseRefreshDia != null) {
mCloseRefreshDia.show();
return;
@ -369,52 +370,6 @@ public class MoreSettingActivity extends BaseActivity {
});
});
binding.llDisableSource.setOnClickListener(v -> {
if (mDisableSourceDia != null) {
mDisableSourceDia.show();
return;
}
HashMap<CharSequence, Boolean> mSources = ReadCrawlerUtil.getDisableSources();
CharSequence[] mSourcesName = new CharSequence[mSources.keySet().size()];
boolean[] isDisables = new boolean[mSources.keySet().size()];
int dSourceCount = 0;
int i = 0;
for (CharSequence sourceName : mSources.keySet()) {
mSourcesName[i] = sourceName;
Boolean isDisable = mSources.get(sourceName);
if (isDisable == null) isDisable = false;
if (isDisable) dSourceCount++;
isDisables[i++] = isDisable;
}
mDisableSourceDia = new MultiChoiceDialog().create(this, "选择禁用的书源",
mSourcesName, isDisables, dSourceCount, (dialog, which) -> {
SharedPreUtils spu = SharedPreUtils.getInstance();
StringBuilder sb = new StringBuilder();
for (CharSequence sourceName : mSources.keySet()) {
if (!mSources.get(sourceName)) {
sb.append(BookSource.getFromName(String.valueOf(sourceName)));
sb.append(",");
}
}
if (sb.lastIndexOf(",") >= 0) sb.deleteCharAt(sb.lastIndexOf(","));
spu.putString(getString(R.string.searchSource), sb.toString());
}, null, new DialogCreator.OnMultiDialogListener() {
@Override
public void onItemClick(DialogInterface dialog, int which, boolean isChecked) {
mSources.put(mSourcesName[which], isChecked);
}
@Override
public void onSelectAll(boolean isSelectAll) {
for (CharSequence sourceName : mSources.keySet()) {
mSources.put(sourceName, isSelectAll);
}
}
});
});
binding.llThreadNum.setOnClickListener(v -> {
View view = LayoutInflater.from(this).inflate(R.layout.dialog_number_picker, null);
NumberPicker threadPick = view.findViewById(R.id.number_picker);
@ -452,7 +407,7 @@ public class MoreSettingActivity extends BaseActivity {
binding.llDownloadAll.setOnClickListener(v -> {
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
if (mDownloadAllDia != null) {
mDownloadAllDia.show();
return;
@ -505,7 +460,7 @@ public class MoreSettingActivity extends BaseActivity {
binding.rlCatheGap.setOnClickListener(v -> binding.scCatheGap.performClick());
binding.rlDeleteCathe.setOnClickListener(v -> {
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
File catheFile = getCacheDir();
String catheFileSize = FileUtils.getFileSize(FileUtils.getDirSize(catheFile));
@ -693,7 +648,7 @@ public class MoreSettingActivity extends BaseActivity {
}
}
private void showPrivateBooksFragment(){
private void showPrivateBooksFragment() {
binding.svContent.setVisibility(View.GONE);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (mPrivateBooksFragment == null) {

@ -46,14 +46,14 @@ import java.util.Locale;
import xyz.fycz.myreader.ActivityManage;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.databinding.ActivityReadBinding;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.enums.LocalBookSource;
import xyz.fycz.myreader.enums.Font;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.BookMark;
@ -427,7 +427,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
binding.readTvPageTip.setText((progress + 1) + "/" + (seekBar.getMax() + 1));
binding.readTvPageTip.setText(String.format("%s/%s", progress + 1, seekBar.getMax() + 1));
}
}
@ -530,12 +530,16 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
binding.llChapterView.setOnClickListener(v -> {
if (mChapters != null && mChapters.size() != 0) {
Chapter curChapter = mChapters.get(mPageLoader.getChapterPos());
String url = curChapter.getUrl();
String url = NetworkUtils.getAbsoluteURL(mReadCrawler.getNameSpace(), curChapter.getUrl());
if (!"本地书籍".equals(mBook.getType()) && !StringHelper.isEmpty(url)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(url);
intent.setData(uri);
startActivity(intent);
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse(url);
intent.setData(uri);
startActivity(intent);
} catch (Exception e) {
ToastUtils.showError(e.getLocalizedMessage());
}
}
}
});
@ -664,7 +668,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
mHandler.removeCallbacks(autoPageRunnable);
/*mHandler.removeCallbacks(sendDownloadNotification);
notificationUtil.cancelAll();
MyApplication.getApplication().shutdownThreadPool();*/
App.getApplication().shutdownThreadPool();*/
if (autoPage) {
autoPageStop();
}
@ -727,12 +731,12 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
"]\n书签添加成功,书签列表可在目录界面查看!");
return true;
} else if (itemId == R.id.action_replace_content) {
Intent ruleIntent = new Intent(this, RuleActivity.class);
Intent ruleIntent = new Intent(this, ReplaceRuleActivity.class);
startActivityForResult(ruleIntent, APPCONST.REQUEST_REFRESH_READ_UI);
} else if (itemId == R.id.action_copy_content) {
new CopyContentDialog(this, mPageLoader.getContent()).show();
} else if (itemId == R.id.action_open_link) {
Uri uri = Uri.parse(mBook.getChapterUrl());
Uri uri = Uri.parse(NetworkUtils.getAbsoluteURL(mReadCrawler.getNameSpace(), mBook.getChapterUrl()));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
} else if (itemId == R.id.action_download) {
@ -756,7 +760,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
Font font = (Font) data.getSerializableExtra(APPCONST.FONT);
mSetting.setFont(font);
// init();
MyApplication.runOnUiThread(() -> mPageLoader.setFont(font));
App.runOnUiThread(() -> mPageLoader.setFont(font));
break;
case APPCONST.REQUEST_CHAPTER_PAGE:
int[] chapterAndPage = data.getIntArrayExtra(APPCONST.CHAPTER_PAGE);
@ -837,7 +841,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
* 初始化
*/
private void init() {
if (MyApplication.isDestroy(this)) return;
if (App.isDestroy(this)) return;
screenOffTimerStart();
mPageLoader.init();
mPageLoader.refreshChapterList();
@ -851,10 +855,12 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
Chapter curChapter = mChapters.get(mPageLoader.getChapterPos());
String url = curChapter.getUrl();
binding.tvChapterTitleTop.setText(curChapter.getTitle());
binding.tvChapterUrl.setText(StringHelper.isEmpty(url) ? curChapter.getId() : url);
binding.tvChapterUrl.setText(StringHelper.isEmpty(url) ? curChapter.getId() :
NetworkUtils.getAbsoluteURL(mReadCrawler.getNameSpace(), url));
binding.readSbChapterProgress.setProgress(mPageLoader.getPagePos());
binding.readSbChapterProgress.setMax(mPageLoader.getAllPagePos() - 1);
binding.readTvPageTip.setText(String.format("%s/%s", binding.readSbChapterProgress.getProgress() + 1, binding.readSbChapterProgress.getMax() + 1));
binding.readTvPageTip.setText(String.format("%s/%s",
binding.readSbChapterProgress.getProgress() + 1, binding.readSbChapterProgress.getMax() + 1));
}
}
/************************书籍相关******************************/
@ -918,7 +924,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
book.setHistoryChapterId("未开始阅读");
book.setNewestChapterTitle("未拆分章节");
book.setAuthor(getString(R.string.local_book));
book.setSource(BookSource.local.toString());
book.setSource(LocalBookSource.local.toString());
book.setDesc("无");
book.setIsCloseUpdate(true);
//判断书籍是否已经添加
@ -1053,7 +1059,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
mBookService.matchHistoryChapterPos(mBook, mChapters);
}
mHandler.sendMessage(mHandler.obtainMessage(1));
initMenu();
mHandler.sendMessage(mHandler.obtainMessage(4));
}
}
@ -1397,7 +1403,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
private void changeNightAndDaySetting(boolean isNight) {
mSetting.setDayStyle(!isNight);
SysManager.saveSetting(mSetting);
MyApplication.getApplication().setNightTheme(isNight);
App.getApplication().setNightTheme(isNight);
//mPageLoader.setPageStyle(!isCurDayStyle);
}
@ -1501,7 +1507,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
ToastUtils.showWarring("无网络连接!");
return;
}
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
MyAlertDialog.build(this)
.setTitle("缓存书籍")
.setSingleChoiceItems(getResources().getStringArray(R.array.download), selectedIndex,
@ -1556,7 +1562,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
mHandler.sendEmptyMessage(9);
mHandler.postDelayed(sendDownloadNotification, 2 * downloadInterval);
}
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
for (Chapter chapter : needDownloadChapters) {
if (StringHelper.isEmpty(chapter.getBookId())) {
chapter.setId(mBook.getId());
@ -1600,7 +1606,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
Notification notification = notificationUtil.build(APPCONST.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
//通知栏大图标
.setLargeIcon(BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.mipmap.ic_launcher))
.setLargeIcon(BitmapFactory.decodeResource(App.getApplication().getResources(), R.mipmap.ic_launcher))
.setOngoing(true)
//点击通知后自动清除
.setAutoCancel(true)
@ -1662,7 +1668,7 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
* 下一页
*/
private void nextPage() {
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
screenOffTimerStart();
autoPage();
});

@ -22,6 +22,7 @@ import xyz.fycz.myreader.model.backup.UserService;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.util.CodeUtil;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.ProgressUtils;
import xyz.fycz.myreader.util.utils.StringUtils;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
@ -201,7 +202,7 @@ public class RegisterActivity extends BaseActivity {
}else if(!binding.cbAgreement.isChecked()){
DialogCreator.createTipDialog(this, "请勾选同意《用户服务协议》");
}else {
ProgressDialog dialog = DialogCreator.createProgressDialog(this, null, "正在注册...");
ProgressUtils.show(this, "正在注册...");
Map<String, String> userRegisterInfo = new HashMap<>();
userRegisterInfo.put("username", username);
userRegisterInfo.put("password", password);
@ -217,12 +218,12 @@ public class RegisterActivity extends BaseActivity {
}else {
ToastUtils.showWarring(info[1]);
}
dialog.dismiss();
ProgressUtils.dismiss();
}
@Override
public void onError(Exception e) {
ToastUtils.showError("注册失败:\n" + e.getLocalizedMessage());
dialog.dismiss();
ProgressUtils.dismiss();
}
});
}
@ -250,4 +251,9 @@ public class RegisterActivity extends BaseActivity {
}
}
@Override
protected void onDestroy() {
super.onDestroy();
ProgressUtils.dismiss();
}
}

@ -0,0 +1,312 @@
package xyz.fycz.myreader.ui.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.schedulers.Schedulers;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.base.observer.MyObserver;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActivityReplaceRuleBinding;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.model.ReplaceRuleManager;
import xyz.fycz.myreader.ui.adapter.ReplaceRuleAdapter;
import xyz.fycz.myreader.ui.adapter.helper.ItemTouchCallback;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.ui.dialog.ReplaceDialog;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.ClipBoardUtil;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import xyz.fycz.myreader.widget.DividerItemDecoration;
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout;
import static android.text.TextUtils.isEmpty;
import static xyz.fycz.myreader.util.UriFileUtil.getPath;
/**
* @author fengyue
* @date 2021/1/19 10:02
*/
public class ReplaceRuleActivity extends BaseActivity {
private ActivityReplaceRuleBinding binding;
private SearchView searchView;
private List<ReplaceRuleBean> mReplaceRules;
private ReplaceRuleAdapter mAdapter;
@Override
protected void bindView() {
binding = ActivityReplaceRuleBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
ReplaceRuleManager.getAll().subscribe(new MySingleObserver<List<ReplaceRuleBean>>() {
@Override
public void onSuccess(@NonNull List<ReplaceRuleBean> replaceRuleBeans) {
mReplaceRules = replaceRuleBeans;
initRuleList();
setUpBarTitle();
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage());
}
});
}
@Override
protected void setUpToolbar(Toolbar toolbar) {
super.setUpToolbar(toolbar);
setUpBarTitle();
setStatusBarColor(R.color.colorPrimary, true);
}
private void setUpBarTitle() {
getSupportActionBar().setTitle(String.format("%s(共%s个)",
getString(R.string.replace_rule), mReplaceRules == null ? 0 : mReplaceRules.size()));
}
protected void initRuleList() {
mAdapter = new ReplaceRuleAdapter(this, mReplaceRules, new ReplaceRuleAdapter.OnSwipeListener() {
@Override
public void onDel(int which, ReplaceRuleBean bean) {
mReplaceRules.remove(bean);
mAdapter.removeItem(which);
setUpBarTitle();
}
@Override
public void onTop(int which, ReplaceRuleBean bean) {
if (which > 0 && which < mReplaceRules.size()) {
mReplaceRules.remove(bean);
mReplaceRules.add(0, bean);
mAdapter.toTop(which, bean);
}
}
});
binding.rvRuleList.setLayoutManager(new LinearLayoutManager(this));
binding.rvRuleList.setAdapter(mAdapter);
//设置分割线
binding.rvRuleList.addItemDecoration(new DividerItemDecoration(this));
//添加数据
mAdapter.refreshItems(mReplaceRules);
}
@SuppressLint("ClickableViewAccessibility")
@Override
protected void initClick() {
super.initClick();
binding.rvRuleList.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP) {
SwipeMenuLayout viewCache = SwipeMenuLayout.getViewCache();
if (null != viewCache) {
viewCache.smoothClose();
}
}
return false;
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_rule, menu);
MenuItem search = menu.findItem(R.id.action_search);
searchView = (SearchView) search.getActionView();
TextView textView = (TextView) searchView.findViewById(R.id.search_src_text);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
searchView.setQueryHint("搜索替换规则");
searchView.setMaxWidth(getResources().getDisplayMetrics().widthPixels);
searchView.onActionViewCollapsed();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
mAdapter.getFilter().filter(newText);
return false;
}
});
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_add_rule) {
ReplaceRuleBean newRuleBean = new ReplaceRuleBean();
newRuleBean.setReplaceSummary("");
newRuleBean.setEnable(true);
newRuleBean.setRegex("");
newRuleBean.setIsRegex(false);
newRuleBean.setReplacement("");
newRuleBean.setSerialNumber(0);
newRuleBean.setUseTo("");
ReplaceDialog replaceDialog = new ReplaceDialog(this, newRuleBean
, () -> {
ToastUtils.showSuccess("内容替换规则添加成功!");
mReplaceRules.add(newRuleBean);
mAdapter.addItem(newRuleBean);
setUpBarTitle();
refreshUI();
});
replaceDialog.show(getSupportFragmentManager(), "replaceRule");
} else if (itemId == R.id.action_import) {
MyAlertDialog.build(this)
.setTitle("导入规则")
.setItems(R.array.import_rule, (dialog, which) -> {
if (which == 0) {
String text = ClipBoardUtil.paste(this);
if (!isEmpty(text)) {
importDataS(text);
} else {
ToastUtils.showError("剪切板内容为空,导入失败");
}
} else if (which == 1) {
ToastUtils.showInfo("请选择内容替换规则JSON文件");
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/json");
startActivityForResult(intent, APPCONST.REQUEST_IMPORT_REPLACE_RULE);
} else {
String[] url = new String[1];
MyAlertDialog.createInputDia(this, "网络导入",
"请输入网址", "", true, 200,
text -> url[0] = text,
(dialog1, which1) -> importDataS(url[0]));
}
}).show();
} else if (itemId == R.id.action_export) {
if (mReplaceRules == null || mReplaceRules.size() == 0) {
ToastUtils.showWarring("当前没有任何规则,无法导出!");
return true;
}
if (FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(mReplaceRules),
FileUtils.getFile(APPCONST.FILE_DIR + "ReplaceRule.json"))) {
DialogCreator.createTipDialog(this,
"内容替换规则导出成功,导出位置:" + APPCONST.FILE_DIR + "ReplaceRule.json");
}
} else if (itemId == R.id.action_reverse) {
for (ReplaceRuleBean ruleBean : mReplaceRules) {
ruleBean.setEnable(!ruleBean.getEnable());
}
ReplaceRuleManager.addDataS(mReplaceRules);
mAdapter.notifyDataSetChanged();
refreshUI();
} else if (itemId == R.id.action_delete) {
DialogCreator.createCommonDialog(this, "删除禁用规则",
"确定要删除所有禁用规则吗?", true,
(dialog, which) -> {
List<ReplaceRuleBean> ruleBeans = new ArrayList<>();
for (ReplaceRuleBean ruleBean : mReplaceRules) {
if (!ruleBean.getEnable()) {
ruleBeans.add(ruleBean);
}
}
ReplaceRuleManager.delDataS(ruleBeans);
mReplaceRules.removeAll(ruleBeans);
mAdapter.removeItems(ruleBeans);
ToastUtils.showSuccess("禁用规则删除成功");
setUpBarTitle();
refreshUI();
}, null);
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == APPCONST.REQUEST_IMPORT_REPLACE_RULE) {
String path = getPath(this, data.getData());
String json = FileUtils.readText(path);
if (!isEmpty(json)) {
importDataS(json);
} else {
ToastUtils.showError("文件读取失败");
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
public void importDataS(String text) {
Observable<Boolean> observable = ReplaceRuleManager.importReplaceRule(text);
if (observable != null) {
observable.subscribe(new MyObserver<Boolean>() {
@Override
public void onNext(Boolean aBoolean) {
if (aBoolean) {
mReplaceRules = ReplaceRuleManager.getAllRules();
mAdapter.setBeans(mReplaceRules);
mAdapter.refreshItems(mReplaceRules);
setUpBarTitle();
refreshUI();
ToastUtils.showSuccess("内容替换规则导入成功");
} else {
ToastUtils.showError("格式不对");
}
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("格式不对");
}
});
} else {
ToastUtils.showError("导入失败");
}
}
@Override
public void onBackPressed() {
if (!"".contentEquals(searchView.getQuery())){
searchView.onActionViewCollapsed();
}else {
super.onBackPressed();
}
}
private void refreshUI() {
Intent result = new Intent();
result.putExtra(APPCONST.RESULT_NEED_REFRESH, true);
setResult(AppCompatActivity.RESULT_OK, result);
}
}

@ -1,258 +0,0 @@
package xyz.fycz.myreader.ui.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.annotations.NonNull;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.base.observer.MyObserver;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActivityRuleBinding;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.model.ReplaceRuleManager;
import xyz.fycz.myreader.ui.adapter.ReplaceRuleAdapter;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.ui.dialog.ReplaceDialog;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.ClipBoardUtil;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import xyz.fycz.myreader.widget.DividerItemDecoration;
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout;
import static android.text.TextUtils.isEmpty;
import static xyz.fycz.myreader.util.UriFileUtil.getPath;
/**
* @author fengyue
* @date 2021/1/19 10:02
*/
public class RuleActivity extends BaseActivity {
private ActivityRuleBinding binding;
private List<ReplaceRuleBean> mReplaceRules;
private ReplaceRuleAdapter mAdapter;
@Override
protected void bindView() {
binding = ActivityRuleBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
ReplaceRuleManager.getAll().subscribe(new MySingleObserver<List<ReplaceRuleBean>>() {
@Override
public void onSuccess(@NonNull List<ReplaceRuleBean> replaceRuleBeans) {
mReplaceRules = replaceRuleBeans;
initRuleList();
setUpBarTitle();
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage());
}
});
}
@Override
protected void setUpToolbar(Toolbar toolbar) {
super.setUpToolbar(toolbar);
setUpBarTitle();
setStatusBarColor(R.color.colorPrimary, true);
}
private void setUpBarTitle() {
getSupportActionBar().setTitle(String.format("%s(共%s个)",
getString(R.string.replace_rule), mReplaceRules == null ? 0 : mReplaceRules.size()));
}
protected void initRuleList() {
mAdapter = new ReplaceRuleAdapter(this, which -> {
mReplaceRules.remove(which);
mAdapter.removeItem(which);
mAdapter.notifyItemRemoved(which);
if(which != mReplaceRules.size())
mAdapter.notifyItemRangeChanged(which, mReplaceRules.size() - which);
setUpBarTitle();
});
binding.rvRuleList.setLayoutManager(new LinearLayoutManager(this));
binding.rvRuleList.setAdapter(mAdapter);
//设置分割线
binding.rvRuleList.addItemDecoration(new DividerItemDecoration(this));
mAdapter.refreshItems(mReplaceRules);
}
@SuppressLint("ClickableViewAccessibility")
@Override
protected void initClick() {
super.initClick();
binding.rvRuleList.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP) {
SwipeMenuLayout viewCache = SwipeMenuLayout.getViewCache();
if (null != viewCache) {
viewCache.smoothClose();
}
}
return false;
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_rule, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add_rule:
ReplaceRuleBean newRuleBean = new ReplaceRuleBean();
newRuleBean.setReplaceSummary("");
newRuleBean.setEnable(true);
newRuleBean.setRegex("");
newRuleBean.setIsRegex(false);
newRuleBean.setReplacement("");
newRuleBean.setSerialNumber(0);
newRuleBean.setUseTo("");
ReplaceDialog replaceDialog = new ReplaceDialog(this, newRuleBean
, () -> {
ToastUtils.showSuccess("内容替换规则添加成功!");
mReplaceRules.add(newRuleBean);
mAdapter.addItem(newRuleBean);
setUpBarTitle();
refreshUI();
});
replaceDialog.show(getSupportFragmentManager(), "replaceRule");
break;
case R.id.action_import:
MyAlertDialog.build(this)
.setTitle("导入规则")
.setItems(R.array.import_rule, (dialog, which) -> {
if (which == 0) {
String text = ClipBoardUtil.paste(this);
if (!isEmpty(text)) {
importDataS(text);
} else {
ToastUtils.showError("剪切板内容为空,导入失败");
}
} else {
ToastUtils.showInfo("请选择内容替换规则JSON文件");
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/json");
startActivityForResult(intent, APPCONST.REQUEST_IMPORT_REPLACE_RULE);
}
}).show();
break;
case R.id.action_export:
if (mReplaceRules == null || mReplaceRules.size() == 0){
ToastUtils.showWarring("当前没有任何规则,无法导出!");
return true;
}
if (FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(mReplaceRules),
FileUtils.getFile(APPCONST.FILE_DIR + "ReplaceRule.json"))) {
DialogCreator.createTipDialog(this,
"内容替换规则导出成功,导出位置:" + APPCONST.FILE_DIR + "ReplaceRule.json");
}
break;
case R.id.action_reverse:
for (ReplaceRuleBean ruleBean : mReplaceRules) {
ruleBean.setEnable(!ruleBean.getEnable());
}
ReplaceRuleManager.addDataS(mReplaceRules);
mAdapter.notifyDataSetChanged();
refreshUI();
break;
case R.id.action_delete:
DialogCreator.createCommonDialog(this, "删除禁用规则",
"确定要删除所有禁用规则吗?", true,
(dialog, which) -> {
List<ReplaceRuleBean> ruleBeans = new ArrayList<>();
for (ReplaceRuleBean ruleBean : mReplaceRules) {
if (!ruleBean.getEnable()) {
ruleBeans.add(ruleBean);
}
}
ReplaceRuleManager.delDataS(ruleBeans);
mReplaceRules.removeAll(ruleBeans);
mAdapter.removeItems(ruleBeans);
ToastUtils.showSuccess("禁用规则删除成功");
setUpBarTitle();
refreshUI();
}, null);
break;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == APPCONST.REQUEST_IMPORT_REPLACE_RULE) {
String path = getPath(this, data.getData());
String json = FileUtils.readText(path);
if (!isEmpty(json)) {
importDataS(json);
} else {
ToastUtils.showError("文件读取失败");
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
public void importDataS(String text) {
Observable<Boolean> observable = ReplaceRuleManager.importReplaceRule(text);
if (observable != null) {
observable.subscribe(new MyObserver<Boolean>() {
@Override
public void onNext(Boolean aBoolean) {
if (aBoolean) {
mReplaceRules = ReplaceRuleManager.getAllRules();
mAdapter.refreshItems(mReplaceRules);
setUpBarTitle();
refreshUI();
ToastUtils.showSuccess("内容替换规则导入成功");
} else {
ToastUtils.showError("格式不对");
}
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("格式不对");
}
});
} else {
ToastUtils.showError("导入失败");
}
}
private void refreshUI() {
Intent result = new Intent();
result.putExtra(APPCONST.RESULT_NEED_REFRESH, true);
setResult(AppCompatActivity.RESULT_OK, result);
}
}

@ -9,13 +9,16 @@ import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -28,16 +31,11 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.annotations.NonNull;
import me.gujun.android.taggroup.TagGroup;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.base.observer.MySingleObserver;
@ -45,9 +43,10 @@ import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActivitySearchBookBinding;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.SearchHistory;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.greendao.service.SearchHistoryService;
import xyz.fycz.myreader.model.SearchEngine;
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
@ -58,7 +57,6 @@ import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.webapi.BaseApi;
import xyz.fycz.myreader.webapi.CommonApi;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
@ -84,21 +82,14 @@ public class SearchBookActivity extends BaseActivity {
private SearchHistoryAdapter mSearchHistoryAdapter;
private int curThreadCount;
private int allThreadCount;
private boolean isStopSearch;
private int inputConfirm = 0;//搜索输入确认
private int confirmTime = 1000;//搜索输入确认时间(毫秒)
private SearchEngine searchEngine;
private Setting mSetting;
//选择禁用更新书源对话框
private AlertDialog mDisableSourceDia;
private Menu menu;
private static String[] suggestion = {"第一序列", "大道朝天", "伏天氏", "终极斗罗", "我师兄实在太稳健了", "烂柯棋缘", "诡秘之主"};
private static String[] suggestion2 = {"不朽凡人", "圣墟", "我是至尊", "龙王传说", "太古神王", "一念永恒", "雪鹰领主", "大主宰"};
@ -145,7 +136,7 @@ public class SearchBookActivity extends BaseActivity {
super.initData(savedInstanceState);
mSetting = SysManager.getSetting();
mSearchHistoryService = SearchHistoryService.getInstance();
showHot = !MyApplication.isApkInDebug(this);
showHot = !App.isApkInDebug(this);
searchEngine = new SearchEngine();
searchEngine.setOnSearchListener(new SearchEngine.OnSearchListener() {
@Override
@ -157,7 +148,6 @@ public class SearchBookActivity extends BaseActivity {
@Override
public void loadMoreSearchBook(ConcurrentMultiValueMap<SearchBookBean, Book> items) {
mBooks.addAll(items);
curThreadCount--;
mSearchBookAdapter.addAll(new ArrayList<>(items.keySet()), searchKey);
mHandler.sendMessage(mHandler.obtainMessage(2));
}
@ -169,7 +159,6 @@ public class SearchBookActivity extends BaseActivity {
@Override
public void searchBookError(Throwable throwable) {
curThreadCount = 0;
mHandler.sendMessage(mHandler.obtainMessage(2));
}
});
@ -299,6 +288,8 @@ public class SearchBookActivity extends BaseActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_search, menu);
this.menu = menu;
initSourceGroupMenu();
return true;
}
@ -310,68 +301,108 @@ public class SearchBookActivity extends BaseActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_disable_source) {
showDisableSourceDia();
}else if (item.getItemId() == R.id.action_hot){
if (item.getItemId() == R.id.action_hot) {
showHot = !showHot;
initSuggestionList();
} else if (item.getItemId() == R.id.action_source_man) {
startActivityForResult(new Intent(this, BookSourceActivity.class),
APPCONST.REQUEST_BOOK_SOURCE);
} else {
if (item.getGroupId() == R.id.source_group) {
item.setChecked(true);
SharedPreUtils sp = SharedPreUtils.getInstance();
if (getString(R.string.all_source).equals(item.getTitle().toString())) {
sp.putString("searchGroup", "");
} else {
sp.putString("searchGroup", item.getTitle().toString());
}
}
}
return super.onOptionsItemSelected(item);
}
private void showDisableSourceDia() {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == APPCONST.REQUEST_BOOK_SOURCE) {
initSourceGroupMenu();
}
}
}
/**
* 初始化书源分组菜单
*/
public void initSourceGroupMenu() {
if (menu == null) return;
String searchGroup = SharedPreUtils.getInstance().getString("");
menu.removeGroup(R.id.source_group);
MenuItem item = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.all_source);
MenuItem localItem = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.local_source);
if ("".equals(searchGroup)) {
item.setChecked(true);
}else if (getString(R.string.local_source).equals(searchGroup)){
localItem.setChecked(true);
}
List<String> groupList = BookSourceManager.getEnableNoLocalGroupList();
for (String groupName : groupList) {
item = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, groupName);
if (groupName.equals(searchGroup)) item.setChecked(true);
}
menu.setGroupCheckable(R.id.source_group, true, true);
}
/*private void showDisableSourceDia() {
if (mDisableSourceDia != null) {
mDisableSourceDia.show();
return;
}
HashMap<CharSequence, Boolean> mSources = ReadCrawlerUtil.getDisableSources();
CharSequence[] mSourcesName = new CharSequence[mSources.keySet().size()];
boolean[] isDisables = new boolean[mSources.keySet().size()];
List<BookSource> sources = BookSourceManager.getAllBookSourceByOrderNum();
CharSequence[] mSourcesName = new CharSequence[sources.size()];
boolean[] isDisables = new boolean[sources.size()];
int dSourceCount = 0;
int i = 0;
for (CharSequence sourceName : mSources.keySet()) {
mSourcesName[i] = sourceName;
Boolean isDisable = mSources.get(sourceName);
if (isDisable == null) isDisable = false;
for (BookSource source : sources) {
mSourcesName[i] = source.getSourceName();
boolean isDisable = !source.getEnable();
if (isDisable) dSourceCount++;
isDisables[i++] = isDisable;
}
mDisableSourceDia = new MultiChoiceDialog().create(this, "选择禁用的书源",
mSourcesName, isDisables, dSourceCount, (dialog, which) -> {
SharedPreUtils spu = SharedPreUtils.getInstance();
StringBuilder sb = new StringBuilder();
for (CharSequence sourceName : mSources.keySet()) {
if (!mSources.get(sourceName)) {
sb.append(BookSource.getFromName(String.valueOf(sourceName)));
sb.append(",");
}
}
if (sb.lastIndexOf(",") >= 0) sb.deleteCharAt(sb.lastIndexOf(","));
spu.putString(getString(R.string.searchSource), sb.toString());
BookSourceManager.saveDatas(sources)
.subscribe(new MySingleObserver<Boolean>() {
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
if (aBoolean){
ToastUtils.showSuccess("保存成功");
}
}
});
}, null, new DialogCreator.OnMultiDialogListener() {
@Override
public void onItemClick(DialogInterface dialog, int which, boolean isChecked) {
mSources.put(mSourcesName[which], isChecked);
sources.get(which).setEnable(!isChecked);
}
@Override
public void onSelectAll(boolean isSelectAll) {
for (CharSequence sourceName : mSources.keySet()) {
mSources.put(sourceName, isSelectAll);
for (BookSource source : sources) {
source.setEnable(!isSelectAll);
}
}
});
}
}*/
/**
* 初始化建议书目
*/
private void initSuggestionList() {
if (!showHot){
if (!showHot) {
binding.tgSuggestBook.setTags(suggestion);
}else {
} else {
SharedPreUtils spu = SharedPreUtils.getInstance();
String cookie = spu.getString(getString(R.string.qdCookie), "");
String url = "https://m.qidian.com/majax/search/auto?kw=&";
@ -385,15 +416,15 @@ public class SearchBookActivity extends BaseActivity {
public void onFinish(Object o, int code) {
parseSuggestionList((String) o);
if (mSuggestions.size() > 0) {
MyApplication.runOnUiThread(() -> binding.tgSuggestBook.setTags(mSuggestions.subList(0, 5)));
App.runOnUiThread(() -> binding.tgSuggestBook.setTags(mSuggestions.subList(0, 5)));
} else {
MyApplication.runOnUiThread(() -> binding.llSuggestBooksView.setVisibility(View.GONE));
App.runOnUiThread(() -> binding.llSuggestBooksView.setVisibility(View.GONE));
}
}
@Override
public void onError(Exception e) {
MyApplication.runOnUiThread(() -> binding.llSuggestBooksView.setVisibility(View.GONE));
App.runOnUiThread(() -> binding.llSuggestBooksView.setVisibility(View.GONE));
}
});
}
@ -421,7 +452,7 @@ public class SearchBookActivity extends BaseActivity {
} else {
binding.tgSuggestBook.setTags(suggestion);
}
}else {
} else {
if (mSuggestions.size() > 0) {
String[] s = binding.tgSuggestBook.getTags();
if (s[0].equals(mSuggestions.get(0))) {
@ -466,14 +497,14 @@ public class SearchBookActivity extends BaseActivity {
initSearchList();
mBooksBean.clear();
mBooks.clear();
ArrayList<ReadCrawler> readCrawlers = ReadCrawlerUtil.getReadCrawlers();
List<ReadCrawler> readCrawlers = ReadCrawlerUtil
.getEnableReadCrawlers(SharedPreUtils.getInstance().getString("searchGroup"));
allThreadCount = readCrawlers.size();
if (allThreadCount == 0) {
ToastUtils.showWarring("当前书源已全部禁用,无法搜索!");
binding.rpb.setIsAutoLoading(false);
return;
}
curThreadCount = allThreadCount;
/*for (ReadCrawler readCrawler : readCrawlers) {
searchBookByCrawler(readCrawler, readCrawler.getSearchCharset());
}*/
@ -488,7 +519,6 @@ public class SearchBookActivity extends BaseActivity {
binding.rpb.setIsAutoLoading(true);
binding.fabSearchStop.setVisibility(View.VISIBLE);
if (StringHelper.isEmpty(searchKey)) {
isStopSearch = true;
stopSearch();
binding.rpb.setIsAutoLoading(false);
binding.rvSearchBooksList.setVisibility(View.GONE);
@ -497,10 +527,7 @@ public class SearchBookActivity extends BaseActivity {
binding.rvSearchBooksList.setAdapter(null);
binding.srlSearchBookList.setEnableRefresh(false);
} else {
isStopSearch = false;
mSearchBookAdapter = new SearchBookAdapter(mBooks, searchEngine);
mSearchBookAdapter = new SearchBookAdapter(mBooks, searchEngine, searchKey);
binding.rvSearchBooksList.setAdapter(mSearchBookAdapter);
//进入书籍详情页
mSearchBookAdapter.setOnItemClickListener((view, pos) -> {
@ -515,7 +542,7 @@ public class SearchBookActivity extends BaseActivity {
getData();
mSearchHistoryService.addOrUpadteHistory(searchKey);
//收起软键盘
InputMethodManager imm = (InputMethodManager) MyApplication.getmContext().getSystemService(Context.INPUT_METHOD_SERVICE);
InputMethodManager imm = (InputMethodManager) App.getmContext().getSystemService(Context.INPUT_METHOD_SERVICE);
assert imm != null;
imm.hideSoftInputFromWindow(binding.etSearchKey.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
@ -535,37 +562,8 @@ public class SearchBookActivity extends BaseActivity {
}
}
private void searchBookByCrawler(ReadCrawler rc, String charset) {
String searchKey = this.searchKey;
if (charset.toLowerCase().equals("gbk")) {
try {
searchKey = URLEncoder.encode(this.searchKey, charset);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
CommonApi.search(searchKey, rc, new ResultCallback() {
@Override
public void onFinish(Object o, int code) {
ConcurrentMultiValueMap<SearchBookBean, Book> sbb =
(ConcurrentMultiValueMap<SearchBookBean, Book>) o;
mBooks.addAll(sbb);
curThreadCount--;
mHandler.sendMessage(mHandler.obtainMessage(2));
}
@Override
public void onError(Exception e) {
curThreadCount--;
mHandler.sendMessage(mHandler.obtainMessage(2));
}
});
}
@Override
protected void onDestroy() {
isStopSearch = true;
stopSearch();
for (int i = 0; i < 9; i++) {
mHandler.removeMessages(i + 1);

@ -0,0 +1,273 @@
package xyz.fycz.myreader.ui.activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.tabs.TabLayout;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.base.observer.MyObserver;
import xyz.fycz.myreader.databinding.ActivitySourceDebugBinding;
import xyz.fycz.myreader.entity.sourcedebug.DebugBook;
import xyz.fycz.myreader.entity.sourcedebug.DebugChapter;
import xyz.fycz.myreader.entity.sourcedebug.DebugEntity;
import xyz.fycz.myreader.entity.sourcedebug.ListResult;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.util.utils.ProgressUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
import xyz.fycz.myreader.widget.codeview.Language;
/**
* @author fengyue
* @date 2021/2/12 18:55
*/
public class SourceDebugActivity extends BaseActivity {
private ActivitySourceDebugBinding binding;
private DebugEntity debugEntity;
private ReadCrawler rc;
private Disposable disposable;
@Override
protected void bindView() {
binding = ActivitySourceDebugBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
@Override
protected void setUpToolbar(Toolbar toolbar) {
super.setUpToolbar(toolbar);
setStatusBarColor(R.color.colorPrimary, true);
switch (debugEntity.getDebugMode()) {
case DebugEntity.SEARCH:
default:
getSupportActionBar().setTitle(R.string.debug_search);
break;
case DebugEntity.INFO:
getSupportActionBar().setTitle(R.string.debug_info);
break;
case DebugEntity.TOC:
getSupportActionBar().setTitle(R.string.debug_toc);
break;
case DebugEntity.CONTENT:
getSupportActionBar().setTitle(R.string.debug_content);
break;
}
initDebugEntity();
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
debugEntity = getIntent().getParcelableExtra("debugEntity");
rc = ReadCrawlerUtil.getReadCrawler(debugEntity.getBookSource());
}
@Override
protected void initWidget() {
super.initWidget();
binding.tvTip.setText(getString(R.string.debug_desc,
debugEntity.getBookSource().getSourceName(),
debugEntity.getBookSource().getSourceUrl(),
debugEntity.getUrl()));
binding.rvParseResult.setLanguage(Language.JSON);
binding.rvSourceCode.setLanguage(Language.HTML);
binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab.getPosition() == 0) {
binding.rvSourceCode.setVisibility(View.GONE);
binding.rvParseResult.setVisibility(View.VISIBLE);
} else if (tab.getPosition() == 1) {
binding.rvSourceCode.setVisibility(View.VISIBLE);
binding.rvParseResult.setVisibility(View.GONE);
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
@Override
public void onBackPressed() {
if (ProgressUtils.isShowing()) {
ProgressUtils.dismiss();
} else {
super.onBackPressed();
}
}
@Override
protected void onDestroy() {
ProgressUtils.dismiss();
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_debug, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_re_request) {
initDebugEntity();
}
return super.onOptionsItemSelected(item);
}
private void initDebugEntity() {
ProgressUtils.show(this, "正在请求...", "取消", (dialog, which) -> {
if (disposable != null) {
disposable.dispose();
}
});
Observable.create((ObservableOnSubscribe<Boolean>) emitter -> {
try {
String url = debugEntity.getUrl();
if (debugEntity.getDebugMode() == DebugEntity.SEARCH && rc.isPost()) {
String[] urlInfo = url.split(",");
url = NetworkUtils.getAbsoluteURL(rc.getNameSpace(), urlInfo[0]);
String body = urlInfo[1];
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody requestBody = RequestBody.create(mediaType, body);
debugEntity.setHtml(OkHttpUtils.getHtml(url, requestBody, rc.getCharset()));
} else {
debugEntity.setHtml(OkHttpUtils.getHtml(NetworkUtils.getAbsoluteURL(rc.getNameSpace(), url), rc.getCharset()));
}
ListResult listResult = new ListResult();
switch (debugEntity.getDebugMode()) {
case DebugEntity.SEARCH:
default:
List<DebugBook> debugBooks = book2DebugBook(rc.getBooksFromSearchHtml(debugEntity.getHtml()).values());
listResult.set信息(String.format("解析完毕,共%s本书籍", debugBooks.size()));
listResult.set结果(debugBooks);
debugEntity.setParseResult(GsonExtensionsKt.getGSON()
.toJson(listResult));
break;
case DebugEntity.INFO:
debugEntity.setParseResult(GsonExtensionsKt.getGSON()
.toJson(book2DebugBook(((BookInfoCrawler) rc).getBookInfo(debugEntity.getHtml(), new Book()))));
break;
case DebugEntity.TOC:
List<DebugChapter> debugChapters = chapter2DebugChapter(rc.getChaptersFromHtml(debugEntity.getHtml()));
listResult.set信息(String.format("解析完毕,共%s个章节", debugChapters.size()));
listResult.set结果(debugChapters);
debugEntity.setParseResult(GsonExtensionsKt.getGSON()
.toJson(listResult));
break;
case DebugEntity.CONTENT:
debugEntity.setParseResult(rc.getContentFormHtml(debugEntity.getHtml()));
break;
}
emitter.onNext(true);
} catch (Exception e) {
e.printStackTrace();
emitter.onError(e);
}
emitter.onComplete();
}).compose(RxUtils::toSimpleSingle).subscribe(new MyObserver<Boolean>() {
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}
@Override
public void onNext(@NonNull Boolean flag) {
binding.rvParseResult.setCode(debugEntity.getParseResult()).apply();
binding.rvSourceCode.setCode(debugEntity.getHtml()).apply();
ProgressUtils.dismiss();
}
@Override
public void onError(Throwable e) {
binding.rvParseResult.setCode(String.format("{\n\b\b\b\b\"result\": \"error\", \n\b\b\b\b\"msg\": \"%s\"\n}"
, e.getLocalizedMessage().replace("\"", "\\\""))).apply();
binding.rvSourceCode.setCode(debugEntity.getHtml()).apply();
ProgressUtils.dismiss();
}
});
}
private List<DebugBook> book2DebugBook(List<Book> books) {
List<DebugBook> debugBooks = new ArrayList<>();
for (Book book : books) {
debugBooks.add(book2DebugBook(book));
}
return debugBooks;
}
private DebugBook book2DebugBook(Book book) {
DebugBook debugBook = new DebugBook();
/*debugBook.setName(book.getName());
debugBook.setAuthor(book.getAuthor());
debugBook.setType(book.getType());
debugBook.setDesc(book.getDesc());
debugBook.setWordCount(book.getWordCount());
debugBook.setStatus(book.getStatus());
debugBook.setLastChapter(book.getNewestChapterTitle());
debugBook.setUpdateTime(book.getUpdateDate());
debugBook.setImgUrl(book.getImgUrl());
debugBook.setTocUrl(book.getChapterUrl());
debugBook.setInfoUrl(book.getInfoUrl());*/
debugBook.set书名(book.getName());
debugBook.set作者(book.getAuthor());
debugBook.set分类(book.getType());
debugBook.set简介(book.getDesc());
debugBook.set字数(book.getWordCount());
debugBook.set连载状态(book.getStatus());
debugBook.set最新章节(book.getNewestChapterTitle());
debugBook.set更新时间(book.getUpdateDate());
debugBook.set封面链接(book.getImgUrl());
debugBook.set目录链接(book.getChapterUrl());
debugBook.set详情链接(book.getInfoUrl());
return debugBook;
}
private List<DebugChapter> chapter2DebugChapter(List<Chapter> chapters) {
List<DebugChapter> debugChapters = new ArrayList<>();
for (Chapter chapter : chapters) {
DebugChapter debugChapter = new DebugChapter();
/*debugChapter.setTitle(chapter.getTitle());
debugChapter.setUrl(chapter.getUrl());*/
debugChapter.set章节名称(chapter.getTitle());
debugChapter.set章节链接(chapter.getUrl());
debugChapters.add(debugChapter);
}
return debugChapters;
}
}

@ -0,0 +1,280 @@
package xyz.fycz.myreader.ui.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.tabs.TabLayout;
import java.net.URLEncoder;
import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActivitySourceEditBinding;
import xyz.fycz.myreader.entity.sourcedebug.DebugEntity;
import xyz.fycz.myreader.entity.sourceedit.EditEntity;
import xyz.fycz.myreader.entity.sourceedit.EditEntityUtil;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.entity.rule.SearchRule;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.ui.adapter.SourceEditAdapter;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.webapi.crawler.source.MatcherCrawler;
/**
* @author fengyue
* @date 2021/2/9 10:54
*/
public class SourceEditActivity extends BaseActivity {
private ActivitySourceEditBinding binding;
private BookSource source;
private List<EditEntity> sourceEntities;
private List<EditEntity> searchEntities;
private List<EditEntity> infoEntities;
private List<EditEntity> tocEntities;
private List<EditEntity> contentEntities;
private EditEntityUtil entityUtil;
private SourceEditAdapter adapter;
@Override
protected void bindView() {
binding = ActivitySourceEditBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
@Override
protected void setUpToolbar(Toolbar toolbar) {
super.setUpToolbar(toolbar);
setStatusBarColor(R.color.colorPrimary, true);
getSupportActionBar().setTitle("书源编辑");
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
source = getIntent().getParcelableExtra(APPCONST.BOOK_SOURCE);
if (source == null) {
source = new BookSource();
source.setEnable(true);
source.setSourceType(APPCONST.XPATH);
}
entityUtil = EditEntityUtil.INSTANCE;
adapter = new SourceEditAdapter();
}
private void initEntities(BookSource source) {
sourceEntities = entityUtil.getSourceEntities(source);
searchEntities = entityUtil.getSearchEntities(source.getSearchRule());
infoEntities = entityUtil.getInfoEntities(source.getInfoRule());
tocEntities = entityUtil.getTocEntities(source.getTocRule());
contentEntities = entityUtil.getContentEntities(source.getContentRule());
}
@Override
protected void initWidget() {
super.initWidget();
binding.cbSourceEnable.setChecked(source.getEnable());
initSpinner();
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.recyclerView.setAdapter(adapter);
upRecyclerView();
}
private void initSpinner() {
ArrayAdapter<CharSequence> sourceTypeAdapter = ArrayAdapter.createFromResource(this,
R.array.source_type, android.R.layout.simple_spinner_item);
sourceTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.sSourceType.setAdapter(sourceTypeAdapter);
int sourceType = 0;
if (source.getSourceType() == null) source.setSourceType(APPCONST.MATCHER);
switch (source.getSourceType()) {
case APPCONST.MATCHER:
sourceType = 0;
break;
case APPCONST.XPATH:
sourceType = 1;
break;
case APPCONST.JSON_PATH:
sourceType = 2;
break;
}
binding.sSourceType.setSelection(sourceType);
}
@Override
protected void initClick() {
super.initClick();
binding.tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
setEditEntities(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_source_edit, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_save) {
BookSource source = getSource();
if (checkSource(source)) {
BookSourceManager.addBookSource(source);
this.source = source;
setResult(Activity.RESULT_OK);
ToastUtils.showSuccess("书源保存成功");
}
return true;
} else if (item.getItemId() == R.id.action_debug_search) {
debug(item.getTitle(), DebugEntity.SEARCH);
} else if (item.getItemId() == R.id.action_debug_info) {
debug(item.getTitle(), DebugEntity.INFO);
} else if (item.getItemId() == R.id.action_debug_toc) {
debug(item.getTitle(), DebugEntity.TOC);
} else if (item.getItemId() == R.id.action_debug_content) {
debug(item.getTitle(), DebugEntity.CONTENT);
}
return super.onOptionsItemSelected(item);
}
private void debug(CharSequence title, int debugMode) {
String hint = "请输入URL";
if (debugMode == DebugEntity.SEARCH) {
hint = "请输入关键词";
}
BookSource source = getSource();
DebugEntity debugEntity = new DebugEntity();
debugEntity.setDebugMode(debugMode);
debugEntity.setBookSource(source);
MyAlertDialog.createInputDia(this, String.valueOf(title), hint,
"", true, 500, text -> {
if (debugMode == DebugEntity.SEARCH) {
try {
MatcherCrawler sc = new MatcherCrawler(source);
if (!sc.getSearchCharset().toLowerCase().equals("utf-8")) {
text = URLEncoder.encode(text, sc.getSearchCharset());
}
} catch (Exception e) {
e.printStackTrace();
}
debugEntity.setUrl(source.getSearchRule().getSearchUrl().replace("{key}", text));
}else {
debugEntity.setUrl(text);
}
}, (dialog, which) -> {
Intent intent = new Intent(this, SourceDebugActivity.class);
intent.putExtra("debugEntity", debugEntity);
startActivity(intent);
});
}
@Override
public void finish() {
BookSource source = getSource();
if (!source.equals(this.source)) {
DialogCreator.createThreeButtonDialog(this, "退出"
, "当前书源已更改,是否保存?", true,
"直接退出", "取消", "保存并退出",
(dialog, which) -> super.finish(), null,
(dialog, which) -> {
if (checkSource(source)) {
BookSourceManager.addBookSource(source);
setResult(Activity.RESULT_OK);
super.finish();
}
}
);
} else {
super.finish();
}
}
private void setEditEntities(int tabPosition) {
switch (tabPosition) {
case 1:
adapter.refreshItems(searchEntities);
break;
case 2:
adapter.refreshItems(infoEntities);
break;
case 3:
adapter.refreshItems(tocEntities);
break;
case 4:
adapter.refreshItems(contentEntities);
break;
default:
adapter.refreshItems(sourceEntities);
break;
}
binding.recyclerView.scrollToPosition(0);
}
private void upRecyclerView() {
initEntities(source);
binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0));
setEditEntities(0);
}
private BookSource getSource() {
BookSource source = entityUtil.getSource(this.source, sourceEntities);
source.setEnable(binding.cbSourceEnable.isChecked());
String sourceType = APPCONST.MATCHER;
switch (binding.sSourceType.getSelectedItemPosition()){
case 0:
sourceType = APPCONST.MATCHER;
break;
case 1:
sourceType = APPCONST.XPATH;
break;
case 2:
sourceType = APPCONST.JSON_PATH;
break;
}
source.setSourceType(sourceType);
source.setSearchRule(entityUtil.getSearchRule(searchEntities));
source.setInfoRule(entityUtil.getInfoRule(infoEntities));
source.setTocRule(entityUtil.getTocRule(tocEntities));
source.setContentRule(entityUtil.getContentRule(contentEntities));
return source;
}
private boolean checkSource(BookSource source) {
if (StringHelper.isEmpty(source.getSourceName()) ||
StringHelper.isEmpty(source.getSourceUrl())) {
ToastUtils.showWarring("书源URL和名称不能为空");
return false;
}
return true;
}
}

@ -19,7 +19,7 @@ import java.io.IOException;
import java.io.InputStream;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.ActivitySplashBinding;
@ -93,7 +93,7 @@ public class SplashActivity extends BaseActivity {
private void start(){
if (BookGroupService.getInstance().curGroupIsPrivate()) {
MyApplication.runOnUiThread(() ->{
App.runOnUiThread(() ->{
MyAlertDialog.showPrivateVerifyDia(SplashActivity.this, needGoTo -> {
myThread.start();
}, () -> {
@ -146,7 +146,7 @@ public class SplashActivity extends BaseActivity {
}
private void downLoadImage() {
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
String url = SharedPreUtils.getInstance().getString("splashImageUrl", "");
if (!url.equals("")) {
InputStream is = null;

@ -0,0 +1,129 @@
package xyz.fycz.myreader.ui.adapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
/**
* @author fengyue
* @date 2021/2/10 18:25
*/
public abstract class BaseSourceAdapter extends BaseListAdapter<BookSource> {
//记录item是否被选中的Map
private HashMap<BookSource, Boolean> mCheckMap = new HashMap<>();
private int mCheckedCount = 0;
@Override
public void refreshItems(List<BookSource> list) {
mCheckMap.clear();
for (BookSource bookSource : list) {
mCheckMap.put(bookSource, false);
}
super.refreshItems(list);
}
@Override
public void addItem(BookSource value) {
mCheckMap.put(value, false);
super.addItem(value);
}
@Override
public void addItem(int index, BookSource value) {
mCheckMap.put(value, false);
super.addItem(index, value);
}
@Override
public void addItems(List<BookSource> values) {
for (BookSource bookSource : values) {
mCheckMap.put(bookSource, false);
}
super.addItems(values);
}
@Override
public void removeItem(BookSource value) {
mCheckMap.remove(value);
super.removeItem(value);
}
@Override
public void removeItems(List<BookSource> value) {
for (BookSource bookSource : value) {
mCheckMap.remove(bookSource);
--mCheckedCount;
}
super.removeItems(value);
}
//设置点击切换
public void setCheckedItem(int pos) {
BookSource bookSource = getItem(pos);
boolean isSelected = mCheckMap.get(bookSource);
if (isSelected) {
mCheckMap.put(bookSource, false);
--mCheckedCount;
} else {
mCheckMap.put(bookSource, true);
++mCheckedCount;
}
notifyItemChanged(pos);
}
public void setCheckedAll(boolean isChecked) {
Set<Map.Entry<BookSource, Boolean>> entrys = mCheckMap.entrySet();
mCheckedCount = 0;
for (Map.Entry<BookSource, Boolean> entry : entrys) {
//如果选中,则增加点击的数量
if (isChecked) {
++mCheckedCount;
}
entry.setValue(isChecked);
}
notifyDataSetChanged();
}
public void reverseChecked() {
Set<Map.Entry<BookSource, Boolean>> entrys = mCheckMap.entrySet();
for (Map.Entry<BookSource, Boolean> entry : entrys) {
//如果选中,则减少点击的数量
if (entry.getValue()) {
mCheckedCount--;
}else {
mCheckedCount++;
}
entry.setValue(!entry.getValue());
}
notifyDataSetChanged();
}
public boolean getItemIsChecked(int pos) {
BookSource bookSource = getItem(pos);
return mCheckMap.get(bookSource);
}
public List<BookSource> getCheckedBookSources() {
List<BookSource> bookSources = new ArrayList<>();
Set<Map.Entry<BookSource, Boolean>> entrys = mCheckMap.entrySet();
for (Map.Entry<BookSource, Boolean> entry : entrys) {
if (entry.getValue()) {
bookSources.add(entry.getKey());
}
}
return bookSources;
}
public int getCheckedCount() {
return mCheckedCount;
}
public HashMap<BookSource, Boolean> getCheckMap() {
return mCheckMap;
}
}

@ -0,0 +1,98 @@
package xyz.fycz.myreader.ui.adapter;
import android.os.AsyncTask;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentActivity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.IViewHolder;
import xyz.fycz.myreader.greendao.GreenDaoManager;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.ui.adapter.helper.ItemTouchCallback;
import xyz.fycz.myreader.ui.adapter.holder.BookSourceHolder;
/**
* @author fengyue
* @date 2020/8/12 20:02
*/
public class BookSourceAdapter extends BaseSourceAdapter {
private final FragmentActivity activity;
private final OnSwipeListener onSwipeListener;
private boolean mEditState;
private final ItemTouchCallback.OnItemTouchListener itemTouchListener = new ItemTouchCallback.OnItemTouchListener() {
@Override
public void onSwiped(int adapterPosition) {
}
@Override
public boolean onMove(int srcPosition, int targetPosition) {
Collections.swap(mList, srcPosition, targetPosition);
notifyItemMoved(srcPosition, targetPosition);
notifyItemChanged(srcPosition);
notifyItemChanged(targetPosition);
AsyncTask.execute(() -> {
for (int i = 1; i <= mList.size(); i++) {
mList.get(i - 1).setOrderNum(i);
}
GreenDaoManager.getDaoSession().getBookSourceDao().insertOrReplaceInTx(mList);
});
return true;
}
};
public BookSourceAdapter(FragmentActivity activity, OnSwipeListener onSwipeListener) {
this.activity = activity;
this.onSwipeListener = onSwipeListener;
}
@Override
protected IViewHolder<BookSource> createViewHolder(int viewType) {
return new BookSourceHolder(activity, this, onSwipeListener);
}
public ItemTouchCallback.OnItemTouchListener getItemTouchListener() {
return itemTouchListener;
}
public boolean ismEditState() {
return mEditState;
}
public void setmEditState(boolean mEditState) {
this.mEditState = mEditState;
setCheckedAll(false);
}
public void removeItem(int pos) {
mList.remove(pos);
notifyItemRemoved(pos);
if (pos != mList.size())
notifyItemRangeChanged(pos, mList.size() - pos);
}
public void toTop(int which, BookSource bean) {
mList.remove(bean);
notifyItemInserted(0);
mList.add(0, bean);
notifyItemRemoved(which);
notifyItemRangeChanged(0, which + 1);
}
public interface OnSwipeListener {
void onDel(int which, BookSource source);
void onTop(int which, BookSource source);
}
}

@ -5,6 +5,7 @@ import android.os.Handler;
import java.util.List;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.IViewHolder;
import xyz.fycz.myreader.greendao.entity.Book;
@ -29,13 +30,6 @@ public class BookStoreBookAdapter extends BaseListAdapter<Book> {
@Override
public void addItems(List<Book> values) {
mList.addAll(values);
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
App.runOnUiThread(this::notifyDataSetChanged);
}
}

@ -1,7 +1,6 @@
package xyz.fycz.myreader.ui.adapter;
import android.content.Context;
import android.content.DialogInterface;
import android.widget.*;
import java.io.BufferedReader;
@ -13,7 +12,7 @@ import java.io.IOException;
import java.util.*;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
@ -49,10 +48,10 @@ public abstract class BookcaseAdapter extends DragAdapter {
protected BookcasePresenter mBookcasePresenter;
protected boolean isGroup;
protected String[] menu = {
MyApplication.getmContext().getResources().getString(R.string.menu_book_Top),
MyApplication.getmContext().getResources().getString(R.string.menu_book_download),
MyApplication.getmContext().getResources().getString(R.string.menu_book_cache),
MyApplication.getmContext().getResources().getString(R.string.menu_book_delete)
App.getmContext().getResources().getString(R.string.menu_book_Top),
App.getmContext().getResources().getString(R.string.menu_book_download),
App.getmContext().getResources().getString(R.string.menu_book_cache),
App.getmContext().getResources().getString(R.string.menu_book_delete)
};

@ -1,6 +1,5 @@
package xyz.fycz.myreader.ui.adapter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
@ -15,7 +14,7 @@ import java.io.IOException;
import java.util.ArrayList;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.ui.activity.ReadActivity;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
@ -154,7 +153,7 @@ public class BookcaseDetailedAdapter extends BookcaseAdapter {
downloadBook(book);
break;
case 2:
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
try {
if (unionChapterCathe(book)) {
DialogCreator.createTipDialog(mContext,

@ -1,6 +1,5 @@
package xyz.fycz.myreader.ui.adapter;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
@ -13,7 +12,7 @@ import java.io.IOException;
import java.util.ArrayList;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.ui.activity.ReadActivity;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
@ -28,11 +27,11 @@ import xyz.fycz.myreader.util.ToastUtils;
public class BookcaseDragAdapter extends BookcaseAdapter {
ViewHolder viewHolder = null;
protected String[] menu = {
MyApplication.getmContext().getResources().getString(R.string.menu_book_detail),
MyApplication.getmContext().getResources().getString(R.string.menu_book_Top),
MyApplication.getmContext().getResources().getString(R.string.menu_book_download),
MyApplication.getmContext().getResources().getString(R.string.menu_book_cache),
MyApplication.getmContext().getResources().getString(R.string.menu_book_delete)
App.getmContext().getResources().getString(R.string.menu_book_detail),
App.getmContext().getResources().getString(R.string.menu_book_Top),
App.getmContext().getResources().getString(R.string.menu_book_download),
App.getmContext().getResources().getString(R.string.menu_book_cache),
App.getmContext().getResources().getString(R.string.menu_book_delete)
};
public BookcaseDragAdapter(Context context, int textViewResourceId, ArrayList<Book> objects,
boolean editState, BookcasePresenter bookcasePresenter, boolean isGroup) {
@ -136,7 +135,7 @@ public class BookcaseDragAdapter extends BookcaseAdapter {
downloadBook(book);
break;
case 3:
MyApplication.getApplication().newThread(new Runnable() {
App.getApplication().newThread(new Runnable() {
@Override
public void run() {
try {

@ -27,7 +27,7 @@ import java.net.URL;
import java.util.ArrayList;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.common.APPCONST;
@ -226,7 +226,7 @@ public class FontsAdapter extends ArrayAdapter<Font> {
}
private void downloadFont(final String url, final Font font, final ViewHolder viewHolder) {
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
HttpURLConnection con = null;
InputStream is = null;
FileOutputStream fos = null;

@ -0,0 +1,61 @@
package xyz.fycz.myreader.ui.adapter;
import android.widget.Filter;
import android.widget.Filterable;
import java.util.ArrayList;
import java.util.List;
import xyz.fycz.myreader.base.adapter.IViewHolder;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.ui.adapter.holder.LocalSourceHolder;
import xyz.fycz.myreader.util.StringHelper;
/**
* @author fengyue
* @date 2021/2/10 18:27
*/
public class LocalSourceAdapter extends BaseSourceAdapter implements Filterable {
private List<BookSource> sources;
public LocalSourceAdapter(List<BookSource> sources) {
this.sources = sources;
}
@Override
protected IViewHolder<BookSource> createViewHolder(int viewType) {
return new LocalSourceHolder(getCheckMap());
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
String charString = constraint.toString();
List<BookSource> mFilterList = new ArrayList<>();
if (charString.isEmpty()) {
//没有过滤的内容,则使用源数据
mFilterList = sources;
} else {
for (BookSource source : sources) {
//这里根据需求,添加匹配规则
if (source.getSourceName().contains(charString))
mFilterList.add(source);
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = mFilterList;
return filterResults;
}
//把过滤后的值返回出来
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
refreshItems((List<BookSource>) results.values);
}
};
}
}

@ -1,37 +1,104 @@
package xyz.fycz.myreader.ui.adapter;
import android.app.Activity;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.IViewHolder;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.ui.adapter.holder.ReplaceRuleHolder;
import xyz.fycz.myreader.util.StringHelper;
/**
* @author fengyue
* @date 2021/1/19 9:51
*/
public class ReplaceRuleAdapter extends BaseListAdapter<ReplaceRuleBean> {
public class ReplaceRuleAdapter extends BaseListAdapter<ReplaceRuleBean> implements Filterable {
private AppCompatActivity activity;
private OnDeleteListener onDeleteListener;
private List<ReplaceRuleBean> beans;
private OnSwipeListener onSwipeListener;
public ReplaceRuleAdapter(AppCompatActivity activity, OnDeleteListener onDeleteListener) {
public ReplaceRuleAdapter(AppCompatActivity activity, List<ReplaceRuleBean> beans, OnSwipeListener onSwipeListener) {
this.activity = activity;
this.onDeleteListener = onDeleteListener;
this.beans = beans;
this.onSwipeListener = onSwipeListener;
}
@Override
protected IViewHolder<ReplaceRuleBean> createViewHolder(int viewType) {
return new ReplaceRuleHolder(activity, onDeleteListener);
return new ReplaceRuleHolder(activity, onSwipeListener);
}
public void removeItem(int pos){
public void removeItem(int pos) {
mList.remove(pos);
notifyItemRemoved(pos);
if (pos != mList.size())
notifyItemRangeChanged(pos, mList.size() - pos);
}
public void toTop(int which, ReplaceRuleBean bean) {
mList.remove(bean);
notifyItemInserted(0);
mList.add(0, bean);
notifyItemRemoved(which);
notifyItemRangeChanged(0, which + 1);
}
public void setBeans(List<ReplaceRuleBean> beans) {
this.beans = beans;
}
public interface OnDeleteListener{
void success(int which);
/**
* 过滤器实现搜索
*
* @return
*/
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
String charString = constraint.toString();
List<ReplaceRuleBean> mFilterList = new ArrayList<>();
if (charString.isEmpty()) {
//没有过滤的内容,则使用源数据
mFilterList = beans;
} else {
for (ReplaceRuleBean bean : beans) {
//这里根据需求,添加匹配规则
if (StringHelper.isEmpty(bean.getReplaceSummary())) {
if (bean.getRegex().contains(charString) ||
bean.getReplacement().contains(charString)) {
mFilterList.add(bean);
}
} else {
if (bean.getReplaceSummary().contains(charString)) {
mFilterList.add(bean);
}
}
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = mFilterList;
return filterResults;
}
//把过滤后的值返回出来
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
refreshItems((List<ReplaceRuleBean>) results.values);
}
};
}
public interface OnSwipeListener {
void onDel(int which, ReplaceRuleBean bean);
void onTop(int which, ReplaceRuleBean bean);
}
}

@ -1,9 +1,8 @@
package xyz.fycz.myreader.ui.adapter;
import android.app.Activity;
import android.text.TextUtils;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.IViewHolder;
@ -24,23 +23,21 @@ import java.util.List;
public class SearchBookAdapter extends BaseListAdapter<SearchBookBean> {
private ConcurrentMultiValueMap<SearchBookBean, Book> mBooks;
private SearchEngine searchEngine;
private String keyWord;
public SearchBookAdapter(ConcurrentMultiValueMap<SearchBookBean, Book> mBooks, SearchEngine searchEngine) {
public SearchBookAdapter(ConcurrentMultiValueMap<SearchBookBean, Book> mBooks, SearchEngine searchEngine, String keyWord) {
this.mBooks = mBooks;
this.searchEngine = searchEngine;
}
public SearchBookAdapter(ConcurrentMultiValueMap<SearchBookBean, Book> mBooks) {
this.mBooks = mBooks;
this.keyWord = keyWord;
}
@Override
protected IViewHolder<SearchBookBean> createViewHolder(int viewType) {
return new SearchBookHolder(mBooks, searchEngine);
return new SearchBookHolder(mBooks, searchEngine, keyWord);
}
public synchronized void addAll(List<SearchBookBean> newDataS, String keyWord) {
List<SearchBookBean> copyDataS = mList;
public void addAll(List<SearchBookBean> newDataS, String keyWord) {
List<SearchBookBean> copyDataS = new ArrayList<>(getItems());
List<SearchBookBean> filterDataS = new ArrayList<>();
switch (SysManager.getSetting().getSearchFilter()) {
@ -110,7 +107,12 @@ public class SearchBookAdapter extends BaseListAdapter<SearchBookBean> {
}
}
}
MyApplication.runOnUiThread(this::notifyDataSetChanged);
synchronized (this) {
App.runOnUiThread(() -> {
mList = copyDataS;
notifyDataSetChanged();
});
}
}
}
}

@ -14,10 +14,6 @@ import xyz.fycz.myreader.greendao.entity.SearchHistory;
import java.util.ArrayList;
/**
* Created by zhao on 2017/7/26.
*/
public class SearchHistoryAdapter extends ArrayAdapter<SearchHistory> {
private int mResourceId;

@ -0,0 +1,17 @@
package xyz.fycz.myreader.ui.adapter;
import xyz.fycz.myreader.base.adapter.BaseListAdapter;
import xyz.fycz.myreader.base.adapter.IViewHolder;
import xyz.fycz.myreader.entity.sourceedit.EditEntity;
import xyz.fycz.myreader.ui.adapter.holder.SourceEditHolder;
/**
* @author fengyue
* @date 2021/2/9 10:08
*/
public class SourceEditAdapter extends BaseListAdapter<EditEntity> {
@Override
protected IViewHolder<EditEntity> createViewHolder(int viewType) {
return new SourceEditHolder();
}
}

@ -0,0 +1,160 @@
package xyz.fycz.myreader.ui.adapter.helper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager.widget.ViewPager;
/**
* @author fengyue
* @date 2021/2/9 10:08
*/
public class ItemTouchCallback extends ItemTouchHelper.Callback {
private ViewPager viewPager;
public void setViewPager(ViewPager viewPager) {
this.viewPager = viewPager;
}
/**
* Item操作的回调
*/
private OnItemTouchListener onItemTouchListener;
/**
* 是否可以拖拽
*/
private boolean isCanDrag = false;
/**
* 是否可以被滑动
*/
private boolean isCanSwipe = false;
/**
* 设置Item操作的回调去更新UI和数据源
*/
public void setOnItemTouchListener(OnItemTouchListener onItemTouchListener) {
this.onItemTouchListener = onItemTouchListener;
}
/**
* 设置是否可以被拖拽
*
* @param canDrag 是true否false
*/
public void setDragEnable(boolean canDrag) {
isCanDrag = canDrag;
}
/**
* 设置是否可以被滑动
*
* @param canSwipe 是true否false
*/
public void setSwipeEnable(boolean canSwipe) {
isCanSwipe = canSwipe;
}
/**
* 当Item被长按的时候是否可以被拖拽
*/
@Override
public boolean isLongPressDragEnabled() {
return isCanDrag;
}
/**
* Item是否可以被滑动(H左右滑动V上下滑动)
*/
@Override
public boolean isItemViewSwipeEnabled() {
return isCanSwipe;
}
/**
* 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
*/
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {// GridLayoutManager
// flag如果值是0,相当于这个功能被关闭
int dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlag = 0;
// create make
return makeMovementFlags(dragFlag, swipeFlag);
} else if (layoutManager instanceof LinearLayoutManager) {// linearLayoutManager
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
int orientation = linearLayoutManager.getOrientation();
int dragFlag = 0;
int swipeFlag = 0;
// 为了方便理解,相当于分为横着的ListView和竖着的ListView
if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是横向的布局
swipeFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
dragFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
} else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是竖向的布局,相当于ListView
dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
swipeFlag = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
}
return makeMovementFlags(dragFlag, swipeFlag);
}
return 0;
}
/**
* 当Item被拖拽的时候被回调
*
* @param recyclerView recyclerView
* @param srcViewHolder 拖拽的ViewHolder
* @param targetViewHolder 目的地的viewHolder
*/
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder srcViewHolder, @NonNull RecyclerView.ViewHolder targetViewHolder) {
if (onItemTouchListener != null) {
return onItemTouchListener.onMove(srcViewHolder.getAdapterPosition(), targetViewHolder.getAdapterPosition());
}
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (onItemTouchListener != null) {
onItemTouchListener.onSwiped(viewHolder.getAdapterPosition());
}
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
final boolean swiping = actionState == ItemTouchHelper.ACTION_STATE_DRAG;
if (viewPager != null) {
viewPager.requestDisallowInterceptTouchEvent(swiping);
}
}
public interface OnItemTouchListener {
/**
* 当某个Item被滑动删除的时候
*
* @param adapterPosition item的position
*/
void onSwiped(int adapterPosition);
/**
* 当两个Item位置互换的时候被回调
*
* @param srcPosition 拖拽的item的position
* @param targetPosition 目的地的Item的position
* @return 开发者处理了操作应该返回true开发者没有处理就返回false
*/
boolean onMove(int srcPosition, int targetPosition);
}
}

@ -0,0 +1,185 @@
package xyz.fycz.myreader.ui.adapter.holder;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentActivity;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.schedulers.Schedulers;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.adapter.ViewHolderImpl;
import xyz.fycz.myreader.base.observer.MyObserver;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.model.ReplaceRuleManager;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.ui.activity.SourceEditActivity;
import xyz.fycz.myreader.ui.adapter.BookSourceAdapter;
import xyz.fycz.myreader.ui.dialog.ReplaceDialog;
import xyz.fycz.myreader.util.ShareUtils;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout;
/**
* @author fengyue
* @date 2021/2/10 16:52
*/
public class BookSourceHolder extends ViewHolderImpl<BookSource> {
private FragmentActivity activity;
private BookSourceAdapter adapter;
private HashMap<BookSource, Boolean> mCheckMap;
private BookSourceAdapter.OnSwipeListener onSwipeListener;
private SwipeMenuLayout layout;
private RelativeLayout rlContent;
private CheckBox cbSourceSelect;
private TextView tvSourceName;
private ImageView ivSwipeLeft;
private Button btTop;
private Button btBan;
private Button btShare;
private Button btDelete;
public BookSourceHolder(FragmentActivity activity, BookSourceAdapter adapter, BookSourceAdapter.OnSwipeListener onSwipeListener) {
this.activity = activity;
this.adapter = adapter;
this.onSwipeListener = onSwipeListener;
mCheckMap = adapter.getCheckMap();
}
@Override
protected int getItemLayoutId() {
return R.layout.item_book_source;
}
@Override
public void initView() {
layout = (SwipeMenuLayout) getItemView();
rlContent = findById(R.id.rl_content);
cbSourceSelect = findById(R.id.cb_source_select);
tvSourceName = findById(R.id.tv_source_name);
ivSwipeLeft = findById(R.id.iv_swipe_left);
btTop = findById(R.id.bt_top);
btBan = findById(R.id.bt_ban);
btShare = findById(R.id.bt_share);
btDelete = findById(R.id.btnDelete);
}
@Override
public void onBind(BookSource data, int pos) {
banOrUse(data);
initClick(data, pos);
layout.setSwipeEnable(!adapter.ismEditState());
if (adapter.ismEditState()) {
cbSourceSelect.setVisibility(View.VISIBLE);
ivSwipeLeft.setVisibility(View.GONE);
} else {
cbSourceSelect.setVisibility(View.GONE);
ivSwipeLeft.setVisibility(View.VISIBLE);
}
cbSourceSelect.setChecked(mCheckMap.get(data));
}
private void initClick(BookSource data, int pos) {
rlContent.setOnClickListener(v -> {
if (adapter.ismEditState()) {
adapter.setCheckedItem(pos);
} else {
Intent intent = new Intent(activity, SourceEditActivity.class);
intent.putExtra(APPCONST.BOOK_SOURCE, data);
activity.getSupportFragmentManager().getFragments().get(1)
.startActivityForResult(intent, APPCONST.REQUEST_EDIT_BOOK_SOURCE);
}
});
btTop.setOnClickListener(v -> {
((SwipeMenuLayout) getItemView()).smoothClose();
BookSourceManager.toTop(data)
.subscribe(new MySingleObserver<Boolean>() {
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
if (aBoolean) {
onSwipeListener.onTop(pos, data);
}
}
});
});
btBan.setOnClickListener(v -> {
((SwipeMenuLayout) getItemView()).smoothClose();
data.setEnable(!data.getEnable());
BookSourceManager.saveData(data)
.subscribe(new MySingleObserver<Boolean>() {
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
if (aBoolean) {
banOrUse(data);
}
}
});
});
btShare.setOnClickListener(v -> {
((SwipeMenuLayout) getItemView()).smoothClose();
ShareUtils.share(activity, GsonExtensionsKt.getGSON().toJson(data));
});
btDelete.setOnClickListener(v -> {
((SwipeMenuLayout) getItemView()).smoothClose();
Observable.create((ObservableOnSubscribe<Boolean>) e -> {
BookSourceManager.removeBookSource(data);
e.onNext(true);
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new MyObserver<Boolean>() {
@Override
public void onNext(Boolean aBoolean) {
onSwipeListener.onDel(pos, data);
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("删除失败");
}
});
});
}
private void banOrUse(BookSource data) {
if (data.getEnable()) {
tvSourceName.setTextColor(getContext().getResources().getColor(R.color.textPrimary));
if (!StringHelper.isEmpty(data.getSourceGroup())) {
tvSourceName.setText(String.format("%s [%s]", data.getSourceName(), data.getSourceGroup()));
} else {
tvSourceName.setText(data.getSourceName());
}
btBan.setText(getContext().getString(R.string.ban));
} else {
tvSourceName.setTextColor(getContext().getResources().getColor(R.color.textSecondary));
if (!StringHelper.isEmpty(data.getSourceGroup())) {
tvSourceName.setText(String.format("(禁用中)%s [%s]", data.getSourceName(), data.getSourceGroup()));
} else {
tvSourceName.setText(String.format("(禁用中)%s", data.getSourceName()));
}
btBan.setText(R.string.enable_use);
}
}
}

@ -1,21 +1,14 @@
package xyz.fycz.myreader.ui.adapter.holder;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.adapter.ViewHolderImpl;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.ui.activity.BookDetailedActivity;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.widget.CoverImageView;
@ -65,12 +58,12 @@ public class BookStoreBookHolder extends ViewHolderImpl<Book> {
tvBookTime.setText(data.getUpdateDate());
if (hasImg){
tvBookImg.setVisibility(View.VISIBLE);
if (!MyApplication.isDestroy(mActivity)) {
if (!App.isDestroy(mActivity)) {
tvBookImg.load(data.getImgUrl(), data.getName(), data.getAuthor());
}
}
if (data.getSource() != null) {
tvBookSource.setText(String.format("书源:%s", BookSource.fromString(data.getSource()).text));
tvBookSource.setText(String.format("书源:%s", BookSourceManager.getSourceNameByStr(data.getSource())));
}else {
tvBookSource.setText(data.getNewestChapterId());
}

@ -0,0 +1,67 @@
package xyz.fycz.myreader.ui.adapter.holder;
import android.widget.CheckBox;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.HashMap;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.adapter.ViewHolderImpl;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.ToastUtils;
/**
* @author fengyue
* @date 2021/2/10 18:28
*/
public class LocalSourceHolder extends ViewHolderImpl<BookSource> {
private HashMap<BookSource, Boolean> mCheckMap;
private CheckBox cbSource;
private TextView tvEnable;
private TextView tvDisable;
private TextView tvCheck;
public LocalSourceHolder(HashMap<BookSource, Boolean> mCheckMap) {
this.mCheckMap = mCheckMap;
}
@Override
protected int getItemLayoutId() {
return R.layout.item_local_source;
}
@Override
public void initView() {
cbSource = findById(R.id.cb_source);
tvEnable = findById(R.id.tv_enable);
tvDisable = findById(R.id.tv_disable);
tvCheck = findById(R.id.tv_check);
}
@Override
public void onBind(BookSource data, int pos) {
banOrUse(data);
cbSource.setChecked(mCheckMap.get(data));
tvEnable.setOnClickListener(v -> {
data.setEnable(true);
banOrUse(data);
});
tvDisable.setOnClickListener(v -> {
data.setEnable(false);
banOrUse(data);
});
tvCheck.setOnClickListener(v -> ToastUtils.showInfo("校验功能即将上线"));
}
private void banOrUse(BookSource data) {
if (data.getEnable()) {
cbSource.setTextColor(getContext().getResources().getColor(R.color.textPrimary));
cbSource.setText(data.getSourceName());
} else {
cbSource.setTextColor(getContext().getResources().getColor(R.color.textSecondary));
cbSource.setText(String.format("(禁用中)%s", data.getSourceName()));
}
}
}

@ -1,9 +1,5 @@
package xyz.fycz.myreader.ui.adapter.holder;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.widget.Button;
import android.widget.RelativeLayout;
@ -42,15 +38,16 @@ import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout;
public class ReplaceRuleHolder extends ViewHolderImpl<ReplaceRuleBean> {
private RelativeLayout rlContent;
private TextView tvRuleSummary;
private Button btTop;
private Button btBan;
private Button btShare;
private Button btDelete;
private AppCompatActivity activity;
private ReplaceRuleAdapter.OnDeleteListener onDeleteListener;
private ReplaceRuleAdapter.OnSwipeListener onSwipeListener;
public ReplaceRuleHolder(AppCompatActivity activity, ReplaceRuleAdapter.OnDeleteListener onDeleteListener) {
public ReplaceRuleHolder(AppCompatActivity activity, ReplaceRuleAdapter.OnSwipeListener onSwipeListener) {
this.activity = activity;
this.onDeleteListener = onDeleteListener;
this.onSwipeListener = onSwipeListener;
}
@ -63,6 +60,7 @@ public class ReplaceRuleHolder extends ViewHolderImpl<ReplaceRuleBean> {
public void initView() {
rlContent = findById(R.id.rl_content);
tvRuleSummary = findById(R.id.tv_rule_summary);
btTop = findById(R.id.bt_top);
btBan = findById(R.id.bt_ban);
btShare = findById(R.id.bt_share);
btDelete = findById(R.id.btnDelete);
@ -82,6 +80,19 @@ public class ReplaceRuleHolder extends ViewHolderImpl<ReplaceRuleBean> {
replaceDialog.show(activity.getSupportFragmentManager(), "");
});
btTop.setOnClickListener(v -> {
((SwipeMenuLayout) getItemView()).smoothClose();
ReplaceRuleManager.toTop(data)
.subscribe(new MySingleObserver<Boolean>() {
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
if (aBoolean){
onSwipeListener.onTop(pos, data);
}
}
});
});
btBan.setOnClickListener(v -> {
((SwipeMenuLayout) getItemView()).smoothClose();
data.setEnable(!data.getEnable());
@ -96,6 +107,7 @@ public class ReplaceRuleHolder extends ViewHolderImpl<ReplaceRuleBean> {
}
});
});
btShare.setOnClickListener(v -> {
((SwipeMenuLayout) getItemView()).smoothClose();
List<ReplaceRuleBean> shareRuleBean = new ArrayList<>();
@ -112,7 +124,7 @@ public class ReplaceRuleHolder extends ViewHolderImpl<ReplaceRuleBean> {
.subscribe(new MyObserver<Boolean>() {
@Override
public void onNext(Boolean aBoolean) {
onDeleteListener.success(pos);
onSwipeListener.onDel(pos, data);
refreshUI();
}

@ -7,14 +7,16 @@ import android.util.Log;
import android.widget.TextView;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.adapter.ViewHolderImpl;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.model.SearchEngine;
import xyz.fycz.myreader.entity.SearchBookBean;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.model.mulvalmap.ConcurrentMultiValueMap;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.utils.KeyWordUtils;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.webapi.crawler.base.BookInfoCrawler;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
@ -29,30 +31,22 @@ import java.util.List;
public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
private ConcurrentMultiValueMap<SearchBookBean, Book> mBooks;
private SearchEngine searchEngine;
private Handler mHandle = new Handler(message -> {
private String keyWord;
switch (message.what) {
case 1:
Book book = (Book) message.obj;
int pos = message.arg1;
initOtherInfo(book);
break;
}
return false;
});
public SearchBookHolder(ConcurrentMultiValueMap<SearchBookBean, Book> mBooks, SearchEngine searchEngine) {
public SearchBookHolder(ConcurrentMultiValueMap<SearchBookBean, Book> mBooks, SearchEngine searchEngine, String keyWord) {
this.mBooks = mBooks;
this.searchEngine = searchEngine;
this.keyWord = keyWord;
}
CoverImageView ivBookImg;
TextView tvBookName;
TextView tvDesc;
TextView tvAuthor;
TextView tvType;
TextView tvSource;
TextView tvNewestChapter;
private CoverImageView ivBookImg;
private TextView tvBookName;
private TextView tvDesc;
private TextView tvAuthor;
private TextView tvType;
private TextView tvSource;
private TextView tvNewestChapter;
@Override
protected int getItemLayoutId() {
return R.layout.listview_search_book_item;
@ -63,7 +57,7 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
ivBookImg = findById(R.id.iv_book_img);
tvBookName = findById(R.id.tv_book_name);
tvAuthor = findById(R.id.tv_book_author);
tvDesc = findById(R.id.tv_book_desc);
tvDesc = findById(R.id.tv_book_desc);
tvType = findById(R.id.tv_book_type);
tvSource = findById(R.id.tv_book_source);
tvNewestChapter = findById(R.id.tv_book_newest_chapter);
@ -76,7 +70,7 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
List<Book> aBooks = mBooks.getValues(data);
int bookCount = aBooks.size();
Book book = aBooks.get(0);
if (StringHelper.isEmpty(book.getImgUrl())){
if (StringHelper.isEmpty(book.getImgUrl())) {
book.setImgUrl("");
}
if (!StringHelper.isEmpty(book.getDesc())) {
@ -89,12 +83,13 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle()));
}
if (!StringHelper.isEmpty(book.getAuthor())) {
tvAuthor.setText(book.getAuthor());
KeyWordUtils.setKeyWord(tvAuthor, book.getAuthor(), keyWord);
}
tvBookName.setText(book.getName());
tvSource.setText(getContext().getString(R.string.source_title_num, BookSource.fromString(book.getSource()).text, bookCount));
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(book.getSource());
if (rc instanceof BookInfoCrawler){
KeyWordUtils.setKeyWord(tvBookName, book.getName(), keyWord);
BookSource source = BookSourceManager.getBookSourceByStr(book.getSource());
tvSource.setText(getContext().getString(R.string.source_title_num, source.getSourceName(), bookCount));
ReadCrawler rc = ReadCrawlerUtil.getReadCrawler(source);
if (rc instanceof BookInfoCrawler) {
if (tvBookName.getTag() == null || !(Boolean) tvBookName.getTag()) {
tvBookName.setTag(true);
} else {
@ -105,19 +100,19 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
BookInfoCrawler bic = (BookInfoCrawler) rc;
searchEngine.getBookInfo(book, bic, isSuccess -> {
if (isSuccess)
mHandle.sendMessage(mHandle.obtainMessage(1, pos, 0, book));
App.runOnUiThread(() -> initOtherInfo(book));
else
tvBookName.setTag(false);
});
}else {
} else {
initOtherInfo(book);
}
}
private void initOtherInfo(Book book){
private void initOtherInfo(Book book) {
//简介
if (StringHelper.isEmpty(tvDesc.getText().toString())) {
tvDesc.setText("简介:" + book.getDesc());
tvDesc.setText(String.format("简介:%s", book.getDesc()));
}
if (StringHelper.isEmpty(tvType.getText().toString())) {
tvType.setText(book.getType());
@ -126,10 +121,10 @@ public class SearchBookHolder extends ViewHolderImpl<SearchBookBean> {
tvNewestChapter.setText(getContext().getString(R.string.newest_chapter, book.getNewestChapterTitle()));
}
if (StringHelper.isEmpty(tvAuthor.getText().toString())) {
tvAuthor.setText(book.getAuthor());
KeyWordUtils.setKeyWord(tvAuthor, book.getAuthor(), keyWord);
}
//图片
if (!MyApplication.isDestroy((Activity) getContext())) {
if (!App.isDestroy((Activity) getContext())) {
ivBookImg.load(book.getImgUrl(), book.getName(), book.getAuthor());
}

@ -0,0 +1,79 @@
package xyz.fycz.myreader.ui.adapter.holder
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.view.View
import android.widget.TextView
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import xyz.fycz.myreader.R
import xyz.fycz.myreader.base.adapter.ViewHolderImpl
import xyz.fycz.myreader.entity.sourceedit.EditEntity
/**
* @author fengyue
* @date 2021/2/9 10:25
*/
class SourceEditHolder : ViewHolderImpl<EditEntity>() {
private var textInputLayout: TextInputLayout? = null
private var editText: TextInputEditText? = null
private var tvTip: TextView? = null
override fun getItemLayoutId(): Int {
return R.layout.item_source_edit
}
override fun initView() {
textInputLayout = findById(R.id.text_input_layout)
editText = findById(R.id.edit_text)
tvTip = findById(R.id.tv_tip)
}
override fun onBind(data: EditEntity, pos: Int) {
if (editText?.getTag(R.id.tag1) == null) {
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
editText?.isCursorVisible = false
editText?.isCursorVisible = true
editText?.isFocusable = true
editText?.isFocusableInTouchMode = true
}
override fun onViewDetachedFromWindow(v: View) {
}
}
editText?.addOnAttachStateChangeListener(listener)
editText?.setTag(R.id.tag1, listener)
}
editText?.getTag(R.id.tag2)?.let {
if (it is TextWatcher) {
editText?.removeTextChangedListener(it)
}
}
textInputLayout?.hint = context.getString(data.hint)
editText?.setText(data.value)
if (data.tip.isNullOrEmpty()) {
tvTip?.visibility = View.GONE
} else {
tvTip?.visibility = View.VISIBLE
tvTip?.text = data.tip
}
val textWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
data.value = (s.toString())
}
}
editText?.addTextChangedListener(textWatcher)
editText?.setTag(R.id.tag2, textWatcher)
}
}

@ -5,8 +5,8 @@ import android.widget.ImageView;
import android.widget.TextView;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.adapter.ViewHolderImpl;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.model.source.BookSourceManager;
/**
* @author fengyue
@ -30,7 +30,7 @@ public class SourceExchangeHolder extends ViewHolderImpl<Book> {
@Override
public void onBind(Book data, int pos) {
sourceTvTitle.setText(BookSource.fromString(data.getSource()).text);
sourceTvTitle.setText(BookSourceManager.getSourceNameByStr(data.getSource()));
sourceTvChapter.setText(data.getNewestChapterTitle());
if (Boolean.parseBoolean(data.getNewestChapterId()))
sourceIv.setVisibility(View.VISIBLE);

@ -8,7 +8,8 @@ import android.os.Handler;
import android.os.Message;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.ui.activity.MainActivity;
@ -48,7 +49,7 @@ public class APPDownloadTip {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (!MyApplication.isDestroy(activity)) {
if (!App.isDestroy(activity)) {
switch (msg.what) {
case 1:
mBookcaseFragment.getTvDownloadTip().setText("获取下载链接失败,请前往浏览器下载!");
@ -81,7 +82,7 @@ public class APPDownloadTip {
error();
return;
}
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
HttpURLConnection con = null;
InputStream is = null;
FileOutputStream fos = null;

@ -16,7 +16,7 @@ import androidx.annotation.NonNull;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.databinding.DialogAudioPlayerBinding;
import xyz.fycz.myreader.model.audio.ReadAloudService;
import xyz.fycz.myreader.ui.activity.ReadActivity;
@ -262,7 +262,7 @@ public class AudioPlayerDialog extends Dialog{
private void updateAloudState(ReadAloudService.Status status) {
aloudStatus = status;
if (!MyApplication.isDestroy(mReadActivity)) {
if (!App.isDestroy(mReadActivity)) {
mReadActivity.autoPageStop();
}
switch (status) {

@ -4,7 +4,8 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.webapi.crawler.*;
@ -127,7 +128,7 @@ public class ChangeSourceDialog {
oneBook = cmvm.getValue(sbb, 0);
threadCount--;
mHandler.sendMessage(mHandler.obtainMessage(1));
MyApplication.getApplication().shutdownThreadPool();
App.getApplication().shutdownThreadPool();
return;
}
if (rc instanceof BookInfoCrawler) {

@ -1,34 +1,17 @@
package xyz.fycz.myreader.ui.dialog;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.*;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.enums.Language;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.enums.ReadStyle;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
import xyz.fycz.myreader.util.BrightUtil;
import xyz.fycz.myreader.util.IOUtils;
import xyz.fycz.myreader.util.StringHelper;
@ -61,7 +44,7 @@ public class DialogCreator {
normalDialog.setNegativeButton("取消", negativeListener);
// 显示
final AlertDialog alertDialog = normalDialog.create();
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
try {
alertDialog.show();
} catch (Exception e) {
@ -101,7 +84,7 @@ public class DialogCreator {
normalDialog.setNegativeButton(key2, key2Listener);
// 显示
// final MyAlertDialog alertDialog = normalDialog.create();
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
try {
// final MyAlertDialog alertDialog = normalDialog.create();
normalDialog.show();
@ -139,7 +122,7 @@ public class DialogCreator {
// 显示
// final MyAlertDialog alertDialog = normalDialog.create();
MyApplication.runOnUiThread(new Runnable() {
App.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
@ -182,7 +165,7 @@ public class DialogCreator {
/* progressDialog.setPositiveButton("确定",positiveListener);
progressDialog.setNegativeButton("取消",negativeListener);*/
// 显示
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
try {
progressDialog.show();
} catch (Exception e) {
@ -226,7 +209,7 @@ public class DialogCreator {
// 第三个按钮
dialog.setPositiveButton(btnText3, positiveListener);
MyApplication.runOnUiThread(new Runnable() {
App.runOnUiThread(new Runnable() {
@Override
public void run() {
try {

@ -1,16 +1,17 @@
package xyz.fycz.myreader.ui.dialog;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@ -18,7 +19,7 @@ import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputLayout;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.greendao.service.BookGroupService;
import xyz.fycz.myreader.util.CyptoUtils;
@ -57,7 +58,7 @@ public class MyAlertDialog {
if (!StringHelper.isEmpty(initText)) editText.setText(initText);
editText.requestFocus();
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
MyApplication.getHandler().postDelayed(() -> imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED), 220);
App.getHandler().postDelayed(() -> imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED), 220);
AlertDialog inputDia;
if (neutralBtn == null) {
inputDia = build(context)
@ -209,6 +210,16 @@ public class MyAlertDialog {
});
}
public static void showTipDialogWithLink(Context context, int msgId){
showTipDialogWithLink(context,"提示", msgId);
}
public static void showTipDialogWithLink(Context context, String title, int msgId){
TextView view = (TextView) LayoutInflater.from(context).inflate(R.layout.dialog_textview, null);
view.setText(msgId);
view.setMovementMethod(LinkMovementMethod.getInstance());
build(context).setTitle(title).setView(view).setPositiveButton("知道了", null).show();
}
public interface OnVerify {
void success(boolean needGoTo);
}

@ -13,7 +13,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.fragment.app.DialogFragment;
import java.util.HashMap;
@ -22,15 +21,14 @@ import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.model.ReplaceRuleManager;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.StringHelper;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
/**
* @author fengyue
@ -119,7 +117,7 @@ public class ReplaceDialog extends DialogFragment {
* 选择书源
*/
private void selectSource(){
List<String> mSources = ReadCrawlerUtil.getAllSources();
List<BookSource> mSources = BookSourceManager.getAllBookSourceByOrderNum();
CharSequence[] mSourcesName = new CharSequence[mSources.size()];
HashMap<CharSequence, Boolean> mSelectSources = new LinkedHashMap<>();
boolean[] isSelects = new boolean[mSources.size()];
@ -128,21 +126,26 @@ public class ReplaceDialog extends DialogFragment {
String selectSource = etRuleSource.getText().toString();
for (CharSequence sourceName : mSources) {
mSourcesName[i] = sourceName;
String source = BookSource.getFromName(String.valueOf(sourceName));
boolean isSelect = selectSource.contains(source);
for (BookSource source : mSources) {
mSourcesName[i] = source.getSourceName();
String sourceStr;
if (StringHelper.isEmpty(source.getSourceEName())){
sourceStr = source.getSourceUrl();
}else {
sourceStr = source.getSourceEName();
}
boolean isSelect = selectSource.contains(sourceStr);
if (isSelect) sSourceCount++;
mSelectSources.put(mSourcesName[i], isSelect);
mSelectSources.put(sourceStr, isSelect);
isSelects[i++] = isSelect;
}
new MultiChoiceDialog().create(activity, "选择书源",
mSourcesName, isSelects, sSourceCount, (dialog, which) -> {
StringBuilder sb = new StringBuilder();
for (CharSequence sourceName : mSelectSources.keySet()) {
if (mSelectSources.get(sourceName)) {
sb.append(BookSource.getFromName(String.valueOf(sourceName)));
for (CharSequence sourceStr : mSelectSources.keySet()) {
if (mSelectSources.get(sourceStr)) {
sb.append(sourceStr);
sb.append(",");
}
}
@ -151,13 +154,20 @@ public class ReplaceDialog extends DialogFragment {
}, null, new DialogCreator.OnMultiDialogListener() {
@Override
public void onItemClick(DialogInterface dialog, int which, boolean isChecked) {
mSelectSources.put(mSourcesName[which], isChecked);
BookSource source = mSources.get(which);
String sourceStr;
if (StringHelper.isEmpty(source.getSourceEName())){
sourceStr = source.getSourceUrl();
}else {
sourceStr = source.getSourceEName();
}
mSelectSources.put(sourceStr, isChecked);
}
@Override
public void onSelectAll(boolean isSelectAll) {
for (CharSequence sourceName : mSelectSources.keySet()) {
mSelectSources.put(sourceName, isSelectAll);
for (CharSequence sourceStr : mSelectSources.keySet()) {
mSelectSources.put(sourceStr, isSelectAll);
}
}
});

@ -126,7 +126,7 @@ public class SourceExchangeDialog extends Dialog {
binding.dialogRvContent.setAdapter(mAdapter);
searchEngine = new SearchEngine();
searchEngine.initSearchEngine(ReadCrawlerUtil.getReadCrawlers());
searchEngine.initSearchEngine(ReadCrawlerUtil.getEnableReadCrawlers());
}
private void initClick() {

@ -0,0 +1,432 @@
package xyz.fycz.myreader.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.annotations.NonNull;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseFragment;
import xyz.fycz.myreader.base.observer.MyObserver;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.FragmentImportSourceBinding;
import xyz.fycz.myreader.greendao.GreenDaoManager;
import xyz.fycz.myreader.greendao.entity.ReplaceRuleBean;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.gen.BookSourceDao;
import xyz.fycz.myreader.model.ReplaceRuleManager;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.ui.activity.BookSourceActivity;
import xyz.fycz.myreader.ui.activity.SourceEditActivity;
import xyz.fycz.myreader.ui.adapter.BookSourceAdapter;
import xyz.fycz.myreader.ui.adapter.helper.ItemTouchCallback;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.util.ShareUtils;
import xyz.fycz.myreader.util.SharedPreUtils;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.ClipBoardUtil;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.util.utils.GsonExtensionsKt;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.widget.DividerItemDecoration;
import xyz.fycz.myreader.widget.swipemenu.SwipeMenuLayout;
import static android.app.Activity.RESULT_OK;
import static android.text.TextUtils.isEmpty;
import static xyz.fycz.myreader.util.UriFileUtil.getPath;
/**
* @author fengyue
* @date 2021/2/10 12:05
*/
public class DIYSourceFragment extends BaseFragment {
private FragmentImportSourceBinding binding;
private final BookSourceActivity sourceActivity;
private List<BookSource> mBookSources;
private BookSourceAdapter mAdapter;
private boolean isSearch;
private PopupMenu featuresMenu;
private ItemTouchCallback itemTouchCallback;
public DIYSourceFragment() {
sourceActivity = (BookSourceActivity) getActivity();
}
public DIYSourceFragment(BookSourceActivity sourceActivity) {
this.sourceActivity = sourceActivity;
}
@Override
protected View bindView(LayoutInflater inflater, ViewGroup container) {
binding = FragmentImportSourceBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
mAdapter = new BookSourceAdapter(getActivity(), new BookSourceAdapter.OnSwipeListener() {
@Override
public void onDel(int which, BookSource bean) {
mBookSources.remove(bean);
mAdapter.removeItem(which);
}
@Override
public void onTop(int which, BookSource bean) {
if (which > 0 && which < mBookSources.size()) {
mBookSources.remove(bean);
mBookSources.add(0, bean);
mAdapter.toTop(which, bean);
}
}
});
refreshSources();
}
@Override
protected void initWidget(Bundle savedInstanceState) {
super.initWidget(savedInstanceState);
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerView.setAdapter(mAdapter);
//设置分割线
binding.recyclerView.addItemDecoration(new DividerItemDecoration(getContext()));
//设置拖拽
itemTouchCallback = new ItemTouchCallback();
itemTouchCallback.setOnItemTouchListener(mAdapter.getItemTouchListener());
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
itemTouchHelper.attachToRecyclerView(binding.recyclerView);
itemTouchCallback.setDragEnable(false);
}
@SuppressLint("ClickableViewAccessibility")
@Override
protected void initClick() {
super.initClick();
binding.recyclerView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP) {
SwipeMenuLayout viewCache = SwipeMenuLayout.getViewCache();
if (null != viewCache) {
viewCache.smoothClose();
}
}
return false;
});
binding.tvNewSource.setOnClickListener(v ->
startActivityForResult(new Intent(getContext(), SourceEditActivity.class),
APPCONST.REQUEST_EDIT_BOOK_SOURCE));
binding.tvImportSource.setOnClickListener(v -> {
MyAlertDialog.build(getContext())
.setTitle("导入书源")
.setItems(R.array.import_rule, (dialog, which) -> {
if (which == 0) {
String text = ClipBoardUtil.paste(getContext());
if (!isEmpty(text)) {
importDataS(text);
} else {
ToastUtils.showError("剪切板内容为空,导入失败");
}
} else if (which == 1) {
ToastUtils.showInfo("请选择书源JSON文件");
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/json");
startActivityForResult(intent, APPCONST.REQUEST_IMPORT_BOOK_SOURCE);
} else {
String[] url = new String[1];
MyAlertDialog.createInputDia(getContext(), "网络导入",
"请输入网址", "", true, 200,
text -> url[0] = text,
(dialog1, which1) -> importDataS(url[0]));
}
}).show();
});
binding.ivGroup.setOnClickListener(this::showSourceGroupMenu);
binding.ivMenu.setOnClickListener(v -> {
if (featuresMenu == null) {
initFeaturesMenu(v);
}
featuresMenu.show();
});
}
public void importDataS(String text) {
Observable<List<BookSource>> observable = BookSourceManager.importSource(text);
if (observable != null) {
observable.subscribe(new MyObserver<List<BookSource>>() {
@Override
public void onNext(List<BookSource> sources) {
int size = sources.size();
if (size > 0) {
refreshSources();
ToastUtils.showSuccess(String.format("成功导入%s个书源", size));
} else {
ToastUtils.showError("格式不对");
}
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("格式不对");
}
});
} else {
ToastUtils.showError("导入失败");
}
}
/**
* 刷新书源
*/
private void refreshSources() {
Single.create((SingleOnSubscribe<List<BookSource>>) emitter -> {
List<BookSource> sourceBeanList;
if (isSearch) {
if (sourceActivity.getSearchView().getQuery().toString().equals("enabled")) {
sourceBeanList = GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.SourceEName.isNull())
.where(BookSourceDao.Properties.Enable.eq(1))
.orderRaw(BookSourceManager.getBookSourceSort())
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
} else {
String term = "%" + sourceActivity.getSearchView().getQuery() + "%";
sourceBeanList = GreenDaoManager.getDaoSession().getBookSourceDao().queryBuilder()
.where(BookSourceDao.Properties.SourceEName.isNull())
.whereOr(BookSourceDao.Properties.SourceName.like(term),
BookSourceDao.Properties.SourceGroup.like(term),
BookSourceDao.Properties.SourceUrl.like(term))
.orderRaw(BookSourceManager.getBookSourceSort())
.orderAsc(BookSourceDao.Properties.OrderNum)
.list();
}
} else {
sourceBeanList = BookSourceManager.getAllNoLocalSource();
}
emitter.onSuccess(sourceBeanList);
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<List<BookSource>>() {
@Override
public void onSuccess(@NonNull List<BookSource> sources) {
mBookSources = sources;
mAdapter.refreshItems(mBookSources);
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage());
}
});
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == APPCONST.REQUEST_IMPORT_BOOK_SOURCE) {
String path = getPath(getContext(), data.getData());
String json = FileUtils.readText(path);
if (!isEmpty(json)) {
importDataS(json);
} else {
ToastUtils.showError("文件读取失败");
}
} else if (requestCode == APPCONST.REQUEST_EDIT_BOOK_SOURCE) {
refreshSources();
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* 显示书源分组菜单
*/
public void showSourceGroupMenu(View view) {
PopupMenu popupMenu = new PopupMenu(sourceActivity, view, Gravity.END);
List<String> groupList = BookSourceManager.getNoLocalGroupList();
popupMenu.getMenu().add(0, 0, 0, "所有书源");
for (int i = 0; i < groupList.size(); i++) {
popupMenu.getMenu().add(0, 0, i + 1, groupList.get(i));
}
popupMenu.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getOrder() > 0) {
sourceActivity.getSearchView().onActionViewExpanded();
sourceActivity.getSearchView().clearFocus();
sourceActivity.getSearchView().setQuery(menuItem.getTitle(), false);
} else {
sourceActivity.getSearchView().onActionViewCollapsed();
}
return true;
});
popupMenu.show();
}
private void initFeaturesMenu(View view) {
featuresMenu = new PopupMenu(sourceActivity, view, Gravity.END);
//获取菜单填充器
MenuInflater inflater = featuresMenu.getMenuInflater();
//填充菜单
inflater.inflate(R.menu.menu_import_source, featuresMenu.getMenu());
featuresMenu.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.action_edit) {
featuresMenu.getMenu().setGroupVisible(R.id.edit_mode, true);
featuresMenu.getMenu().setGroupVisible(R.id.non_edit_mode, false);
mAdapter.setmEditState(true);
itemTouchCallback.setDragEnable(true);
} else if (itemId == R.id.action_finish) {
featuresMenu.getMenu().setGroupVisible(R.id.edit_mode, false);
featuresMenu.getMenu().setGroupVisible(R.id.non_edit_mode, true);
mAdapter.setmEditState(false);
itemTouchCallback.setDragEnable(false);
} else if (itemId == R.id.action_export) {
exportSources(mBookSources);
} else if (itemId == R.id.action_share) {
shareSources(mBookSources);
} else if (itemId == R.id.action_reverse) {
reverseSources(mBookSources);
} else if (itemId == R.id.action_delete) {
deleteSources(mBookSources, true);
} else if (itemId == R.id.action_select_all) {
mAdapter.setCheckedAll(true);
} else if (itemId == R.id.action_reverse_selected) {
mAdapter.reverseChecked();
} else if (itemId == R.id.action_export_selected) {
List<BookSource> bookSources = mAdapter.getCheckedBookSources();
exportSources(bookSources);
} else if (itemId == R.id.action_share_selected) {
List<BookSource> bookSources = mAdapter.getCheckedBookSources();
shareSources(bookSources);
} else if (itemId == R.id.action_reverse_enable_selected) {
List<BookSource> bookSources = mAdapter.getCheckedBookSources();
reverseSources(bookSources);
} else if (itemId == R.id.action_delete_selected) {
List<BookSource> bookSources = mAdapter.getCheckedBookSources();
deleteSources(bookSources, false);
}
return true;
});
}
private void exportSources(List<BookSource> sources) {
if (sources == null || sources.size() == 0) {
ToastUtils.showWarring("当前没有选择任何书源,无法导出!");
return;
}
Single.create((SingleOnSubscribe<Boolean>) emitter -> {
emitter.onSuccess(FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(sources),
FileUtils.getFile(APPCONST.FILE_DIR + "BookSources.json")));
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<Boolean>() {
@Override
public void onSuccess(@NonNull Boolean aBoolean) {
if (aBoolean) {
DialogCreator.createTipDialog(sourceActivity,
"书源导出成功,导出位置:" + APPCONST.FILE_DIR + "BookSources.json");
} else {
ToastUtils.showError("书源导出失败");
}
}
});
}
private void shareSources(List<BookSource> bookSources) {
if (bookSources == null || bookSources.size() == 0) {
ToastUtils.showWarring("当前没有选择任何书源,无法分享!");
return;
}
Single.create((SingleOnSubscribe<File>) emitter -> {
File share = FileUtils.getFile(APPCONST.SHARE_FILE_DIR + "ShareBookSources.json");
if (FileUtils.writeText(GsonExtensionsKt.getGSON().toJson(bookSources), share)){
emitter.onSuccess(share);
}else {
emitter.onError(new Exception("书源文件写出失败"));
}
}).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<File>() {
@Override
public void onSuccess(@NonNull File share) {
ShareUtils.share(sourceActivity, share, "书源分享", "text/plain");
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("书源分享失败\n" + e.getLocalizedMessage());
}
});
}
private void reverseSources(List<BookSource> mBookSources) {
for (BookSource source : mBookSources) {
source.setEnable(!source.getEnable());
}
BookSourceManager.saveDatas(mBookSources);
mAdapter.notifyDataSetChanged();
}
private void deleteSources(List<BookSource> mBookSources, boolean isDisabled) {
if (mBookSources == null || mBookSources.size() == 0) {
ToastUtils.showWarring("当前没有选择任何书源,无法删除!");
return;
}
String title, msg, successTip;
if (isDisabled) {
title = "删除禁用书源";
msg = "确定要删除所有禁用书源吗?";
successTip = "禁用书源删除成功";
} else {
title = "删除选中书源";
msg = "确定要删除所有选中书源吗?";
successTip = "选中书源删除成功";
}
DialogCreator.createCommonDialog(sourceActivity, title,
msg, true,
(dialog, which) -> {
List<BookSource> sources = new ArrayList<>();
if (isDisabled) {
for (BookSource source : mBookSources) {
if (!source.getEnable()) {
sources.add(source);
}
}
} else {
sources.addAll(mBookSources);
}
BookSourceManager.removeBookSources(sources);
mBookSources.removeAll(sources);
mAdapter.removeItems(sources);
ToastUtils.showSuccess(successTip);
}, null);
}
public void startSearch(String newText) {
isSearch = !TextUtils.isEmpty(newText);
refreshSources();
}
}

@ -0,0 +1,125 @@
package xyz.fycz.myreader.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.List;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.annotations.NonNull;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.base.BaseFragment;
import xyz.fycz.myreader.base.observer.MySingleObserver;
import xyz.fycz.myreader.databinding.FragmentLocalSourceBinding;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.ui.activity.BookSourceActivity;
import xyz.fycz.myreader.ui.adapter.LocalSourceAdapter;
import xyz.fycz.myreader.util.ToastUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.widget.DividerItemDecoration;
/**
* @author fengyue
* @date 2021/2/10 18:46
*/
public class LocalSourceFragment extends BaseFragment {
private FragmentLocalSourceBinding binding;
private BookSourceActivity sourceActivity;
private List<BookSource> mBookSources;
private LocalSourceAdapter mAdapter;
public LocalSourceFragment() {
}
public LocalSourceFragment(BookSourceActivity sourceActivity) {
this.sourceActivity = sourceActivity;
}
@Override
protected View bindView(LayoutInflater inflater, ViewGroup container) {
binding = FragmentLocalSourceBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
protected void initData(Bundle savedInstanceState) {
super.initData(savedInstanceState);
Single.create((SingleOnSubscribe<List<BookSource>>) emitter -> emitter.onSuccess(BookSourceManager.getAllLocalSource())).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver<List<BookSource>>() {
@Override
public void onSuccess(@NonNull List<BookSource> sources) {
mBookSources = sources;
initSourceList();
}
@Override
public void onError(Throwable e) {
ToastUtils.showError("数据加载失败\n" + e.getLocalizedMessage());
}
});
}
private void initSourceList() {
mAdapter = new LocalSourceAdapter(mBookSources);
mAdapter.setOnItemClickListener((view, pos) -> {
mAdapter.setCheckedItem(pos);
if (mAdapter.getCheckedCount() == mAdapter.getItemCount()) {
binding.tvSelectAll.setText(R.string.cancle_select_all);
} else {
binding.tvSelectAll.setText(R.string.select_all);
}
});
binding.tvSelectAll.setOnClickListener(v -> {
if (mAdapter.getCheckedCount() != mAdapter.getItemCount()) {
mAdapter.setCheckedAll(true);
binding.tvSelectAll.setText(R.string.cancle_select_all);
} else {
mAdapter.setCheckedAll(false);
binding.tvSelectAll.setText(R.string.select_all);
}
});
binding.tvEnableSelected.setOnClickListener(v -> changeSourcesStatus(true));
binding.tvDisableSelected.setOnClickListener(v -> changeSourcesStatus(false));
binding.tvCheckSelected.setOnClickListener(v -> ToastUtils.showInfo("校验功能即将上线"));
binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerView.setAdapter(mAdapter);
//设置分割线
binding.recyclerView.addItemDecoration(new DividerItemDecoration(getContext()));
mAdapter.refreshItems(mBookSources);
}
private void changeSourcesStatus(boolean isEnable) {
List<BookSource> sources = mAdapter.getCheckedBookSources();
if (sources.size() == 0) {
ToastUtils.showWarring("请选择书源");
} else {
for (BookSource source : sources) {
source.setEnable(isEnable);
}
BookSourceManager.saveDatas(sources)
.subscribe(new MySingleObserver<Boolean>() {
@Override
public void onSuccess(@androidx.annotation.NonNull Boolean aBoolean) {
if (aBoolean) {
mAdapter.notifyDataSetChanged();
}
}
});
}
}
public void startSearch(String newText) {
if (mAdapter != null) {
mAdapter.getFilter().filter(newText);
}
}
}

@ -17,23 +17,28 @@ import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BaseFragment;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.FragmentMineBinding;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.model.source.BookSourceManager;
import xyz.fycz.myreader.model.backup.UserService;
import xyz.fycz.myreader.model.storage.Backup;
import xyz.fycz.myreader.model.storage.Restore;
import xyz.fycz.myreader.ui.activity.AboutActivity;
import xyz.fycz.myreader.ui.activity.BookSourceActivity;
import xyz.fycz.myreader.ui.activity.FeedbackActivity;
import xyz.fycz.myreader.ui.activity.LoginActivity;
import xyz.fycz.myreader.ui.activity.MoreSettingActivity;
import xyz.fycz.myreader.ui.activity.SourceEditActivity;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.util.SharedPreUtils;
@ -91,15 +96,15 @@ public class MineFragment extends BaseFragment {
isLogin = UserService.isLogin();
mSetting = SysManager.getSetting();
webSynMenu = new String[]{
MyApplication.getmContext().getString(R.string.menu_backup_webBackup),
MyApplication.getmContext().getString(R.string.menu_backup_webRestore),
MyApplication.getmContext().getString(R.string.menu_backup_autoSyn)
App.getmContext().getString(R.string.menu_backup_webBackup),
App.getmContext().getString(R.string.menu_backup_webRestore),
App.getmContext().getString(R.string.menu_backup_autoSyn)
};
backupMenu = new String[]{
MyApplication.getmContext().getResources().getString(R.string.menu_backup_backup),
MyApplication.getmContext().getResources().getString(R.string.menu_backup_restore),
App.getmContext().getResources().getString(R.string.menu_backup_backup),
App.getmContext().getResources().getString(R.string.menu_backup_restore),
};
themeMode = MyApplication.getApplication().isNightFS() ? 0 : mSetting.isDayStyle() ? 1 : 2;
themeMode = App.getApplication().isNightFS() ? 0 : mSetting.isDayStyle() ? 1 : 2;
themeModeArr = getResources().getStringArray(R.array.theme_mode_arr);
}
@ -119,7 +124,7 @@ public class MineFragment extends BaseFragment {
if (isLogin) {
DialogCreator.createCommonDialog(getActivity(), "退出登录", "确定要退出登录吗?"
, true, (dialog, which) -> {
File file = MyApplication.getApplication().getFileStreamPath("userConfig.fy");
File file = App.getApplication().getFileStreamPath("userConfig.fy");
if (file.delete()) {
ToastUtils.showSuccess("退出成功");
isLogin = false;
@ -135,6 +140,11 @@ public class MineFragment extends BaseFragment {
getActivity().startActivityForResult(intent, APPCONST.REQUEST_LOGIN);
}
});
binding.mineRlBookSource.setOnClickListener(v -> {
startActivity(new Intent(getContext(), BookSourceActivity.class));
});
binding.mineRlBackup.setOnClickListener(v -> {
AlertDialog bookDialog = MyAlertDialog.build(getContext())
.setTitle(getContext().getResources().getString(R.string.menu_bookcase_backup))
@ -161,9 +171,9 @@ public class MineFragment extends BaseFragment {
return;
}
if (mSetting.isAutoSyn()) {
webSynMenu[2] = MyApplication.getmContext().getString(R.string.menu_backup_autoSyn) + "已开启";
webSynMenu[2] = App.getmContext().getString(R.string.menu_backup_autoSyn) + "已开启";
} else {
webSynMenu[2] = MyApplication.getmContext().getString(R.string.menu_backup_autoSyn) + "已关闭";
webSynMenu[2] = App.getmContext().getString(R.string.menu_backup_autoSyn) + "已关闭";
}
MyAlertDialog.build(getContext())
.setTitle(getActivity().getString(R.string.menu_bookcase_syn))
@ -228,7 +238,7 @@ public class MineFragment extends BaseFragment {
}
dialog.dismiss();
binding.tvThemeModeSelect.setText(themeModeArr[themeMode]);
MyApplication.getApplication().initNightTheme();
App.getApplication().initNightTheme();
})
.setNegativeButton("取消", null)
.create();
@ -269,7 +279,7 @@ public class MineFragment extends BaseFragment {
} else {
DialogCreator.createTipDialog(getContext(), "未给予储存权限,备份失败!");
}*/
Backup.INSTANCE.backup(MyApplication.getmContext(), APPCONST.BACKUP_FILE_DIR, new Backup.CallBack() {
Backup.INSTANCE.backup(App.getmContext(), APPCONST.BACKUP_FILE_DIR, new Backup.CallBack() {
@Override
public void backupSuccess() {
DialogCreator.createTipDialog(getContext(), "备份成功,备份文件路径:" + APPCONST.BACKUP_FILE_DIR);
@ -376,7 +386,7 @@ public class MineFragment extends BaseFragment {
DialogCreator.createCommonDialog(getContext(), "确认同步吗?", "将书架从网络同步至本地会覆盖原有书架!", true,
(dialogInterface, i) -> {
dialogInterface.dismiss();
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
/*if (UserService.webRestore()) {
mHandler.sendMessage(mHandler.obtainMessage(7));
// DialogCreator.createTipDialog(mMainActivity,

@ -12,7 +12,7 @@ import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BaseFragment;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.databinding.FragmentPrivateBookcaseBinding;
@ -97,7 +97,7 @@ public class PrivateBooksFragment extends BaseFragment {
});
binding.llHideBooks.setOnClickListener(v -> {
MyApplication.runOnUiThread(() -> {
App.runOnUiThread(() -> {
String privateGroupId = SharedPreUtils.getInstance().getString("privateGroupId");
List<Book> mBooks = BookService.getInstance().getAllBooks();

@ -9,41 +9,30 @@ import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.PopupMenu;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import java.text.Collator;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.android.material.textfield.TextInputLayout;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.MyApplication;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.application.SysManager;
import xyz.fycz.myreader.base.BasePresenter;
import xyz.fycz.myreader.ui.dialog.BookGroupDialog;
import xyz.fycz.myreader.webapi.callback.ResultCallback;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.ui.dialog.MultiChoiceDialog;
import xyz.fycz.myreader.ui.dialog.MyAlertDialog;
import xyz.fycz.myreader.greendao.entity.BookGroup;
import xyz.fycz.myreader.greendao.service.BookGroupService;
@ -54,7 +43,6 @@ import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.ui.dialog.DialogCreator;
import xyz.fycz.myreader.widget.custom.DragSortGridView;
import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.enums.BookSource;
import xyz.fycz.myreader.enums.BookcaseStyle;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.Chapter;
@ -69,7 +57,7 @@ import xyz.fycz.myreader.util.notification.NotificationUtil;
import xyz.fycz.myreader.util.utils.NetworkUtils;
import xyz.fycz.myreader.webapi.CommonApi;
import static xyz.fycz.myreader.application.MyApplication.checkVersionByServer;
import static xyz.fycz.myreader.application.App.checkVersionByServer;
public class BookcasePresenter implements BasePresenter {
@ -123,8 +111,8 @@ public class BookcasePresenter implements BasePresenter {
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
if (!MyApplication.isDestroy(mMainActivity)) {
MyApplication.runOnUiThread(() -> mBookcaseAdapter.notifyDataSetChanged());
if (!App.isDestroy(mMainActivity)) {
App.runOnUiThread(() -> mBookcaseAdapter.notifyDataSetChanged());
finishLoadBookCount++;
mBookcaseFragment.getSrlContent().finishRefresh();
}
@ -137,7 +125,7 @@ public class BookcasePresenter implements BasePresenter {
break;
case 4:
showErrorLoadingBooks();
if (MyApplication.isApkInDebug(mMainActivity)) {
if (App.isApkInDebug(mMainActivity)) {
if (isFirstRefresh) {
initBook();
isFirstRefresh = false;
@ -435,7 +423,7 @@ public class BookcasePresenter implements BasePresenter {
});
es.submit(update);
}
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
while (true) {
if (finishLoadBookCount == mBooks.size()) {
mHandler.sendMessage(mHandler.obtainMessage(4));
@ -553,7 +541,7 @@ public class BookcasePresenter implements BasePresenter {
*/
private void editBookcase(boolean isEdit) {
if (isEdit) {
if (mBooks.size() > 0) {
if (canEditBookcase()) {
mBookcaseFragment.getSrlContent().setEnableRefresh(false);
mBookcaseAdapter.setmEditState(true);
if (mSetting.getSortStyle() == 0) {
@ -582,6 +570,10 @@ public class BookcasePresenter implements BasePresenter {
}
}
public boolean canEditBookcase(){
return mBooks.size() > 0;
}
/**
* 分组管理对话框
*/
@ -657,10 +649,10 @@ public class BookcasePresenter implements BasePresenter {
ToastUtils.showWarring("当前书架没有任何书籍,无法一键缓存!");
return;
}
MyApplication.getApplication().newThread(() -> {
App.getApplication().newThread(() -> {
ArrayList<Book> needDownloadBooks = new ArrayList<>();
for (Book book : mBooks) {
//if (!BookSource.pinshu.toString().equals(book.getSource()) && !"本地书籍".equals(book.getType())
//if (!LocalBookSource.pinshu.toString().equals(book.getSource()) && !"本地书籍".equals(book.getType())
if (!"本地书籍".equals(book.getType())
&& book.getIsDownLoadAll()) {
needDownloadBooks.add(book);
@ -707,7 +699,7 @@ public class BookcasePresenter implements BasePresenter {
Notification notification = notificationUtil.build(APPCONST.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
//通知栏大图标
.setLargeIcon(BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.mipmap.ic_launcher))
.setLargeIcon(BitmapFactory.decodeResource(App.getApplication().getResources(), R.mipmap.ic_launcher))
.setOngoing(false)
//点击通知后自动清除
.setAutoCancel(true)
@ -840,7 +832,7 @@ public class BookcasePresenter implements BasePresenter {
Notification notification = notificationUtil.build(APPCONST.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
//通知栏大图标
.setLargeIcon(BitmapFactory.decodeResource(MyApplication.getApplication().getResources(), R.mipmap.ic_launcher))
.setLargeIcon(BitmapFactory.decodeResource(App.getApplication().getResources(), R.mipmap.ic_launcher))
.setOngoing(true)
//点击通知后自动清除
.setAutoCancel(true)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save