Merge pull request #3 from gedoor/master

update
pull/145/head
52fisher 5 years ago committed by GitHub
commit 29ce26bbb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 15
      app/build.gradle
  3. 11
      app/proguard-rules.pro
  4. 60
      app/src/main/AndroidManifest.xml
  5. 47
      app/src/main/assets/updateLog.md
  6. 53
      app/src/main/assets/web/bookshelf.html
  7. 70
      app/src/main/assets/web/bookshelf.js
  8. 639
      app/src/main/assets/web/index.html
  9. 177
      app/src/main/assets/web/index.js
  10. 44
      app/src/main/java/io/legado/app/App.kt
  11. 12
      app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
  12. 2
      app/src/main/java/io/legado/app/base/BaseFragment.kt
  13. 22
      app/src/main/java/io/legado/app/constant/AppConst.kt
  14. 4
      app/src/main/java/io/legado/app/constant/AppPattern.kt
  15. 1
      app/src/main/java/io/legado/app/constant/EventBus.kt
  16. 2
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  17. 17
      app/src/main/java/io/legado/app/constant/RSSKeywords.kt
  18. 2
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  19. 23
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  20. 14
      app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt
  21. 6
      app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt
  22. 3
      app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt
  23. 3
      app/src/main/java/io/legado/app/data/entities/Book.kt
  24. 9
      app/src/main/java/io/legado/app/data/entities/BookProgress.kt
  25. 19
      app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt
  26. 27
      app/src/main/java/io/legado/app/help/ActivityHelp.kt
  27. 24
      app/src/main/java/io/legado/app/help/AppConfig.kt
  28. 13
      app/src/main/java/io/legado/app/help/BlurTransformation.kt
  29. 27
      app/src/main/java/io/legado/app/help/BookHelp.kt
  30. 7
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  31. 55
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  32. 10
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  33. 25
      app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
  34. 30
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  35. 50
      app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt
  36. 40
      app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt
  37. 12
      app/src/main/java/io/legado/app/lib/theme/ATHUtils.kt
  38. 5
      app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt
  39. 8
      app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
  40. 2
      app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt
  41. 1
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
  42. 76
      app/src/main/java/io/legado/app/model/WebBook.kt
  43. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt
  44. 3
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt
  45. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt
  46. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt
  47. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  48. 4
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  49. 2
      app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt
  50. 15
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  51. 38
      app/src/main/java/io/legado/app/model/rss/RssParser.kt
  52. 2
      app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt
  53. 6
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  54. 3
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  55. 4
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  56. 59
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  57. 37
      app/src/main/java/io/legado/app/service/DownloadService.kt
  58. 3
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  59. 4
      app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
  60. 6
      app/src/main/java/io/legado/app/service/WebService.kt
  61. 24
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  62. 14
      app/src/main/java/io/legado/app/ui/README.md
  63. 16
      app/src/main/java/io/legado/app/ui/about/AboutActivity.kt
  64. 18
      app/src/main/java/io/legado/app/ui/about/DonateFragment.kt
  65. 17
      app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt
  66. 30
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  67. 12
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt
  68. 9
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt
  69. 74
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt
  70. 56
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
  71. 2
      app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt
  72. 52
      app/src/main/java/io/legado/app/ui/book/changecover/DiffCallBack.kt
  73. 2
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt
  74. 31
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
  75. 20
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  76. 2
      app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt
  77. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt
  78. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkFragment.kt
  79. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt
  80. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt
  81. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt
  82. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt
  83. 4
      app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt
  84. 2
      app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt
  85. 2
      app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt
  86. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt
  87. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt
  88. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
  89. 38
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  90. 60
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  91. 8
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  92. 2
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  93. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  94. 4
      app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt
  95. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt
  96. 21
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  97. 4
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  98. 74
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  99. 5
      app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
  100. 31
      app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,7 +3,7 @@
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
## 阅读3.0 ## 阅读3.0
书源规则 https://celeter.github.io/?tdsourcetag=s_pctim_aiomsg 书源规则 https://celeter.github.io
## 免责声明 ## 免责声明
https://gedoor.github.io/MyBookshelf/disclaimer.html https://gedoor.github.io/MyBookshelf/disclaimer.html

@ -55,7 +55,7 @@ android {
signingConfig signingConfigs.myConfig signingConfig signingConfigs.myConfig
} }
applicationIdSuffix '.release' applicationIdSuffix '.release'
minifyEnabled false minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
debug { debug {
@ -75,8 +75,11 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility = '1.8' // Flag to enable support for the new language APIs
targetCompatibility = '1.8' //coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions { kotlinOptions {
@ -103,7 +106,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//fireBase //fireBase
implementation 'com.google.firebase:firebase-core:17.2.2' implementation 'com.google.firebase:firebase-core:17.2.3'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
//androidX //androidX
@ -137,7 +140,7 @@ dependencies {
implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version" implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version"
//liveEventBus //liveEventBus
implementation 'com.jeremyliao:live-event-bus-x:1.4.5' implementation 'com.jeremyliao:live-event-bus-x:1.5.7'
// //
def coroutines_version = '1.3.3' def coroutines_version = '1.3.3'
@ -164,7 +167,7 @@ dependencies {
implementation 'org.nanohttpd:nanohttpd-websocket:2.3.1' implementation 'org.nanohttpd:nanohttpd-websocket:2.3.1'
// //
implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.6' implementation 'cn.bingoogolapple:bga-qrcode-zxing:1.3.7'
// //
implementation 'com.jaredrummler:colorpicker:1.1.0' implementation 'com.jaredrummler:colorpicker:1.1.0'

@ -154,6 +154,8 @@
-keep class **.analyzeRule.**{*;} -keep class **.analyzeRule.**{*;}
# 保持web类 # 保持web类
-keep class **.web.**{*;} -keep class **.web.**{*;}
#数据类
-keep class **.data.**{*;}
-dontwarn rx.** -dontwarn rx.**
@ -166,7 +168,10 @@
-dontnote org.python.core.** -dontnote org.python.core.**
-dontwarn com.hwangjr.rxbus.** -dontwarn com.hwangjr.rxbus.**
-dontwarn okhttp3.** -dontwarn okhttp3.**
-dontwarn org.conscrypt.**
-dontwarn com.jeremyliao.liveeventbus.**
-keep class com.jeremyliao.liveeventbus.** { *; }
-keep class retrofit2.**{*;} -keep class retrofit2.**{*;}
-keep class okhttp3.**{*;} -keep class okhttp3.**{*;}
-keep class okio.**{*;} -keep class okio.**{*;}
@ -183,6 +188,7 @@
-keep class com.gyf.barlibrary.* {*;} -keep class com.gyf.barlibrary.* {*;}
##JSOUP ##JSOUP
-keep class org.jsoup.**{*;} -keep class org.jsoup.**{*;}
-keep class **.xpath.**{*;}
-keep class org.slf4j.**{*;} -keep class org.slf4j.**{*;}
-dontwarn org.slf4j.** -dontwarn org.slf4j.**
@ -206,8 +212,6 @@
-keep class javax.script.** { *; } -keep class javax.script.** { *; }
-keep class com.sun.script.javascript.** { *; } -keep class com.sun.script.javascript.** { *; }
-keep class org.mozilla.javascript.** { *; } -keep class org.mozilla.javascript.** { *; }
-dontwarn org.mozilla.javascript.**
-dontwarn sun.**
###EPUB ###EPUB
-dontwarn nl.siegmann.epublib.** -dontwarn nl.siegmann.epublib.**
@ -222,9 +226,6 @@
-keepclassmembers class * { -keepclassmembers class * {
public <init> (org.json.JSONObject); public <init> (org.json.JSONObject);
} }
-keep public class com.kunfei.bookshelf.R$*{
public static final int *;
}
-keepclassmembers enum * { -keepclassmembers enum * {
public static **[] values(); public static **[] values();
public static ** valueOf(java.lang.String); public static ** valueOf(java.lang.String);

@ -133,18 +133,24 @@
android:name=".ui.about.DonateActivity" android:name=".ui.about.DonateActivity"
android:launchMode="singleTask" /> android:launchMode="singleTask" />
<!--书源管理--> <!--书源管理-->
<activity android:name=".ui.book.source.manage.BookSourceActivity"> <activity
android:name=".ui.book.source.manage.BookSourceActivity"
android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:host="booksource" android:host="booksource"
android:scheme="yuedu" /> android:scheme="yuedu" />
</intent-filter> </intent-filter>
</activity> </activity>
<!--订阅源管理--> <!--订阅源管理-->
<activity android:name=".ui.rss.source.manage.RssSourceActivity"> <activity
android:name=".ui.rss.source.manage.RssSourceActivity"
android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -159,7 +165,7 @@
<!--替换规则界面--> <!--替换规则界面-->
<activity <activity
android:name=".ui.replacerule.ReplaceRuleActivity" android:name=".ui.replacerule.ReplaceRuleActivity"
android:launchMode="singleTask"> android:launchMode="singleTop">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -171,16 +177,43 @@
android:scheme="yuedu" /> android:scheme="yuedu" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ui.book.arrange.ArrangeBookActivity" /> <activity
<activity android:name=".ui.book.source.debug.BookSourceDebugActivity" /> android:name=".ui.book.arrange.ArrangeBookActivity"
<activity android:name=".ui.chapterlist.ChapterListActivity" /> android:launchMode="singleTop" />
<activity android:name=".ui.rss.read.ReadRssActivity" /> <activity
<activity android:name=".ui.importbook.ImportBookActivity" /> android:name=".ui.book.source.debug.BookSourceDebugActivity"
<activity android:name=".ui.explore.ExploreShowActivity" /> android:launchMode="singleTop" />
<activity android:name=".ui.rss.source.debug.RssSourceDebugActivity" /> <activity
<activity android:name=".ui.rss.article.RssArticlesActivity" /> android:name="io.legado.app.ui.book.chapterlist.ChapterListActivity"
<activity android:name=".ui.rss.favorites.RssFavoritesActivity" /> android:launchMode="singleTop" />
<activity android:name=".ui.download.DownloadActivity" /> <!--RSS阅读-->
<activity
android:name=".ui.rss.read.ReadRssActivity"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true"
android:launchMode="singleTop" />
<activity
android:name="io.legado.app.ui.book.local.ImportBookActivity"
android:launchMode="singleTop" />
<activity
android:name="io.legado.app.ui.book.explore.ExploreShowActivity"
android:launchMode="singleTop" />
<activity
android:name=".ui.rss.source.debug.RssSourceDebugActivity"
android:launchMode="singleTop" />
<activity
android:name=".ui.rss.article.RssArticlesActivity"
android:launchMode="singleTop" />
<activity
android:name=".ui.rss.favorites.RssFavoritesActivity"
android:launchMode="singleTop" />
<activity
android:name="io.legado.app.ui.book.download.DownloadActivity"
android:launchMode="singleTop" />
<activity
android:name=".ui.login.SourceLogin"
android:configChanges="orientation|screenSize"
android:hardwareAccelerated="true" />
<activity <activity
android:name=".receiver.SharedReceiverActivity" android:name=".receiver.SharedReceiverActivity"
android:label="@string/receiving_shared_label"> android:label="@string/receiving_shared_label">
@ -195,7 +228,6 @@
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ui.login.SourceLogin" />
<service android:name=".service.CheckSourceService" /> <service android:name=".service.CheckSourceService" />
<service android:name=".service.DownloadService" /> <service android:name=".service.DownloadService" />

@ -2,10 +2,57 @@
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 * 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
**2020/03/09**
* 底部文字对齐
* 主题添加阴影调节 by yangyxd
**2020/03/08**
* 订阅长按保存图片
* 订阅全屏播放
* 书架全部分组可以隐藏了
* 内置web书架基本能用了 by 六月
* 书架整理加入未分组
* 显示总进度
* 隐藏状态栏时,标题显示在上方
**2020/03/07**
* 添加标题上下间距调整
* 添加标题大小调整
* 书籍整理添加批量启用禁用更新
* 换源禁用书源不显示
* 修复搜索界面简介最下面显示半行文字
* 搜索历史改为多行
**2020/03/06**
* 添加隐藏标题
* 行距段距改成倍距,根据字体大小变化
* 修复翻页时右下角页数闪烁
* 修复朗读错行
* 添加底部分隔线,开关在边距设置里
**2020/03/05**
* 修复翻页动画
* 修复主题模式跟随
* 修复滚动翻页切换章节时跳动
* 适配阅读3.0的web做源
* 本地目录规则网络导入
**2020/03/04**
* 修复仿真翻页动画
* 添加阅读记录同步,正常退出进入软件时同步阅读记录
**2020/03/03**
* 修复bug
* 优化排版,确保段距为0时每行在相同的位置
* 修复底部遮挡
**2020/03/02** **2020/03/02**
* 添加书源登录 * 添加书源登录
* 替换规则实时生效 * 替换规则实时生效
* 页面最后一行计算是否能放下时不计算行距 * 页面最后一行计算是否能放下时不计算行距
* 优化翻页动画
* 优化书源校验
* 按键翻页有动画了
**2020/03/01** **2020/03/01**
* 修复书源解析的一个bug * 修复书源解析的一个bug

@ -1,32 +1,39 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8" />
<title>阅读书架</title> <title>阅读3.0书架</title>
<link href="bookshelf.css" rel="stylesheet"/> <link rel="icon" href="favicon.ico">
<link href="bookshelf.css" rel="stylesheet" />
</head> </head>
<body> <body>
<button id="top" class="top"></button> <button id="top" class="top"></button>
<button id="showchapter" class="showchapter"></button> <button id="showchapter" class="showchapter"></button>
<button id="hidebooks" class="hidebooks"></button> <button id="hidebooks" class="hidebooks"></button>
<div class="nav"> <div class="nav">
<button id="back">返回</button> <button id="back">返回</button>
<button id="type">所有书籍 ▼</button> <button id="type">所有书籍 ▼</button>
<button id="sort">手动排序 ▼</button> <button id="sort">手动排序 ▼</button>
<button id="setting">阅读设置</button> <button id="setting">阅读设置</button>
<input type="text" class="address" id="address" title="阅读APP地址或IP" value=""/> <input type="text" class="address" id="address" title="阅读APP地址或IP" value="" />
<button id="refresh">重新加载</button> <button id="refresh">重新加载</button>
</div> </div>
<div class="allcontent" id="allcontent"> <div class="allcontent" id="allcontent">
<div id="books" class="books"></div> <div id="books" class="books"></div>
<div id="more" class="more"> <div id="more" class="more">
<div id="info" class="info"></div> <div id="info" class="info"></div>
<div class="clear"></div> <div class="clear"></div>
<div id="chapter" class="chapter"></div> <div id="chapter" class="chapter"></div>
<div id="content" class="content"></div> <div id="content" class="content"></div>
<div id="page" class="button">
<center><button id='up'>上一章</button><button id='down'>下一章</button></center>
</div>
</div>
</div> </div>
</div> <script src="bookshelf.js"></script>
<script src="bookshelf.js"></script>
</body> </body>
</html> </html>

@ -1,11 +1,14 @@
var $ = document.querySelector.bind(document) var $ = document.querySelector.bind(document)
, $$ = document.querySelectorAll.bind(document) , $$ = document.querySelectorAll.bind(document)
, $c = document.createElement.bind(document) , $c = document.createElement.bind(document)
, randomImg = "http://acg.bakayun.cn/randbg.php?t=dfzh" , randomImg = "http://api.mtyqx.cn/api/random.php"
, randomImg2 = "http://img.xjh.me/random_img.php" , randomImg2 = "http://img.xjh.me/random_img.php"
, books , books
; ;
var now_chapter = -1;
var sum_chapter = 0;
var formatTime = value => { var formatTime = value => {
return new Date(value).toLocaleString('zh-CN', { return new Date(value).toLocaleString('zh-CN', {
hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"
@ -13,13 +16,13 @@ var formatTime = value => {
}; };
var apiMap = { var apiMap = {
getBookshelf: "/getBookshelf", "getBookshelf": "/getBookshelf",
getChapterList: "/getChapterList", "getChapterList": "/getChapterList",
getBookContent: "/getBookContent", "getBookContent": "/getBookContent",
saveBook: "/saveBook" "saveBook": "/saveBook"
}; };
var apiAddress = (apiName, url) => { var apiAddress = (apiName, url, index) => {
let address = $('#address').value || window.location.host; let address = $('#address').value || window.location.host;
if (!(/^http|^\/\//).test(address)) { if (!(/^http|^\/\//).test(address)) {
address = "//" + address; address = "//" + address;
@ -28,6 +31,9 @@ var apiAddress = (apiName, url) => {
address += ":1122"; address += ":1122";
} }
localStorage.setItem('address', address); localStorage.setItem('address', address);
if (apiName == "getBookContent") {
return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "") + "&index=" + index;
}
return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : ""); return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "");
}; };
@ -41,12 +47,12 @@ var init = () => {
alert(getBookshelf.errorMsg); alert(getBookshelf.errorMsg);
return; return;
} }
books = data.data.sort((book1, book2) => book1.serialNumber - book2.serialNumber); books = data.data;
books.forEach(book => { books.forEach((book, i) => {
let bookDiv = $c("div"); let bookDiv = $c("div");
let img = $c("img"); let img = $c("img");
img.src = book.coverUrl || randomImg; img.src = book.coverUrl || randomImg;
img.setAttribute("data-series-num", book.serialNumber); img.setAttribute("data-series-num", i);
bookDiv.appendChild(img); bookDiv.appendChild(img);
bookDiv.innerHTML += `<table><tbody> bookDiv.innerHTML += `<table><tbody>
<tr><td>书名</td><td>${book.name}</td></tr> <tr><td>书名</td><td>${book.name}</td></tr>
@ -59,6 +65,8 @@ var init = () => {
}); });
$$('#books img').forEach(bookImg => $$('#books img').forEach(bookImg =>
bookImg.addEventListener("click", () => { bookImg.addEventListener("click", () => {
now_chapter = -1;
sum_chapter = 0;
$('#allcontent').classList.add("read"); $('#allcontent').classList.add("read");
var book = books[bookImg.getAttribute("data-series-num")]; var book = books[bookImg.getAttribute("data-series-num")];
$("#info").innerHTML = `<img src="${bookImg.src}"> $("#info").innerHTML = `<img src="${bookImg.src}">
@ -85,11 +93,13 @@ var init = () => {
data.data.forEach(chapter => { data.data.forEach(chapter => {
let ch = $c("button"); let ch = $c("button");
ch.setAttribute("data-url", chapter.durChapterUrl); ch.setAttribute("data-url", chapter.bookUrl);
ch.setAttribute("title", chapter.durChapterName); ch.setAttribute("data-index", chapter.index);
ch.innerHTML = chapter.durChapterName.length > 15 ? chapter.durChapterName.substring(0, 14) + "..." : chapter.durChapterName; ch.setAttribute("title", chapter.title);
ch.innerHTML = chapter.title.length > 15 ? chapter.title.substring(0, 14) + "..." : chapter.title;
$("#chapter").appendChild(ch); $("#chapter").appendChild(ch);
}); });
sum_chapter = data.data.length;
$('#chapter').scrollTop = 0; $('#chapter').scrollTop = 0;
$("#content").innerHTML = "章节列表加载完成!"; $("#content").innerHTML = "章节列表加载完成!";
}); });
@ -126,15 +136,47 @@ $('#showchapter').addEventListener("click", () => {
window.location.hash = "#chapter"; window.location.hash = "#chapter";
}); });
$('#up').addEventListener('click', e => {
if (now_chapter > 0) {
now_chapter--;
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent("click", true, false);
$('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent);
} else if (now_chapter == 0) {
alert("已经是第一章了^_^!")
} else {
}
});
$('#down').addEventListener('click', e => {
if (now_chapter > -1) {
if (now_chapter < sum_chapter - 1) {
now_chapter++;
let clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent("click", true, false);
$('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent);
} else {
alert("已经是最后一章了^_^!")
}
}
});
$('#chapter').addEventListener("click", (e) => { $('#chapter').addEventListener("click", (e) => {
if (e.target.tagName === "BUTTON") { if (e.target.tagName === "BUTTON") {
var url = e.target.getAttribute("data-url"); var url = e.target.getAttribute("data-url");
var index = e.target.getAttribute("data-index");
var name = e.target.getAttribute("title"); var name = e.target.getAttribute("title");
if (!url) { if (!url) {
alert("未取得章节地址"); alert("未取得书籍地址");
}
if (!index && (0 != index)) {
alert("未取得章节索引");
} }
now_chapter = parseInt(index);
$("#content").innerHTML = "<p>" + name + " 加载中...</p>"; $("#content").innerHTML = "<p>" + name + " 加载中...</p>";
fetch(apiAddress("getBookContent", url), { mode: "cors" }) fetch(apiAddress("getBookContent", url, index), { mode: "cors" })
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (!data.isSuccess) { if (!data.isSuccess) {

@ -3,305 +3,370 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>书源编辑器v3.8</title> <title>阅读3.0书源编辑器_V4.0</title>
<link rel="stylesheet" type="text/css" href="/index.css"/> <link rel="icon" href="favicon.ico">
<link rel="stylesheet" type="text/css" href="index.css" />
</head> </head>
<body> <body>
<div class="editor"> <div class="editor">
<div class="setbox"> <div class="setbox">
<div class="rules"> <div class="rules">
<div><b>书源基础信息</b></div> <div><b>基本</b></div>
<div> <div>
<div>书源名称:</div> <div>源URL :</div>
<textarea rows="1" id="bookSourceName" placeholder="书源名称(bookSourceName) | 会显示在书源列表"></textarea> <textarea rows="1" id="bookSourceUrl" class="base" title="bookSourceUrl"
</div> placeholder="<必填>通常填写网站主页,例: https://www.qidian.com"></textarea>
<div> </div>
<div>书源分组:</div> <div>
<textarea rows="1" id="bookSourceGroup" placeholder="书源分组(bookSourceGroup) | 描述书源的特征信息"></textarea> <div>源类型 :</div>
</div> <textarea rows="1" id="bookSourceType" class="base" title="bookSourceType"
<div> placeholder="&lt;必填&gt;0:文本 1:音频"></textarea>
<div>书源域名:</div> </div>
<textarea rows="1" id="bookSourceUrl" <div>
placeholder="书源URL(bookSourceUrl) | 通常填写网站主页(标头不可省略),例: https://www.qidian.com"></textarea> <div>源名称 :</div>
</div> <textarea rows="1" id="bookSourceName" class="base" title="bookSourceName"
<div> placeholder="&lt;必填&gt;会显示在书源列表"></textarea>
<div>登录网页:</div> </div>
<textarea rows="1" id="loginUrl" placeholder="登录URL(loginUrl) | 填写网站登录网址,仅在需要登录的书源有用"></textarea> <div>
</div> <div>源分组 :</div>
<div><b>书籍发现规则</b></div> <textarea rows="1" id="bookSourceGroup" class="base" title="bookSourceGroup"
<div> placeholder="&lt;选填&gt;描述书源的特征信息"></textarea>
<div>发现菜单:</div> </div>
<textarea rows="5" id="ruleFindUrl" <div>
placeholder="发现分类菜单规则(ruleFindUrl),将显示在发现菜单&#10;每行一条发现分类(网址域名可省略):&#10;名称1::网址(Url)1&#10;名称2::网址(Url)2&#10;..."></textarea> <div>登录地址:</div>
</div> <textarea rows="1" id="loginUrl" class="base" title="loginUrl"
<div> placeholder="&lt;选填&gt;填写网站登录网址,仅在需要登录的书源有用"></textarea>
<div>结果列表:</div> </div>
<textarea rows="1" id="ruleFindList" <div>
placeholder="发现页列表规则(ruleFindList) | 选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea> <div>链接验证:</div>
</div> <textarea rows="1" id="bookUrlPattern" class="base" title="bookUrlPattern"
<div> placeholder="&lt;选填&gt;当详情页URL与源URL的域名不一致时有效,用于添加网址"></textarea>
<div>书籍名称:</div> </div>
<textarea rows="1" id="ruleFindName" <div>
placeholder="发现页书名规则(ruleFindName) | 选择节点书名 (规则结果为String)"></textarea> <div>请求头 :</div>
</div> <textarea rows="3" id="header" class="base" title="header" placeholder="&lt;选填&gt;客户端标识"></textarea>
<div> </div>
<div>书籍作者:</div> <p></p>
<textarea rows="1" id="ruleFindAuthor" <div><b>搜索</b></div>
placeholder="发现页作者规则(ruleFindAuthor) | 选择节点作者 (规则结果为String)"></textarea> <div>
</div> <div>搜索地址:</div>
<div> <textarea rows="1" id="searchUrl" class="base" title="searchUrl"
<div>书籍分类:</div> placeholder="[域名可省略]/search.php@kw={{key}}"></textarea>
<textarea rows="1" id="ruleFindKind" </div>
placeholder="发现页分类规则(ruleFindKind) | 选择节点分类信息 (规则结果为List&lt;String&gt;)"></textarea> <div>
</div> <div>列表规则:</div>
<div> <textarea rows="3" id="ruleSearch_bookList" class="ruleSearch" title="bookList"
<div>最新章节:</div> placeholder="选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea>
<textarea rows="1" id="ruleFindLastChapter" </div>
placeholder="发现页最新章节规则(ruleFindLastChapter) | 选择节点最新章节 (规则结果为String)"></textarea> <div>
</div> <div>书名规则:</div>
<div> <textarea rows="1" id="ruleSearch_name" class="ruleSearch" title="name"
<div>简介内容:</div> placeholder="选择节点书名 (规则结果为String)"></textarea>
<textarea rows="1" id="ruleFindIntroduce" </div>
placeholder="发现页简介规则(ruleFindIntroduce) | 选择节点书籍简介 (规则结果为String)"></textarea> <div>
</div> <div>作者规则:</div>
<div> <textarea rows="1" id="ruleSearch_author" class="ruleSearch" title="author"
<div>封面链接:</div> placeholder="选择节点作者 (规则结果为String)"></textarea>
<textarea rows="1" id="ruleFindCoverUrl" </div>
placeholder="发现页封面规则(ruleFindCoverUrl) | 选择节点书籍封面 (规则结果为Url)"></textarea> <div>
</div> <div>分类规则:</div>
<div> <textarea rows="1" id="ruleSearch_kind" class="ruleSearch" title="kind"
<div>详情链接:</div> placeholder="选择节点分类信息 (规则结果为String)"></textarea>
<textarea rows="1" id="ruleFindNoteUrl" </div>
placeholder="发现页详情规则(ruleFindNoteUrl) | 选择书籍详情页网址 (规则结果为Url)"></textarea> <div>
</div> <div>字数规则:</div>
<div><b>书籍搜索规则</b></div> <textarea rows="1" id="ruleSearch_wordCount" class="ruleSearch" title="wordCount"
<div> placeholder="选择节点字数信息 (规则结果为String)"></textarea>
<div>搜索网址:</div> </div>
<textarea rows="1" id="ruleSearchUrl" <div>
placeholder="搜索网址(ruleSearchUrl) | [域名可省略]/search.php@kw=searchKey|char=utf-8"></textarea> <div>最新章节:</div>
</div> <textarea rows="1" id="ruleSearch_lastChapter" class="ruleSearch" title="lastChapter"
<div> placeholder="选择节点最新章节 (规则结果为String)"></textarea>
<div>结果验证:</div> </div>
<textarea rows="1" id="ruleBookUrlPattern" <div>
placeholder="搜索页URL验证(ruleBookUrlPattern) | 正则验证URL是否为详情页,成功则跳过搜索页解析"></textarea> <div>简介规则:</div>
</div> <textarea rows="1" id="ruleSearch_intro" class="ruleSearch" title="intro"
<div> placeholder="选择节点书籍简介 (规则结果为String)"></textarea>
<div>结果列表:</div> </div>
<textarea rows="1" id="ruleSearchList" <div>
placeholder="搜索页列表规则(ruleSearchList) | 选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea> <div>封面规则:</div>
</div> <textarea rows="1" id="ruleSearch_coverUrl" class="ruleSearch" title="coverUrl"
<div> placeholder="选择节点书籍封面 (规则结果为String类型的url)"></textarea>
<div>书籍名称:</div> </div>
<textarea rows="1" id="ruleSearchName" <div>
placeholder="搜索页书名规则(ruleSearchName) | 选择节点书名 (规则结果为String)"></textarea> <div>详情地址:</div>
</div> <textarea rows="1" id="ruleSearch_bookUrl" class="ruleSearch" title="bookUrl"
<div> placeholder="选择书籍详情页网址 (规则结果为String类型的url)"></textarea>
<div>书籍作者:</div> </div>
<textarea rows="1" id="ruleSearchAuthor" <p></p>
placeholder="搜索页作者规则(ruleSearchAuthor) | 选择节点作者 (规则结果为String)"></textarea> <div><b>发现</b></div>
</div> <div>
<div> <div>发现地址:</div>
<div>书籍分类:</div> <textarea rows="6" id="exploreUrl" class="base" title="exploreUrl"
<textarea rows="1" id="ruleSearchKind" placeholder="内容能显示在发现菜单&#10;每行一条发现分类(网址域名可省略),例:&#10;名称1::网址(Url)1&#10;名称2::网址(Url)2&#10;..."></textarea>
placeholder="搜索页分类规则(ruleSearchKind) | 选择节点分类信息 (规则结果为List&lt;String&gt;)"></textarea> </div>
</div> <div>
<div> <div>列表规则:</div>
<div>最新章节:</div> <textarea rows="1" id="ruleExplore_bookList" class="ruleExplore" title="bookList"
<textarea rows="1" id="ruleSearchLastChapter" placeholder="选择书籍节点 (规则结果为List&lt;Element&gt;)"></textarea>
placeholder="搜索页最新章节规则(ruleSearchLastChapter) | 选择节点最新章节 (规则结果为String)"></textarea> </div>
</div> <div>
<div> <div>书名规则:</div>
<div>简介内容:</div> <textarea rows="1" id="ruleExplore_name" class="ruleExplore" title="name"
<textarea rows="1" id="ruleSearchIntroduce" placeholder="选择节点书名 (规则结果为String)"></textarea>
placeholder="搜索页简介规则(ruleSearchIntroduce) | 选择节点书籍简介 (规则结果为String)"></textarea> </div>
</div> <div>
<div> <div>作者规则:</div>
<div>封面链接:</div> <textarea rows="1" id="ruleExplore_author" class="ruleExplore" title="author"
<textarea rows="1" id="ruleSearchCoverUrl" placeholder="选择节点作者 (规则结果为String)"></textarea>
placeholder="搜索页封面规则(ruleSearchCoverUrl) | 选择节点书籍封面 (规则结果为Url)"></textarea> </div>
</div> <div>
<div> <div>分类规则:</div>
<div>详情链接:</div> <textarea rows="1" id="ruleExplore_kind" class="ruleExplore" title="kind"
<textarea rows="1" id="ruleSearchNoteUrl" placeholder="选择节点分类信息 (规则结果为String)"></textarea>
placeholder="搜索页详情规则(ruleSearchNoteUrl) | 选择书籍详情页网址 (规则结果为Url)"></textarea> </div>
</div> <div>
<div><b>书籍详情规则</b></div> <div>字数规则:</div>
<div> <textarea rows="1" id="ruleExplore_wordCount" class="ruleExplore" title="wordCount"
<div>页面处理:</div> placeholder="选择节点字数信息 (规则结果为String)"></textarea>
<textarea rows="1" id="ruleBookInfoInit" </div>
placeholder="详情页信息预处理(ruleBookInfoInit) | 用于加速详情信息检索"></textarea> <div>
</div> <div>最新章节:</div>
<div> <textarea rows="1" id="ruleExplore_lastChapter" class="ruleExplore" title="lastChapter"
<div>书籍名称:</div> placeholder="选择节点最新章节 (规则结果为String)"></textarea>
<textarea rows="1" id="ruleBookName" </div>
placeholder="书名规则(ruleBookName) | 选择详情页书名 (规则结果为String)"></textarea> <div>
</div> <div>简介规则:</div>
<div> <textarea rows="1" id="ruleExplore_intro" class="ruleExplore" title="intro"
<div>书籍作者:</div> placeholder="选择节点书籍简介 (规则结果为String)"></textarea>
<textarea rows="1" id="ruleBookAuthor" </div>
placeholder="作者规则(ruleBookAuthor) | 选择详情页作者 (规则结果为String)"></textarea> <div>
</div> <div>封面规则:</div>
<div> <textarea rows="1" id="ruleExplore_coverUrl" class="ruleExplore" title="coverUrl"
<div>书籍分类:</div> placeholder="选择节点书籍封面 (规则结果为String类型的url)"></textarea>
<textarea rows="1" id="ruleBookKind" </div>
placeholder="分类规则(ruleBookKind) | 选择详情页分类信息 (规则结果为List&lt;String&gt;)"></textarea> <div>
</div> <div>详情地址:</div>
<div> <textarea rows="1" id="ruleExplore_bookUrl" class="ruleExplore" title="bookUrl"
<div>最新章节:</div> placeholder="选择书籍详情页网址 (规则结果为String类型的url)"></textarea>
<textarea rows="1" id="ruleBookLastChapter" </div>
placeholder="最新章节规则(ruleBookLastChapter) | 选择详情页最新章节 (规则结果为String)"></textarea> <p></p>
</div> <div><b>详情</b></div>
<div> <div>
<div>简介内容:</div> <div>预处理 :</div>
<textarea rows="1" id="ruleIntroduce" <textarea rows="3" id="ruleBookInfo_init" class="ruleBookInfo" title="init"
placeholder="简介规则(ruleIntroduce) | 选择详情页书籍简介 (规则结果为String)"></textarea> placeholder="用于加速详情信息检索,只支持AllInOne规则"></textarea>
</div> </div>
<div> <div>
<div>封面链接:</div> <div>书名规则:</div>
<textarea rows="1" id="ruleCoverUrl" placeholder="封面规则(ruleCoverUrl) | 选择详情页书籍封面 (规则结果为Url)"></textarea> <textarea rows="1" id="ruleBookInfo_name" class="ruleBookInfo" title="name"
</div> placeholder="选择节点书名 (规则结果为String)"></textarea>
<div> </div>
<div>目录链接:</div> <div>
<textarea rows="1" id="ruleChapterUrl" <div>作者规则:</div>
placeholder="目录URL规则(ruleChapterUrl) | 选择目录页网址 (规则结果为Url, 与详情页相同时可省略)"></textarea> <textarea rows="1" id="ruleBookInfo_author" class="ruleBookInfo" title="author"
</div> placeholder="选择节点作者 (规则结果为String)"></textarea>
<div><b>目录列表规则</b></div> </div>
<div> <div>
<div>目录翻页:</div> <div>分类规则:</div>
<textarea rows="1" id="ruleChapterUrlNext" <textarea rows="1" id="ruleBookInfo_kind" class="ruleBookInfo" title="kind"
placeholder="目录下一页规则(ruleChapterUrlNext) | 选择目录下一页链接 (规则结果为List&lt;Url&gt;)"></textarea> placeholder="选择节点分类信息 (规则结果为String)"></textarea>
</div> </div>
<div> <div>
<div>目录列表:</div> <div>字数规则:</div>
<textarea rows="1" id="ruleChapterList" <textarea rows="1" id="ruleBookInfo_wordCount" class="ruleBookInfo" title="wordCount"
placeholder="目录列表规则(ruleChapterList) | 选择目录列表的章节节点 (规则结果为List&lt;Element&gt;)"></textarea> placeholder="选择节点字数信息 (规则结果为String)"></textarea>
</div> </div>
<div> <div>
<div>章节名称:</div> <div>最新章节:</div>
<textarea rows="1" id="ruleChapterName" <textarea rows="1" id="ruleBookInfo_lastChapter" class="ruleBookInfo" title="lastChapter"
placeholder="章节名称规则(ruleChapterName) | 选择章节名称 (规则结果为String)"></textarea> placeholder="选择节点最新章节 (规则结果为String)"></textarea>
</div> </div>
<div> <div>
<div>章节链接:</div> <div>简介规则:</div>
<textarea rows="1" id="ruleContentUrl" <textarea rows="1" id="ruleBookInfo_intro" class="ruleBookInfo" title="intro"
placeholder="章节URL规则(ruleContentUrl) | 选择章节链接 (规则结果为Url)"></textarea> placeholder="选择节点书籍简介 (规则结果为String)"></textarea>
</div> </div>
<div><b>正文阅读规则</b></div> <div>
<div> <div>封面规则:</div>
<div>章节正文:</div> <textarea rows="1" id="ruleBookInfo_coverUrl" class="ruleBookInfo" title="coverUrl"
<textarea rows="1" id="ruleBookContent" placeholder="选择节点书籍封面 (规则结果为String类型的url)"></textarea>
placeholder="正文规则(ruleBookContent) | 选择正文内容 (规则结果为String)"></textarea> </div>
</div> <div>
<div> <div>目录地址:</div>
<div>正文翻页:</div> <textarea rows="1" id="ruleBookInfo_tocUrl" class="ruleBookInfo" title="tocUrl"
<textarea rows="1" id="ruleContentUrlNext" placeholder="选择书籍详情页网址 (规则结果为String类型的url, 与详情页相同时可省略)"></textarea>
placeholder="正文翻页URL规则(ruleContentUrlNext) | 选择下一分页(不是下一章)链接 (规则结果为Url)"></textarea> </div>
</div> <p></p>
<div><b>其它规则</b></div> <div><b>目录</b></div>
<div> <div>
<div>浏览标识:</div> <div>列表规则:</div>
<textarea rows="1" id="httpUserAgent" <textarea rows="3" id="ruleToc_chapterList" class="ruleToc" title="chapterList"
placeholder="浏览器UA(HttpUserAgent) | 浏览器标识:User-Agent (可选)"></textarea> placeholder="选择目录列表的章节节点 (规则结果为List&lt;Element&gt;)"></textarea>
</div> </div>
<div> <div>
<div>排序编号:</div> <div>章节名称:</div>
<textarea rows="1" id="serialNumber" placeholder="整数: 0~N (可选,默认0) | 数字越小越靠前"></textarea> <textarea rows="1" id="ruleToc_chapterName" class="ruleToc" title="chapterName"
</div> placeholder="选择章节名称 (规则结果为String)"></textarea>
<div> </div>
<div>搜索权重:</div> <div>
<textarea rows="1" id="weight" placeholder="整数: 0~N (可选,默认0) | 数字越大越靠前"></textarea> <div>章节地址:</div>
</div> <textarea rows="1" id="ruleToc_chapterUrl" class="ruleToc" title="chapterUrl"
<div> placeholder="选择章节链接 (规则结果为String类型的Url)"></textarea>
<div>是否启用:</div> </div>
<textarea rows="1" id="enable" placeholder="默认启用=true,手动启用=false (可选,默认true)"></textarea> <div>
<div>收费标识:</div>
<textarea rows="1" id="ruleToc_isVip" class="ruleToc" title="isVip"
placeholder="章节是否为VIP章节 (规则结果为Bool)"></textarea>
</div>
<div>
<div>章节信息:</div>
<textarea rows="1" id="ruleToc_updateTime" class="ruleToc" title="updateTime"
placeholder="选择章节信息 (规则结果为String)"></textarea>
</div>
<div>
<div>翻页规则:</div>
<textarea rows="1" id="ruleToc_nextTocUrl" class="ruleToc" title="nextTocUrl"
placeholder="选择目录下一页链接 (规则结果为List&lt;String&gt;或String)"></textarea>
</div>
<p></p>
<div><b>正文</b></div>
<div>
<div>正文规则:</div>
<textarea rows="1" id="ruleContent_content" class="ruleContent" title="content"
placeholder="选择正文内容 (规则结果为String)"></textarea>
</div>
<div>
<div>翻页规则:</div>
<textarea rows="1" id="ruleContent_nextContentUrl" class="ruleContent" title="nextContentUrl"
placeholder="选择下一分页(不是下一章)链接 (规则结果为String类型的Url)"></textarea>
</div>
<div>
<div>脚本注入:</div>
<textarea rows="3" id="ruleContent_webJs" class="ruleContent" title="webJs"
placeholder="注入javascript,用于模拟鼠标点击等,无返回结果"></textarea>
</div>
<div>
<div>资源正则:</div>
<textarea rows="1" id="ruleContent_sourceRegex" class="ruleContent" title="sourceRegex"
placeholder="匹配资源的url特征,用于嗅探"></textarea>
</div>
<p></p>
<div><b>其它规则</b></div>
<div>
<div>启用搜索:</div>
<textarea rows="1" id="enabled" class="base" title="enabled"
placeholder="启用: true 关闭: false (可选,默认true)"></textarea>
</div>
<div>
<div>启用发现:</div>
<textarea rows="1" id="enabledExplore" class="base" title="enabledExplore"
placeholder="启用: true 关闭: false (可选,默认true)"></textarea>
</div>
<div>
<div>搜索权重:</div>
<textarea rows="1" id="weight" class="base" title="weight"
placeholder="整数: 0~N (可选,默认0) | 数字越大越靠前"></textarea>
</div>
<div>
<div>排序编号:</div>
<textarea rows="1" id="customOrder" class="base" title="customOrder"
placeholder="整数: 0~N (可选,默认0) | 数字越小越靠前"></textarea>
</div>
<div>
<div>更新时间:</div>
<textarea rows="1" id="lastUpdateTime" class="base" title="lastUpdateTime"
placeholder="整数: 0~N (可选,默认0) | 暂未使用"></textarea>
</div>
</div> </div>
</div> </div>
</div> <div class="menu">
<div class="menu"> <svg class="button">
<svg class="button"> <text x="50%" y="55%">⇈推送书源</text>
<text x="50%" y="55%">⇈推送书源</text> <rect id="push"></rect>
<rect id="push"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">⇊拉取书源</text>
<text x="50%" y="55%">⇊拉取书源</text> <rect id="pull"></rect>
<rect id="pull"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">⋘编辑书源</text>
<text x="50%" y="55%">⋘编辑书源</text> <rect id="editor"></rect>
<rect id="editor"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">⋙生成书源</text>
<text x="50%" y="55%">⋙生成书源</text> <rect id="conver"></rect>
<rect id="conver"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">✗清空表单</text>
<text x="50%" y="55%">✗清空表单</text> <rect id="initial"></rect>
<rect id="initial"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">↶撤销操作</text>
<text x="50%" y="55%">↶撤销操作</text> <rect id="undo"></rect>
<rect id="undo"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">↷重做操作</text>
<text x="50%" y="55%">↷重做操作</text> <rect id="redo"></rect>
<rect id="redo"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">⇏调试书源</text>
<text x="50%" y="55%">⇏调试书源</text> <rect id="debug"></rect>
<rect id="debug"></rect> </svg>
</svg> <svg class="button">
<svg class="button"> <text x="50%" y="55%">✓保存书源</text>
<text x="50%" y="55%">✓保存书源</text> <rect id="accept"></rect>
<rect id="accept"></rect> </svg>
</svg> </div>
</div> <div class="outbox">
<div class="outbox"> <div class="tabbox">
<div class="tabbox"> <div class="tabtitle">
<div class="tabtitle"> <div name="编辑书源" class="tab1 this">编辑书源</div>
<div name="编辑书源" class="tab1 this">编辑书源</div> <div name="调试书源" class="tab2">调试书源</div>
<div name="调试书源" class="tab2">调试书源</div> <div name="书源列表" class="tab3">书源列表</div>
<div name="书源列表" class="tab3">书源列表</div> <div name="帮助信息" class="tab4">帮助信息</div>
<div name="帮助信息" class="tab4">帮助信息</div> </div>
</div> <div class="tabbody">
<div class="tabbody"> <div class="tab1 this">
<div class="tab1 this"> <textarea class="context" id="RuleJsonString"
<textarea class="context" id="RuleJsonString" placeholder="这里输出序列化的JSON数据,可直接导入'阅读'APP"></textarea> placeholder="这里输出序列化的JSON数据,可直接导入'阅读'APP"></textarea>
</div> </div>
<div class="tab2"> <div class="tab2">
<input type="text" class="inputbox" id="DebugKey" placeholder="我的"> <input type="text" class="inputbox" id="DebugKey" placeholder="输入搜索关键字,默认搜「我的」">
<textarea class="context" id="DebugConsole" placeholder="这里用于输出调试信息"></textarea> <textarea class="context" id="DebugConsole" placeholder="这里用于输出调试信息"></textarea>
</div> </div>
<div class="tab3"> <div class="tab3">
<div class="titlebar"> <input type="text" class="inputbox" id="Filter" placeholder="输入筛选关键词(源名称、源URL或源分组)后按回车筛选源">
<button id="Import">导入书源文件</button> <div class="titlebar">
<button id="Export">导出书源文件</button> <button id="Import">导入书源文件</button>
<button id="Delete">删除选中书源</button> <button id="Export">导出书源文件</button>
<button id="ClrAll">清空当前列表</button> <button id="Delete">删除选中书源</button>
<button id="ClrAll">清空当前列表</button>
</div>
<div class="context" id="RuleList"></div>
</div> </div>
<div class="context" id="RuleList"></div> <div class="tab4">
</div> <div class="context link">
<div class="tab4"> <a target="_blank" href="https://celeter.github.io">源制作教程</a>
<div class="context link"> <a target="_blank" href="https://zhuanlan.zhihu.com/p/29436838">Xpath基础教程</a>
<a target="_blank" href="https://gedoor.github.io/MyBookshelf/sourcerule.html">官方书源教程</a> <a target="_blank" href="https://zhuanlan.zhihu.com/p/32187820">Xpath高级教程</a>
<a target="_blank" href="https://zhuanlan.zhihu.com/p/29436838">Xpath基础教程</a> <a target="_blank" href="https://www.w3cschool.cn/regex_rmjc/?">正则表达式教程</a>
<a target="_blank" href="https://zhuanlan.zhihu.com/p/32187820">Xpath高级教程</a> <a target="_blank" href="https://regexr.com/">正则表达式在线验证工具</a>
<a target="_blank" href="https://www.w3cschool.cn/regex_rmjc/?">正则表达式教程</a> <div>^$()[]{}.?+*| 这些是Java正则特殊符号,匹配需转义
<a target="_blank" href="https://regexr.com/">正则表达式在线验证工具</a> <br>(?s) 前缀表示跨行解析
<div>^$()[]{}.?+*| 这些是Java正则特殊符号,匹配需转义 <br>(?m) 前缀表示逐行匹配
<br>(?s) 前缀表示跨行解析 <br>(?i) 前缀表示忽略大小写
<br>(?m) 前缀表示逐行匹配 </div>
<br>(?i) 前缀表示忽略大小写 <a target="_blank" href="https://www.beta.browxy.com/">代码在线运行工具</a>
<a target="_blank" href="bookshelf.html">阅读书架(测试)</a>
</div> </div>
<a target="_blank" href="https://www.beta.browxy.com/">代码在线运行工具</a>
<a target="_blank" href="/bookshelf.html">阅读书架(测试)</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="/index.js"></script>
</body> </body>
</html> </html>

@ -20,11 +20,40 @@ function hashParam(key, val) {
// 创建书源规则容器对象 // 创建书源规则容器对象
const RuleJSON = (() => { const RuleJSON = (() => {
let ruleJson = {}; let ruleJson = {};
$$('.rules textarea').forEach(item => ruleJson[item.id] = ''); let searchJson = {};
// for (let item of $$('.rules textarea')) ruleJson[item.id] = ''; let exploreJson = {};
ruleJson.serialNumber = 0; let bookInfoJson = {};
let tocJson = {};
let contentJson = {};
// 基本以及其他
$$('.rules .base').forEach(item => ruleJson[item.title] = '');
ruleJson.lastUpdateTime = 0;
ruleJson.customOrder = 0;
ruleJson.weight = 0; ruleJson.weight = 0;
ruleJson.enable = true; ruleJson.enabled = true;
ruleJson.enabledExplore = true;
// 搜索规则
$$('.rules .ruleSearch').forEach(item => searchJson[item.title] = '');
ruleJson.ruleSearch = JSON.stringify(searchJson);
// 发现规则
$$('.rules .ruleExplore').forEach(item => exploreJson[item.title] = '');
ruleJson.ruleExplore = JSON.stringify(exploreJson);
// 详情页规则
$$('.rules .ruleBookInfo').forEach(item => bookInfoJson[item.title] = '');
ruleJson.ruleBookInfo = JSON.stringify(bookInfoJson);
// 目录规则
$$('.rules .ruleToc').forEach(item => tocJson[item.title] = '');
ruleJson.ruleToc = JSON.stringify(tocJson);
// 正文规则
$$('.rules .ruleContent').forEach(item => contentJson[item.title] = '');
ruleJson.ruleContent = JSON.stringify(contentJson);
return ruleJson; return ruleJson;
})(); })();
// 选项卡Tab切换事件处理 // 选项卡Tab切换事件处理
@ -72,15 +101,113 @@ function HttpPost(url, data) {
} }
// 将书源表单转化为书源对象 // 将书源表单转化为书源对象
function rule2json() { function rule2json() {
Object.keys(RuleJSON).forEach((key) => RuleJSON[key] = $('#' + key).value); // 转换base
RuleJSON.serialNumber = RuleJSON.serialNumber == '' ? 0 : parseInt(RuleJSON.serialNumber); Object.keys(RuleJSON).forEach(key => {
if (!key.startsWith("rule")) {
RuleJSON[key] = $('#' + key).value;
}
});
// 转换搜索规则
let searchJson = {};
Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => {
searchJson[key] = $('#' + 'ruleSearch_' + key).value;
});
RuleJSON.ruleSearch = JSON.stringify(searchJson);
// 转换发现规则
let exploreJson = {};
Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => {
exploreJson[key] = $('#' + 'ruleExplore_' + key).value;
});
RuleJSON.ruleExplore = JSON.stringify(exploreJson);
// 转换详情页规则
let bookInfoJson = {};
Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => {
bookInfoJson[key] = $('#' + 'ruleBookInfo_' + key).value;
});
RuleJSON.ruleBookInfo = JSON.stringify(bookInfoJson);
// 转换目录规则
let tocJson = {};
Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => {
tocJson[key] = $('#' + 'ruleToc_' + key).value;
});
RuleJSON.ruleToc = JSON.stringify(tocJson);
// 转换正文规则
let contentJson = {};
Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => {
contentJson[key] = $('#' + 'ruleContent_' + key).value;
});
RuleJSON.ruleContent = JSON.stringify(contentJson);
RuleJSON.lastUpdateTime = RuleJSON.lastUpdateTime == '' ? 0 : parseInt(RuleJSON.lastUpdateTime);
RuleJSON.customOrder = RuleJSON.customOrder == '' ? 0 : parseInt(RuleJSON.customOrder);
RuleJSON.weight = RuleJSON.weight == '' ? 0 : parseInt(RuleJSON.weight); RuleJSON.weight = RuleJSON.weight == '' ? 0 : parseInt(RuleJSON.weight);
RuleJSON.enable = RuleJSON.enable == '' || RuleJSON.enable.toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true'; RuleJSON.bookSourceType == RuleJSON.bookSourceType == '' ? 0 : parseInt(RuleJSON.weight);
RuleJSON.enabled = RuleJSON.enabled == '' || String(RuleJSON.enabled).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true';
RuleJSON.enabledExplore = RuleJSON.enabledExplore == '' || String(RuleJSON.enabledExplore).toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true';
return RuleJSON; return RuleJSON;
} }
// 将书源对象填充到书源表单 // 将书源对象填充到书源表单
function json2rule(RuleEditor) { function json2rule(RuleEditor) {
Object.keys(RuleJSON).forEach((key) => $("#" + key).value = RuleEditor[key] ? RuleEditor[key] : ''); // 转换base
Object.keys(RuleJSON).forEach(key => {
if (!key.startsWith("rule")) {
let val = RuleEditor[key];
if (typeof val == "number") {
$("#" + key).value = val ? String(val) : '0';
}
else if (typeof val == "boolean") {
$("#" + key).value = val ? String(val) : 'false';
}
else {
$("#" + key).value = val ? String(val) : '';
}
}
});
// 转换搜索规则
if (RuleEditor.ruleSearch) {
let searchJson = JSON.parse(RuleEditor.ruleSearch);
Object.keys(JSON.parse(RuleJSON.ruleSearch)).forEach(key => {
$('#' + 'ruleSearch_' + key).value = searchJson[key] ? searchJson[key] : '';
});
}
// 转换发现规则
if (RuleEditor.ruleExplore) {
let exploreJson = JSON.parse(RuleEditor.ruleExplore);
Object.keys(JSON.parse(RuleJSON.ruleExplore)).forEach(key => {
$('#' + 'ruleExplore_' + key).value = exploreJson[key] ? exploreJson[key] : '';
});
}
// 转换详情页规则
if (RuleEditor.ruleBookInfo) {
let bookInfoJson = JSON.parse(RuleEditor.ruleBookInfo);
Object.keys(JSON.parse(RuleJSON.ruleBookInfo)).forEach(key => {
$('#' + 'ruleBookInfo_' + key).value = bookInfoJson[key] ? bookInfoJson[key] : '';
});
}
// 转换目录规则
if (RuleEditor.ruleToc) {
let tocJson = JSON.parse(RuleEditor.ruleToc);
Object.keys(JSON.parse(RuleJSON.ruleToc)).forEach(key => {
$('#' + 'ruleToc_' + key).value = tocJson[key] ? tocJson[key] : '';
});
}
// 转换正文规则
if (RuleEditor.ruleContent) {
let contentJson = JSON.parse(RuleEditor.ruleContent);
Object.keys(JSON.parse(RuleJSON.ruleContent)).forEach(key => {
$('#' + 'ruleContent_' + key).value = contentJson[key] ? contentJson[key] : '';
});
}
} }
// 记录操作过程 // 记录操作过程
var course = { "old": [], "now": {}, "new": [] }; var course = { "old": [], "now": {}, "new": [] };
@ -153,16 +280,16 @@ $('.menu').addEventListener('click', e => {
}); });
failMsg = '\n推送失败的书源将用红色字体标注!'; failMsg = '\n推送失败的书源将用红色字体标注!';
} }
alert(`批量推送书源到「阅读APP」\n共计: ${RuleSources.length}\n成功: ${okData.length}\n失败: ${RuleSources.length - okData.length}${failMsg}`); alert(`批量推送书源到「阅读3.0APP」\n共计: ${RuleSources.length}\n成功: ${okData.length}\n失败: ${RuleSources.length - okData.length}${failMsg}`);
} }
else { else {
alert(`批量推送书源到「阅读APP」成功!\n共计: ${RuleSources.length}`); alert(`批量推送书源到「阅读3.0APP」成功!\n共计: ${RuleSources.length}`);
} }
} }
else { else {
alert(`批量推送书源失败!\nErrorMsg: ${json.errorMsg}`); alert(`批量推送书源失败!\nErrorMsg: ${json.errorMsg}`);
} }
}).catch(err => { alert(`批量推送书源失败,无法连接到「阅读APP」!\n${err}`); }); }).catch(err => { alert(`批量推送书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
thisNode.setAttribute('class', ''); thisNode.setAttribute('class', '');
})(); })();
return; return;
@ -181,7 +308,7 @@ $('.menu').addEventListener('click', e => {
else { else {
alert(`批量拉取书源失败!\nErrorMsg: ${json.errorMsg}`); alert(`批量拉取书源失败!\nErrorMsg: ${json.errorMsg}`);
} }
}).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读APP」!\n${err}`); }); }).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
thisNode.setAttribute('class', ''); thisNode.setAttribute('class', '');
})(); })();
return; return;
@ -244,9 +371,9 @@ $('.menu').addEventListener('click', e => {
(async () => { (async () => {
let saveRule = [rule2json()]; let saveRule = [rule2json()];
await HttpPost(`/saveSources`, saveRule).then(json => { await HttpPost(`/saveSources`, saveRule).then(json => {
alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`); alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读3.0APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`);
setRule(saveRule[0]); setRule(saveRule[0]);
}).catch(err => { alert(`保存书源失败,无法连接到「阅读APP」!\n${err}`); }); }).catch(err => { alert(`保存书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
thisNode.setAttribute('class', ''); thisNode.setAttribute('class', '');
})(); })();
return; return;
@ -261,6 +388,26 @@ $('#DebugKey').addEventListener('keydown', e => {
$('#debug').dispatchEvent(clickEvent); $('#debug').dispatchEvent(clickEvent);
} }
}); });
$('#Filter').addEventListener('keydown', e => {
if (e.keyCode == 13) {
let cashList = [];
$('#RuleList').innerHTML = "";
let sKey = Filter.value ? Filter.value : '';
if (sKey == '') {
cashList = RuleSources;
} else {
let patt = new RegExp(sKey);
RuleSources.forEach(source => {
if (patt.test(source.bookSourceUrl) || patt.test(source.bookSourceName) || patt.test(source.bookSourceGroup)) {
cashList.push(source);
}
})
}
cashList.forEach(source => {
$('#RuleList').innerHTML += newRule(source);
})
}
});
// 列表规则更改事件 // 列表规则更改事件
$('#RuleList').addEventListener('click', e => { $('#RuleList').addEventListener('click', e => {
@ -348,7 +495,7 @@ $('.tab3>.titlebar').addEventListener('click', e => {
console.log(deleteSources); console.log(deleteSources);
console.log(`以上书源已删除!`) console.log(`以上书源已删除!`)
} }
}).catch(err => { alert(`删除书源失败,无法连接到「阅读APP」!\n${err}`); }); }).catch(err => { alert(`删除书源失败,无法连接到「阅读3.0APP」!\n${err}`); });
} }
break; break;
case 'ClrAll': case 'ClrAll':

@ -1,13 +1,11 @@
package io.legado.app package io.legado.app
import android.app.Activity
import android.app.Application import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
@ -48,19 +46,15 @@ class App : Application() {
versionCode = it.versionCode versionCode = it.versionCode
versionName = it.versionName versionName = it.versionName
} }
if (!ThemeStore.isConfigured(this, versionCode)) applyTheme()
initNightMode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId()
applyDayNight()
LiveEventBus.get() LiveEventBus
.config() .config()
.supportBroadcast(this) .supportBroadcast(this)
.lifecycleObserverAlwaysActive(true) .lifecycleObserverAlwaysActive(true)
.autoClear(false) .autoClear(false)
registerActivityLife() registerActivityLifecycleCallbacks(ActivityHelp)
} }
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
@ -120,9 +114,7 @@ class App : Application() {
*/ */
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private fun createChannelId() { private fun createChannelId() {
val notificationManager = (getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager)?.let {
getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
notificationManager?.let {
//用唯一的ID创建渠道对象 //用唯一的ID创建渠道对象
val downloadChannel = NotificationChannel( val downloadChannel = NotificationChannel(
channelIdDownload, channelIdDownload,
@ -161,32 +153,4 @@ class App : Application() {
} }
} }
private fun registerActivityLife() {
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityDestroyed(activity: Activity) {
ActivityHelp.remove(activity)
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
ActivityHelp.add(activity)
}
})
}
} }

@ -1,6 +1,7 @@
package io.legado.app.base package io.legado.app.base
import android.os.Bundle import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -18,6 +19,14 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
job = Job() job = Job()
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onFragmentCreated(view, savedInstanceState)
observeLiveBus()
}
abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
job.cancel() job.cancel()
@ -30,4 +39,7 @@ abstract class BaseDialogFragment : DialogFragment(), CoroutineScope {
): Coroutine<T> { ): Coroutine<T> {
return Coroutine.async(scope, context) { block() } return Coroutine.async(scope, context) { block() }
} }
open fun observeLiveBus() {
}
} }

@ -1,5 +1,6 @@
package io.legado.app.base package io.legado.app.base
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.view.SupportMenuInflater import androidx.appcompat.view.SupportMenuInflater
@ -18,6 +19,7 @@ abstract class BaseFragment(layoutID: Int) : Fragment(layoutID),
private set private set
val menuInflater: MenuInflater val menuInflater: MenuInflater
@SuppressLint("RestrictedApi")
get() = SupportMenuInflater(requireContext()) get() = SupportMenuInflater(requireContext())
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext

@ -4,8 +4,6 @@ import android.annotation.SuppressLint
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.putPrefBoolean
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import javax.script.ScriptEngine import javax.script.ScriptEngine
import javax.script.ScriptEngineManager import javax.script.ScriptEngineManager
@ -29,14 +27,18 @@ object AppConst {
ScriptEngineManager().getEngineByName("rhino") ScriptEngineManager().getEngineByName("rhino")
} }
val TIME_FORMAT: SimpleDateFormat by lazy { val timeFormat: SimpleDateFormat by lazy {
SimpleDateFormat("HH:mm") SimpleDateFormat("HH:mm")
} }
val DATE_FORMAT: SimpleDateFormat by lazy { val dateFormat: SimpleDateFormat by lazy {
SimpleDateFormat("yyyy/MM/dd HH:mm") SimpleDateFormat("yyyy/MM/dd HH:mm")
} }
val fileNameFormat: SimpleDateFormat by lazy {
SimpleDateFormat("yy-MM-dd-HH-mm-ss")
}
val keyboardToolChars: List<String> by lazy { val keyboardToolChars: List<String> by lazy {
arrayListOf( arrayListOf(
"@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", "$", "#", "!", ".", "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", "$", "#", "!", ".",
@ -48,18 +50,6 @@ object AppConst {
val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local)) val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local))
val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio)) val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio))
var bookGroupLocalShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupLocal", false)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupLocal", value)
}
var bookGroupAudioShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupAudio", false)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupAudio", value)
}
const val notificationIdRead = 1144771 const val notificationIdRead = 1144771
const val notificationIdAudio = 1144772 const val notificationIdAudio = 1144772
const val notificationIdWeb = 1144773 const val notificationIdWeb = 1144773

@ -2,7 +2,9 @@ package io.legado.app.constant
import java.util.regex.Pattern import java.util.regex.Pattern
object Pattern { object AppPattern {
val JS_PATTERN: Pattern = Pattern.compile("(<js>[\\w\\W]*?</js>|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE) val JS_PATTERN: Pattern = Pattern.compile("(<js>[\\w\\W]*?</js>|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE)
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}") val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
val authorRegex = "\\s*者\\s*[::]".toRegex()
} }

@ -20,6 +20,5 @@ object EventBus {
const val SHOW_RSS = "showRss" const val SHOW_RSS = "showRss"
const val WEB_SERVICE_STOP = "webServiceStop" const val WEB_SERVICE_STOP = "webServiceStop"
const val UP_DOWNLOAD = "upDownload" const val UP_DOWNLOAD = "upDownload"
const val UP_TABS = "upTabs"
const val SAVE_CONTENT = "saveContent" const val SAVE_CONTENT = "saveContent"
} }

@ -27,11 +27,13 @@ object PreferKey {
const val fontFolder = "fontFolder" const val fontFolder = "fontFolder"
const val backupPath = "backupUri" const val backupPath = "backupUri"
const val threadCount = "threadCount" const val threadCount = "threadCount"
const val webPort = "webPort"
const val keepLight = "keep_light" const val keepLight = "keep_light"
const val webService = "webService" const val webService = "webService"
const val webDavUrl = "web_dav_url" const val webDavUrl = "web_dav_url"
const val webDavAccount = "web_dav_account" const val webDavAccount = "web_dav_account"
const val webDavPassword = "web_dav_password" const val webDavPassword = "web_dav_password"
const val webDavCreateDir = "webDavCreateDir"
const val changeSourceLoadToc = "changeSourceLoadToc" const val changeSourceLoadToc = "changeSourceLoadToc"
const val chineseConverterType = "chineseConverterType" const val chineseConverterType = "chineseConverterType"
const val launcherIcon = "launcherIcon" const val launcherIcon = "launcherIcon"

@ -1,17 +0,0 @@
package io.legado.app.constant
object RSSKeywords {
const val RSS_ITEM = "item"
const val RSS_ITEM_TITLE = "title"
const val RSS_ITEM_LINK = "link"
const val RSS_ITEM_CATEGORY = "category"
const val RSS_ITEM_THUMBNAIL = "media:thumbnail"
const val RSS_ITEM_ENCLOSURE = "enclosure"
const val RSS_ITEM_DESCRIPTION = "description"
const val RSS_ITEM_CONTENT = "content:encoded"
const val RSS_ITEM_PUB_DATE = "pubDate"
const val RSS_ITEM_TIME = "time"
const val RSS_ITEM_URL = "url"
const val RSS_ITEM_TYPE = "type"
}

@ -32,7 +32,7 @@ abstract class AppDatabase : RoomDatabase() {
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.addCallback(object : Callback() { .addCallback(object : Callback() {
override fun onDestructiveMigration(db: SupportSQLiteDatabase) { override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
GlobalScope.launch { Restore.restore(Backup.backupPath) } GlobalScope.launch { Restore.restoreDatabase(Backup.backupPath) }
} }
}) })
.build() .build()

@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookProgress
@Dao @Dao
interface BookDao { interface BookDao {
@ -26,6 +27,9 @@ interface BookDao {
@Query("SELECT * FROM books WHERE (`group` & :group) > 0") @Query("SELECT * FROM books WHERE (`group` & :group) > 0")
fun observeByGroup(group: Int): LiveData<List<Book>> fun observeByGroup(group: Int): LiveData<List<Book>>
@Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0")
fun observeNoGroup(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'") @Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'")
fun liveDataSearch(key: String): LiveData<List<Book>> fun liveDataSearch(key: String): LiveData<List<Book>>
@ -67,4 +71,23 @@ interface BookDao {
@Query("update books set `group` = :newGroupId where `group` = :oldGroupId") @Query("update books set `group` = :newGroupId where `group` = :oldGroupId")
fun upGroup(oldGroupId: Int, newGroupId: Int) fun upGroup(oldGroupId: Int, newGroupId: Int)
@get:Query("select bookUrl, durChapterIndex, durChapterPos, durChapterTime, durChapterTitle from books")
val allBookProgress: List<BookProgress>
@Query(
"""
update books set
durChapterIndex = :durChapterIndex, durChapterPos = :durChapterPos,
durChapterTime = :durChapterTime, durChapterTitle = :durChapterTitle
where bookUrl = :bookUrl and durChapterTime < :durChapterTime
"""
)
fun upBookProgress(
bookUrl: String,
durChapterIndex: Int,
durChapterPos: Int,
durChapterTime: Long,
durChapterTitle: String?
)
} }

@ -33,14 +33,20 @@ interface ReplaceRuleDao {
fun findByIds(vararg ids: Long): List<ReplaceRule> fun findByIds(vararg ids: Long): List<ReplaceRule>
@Query( @Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1 """
AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')""" SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :scope || '%' or scope is null or scope = '')
order by sortOrder
"""
) )
fun findEnabledByScope(scope: String): List<ReplaceRule> fun findEnabledByScope(scope: String): List<ReplaceRule>
@Query( @Query(
"""SELECT * FROM replace_rules WHERE isEnabled = 1 """
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')""" SELECT * FROM replace_rules WHERE isEnabled = 1
AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')
order by sortOrder
"""
) )
fun findEnabledByScope(name: String, origin: String): List<ReplaceRule> fun findEnabledByScope(name: String, origin: String): List<ReplaceRule>

@ -27,7 +27,7 @@ interface SearchBookDao {
select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder
from searchBooks as t1 inner join book_sources as t2 from searchBooks as t1 inner join book_sources as t2
on t1.origin = t2.bookSourceUrl on t1.origin = t2.bookSourceUrl
where t1.name = :name and t1.author = :author where t1.name = :name and t1.author = :author and t2.enabled = 1
order by t2.customOrder order by t2.customOrder
""" """
) )
@ -38,7 +38,7 @@ interface SearchBookDao {
select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder
from searchBooks as t1 inner join book_sources as t2 from searchBooks as t1 inner join book_sources as t2
on t1.origin = t2.bookSourceUrl on t1.origin = t2.bookSourceUrl
where t1.name = :name and t1.author = :author and originName like '%'||:key||'%' where t1.name = :name and t1.author = :author and originName like '%'||:key||'%' and t2.enabled = 1
order by t2.customOrder order by t2.customOrder
""" """
) )
@ -49,7 +49,7 @@ interface SearchBookDao {
select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, t1.wordCount, t2.customOrder as originOrder
from searchBooks as t1 inner join book_sources as t2 from searchBooks as t1 inner join book_sources as t2
on t1.origin = t2.bookSourceUrl on t1.origin = t2.bookSourceUrl
where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' and t2.enabled = 1
order by t2.customOrder order by t2.customOrder
""" """
) )

@ -16,6 +16,9 @@ interface TxtTocRuleDao {
@get:Query("select * from txtTocRules where enable = 1 order by serialNumber") @get:Query("select * from txtTocRules where enable = 1 order by serialNumber")
val enabled: List<TxtTocRule> val enabled: List<TxtTocRule>
@get:Query("select ifNull(max(serialNumber), 0) from txtTocRules")
val lastOrderNum: Int
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg rule: TxtTocRule) fun insert(vararg rule: TxtTocRule)

@ -5,6 +5,7 @@ import androidx.room.Entity
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import io.legado.app.constant.AppPattern
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
@ -86,7 +87,7 @@ data class Book(
@IgnoredOnParcel @IgnoredOnParcel
override var tocHtml: String? = null override var tocHtml: String? = null
fun getRealAuthor() = author.replace("作者:", "") fun getRealAuthor() = author.replace(AppPattern.authorRegex, "")
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0) fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)

@ -0,0 +1,9 @@
package io.legado.app.data.entities
data class BookProgress(
val bookUrl: String,
val durChapterIndex: Int,
val durChapterPos: Int,
val durChapterTime: Long,
val durChapterTitle: String?
)

@ -1,11 +1,14 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import android.os.Parcelable import android.os.Parcelable
import android.text.TextUtils
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import java.util.regex.Pattern
import java.util.regex.PatternSyntaxException
@Parcelize @Parcelize
@Entity( @Entity(
@ -37,4 +40,20 @@ data class ReplaceRule(
override fun hashCode(): Int { override fun hashCode(): Int {
return id.hashCode() return id.hashCode()
} }
fun isValid(): Boolean{
if (TextUtils.isEmpty(pattern)){
return false;
}
//判断正则表达式是否正确
if (isRegex){
try {
Pattern.compile(pattern);
}
catch (ex: PatternSyntaxException){
return false;
}
}
return true;
}
} }

@ -1,13 +1,15 @@
package io.legado.app.help package io.legado.app.help
import android.app.Activity import android.app.Activity
import android.app.Application
import android.os.Bundle
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.* import java.util.*
/** /**
* Activity管理器,管理项目中Activity的状态 * Activity管理器,管理项目中Activity的状态
*/ */
object ActivityHelp { object ActivityHelp : Application.ActivityLifecycleCallbacks {
private val activities: MutableList<WeakReference<Activity>> = arrayListOf() private val activities: MutableList<WeakReference<Activity>> = arrayListOf()
@ -86,4 +88,27 @@ object ActivityHelp {
} }
} }
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityDestroyed(activity: Activity) {
remove(activity)
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
add(activity)
}
} }

@ -92,4 +92,28 @@ object AppConfig {
set(value) { set(value) {
App.INSTANCE.putPrefInt(PreferKey.systemTypefaces, value) App.INSTANCE.putPrefInt(PreferKey.systemTypefaces, value)
} }
var bookGroupAllShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupAll", true)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupAll", value)
}
var bookGroupLocalShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupLocal", false)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupLocal", value)
}
var bookGroupAudioShow: Boolean
get() = App.INSTANCE.getPrefBoolean("bookGroupAudio", false)
set(value) {
App.INSTANCE.putPrefBoolean("bookGroupAudio", value)
}
var elevation: Int
get() = App.INSTANCE.getPrefInt("elevation", -1)
set(value) {
App.INSTANCE.putPrefInt("elevation", value)
}
} }

@ -8,10 +8,8 @@ import android.renderscript.Allocation
import android.renderscript.Element import android.renderscript.Element
import android.renderscript.RenderScript import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur import android.renderscript.ScriptIntrinsicBlur
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.CenterCrop
import java.security.MessageDigest import java.security.MessageDigest
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -21,15 +19,16 @@ import kotlin.math.roundToInt
* 模糊 * 模糊
* @radius: 0..25 * @radius: 0..25
*/ */
class BlurTransformation(context: Context, private val radius: Int) : BitmapTransformation() { class BlurTransformation(context: Context, private val radius: Int) : CenterCrop() {
private val rs: RenderScript = RenderScript.create(context) private val rs: RenderScript = RenderScript.create(context)
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap { override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val transform = super.transform(pool, toTransform, outWidth, outHeight)
//图片缩小1/2 //图片缩小1/2
val width = (min(outWidth, toTransform.width) / 2f).roundToInt() val width = (min(outWidth, transform.width) / 2f).roundToInt()
val height = (min(outHeight, toTransform.height) / 2f).roundToInt() val height = (min(outHeight, transform.height) / 2f).roundToInt()
val blurredBitmap = Bitmap.createScaledBitmap(toTransform, width, height, false); val blurredBitmap = Bitmap.createScaledBitmap(transform, width, height, false);
// Allocate memory for Renderscript to work with // Allocate memory for Renderscript to work with
//分配用于渲染脚本的内存 //分配用于渲染脚本的内存
val input = Allocation.createFromBitmap( val input = Allocation.createFromBitmap(

@ -11,7 +11,10 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.model.localBook.AnalyzeTxtFile
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.JaccardSimilarity import org.apache.commons.text.similarity.JaccardSimilarity
import org.jetbrains.anko.toast
import java.io.File import java.io.File
import kotlin.math.min import kotlin.math.min
@ -177,7 +180,7 @@ object BookHelp {
fun formatAuthor(author: String?): String { fun formatAuthor(author: String?): String {
return author return author
?.replace("\\s*者[\\s::]*".toRegex(), "") ?.replace("\\s*者\\s*[::]\n*".toRegex(), "")
?.replace("\\s+".toRegex(), " ") ?.replace("\\s+".toRegex(), " ")
?.trim { it <= ' ' } ?.trim { it <= ' ' }
?: "" ?: ""
@ -262,7 +265,7 @@ object BookHelp {
} }
} }
fun disposeContent( suspend fun disposeContent(
title: String, title: String,
name: String, name: String,
origin: String?, origin: String?,
@ -272,13 +275,19 @@ object BookHelp {
var c = content var c = content
if (enableReplace) { if (enableReplace) {
upReplaceRules(name, origin) upReplaceRules(name, origin)
for (item in replaceRules) { replaceRules.forEach { item ->
item.pattern.let { item.pattern.let {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
c = if (item.isRegex) { try {
c.replace(it.toRegex(), item.replacement) c = if (item.isRegex) {
} else { c.replace(it.toRegex(), item.replacement)
c.replace(it, item.replacement) } else {
c.replace(it, item.replacement)
}
} catch (e: Exception) {
withContext(Main) {
App.INSTANCE.toast("${item.name}替换出错")
}
} }
} }
} }
@ -291,6 +300,8 @@ object BookHelp {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c)
2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c)
} }
return c.replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}") return c
.replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}")
.replace("[\\n\\s]+$".toRegex(), "") //移除尾部空行
} }
} }

@ -1,13 +1,14 @@
package io.legado.app.help package io.legado.app.help
import android.util.Base64 import android.util.Base64
import io.legado.app.constant.AppConst.DATE_FORMAT import androidx.annotation.Keep
import io.legado.app.constant.AppConst.dateFormat
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.EncoderUtils import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import java.util.* import java.util.*
@Keep
@Suppress("unused") @Suppress("unused")
interface JsExtensions { interface JsExtensions {
@ -49,6 +50,6 @@ interface JsExtensions {
} }
fun timeFormat(time: Long): String { fun timeFormat(time: Long): String {
return DATE_FORMAT.format(Date(time)) return dateFormat.format(Date(time))
} }
} }

@ -4,6 +4,7 @@ import android.graphics.Color
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.Keep
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
@ -15,6 +16,7 @@ import java.io.File
/** /**
* 阅读界面配置 * 阅读界面配置
*/ */
@Keep
object ReadBookConfig { object ReadBookConfig {
const val readConfigFileName = "readConfig.json" const val readConfigFileName = "readConfig.json"
private val configFilePath = private val configFilePath =
@ -27,6 +29,7 @@ object ReadBookConfig {
val durConfig get() = getConfig(styleSelect) val durConfig get() = getConfig(styleSelect)
private val shareConfig get() = getConfig(5) private val shareConfig get() = getConfig(5)
var bg: Drawable? = null var bg: Drawable? = null
var bgMeanColor: Int = 0
init { init {
upConfig() upConfig()
@ -67,7 +70,13 @@ object ReadBookConfig {
val dm = resources.displayMetrics val dm = resources.displayMetrics
val width = dm.widthPixels val width = dm.widthPixels
val height = dm.heightPixels val height = dm.heightPixels
bg = durConfig.bgDrawable(width, height) bg = durConfig.bgDrawable(width, height).apply {
if (this is BitmapDrawable) {
bgMeanColor = BitmapUtils.getMeanColor(bitmap)
} else if (this is ColorDrawable) {
bgMeanColor = color
}
}
} }
fun save() { fun save() {
@ -155,10 +164,24 @@ object ReadBookConfig {
if (shareLayout) shareConfig.paragraphSpacing = value if (shareLayout) shareConfig.paragraphSpacing = value
else durConfig.paragraphSpacing = value else durConfig.paragraphSpacing = value
var titleCenter: Boolean var titleMode: Int
get() = if (shareLayout) shareConfig.titleCenter else durConfig.titleCenter get() = if (shareLayout) shareConfig.titleMode else durConfig.titleMode
set(value) =
if (shareLayout) shareConfig.titleMode = value else durConfig.titleMode = value
var titleSize: Int
get() = if (shareLayout) shareConfig.titleSize else durConfig.titleSize
set(value) =
if (shareLayout) shareConfig.titleSize = value else durConfig.titleSize = value
var titleTopSpacing: Int
get() = if (shareLayout) shareConfig.titleTopSpacing else durConfig.titleTopSpacing
set(value) =
if (shareLayout) shareConfig.titleTopSpacing = value
else durConfig.titleTopSpacing = value
var titleBottomSpacing: Int
get() = if (shareLayout) shareConfig.titleBottomSpacing else durConfig.titleBottomSpacing
set(value) = set(value) =
if (shareLayout) shareConfig.titleCenter = value else durConfig.titleCenter = value if (shareLayout) shareConfig.titleBottomSpacing = value
else durConfig.titleBottomSpacing = value
var paddingBottom: Int var paddingBottom: Int
get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom get() = if (shareLayout) shareConfig.paddingBottom else durConfig.paddingBottom
@ -228,6 +251,19 @@ object ReadBookConfig {
if (shareLayout) shareConfig.footerPaddingTop = value if (shareLayout) shareConfig.footerPaddingTop = value
else durConfig.footerPaddingTop = value else durConfig.footerPaddingTop = value
var showHeaderLine: Boolean
get() = if (shareLayout) shareConfig.showHeaderLine else durConfig.showHeaderLine
set(value) =
if (shareLayout) shareConfig.showHeaderLine = value
else durConfig.showHeaderLine = value
var showFooterLine: Boolean
get() = if (shareLayout) shareConfig.showFooterLine else durConfig.showFooterLine
set(value) =
if (shareLayout) shareConfig.showFooterLine = value
else durConfig.showFooterLine = value
@Keep
class Config( class Config(
private var bgStr: String = "#EEEEEE",//白天背景 private var bgStr: String = "#EEEEEE",//白天背景
private var bgStrNight: String = "#000000",//夜间背景 private var bgStrNight: String = "#000000",//夜间背景
@ -239,10 +275,13 @@ object ReadBookConfig {
private var textColorNight: String = "#ADADAD",//夜间文字颜色 private var textColorNight: String = "#ADADAD",//夜间文字颜色
var textBold: Boolean = false,//是否粗体字 var textBold: Boolean = false,//是否粗体字
var textSize: Int = 20,//文字大小 var textSize: Int = 20,//文字大小
var letterSpacing: Float = 1f,//字间距 var letterSpacing: Float = 0.5f,//字间距
var lineSpacingExtra: Int = 12,//行间距 var lineSpacingExtra: Int = 12,//行间距
var paragraphSpacing: Int = 12,//段距 var paragraphSpacing: Int = 12,//段距
var titleCenter: Boolean = true,//标题居中 var titleMode: Int = 0,//标题居中
var titleSize: Int = 0,
var titleTopSpacing: Int = 0,
var titleBottomSpacing: Int = 0,
var paddingBottom: Int = 6, var paddingBottom: Int = 6,
var paddingLeft: Int = 16, var paddingLeft: Int = 16,
var paddingRight: Int = 16, var paddingRight: Int = 16,
@ -254,7 +293,9 @@ object ReadBookConfig {
var footerPaddingBottom: Int = 6, var footerPaddingBottom: Int = 6,
var footerPaddingLeft: Int = 16, var footerPaddingLeft: Int = 16,
var footerPaddingRight: Int = 16, var footerPaddingRight: Int = 16,
var footerPaddingTop: Int = 6 var footerPaddingTop: Int = 6,
var showHeaderLine: Boolean = false,
var showFooterLine: Boolean = true
) { ) {
fun setBg(bgType: Int, bg: String) { fun setBg(bgType: Int, bg: String) {
if (AppConfig.isNightTheme) { if (AppConfig.isNightTheme) {

@ -57,6 +57,16 @@ object HttpHelper {
return null return null
} }
suspend fun simpleGetByteAsync(url: String): ByteArray? {
NetworkUtils.getBaseUrl(url)?.let { baseUrl ->
return getByteRetrofit(baseUrl)
.create(HttpGetApi::class.java)
.getMapByteAsync(url, mapOf(), mapOf())
.body()
}
return null
}
inline fun <reified T> getApiService(baseUrl: String, encode: String? = null): T { inline fun <reified T> getApiService(baseUrl: String, encode: String? = null): T {
return getRetrofit(baseUrl, encode).create(T::class.java) return getRetrofit(baseUrl, encode).create(T::class.java)
} }

@ -36,7 +36,7 @@ object ImportOldData {
try {// Book source try {// Book source
val sourceFile = val sourceFile =
FileUtils.createFileIfNotExist(file, "myBookSource.json") FileUtils.getFile(file, "myBookSource.json")
val json = sourceFile.readText() val json = sourceFile.readText()
val importCount = importOldSource(json) val importCount = importOldSource(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -49,12 +49,17 @@ object ImportOldData {
} }
try {// Replace rules try {// Replace rules
val ruleFile = val ruleFile = FileUtils.getFile(file, "myBookReplaceRule.json")
FileUtils.createFileIfNotExist(file, "myBookReplaceRule.json") if (ruleFile.exists()) {
val json = ruleFile.readText() val json = ruleFile.readText()
val importCount = importOldReplaceRule(json) val importCount = importOldReplaceRule(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
context.toast("成功导入替换规则${importCount}") context.toast("成功导入替换规则${importCount}")
}
} else {
withContext(Dispatchers.Main) {
context.toast("未找到替换规则")
}
} }
} catch (e: Exception) { } catch (e: Exception) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -131,13 +136,17 @@ object ImportOldData {
return bookSources.size return bookSources.size
} }
fun importOldReplaceRule(json: String): Int { fun importOldReplaceRule(json: String): Int {
val replaceRules = mutableListOf<ReplaceRule>() val replaceRules = mutableListOf<ReplaceRule>()
val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$") val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$")
for (item in items) { for (item in items) {
val jsonItem = Restore.jsonPath.parse(item) val jsonItem = Restore.jsonPath.parse(item)
OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let { OldRule.jsonToReplaceRule(jsonItem.jsonString())?.let {
replaceRules.add(it) if (it.isValid()){
replaceRules.add(it)
}
} }
} }
App.db.replaceRuleDao().insert(*replaceRules.toTypedArray()) App.db.replaceRuleDao().insert(*replaceRules.toTypedArray())

@ -2,20 +2,25 @@ package io.legado.app.help.storage
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.appcompat.app.AppCompatDelegate
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.Configuration import com.jayway.jsonpath.Configuration
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.Option import com.jayway.jsonpath.Option
import com.jayway.jsonpath.ParseContext import com.jayway.jsonpath.ParseContext
import io.legado.app.App import io.legado.app.App
import io.legado.app.BuildConfig
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.* import io.legado.app.data.entities.*
import io.legado.app.help.AppConfig
import io.legado.app.help.LauncherIconHelp import io.legado.app.help.LauncherIconHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.anko.defaultSharedPreferences import org.jetbrains.anko.defaultSharedPreferences
import java.io.File import java.io.File
@ -60,10 +65,11 @@ object Restore {
} }
} }
} }
restore(Backup.backupPath) restoreDatabase()
restoreConfig()
} }
suspend fun restore(path: String) { suspend fun restoreDatabase(path: String = Backup.backupPath) {
withContext(IO) { withContext(IO) {
fileToListT<Book>(path, "bookshelf.json")?.let { fileToListT<Book>(path, "bookshelf.json")?.let {
App.db.bookDao().insert(*it.toTypedArray()) App.db.bookDao().insert(*it.toTypedArray())
@ -83,11 +89,16 @@ object Restore {
fileToListT<ReplaceRule>(path, "replaceRule.json")?.let { fileToListT<ReplaceRule>(path, "replaceRule.json")?.let {
App.db.replaceRuleDao().insert(*it.toTypedArray()) App.db.replaceRuleDao().insert(*it.toTypedArray())
} }
}
}
suspend fun restoreConfig(path: String = Backup.backupPath) {
withContext(IO) {
try { try {
val file = val file =
FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName) FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName)
val configFile = val configFile =
File(App.INSTANCE.filesDir.absolutePath + File.separator + ReadBookConfig.readConfigFileName) FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName)
if (file.exists()) { if (file.exists()) {
file.copyTo(configFile, true) file.copyTo(configFile, true)
ReadBookConfig.upConfig() ReadBookConfig.upConfig()
@ -119,7 +130,18 @@ object Restore {
ChapterProvider.upStyle() ChapterProvider.upStyle()
ReadBook.loadContent() ReadBook.loadContent()
} }
LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon)) withContext(Main) {
if (AppConfig.isNightTheme && AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_YES) {
App.INSTANCE.applyDayNight()
} else if (!AppConfig.isNightTheme && AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
App.INSTANCE.applyDayNight()
} else {
postEvent(EventBus.RECREATE, "true")
}
if (!BuildConfig.DEBUG) {
LauncherIconHelp.changeIcon(App.INSTANCE.getPrefString(PreferKey.launcherIcon))
}
}
} }
private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? { private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? {

@ -0,0 +1,50 @@
package io.legado.app.help.storage
import io.legado.app.App
import io.legado.app.data.entities.BookProgress
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.webdav.WebDav
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
@Suppress("BlockingMethodInNonBlockingContext")
object SyncBookProgress {
const val fileName = "bookProgress.json"
private val file = FileUtils.createFileIfNotExist(App.INSTANCE.cacheDir, fileName)
private val webDavUrl = "${WebDavHelp.rootWebDavUrl}$fileName"
fun uploadBookProgress() {
Coroutine.async {
val value = App.db.bookDao().allBookProgress
if (value.isNotEmpty()) {
val json = GSON.toJson(value)
file.writeText(json)
if (WebDavHelp.initWebDav()) {
WebDav(webDavUrl).upload(file.absolutePath)
}
}
}
}
fun downloadBookProgress() {
Coroutine.async {
if (WebDavHelp.initWebDav()) {
WebDav(webDavUrl).downloadTo(file.absolutePath, true)
if (file.exists()) {
val json = file.readText()
GSON.fromJsonArray<BookProgress>(json)?.forEach {
App.db.bookDao().upBookProgress(
it.bookUrl,
it.durChapterIndex,
it.durChapterPos,
it.durChapterTime,
it.durChapterTitle
)
}
}
}
}
}
}

@ -10,6 +10,7 @@ import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.http.HttpAuth import io.legado.app.lib.webdav.http.HttpAuth
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.ZipUtils import io.legado.app.utils.ZipUtils
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
@ -25,31 +26,36 @@ object WebDavHelp {
private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/" private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/"
private val zipFilePath = "${FileUtils.getCachePath()}${File.separator}backup.zip" private val zipFilePath = "${FileUtils.getCachePath()}${File.separator}backup.zip"
private fun getWebDavUrl(): String { val rootWebDavUrl: String
get() {
var url = App.INSTANCE.getPrefString(PreferKey.webDavUrl) var url = App.INSTANCE.getPrefString(PreferKey.webDavUrl)
if (url.isNullOrEmpty()) { if (url.isNullOrEmpty()) {
url = defaultWebDavUrl url = defaultWebDavUrl
} }
if (!url.endsWith("/")) url += "/" if (!url.endsWith("/")) url = "${url}/"
if (App.INSTANCE.getPrefBoolean(PreferKey.webDavCreateDir, true)) {
url = "${url}legado/"
}
return url return url
} }
private fun initWebDav(): Boolean { fun initWebDav(): Boolean {
val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount) val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount)
val password = App.INSTANCE.getPrefString(PreferKey.webDavPassword) val password = App.INSTANCE.getPrefString(PreferKey.webDavPassword)
if (!account.isNullOrBlank() && !password.isNullOrBlank()) { if (!account.isNullOrBlank() && !password.isNullOrBlank()) {
HttpAuth.auth = HttpAuth.Auth(account, password) HttpAuth.auth = HttpAuth.Auth(account, password)
WebDav(rootWebDavUrl).makeAsDir()
return true return true
} }
return false return false
} }
private fun getWebDavFileNames(): ArrayList<String> { private fun getWebDavFileNames(): ArrayList<String> {
val url = getWebDavUrl() val url = rootWebDavUrl
val names = arrayListOf<String>() val names = arrayListOf<String>()
if (initWebDav()) { if (initWebDav()) {
try { try {
var files = WebDav(url + "legado/").listFiles() var files = WebDav(url).listFiles()
files = files.reversed() files = files.reversed()
for (index: Int in 0 until min(10, files.size)) { for (index: Int in 0 until min(10, files.size)) {
files[index].displayName?.let { files[index].displayName?.let {
@ -81,12 +87,17 @@ object WebDavHelp {
private fun restoreWebDav(name: String, success: () -> Unit) { private fun restoreWebDav(name: String, success: () -> Unit) {
Coroutine.async { Coroutine.async {
getWebDavUrl().let { rootWebDavUrl.let {
val file = WebDav(it + "legado/" + name) if (name == SyncBookProgress.fileName) {
file.downloadTo(zipFilePath, true) SyncBookProgress.downloadBookProgress()
@Suppress("BlockingMethodInNonBlockingContext") } else {
ZipUtils.unzipFile(zipFilePath, Backup.backupPath) val webDav = WebDav(it + name)
Restore.restore(Backup.backupPath) webDav.downloadTo(zipFilePath, true)
@Suppress("BlockingMethodInNonBlockingContext")
ZipUtils.unzipFile(zipFilePath, Backup.backupPath)
Restore.restoreDatabase()
Restore.restoreConfig()
}
} }
}.onSuccess { }.onSuccess {
success.invoke() success.invoke()
@ -102,10 +113,9 @@ object WebDavHelp {
} }
FileUtils.deleteFile(zipFilePath) FileUtils.deleteFile(zipFilePath)
if (ZipUtils.zipFiles(paths, zipFilePath)) { if (ZipUtils.zipFiles(paths, zipFilePath)) {
WebDav(getWebDavUrl() + "legado").makeAsDir() val backupDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val putUrl = getWebDavUrl() + "legado/backup" + .format(Date(System.currentTimeMillis()))
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) val putUrl = "${rootWebDavUrl}backup${backupDate}.zip"
.format(Date(System.currentTimeMillis())) + ".zip"
WebDav(putUrl).upload(zipFilePath) WebDav(putUrl).upload(zipFilePath)
} }
} }

@ -19,4 +19,16 @@ object ATHUtils {
a.recycle() a.recycle()
} }
} }
@JvmOverloads
fun resolveFloat(context: Context, @AttrRes attr: Int, fallback: Float = 0.0f): Float {
val a = context.theme.obtainStyledAttributes(intArrayOf(attr))
return try {
a.getFloat(0, fallback)
} catch (e: Exception) {
fallback
} finally {
a.recycle()
}
}
} }

@ -107,4 +107,7 @@ val Context.isDarkTheme: Boolean
get() = ColorUtils.isColorLight(ThemeStore.primaryColor(this)) get() = ColorUtils.isColorLight(ThemeStore.primaryColor(this))
val Fragment.isDarkTheme: Boolean val Fragment.isDarkTheme: Boolean
get() = requireContext().isDarkTheme get() = requireContext().isDarkTheme
val Context.elevation: Float
get() = ThemeStore.elevation(this)

@ -284,6 +284,14 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
) )
} }
@CheckResult
fun elevation(context: Context): Float {
return prefs(context).getFloat(
ThemeStorePrefKeys.KEY_ELEVATION,
ATHUtils.resolveFloat(context, android.R.attr.elevation, context.resources.getDimension(R.dimen.design_appbar_elevation))
)
}
@CheckResult @CheckResult
@ColorInt @ColorInt
fun bottomBackground(context: Context): Int { fun bottomBackground(context: Context): Int {

@ -27,4 +27,6 @@ object ThemeStorePrefKeys {
const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar" const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"
const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar" const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"
const val KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark" const val KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark"
const val KEY_ELEVATION = "elevation"
} }

@ -202,7 +202,6 @@ constructor(urlStr: String) {
* 上传文件 * 上传文件
*/ */
@Throws(IOException::class) @Throws(IOException::class)
@JvmOverloads
fun upload(localPath: String, contentType: String? = null): Boolean { fun upload(localPath: String, contentType: String? = null): Boolean {
val file = File(localPath) val file = File(localPath)
if (!file.exists()) return false if (!file.exists()) return false

@ -84,8 +84,8 @@ class WebBook(val bookSource: BookSource) {
scope: CoroutineScope = Coroutine.DEFAULT, scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<Book> { ): Coroutine<Book> {
book.type = bookSource.bookSourceType
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
book.type = bookSource.bookSourceType
val body = val body =
if (!book.infoHtml.isNullOrEmpty()) { if (!book.infoHtml.isNullOrEmpty()) {
book.infoHtml book.infoHtml
@ -111,8 +111,8 @@ class WebBook(val bookSource: BookSource) {
scope: CoroutineScope = Coroutine.DEFAULT, scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<List<BookChapter>> { ): Coroutine<List<BookChapter>> {
book.type = bookSource.bookSourceType
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
book.type = bookSource.bookSourceType
val body = val body =
if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) { if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {
book.tocHtml book.tocHtml
@ -139,36 +139,50 @@ class WebBook(val bookSource: BookSource) {
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<String> { ): Coroutine<String> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
if (bookSource.getContentRule().content.isNullOrEmpty()) { getContentSuspend(
Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") book, bookChapter, nextChapterUrl, scope
return@async bookChapter.url
}
val body =
if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) {
book.tocHtml
} else {
val analyzeUrl =
AnalyzeUrl(
book = book,
ruleUrl = bookChapter.url,
baseUrl = book.tocUrl,
headerMapF = bookSource.getHeaderMap()
)
analyzeUrl.getResponseAwait(
bookSource.bookSourceUrl,
jsStr = bookSource.getContentRule().webJs,
sourceRegex = bookSource.getContentRule().sourceRegex
).body
}
BookContent.analyzeContent(
this,
body,
book,
bookChapter,
bookSource,
bookChapter.url,
nextChapterUrl
) )
} }
} }
/**
* 章节内容
*/
suspend fun getContentSuspend(
book: Book,
bookChapter: BookChapter,
nextChapterUrl: String? = null,
scope: CoroutineScope = Coroutine.DEFAULT
): String {
if (bookSource.getContentRule().content.isNullOrEmpty()) {
Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}")
return bookChapter.url
}
val body =
if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) {
book.tocHtml
} else {
val analyzeUrl =
AnalyzeUrl(
book = book,
ruleUrl = bookChapter.url,
baseUrl = book.tocUrl,
headerMapF = bookSource.getHeaderMap()
)
analyzeUrl.getResponseAwait(
bookSource.bookSourceUrl,
jsStr = bookSource.getContentRule().webJs,
sourceRegex = bookSource.getContentRule().sourceRegex
).body
}
return BookContent.analyzeContent(
scope,
body,
book,
bookChapter,
bookSource,
bookChapter.url,
nextChapterUrl
)
}
} }

@ -1,12 +1,14 @@
package io.legado.app.model.analyzeRule package io.legado.app.model.analyzeRule
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.ReadContext import com.jayway.jsonpath.ReadContext
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@Keep
class AnalyzeByJSonPath { class AnalyzeByJSonPath {
private var ctx: ReadContext? = null private var ctx: ReadContext? = null

@ -2,6 +2,7 @@ package io.legado.app.model.analyzeRule
import android.text.TextUtils.isEmpty import android.text.TextUtils.isEmpty
import android.text.TextUtils.join import android.text.TextUtils.join
import androidx.annotation.Keep
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -15,7 +16,7 @@ import java.util.*
* Created by GKF on 2018/1/25. * Created by GKF on 2018/1/25.
* 书源规则解析 * 书源规则解析
*/ */
@Keep
class AnalyzeByJSoup { class AnalyzeByJSoup {
private var element: Element? = null private var element: Element? = null

@ -1,8 +1,10 @@
package io.legado.app.model.analyzeRule package io.legado.app.model.analyzeRule
import androidx.annotation.Keep
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@Keep
object AnalyzeByRegex { object AnalyzeByRegex {
fun getElement(res: String, regs: Array<String>, index: Int = 0): List<String>? { fun getElement(res: String, regs: Array<String>, index: Int = 0): List<String>? {

@ -1,6 +1,7 @@
package io.legado.app.model.analyzeRule package io.legado.app.model.analyzeRule
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
@ -9,6 +10,7 @@ import org.seimicrawler.xpath.JXDocument
import org.seimicrawler.xpath.JXNode import org.seimicrawler.xpath.JXNode
import java.util.* import java.util.*
@Keep
class AnalyzeByXPath { class AnalyzeByXPath {
private var jxDocument: JXDocument? = null private var jxDocument: JXDocument? = null
private var jxNode: JXNode? = null private var jxNode: JXNode? = null

@ -3,7 +3,7 @@ package io.legado.app.model.analyzeRule
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.constant.AppConst.SCRIPT_ENGINE import io.legado.app.constant.AppConst.SCRIPT_ENGINE
import io.legado.app.constant.Pattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions

@ -4,8 +4,8 @@ import android.annotation.SuppressLint
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.constant.AppConst.SCRIPT_ENGINE import io.legado.app.constant.AppConst.SCRIPT_ENGINE
import io.legado.app.constant.Pattern.EXP_PATTERN import io.legado.app.constant.AppPattern.EXP_PATTERN
import io.legado.app.constant.Pattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.http.* import io.legado.app.help.http.*

@ -20,7 +20,7 @@ object AnalyzeTxtFile {
private const val BUFFER_SIZE = 512 * 1024 private const val BUFFER_SIZE = 512 * 1024
//没有标题的时候,每个章节的最大长度 //没有标题的时候,每个章节的最大长度
private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024 private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024
private val cacheFolder: File by lazy { val cacheFolder: File by lazy {
val rootFile = App.INSTANCE.getExternalFilesDir(null) val rootFile = App.INSTANCE.getExternalFilesDir(null)
?: App.INSTANCE.externalCacheDir ?: App.INSTANCE.externalCacheDir
?: App.INSTANCE.cacheDir ?: App.INSTANCE.cacheDir

@ -1,8 +1,10 @@
package io.legado.app.model.localBook package io.legado.app.model.localBook
import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import io.legado.app.App import io.legado.app.App
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.utils.FileUtils
object LocalBook { object LocalBook {
@ -27,4 +29,17 @@ object LocalBook {
} }
} }
fun deleteBook(book: Book, deleteOriginal: Boolean) {
kotlin.runCatching {
if (book.isTxt()) {
val bookFile = FileUtils.getFile(AnalyzeTxtFile.cacheFolder, book.originName)
bookFile.delete()
}
if (deleteOriginal) {
val uri = Uri.parse(book.bookUrl)
DocumentFile.fromSingleUri(App.INSTANCE, uri)?.delete()
}
}
}
} }

@ -1,6 +1,5 @@
package io.legado.app.model.rss package io.legado.app.model.rss
import io.legado.app.constant.RSSKeywords
import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssArticle
import io.legado.app.model.Debug import io.legado.app.model.Debug
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
@ -34,26 +33,26 @@ object RssParser {
// Start parsing the item // Start parsing the item
if (eventType == XmlPullParser.START_TAG) { if (eventType == XmlPullParser.START_TAG) {
when { when {
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM, true) -> xmlPullParser.name.equals(RSS_ITEM, true) ->
insideItem = true insideItem = true
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TITLE, true) -> xmlPullParser.name.equals(RSS_ITEM_TITLE, true) ->
if (insideItem) currentArticle.title = xmlPullParser.nextText().trim() if (insideItem) currentArticle.title = xmlPullParser.nextText().trim()
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_LINK, true) -> xmlPullParser.name.equals(RSS_ITEM_LINK, true) ->
if (insideItem) currentArticle.link = xmlPullParser.nextText().trim() if (insideItem) currentArticle.link = xmlPullParser.nextText().trim()
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_THUMBNAIL, true) -> xmlPullParser.name.equals(RSS_ITEM_THUMBNAIL, true) ->
if (insideItem) currentArticle.image = if (insideItem) currentArticle.image =
xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL) xmlPullParser.getAttributeValue(null, RSS_ITEM_URL)
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_ENCLOSURE, true) -> xmlPullParser.name.equals(RSS_ITEM_ENCLOSURE, true) ->
if (insideItem) { if (insideItem) {
val type = val type =
xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_TYPE) xmlPullParser.getAttributeValue(null, RSS_ITEM_TYPE)
if (type != null && type.contains("image/")) { if (type != null && type.contains("image/")) {
currentArticle.image = currentArticle.image =
xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL) xmlPullParser.getAttributeValue(null, RSS_ITEM_URL)
} }
} }
xmlPullParser.name xmlPullParser.name
.equals(RSSKeywords.RSS_ITEM_DESCRIPTION, true) -> .equals(RSS_ITEM_DESCRIPTION, true) ->
if (insideItem) { if (insideItem) {
val description = xmlPullParser.nextText() val description = xmlPullParser.nextText()
currentArticle.description = description.trim() currentArticle.description = description.trim()
@ -61,7 +60,7 @@ object RssParser {
currentArticle.image = getImageUrl(description) currentArticle.image = getImageUrl(description)
} }
} }
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_CONTENT, true) -> xmlPullParser.name.equals(RSS_ITEM_CONTENT, true) ->
if (insideItem) { if (insideItem) {
val content = xmlPullParser.nextText().trim() val content = xmlPullParser.nextText().trim()
currentArticle.content = content currentArticle.content = content
@ -70,7 +69,7 @@ object RssParser {
} }
} }
xmlPullParser.name xmlPullParser.name
.equals(RSSKeywords.RSS_ITEM_PUB_DATE, true) -> .equals(RSS_ITEM_PUB_DATE, true) ->
if (insideItem) { if (insideItem) {
val nextTokenType = xmlPullParser.next() val nextTokenType = xmlPullParser.next()
if (nextTokenType == XmlPullParser.TEXT) { if (nextTokenType == XmlPullParser.TEXT) {
@ -79,7 +78,7 @@ object RssParser {
// Skip to be able to find date inside 'tag' tag // Skip to be able to find date inside 'tag' tag
continue@loop continue@loop
} }
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TIME, true) -> xmlPullParser.name.equals(RSS_ITEM_TIME, true) ->
if (insideItem) currentArticle.pubDate = xmlPullParser.nextText() if (insideItem) currentArticle.pubDate = xmlPullParser.nextText()
} }
} else if (eventType == XmlPullParser.END_TAG } else if (eventType == XmlPullParser.END_TAG
@ -129,4 +128,17 @@ object RssParser {
} }
return url return url
} }
private const val RSS_ITEM = "item"
private const val RSS_ITEM_TITLE = "title"
private const val RSS_ITEM_LINK = "link"
private const val RSS_ITEM_CATEGORY = "category"
private const val RSS_ITEM_THUMBNAIL = "media:thumbnail"
private const val RSS_ITEM_ENCLOSURE = "enclosure"
private const val RSS_ITEM_DESCRIPTION = "description"
private const val RSS_ITEM_CONTENT = "content:encoded"
private const val RSS_ITEM_PUB_DATE = "pubDate"
private const val RSS_ITEM_TIME = "time"
private const val RSS_ITEM_URL = "url"
private const val RSS_ITEM_TYPE = "type"
} }

@ -1,5 +1,6 @@
package io.legado.app.model.rss package io.legado.app.model.rss
import androidx.annotation.Keep
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssArticle
@ -8,6 +9,7 @@ import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
@Keep
object RssParserByRule { object RssParserByRule {
@Throws(Exception::class) @Throws(Exception::class)

@ -39,7 +39,7 @@ object BookContent {
var contentData = analyzeContent( var contentData = analyzeContent(
book, baseUrl, body, contentRule, bookChapter, bookSource book, baseUrl, body, contentRule, bookChapter, bookSource
) )
content.append(contentData.content.replace(bookChapter.title, "")) content.append(contentData.content.replace(bookChapter.title, "")).append("\n")
if (contentData.nextUrl.size == 1) { if (contentData.nextUrl.size == 1) {
var nextUrl = contentData.nextUrl[0] var nextUrl = contentData.nextUrl[0]
val nextChapterUrl = if (!nextChapterUrlF.isNullOrEmpty()) val nextChapterUrl = if (!nextChapterUrlF.isNullOrEmpty())
@ -64,7 +64,7 @@ object BookContent {
) )
nextUrl = nextUrl =
if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else "" if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else ""
content.append(contentData.content) content.append(contentData.content).append("\n")
} }
} }
Debug.log(bookSource.bookSourceUrl, "◇本章总页数:${nextUrlList.size}") Debug.log(bookSource.bookSourceUrl, "◇本章总页数:${nextUrlList.size}")
@ -91,7 +91,7 @@ object BookContent {
} }
} }
for (item in contentDataList) { for (item in contentDataList) {
content.append(item.content) content.append(item.content).append("\n")
} }
} }

@ -2,6 +2,7 @@ package io.legado.app.model.webBook
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.model.Debug import io.legado.app.model.Debug
@ -38,7 +39,7 @@ object BookInfo {
Debug.log(bookSource.bookSourceUrl, "${book.name}") Debug.log(bookSource.bookSourceUrl, "${book.name}")
Debug.log(bookSource.bookSourceUrl, "┌获取作者") Debug.log(bookSource.bookSourceUrl, "┌获取作者")
analyzeRule.getString(infoRule.author).let { analyzeRule.getString(infoRule.author).let {
if (it.isNotEmpty()) book.author = it if (it.isNotEmpty()) book.author = it.replace(AppPattern.authorRegex, "")
} }
Debug.log(bookSource.bookSourceUrl, "${book.author}") Debug.log(bookSource.bookSourceUrl, "${book.author}")
Debug.log(bookSource.bookSourceUrl, "┌获取分类") Debug.log(bookSource.bookSourceUrl, "┌获取分类")

@ -124,16 +124,16 @@ abstract class BaseReadAloudService : BaseService(),
open fun play() { open fun play() {
pause = false pause = false
postEvent(EventBus.ALOUD_STATE, Status.PLAY)
upNotification() upNotification()
postEvent(EventBus.ALOUD_STATE, Status.PLAY)
} }
@CallSuper @CallSuper
open fun pauseReadAloud(pause: Boolean) { open fun pauseReadAloud(pause: Boolean) {
postEvent(EventBus.ALOUD_STATE, Status.PAUSE)
BaseReadAloudService.pause = pause BaseReadAloudService.pause = pause
upNotification() upNotification()
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED) upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED)
postEvent(EventBus.ALOUD_STATE, Status.PAUSE)
} }
@CallSuper @CallSuper

@ -18,11 +18,12 @@ import org.jetbrains.anko.toast
import java.util.concurrent.Executors import java.util.concurrent.Executors
class CheckSourceService : BaseService() { class CheckSourceService : BaseService() {
private var searchPool = private val threadCount = AppConfig.threadCount
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
private var task: Coroutine<*>? = null private var task: Coroutine<*>? = null
private val allIds = LinkedHashSet<String>() private val allIds = ArrayList<String>()
private val checkedIds = LinkedHashSet<String>() private val checkedIds = ArrayList<String>()
private var processIndex = 0
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -50,30 +51,44 @@ class CheckSourceService : BaseService() {
allIds.clear() allIds.clear()
checkedIds.clear() checkedIds.clear()
allIds.addAll(ids) allIds.addAll(ids)
processIndex = 0
updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) updateNotification(0, getString(R.string.progress_show, 0, allIds.size))
task = execute(context = searchPool) { task = execute {
allIds.forEach { sourceUrl -> for (i in 0 until threadCount) {
App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> check()
val webBook = WebBook(source)
webBook.searchBook("我的", scope = this, context = searchPool)
.onError(IO) {
source.addGroup("失效")
App.db.bookSourceDao().update(source)
}.onFinally {
checkedIds.add(sourceUrl)
updateNotification(
checkedIds.size,
getString(R.string.progress_show, checkedIds.size, allIds.size)
)
}
}
} }
}.onError { }.onError {
toast("校验书源出错:${it.localizedMessage}") toast("校验书源出错:${it.localizedMessage}")
} }
}
task?.invokeOnCompletion { private fun check() {
stopSelf() synchronized(this) {
processIndex++
}
if (processIndex < allIds.size) {
val sourceUrl = allIds[processIndex]
App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source ->
val webBook = WebBook(source)
webBook.searchBook("我的", scope = this, context = searchPool)
.onError(IO) {
source.addGroup("失效")
App.db.bookSourceDao().update(source)
}.onFinally(IO) {
check()
checkedIds.add(sourceUrl)
updateNotification(
checkedIds.size,
getString(R.string.progress_show, checkedIds.size, allIds.size)
)
synchronized(this) {
if (processIndex >= allIds.size + threadCount - 1) {
stopSelf()
}
}
}
}
} }
} }

@ -29,8 +29,10 @@ class DownloadService : BaseService() {
private val handler = Handler() private val handler = Handler()
private var runnable: Runnable = Runnable { upDownload() } private var runnable: Runnable = Runnable { upDownload() }
private val downloadMap = hashMapOf<String, LinkedHashSet<BookChapter>>() private val downloadMap = hashMapOf<String, LinkedHashSet<BookChapter>>()
private val downloadCount = hashMapOf<String, DownloadCount>();
private val finalMap = hashMapOf<String, LinkedHashSet<BookChapter>>() private val finalMap = hashMapOf<String, LinkedHashSet<BookChapter>>()
private var notificationContent = "正在启动下载" private var notificationContent = "正在启动下载"
private val notificationBuilder by lazy { private val notificationBuilder by lazy {
val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload) val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload)
.setSmallIcon(R.drawable.ic_download) .setSmallIcon(R.drawable.ic_download)
@ -97,6 +99,11 @@ class DownloadService : BaseService() {
finalMap.remove(bookUrl) finalMap.remove(bookUrl)
} }
private fun updateNotification(downloadCount:DownloadCount, totalCount: Int, content: String){
notificationContent =
"进度:${downloadCount.downloadFinishedCount}/$totalCount,成功:${downloadCount.successCount},$content"
}
private fun download() { private fun download() {
val task = Coroutine.async(this, context = searchPool) { val task = Coroutine.async(this, context = searchPool) {
downloadMap.forEach { entry -> downloadMap.forEach { entry ->
@ -106,6 +113,9 @@ class DownloadService : BaseService() {
val bookSource = val bookSource =
App.db.bookSourceDao().getBookSource(book.origin) ?: return@async App.db.bookSourceDao().getBookSource(book.origin) ?: return@async
val webBook = WebBook(bookSource) val webBook = WebBook(bookSource)
downloadCount[entry.key] = DownloadCount()
entry.value.forEach { chapter -> entry.value.forEach { chapter ->
if (!isActive) return@async if (!isActive) return@async
if (downloadMap.containsKey(book.bookUrl)) { if (downloadMap.containsKey(book.bookUrl)) {
@ -116,16 +126,19 @@ class DownloadService : BaseService() {
scope = this, scope = this,
context = searchPool context = searchPool
) )
.onStart { //.onStart {
notificationContent = chapter.title // notificationContent = "启动:" + chapter.title
} //}
.onSuccess(IO) { content -> .onSuccess(IO) { content ->
content?.let { content?.let {
downloadCount[entry.key]?.increaseSuccess()
BookHelp.saveContent(book, chapter, content) BookHelp.saveContent(book, chapter, content)
} }
} }
.onFinally(IO) { .onFinally(IO) {
synchronized(this@DownloadService) { synchronized(this@DownloadService) {
downloadCount[entry.key]?.increaseFinished()
downloadCount[entry.key]?.let { updateNotification(it, entry.value.size, chapter.title) }
val chapterMap = val chapterMap =
finalMap[book.bookUrl] finalMap[book.bookUrl]
?: linkedSetOf<BookChapter>().apply { ?: linkedSetOf<BookChapter>().apply {
@ -135,9 +148,14 @@ class DownloadService : BaseService() {
if (chapterMap.size == entry.value.size) { if (chapterMap.size == entry.value.size) {
downloadMap.remove(book.bookUrl) downloadMap.remove(book.bookUrl)
finalMap.remove(book.bookUrl) finalMap.remove(book.bookUrl)
downloadCount.remove(entry.key)
} }
} }
} }
} else{
//无需下载的,设置为增加成功
downloadCount[entry.key]?.increaseSuccess()
downloadCount[entry.key]?.increaseFinished()
} }
} }
} }
@ -176,4 +194,17 @@ class DownloadService : BaseService() {
val notification = builder.build() val notification = builder.build()
startForeground(AppConst.notificationIdDownload, notification) startForeground(AppConst.notificationIdDownload, notification)
} }
}
class DownloadCount{
@Volatile public var downloadFinishedCount = 0 // 下载完成的条目数量
@Volatile public var successCount = 0 //下载成功的条目数量
fun increaseSuccess(){
++successCount;
}
fun increaseFinished(){
++downloadFinishedCount;
}
} }

@ -13,7 +13,6 @@ import io.legado.app.utils.FileUtils
import io.legado.app.utils.LogUtils import io.legado.app.utils.LogUtils
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import org.jetbrains.anko.toast
import java.io.File import java.io.File
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.FileInputStream import java.io.FileInputStream
@ -84,8 +83,6 @@ class HttpReadAloudService : BaseReadAloudService(),
break break
} }
} }
}.onError {
toast("下载朗读文件出错:${it.localizedMessage}")
} }
} }

@ -175,9 +175,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
} }
override fun onError(s: String) { override fun onError(s: String) {
launch { pauseReadAloud(true)
toast(s)
}
} }
} }

@ -3,12 +3,12 @@ package io.legado.app.service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseService import io.legado.app.base.BaseService
import io.legado.app.constant.IntentAction
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.IntentAction
import io.legado.app.constant.PreferKey
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.getPrefInt import io.legado.app.utils.getPrefInt
@ -96,7 +96,7 @@ class WebService : BaseService() {
} }
private fun getPort(): Int { private fun getPort(): Int {
var port = App.INSTANCE.getPrefInt("webPort", 1122) var port = getPrefInt(PreferKey.webPort, 1122)
if (port > 65530 || port < 1024) { if (port > 65530 || port < 1024) {
port = 1122 port = 1122
} }

@ -69,11 +69,11 @@ object ReadBook {
nextTextChapter = null nextTextChapter = null
book?.let { book?.let {
if (curTextChapter == null) { if (curTextChapter == null) {
loadContent(durChapterIndex) loadContent(durChapterIndex, upContent)
} else if (upContent) { } else if (upContent) {
callBack?.upContent() callBack?.upContent()
} }
loadContent(durChapterIndex.plus(1)) loadContent(durChapterIndex.plus(1), upContent)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
for (i in 2..10) { for (i in 2..10) {
delay(100) delay(100)
@ -99,11 +99,11 @@ object ReadBook {
prevTextChapter = null prevTextChapter = null
book?.let { book?.let {
if (curTextChapter == null) { if (curTextChapter == null) {
loadContent(durChapterIndex) loadContent(durChapterIndex, upContent)
} else if (upContent) { } else if (upContent) {
callBack?.upContent() callBack?.upContent()
} }
loadContent(durChapterIndex.minus(1)) loadContent(durChapterIndex.minus(1), upContent)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
for (i in -5..-2) { for (i in -5..-2) {
delay(100) delay(100)
@ -190,13 +190,13 @@ object ReadBook {
loadContent(durChapterIndex - 1) loadContent(durChapterIndex - 1)
} }
fun loadContent(index: Int) { fun loadContent(index: Int, upContent: Boolean = true) {
book?.let { book -> book?.let { book ->
if (addLoading(index)) { if (addLoading(index)) {
Coroutine.async { Coroutine.async {
App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter -> App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter ->
BookHelp.getContent(book, chapter)?.let { BookHelp.getContent(book, chapter)?.let {
contentLoadFinish(chapter, it) contentLoadFinish(chapter, it, upContent)
removeLoading(chapter.index) removeLoading(chapter.index)
} ?: download(chapter) } ?: download(chapter)
} ?: removeLoading(index) } ?: removeLoading(index)
@ -262,7 +262,11 @@ object ReadBook {
/** /**
* 内容加载完成 * 内容加载完成
*/ */
private fun contentLoadFinish(chapter: BookChapter, content: String) { private fun contentLoadFinish(
chapter: BookChapter,
content: String,
upContent: Boolean = true
) {
Coroutine.async { Coroutine.async {
if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) { if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) {
val c = BookHelp.disposeContent( val c = BookHelp.disposeContent(
@ -275,18 +279,18 @@ object ReadBook {
when (chapter.index) { when (chapter.index) {
durChapterIndex -> { durChapterIndex -> {
curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize)
callBack?.upContent() if (upContent) callBack?.upContent()
callBack?.upView() callBack?.upView()
curPageChanged() curPageChanged()
callBack?.contentLoadFinish() callBack?.contentLoadFinish()
} }
durChapterIndex - 1 -> { durChapterIndex - 1 -> {
prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize)
callBack?.upContent(-1) if (upContent) callBack?.upContent(-1)
} }
durChapterIndex + 1 -> { durChapterIndex + 1 -> {
nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize)
callBack?.upContent(1) if (upContent) callBack?.upContent(1)
} }
} }
} }

@ -7,14 +7,14 @@
* book\read 书籍阅读界面 * book\read 书籍阅读界面
* book\search 搜索书籍界面 * book\search 搜索书籍界面
* book\source 搜索书源界面 * book\source 搜索书源界面
* changeCover 封面换源界面 * book\changeCover 封面换源界面
* changeSource 换源界面 * book\changeSource 换源界面
* chapterList 目录界面 * book\chapterList 目录界面
* config 配置界面 * book\download 下载界面
* download 下载界面 * book\explore 发现界面
* explore 发现界面 * book\local 书籍导入界面
* fileChooser 文件选择界面 * fileChooser 文件选择界面
* importBook 书籍导入界面 * config 配置界面
* main 主界面 * main 主界面
* qrCode 二维码扫描界面 * qrCode 二维码扫描界面
* replaceRule 替换净化界面 * replaceRule 替换净化界面

@ -1,13 +1,19 @@
package io.legado.app.ui.about package io.legado.app.ui.about
import android.os.Bundle import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.lib.theme.accentColor
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import kotlinx.android.synthetic.main.activity_about.*
import org.jetbrains.anko.share import org.jetbrains.anko.share
class AboutActivity : BaseActivity(R.layout.activity_about) { class AboutActivity : BaseActivity(R.layout.activity_about) {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
@ -17,6 +23,16 @@ class AboutActivity : BaseActivity(R.layout.activity_about) {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.fl_fragment, aboutFragment, fTag) .replace(R.id.fl_fragment, aboutFragment, fTag)
.commit() .commit()
tv_app_summary.post {
val span = ForegroundColorSpan(accentColor)
val spannableString = SpannableString(tv_app_summary.text)
val start = spannableString.indexOf("开源阅读软件")
spannableString.setSpan(
span, start, start + 6,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
tv_app_summary.text = spannableString
}
} }
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {

@ -2,7 +2,6 @@ package io.legado.app.ui.about
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.preference.Preference import androidx.preference.Preference
@ -12,7 +11,6 @@ import io.legado.app.utils.ACache
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import io.legado.app.utils.sendToClip import io.legado.app.utils.sendToClip
import org.jetbrains.anko.longToast import org.jetbrains.anko.longToast
import java.net.URLEncoder
class DonateFragment : PreferenceFragmentCompat() { class DonateFragment : PreferenceFragmentCompat() {
@ -36,7 +34,6 @@ class DonateFragment : PreferenceFragmentCompat() {
"zfbHbRwm" -> requireContext().openUrl(zfbHbRwmUrl) "zfbHbRwm" -> requireContext().openUrl(zfbHbRwmUrl)
"zfbSkRwm" -> requireContext().openUrl(zfbSkRwmUrl) "zfbSkRwm" -> requireContext().openUrl(zfbSkRwmUrl)
"qqSkRwm" -> requireContext().openUrl(qqSkRwmUrl) "qqSkRwm" -> requireContext().openUrl(qqSkRwmUrl)
"zfbSk" -> aliDonate(requireContext())
"zfbHbSsm" -> getZfbHb(requireContext()) "zfbHbSsm" -> getZfbHb(requireContext())
"gzGzh" -> requireContext().sendToClip("开源阅读软件") "gzGzh" -> requireContext().sendToClip("开源阅读软件")
} }
@ -59,19 +56,4 @@ class DonateFragment : PreferenceFragmentCompat() {
} }
} }
private fun aliDonate(context: Context) {
try {
val qrCode = URLEncoder.encode(
"https://qr.alipay.com/tsx06677nwdk3javroq4ef0?_s=Dweb-other",
"utf-8"
)
val aliPayQr =
"alipayqr://platformapi/startapp?saId=10000007&qrcode=$qrCode&_t=${System.currentTimeMillis()}"
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(aliPayQr))
context.startActivity(intent)
} catch (e: Exception) {
e.printStackTrace()
}
}
} }

@ -11,7 +11,7 @@ import android.widget.SeekBar
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions.bitmapTransform
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
@ -25,8 +25,8 @@ import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.dialogs.okButton
import io.legado.app.service.AudioPlayService import io.legado.app.service.AudioPlayService
import io.legado.app.service.help.AudioPlay import io.legado.app.service.help.AudioPlay
import io.legado.app.ui.changesource.ChangeSourceDialog import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_audio_play.* import kotlinx.android.synthetic.main.activity_audio_play.*
import org.apache.commons.lang3.time.DateFormatUtils import org.apache.commons.lang3.time.DateFormatUtils
@ -46,8 +46,9 @@ class AudioPlayActivity :
private var adjustProgress = false private var adjustProgress = false
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.background.alpha = 0 setSupportActionBar(toolbar)
AudioPlay.titleData.observe(this, Observer { title_bar.title = it }) supportActionBar?.setDisplayHomeAsUpEnabled(true)
AudioPlay.titleData.observe(this, Observer { toolbar.title = it })
AudioPlay.coverData.observe(this, Observer { upCover(it) }) AudioPlay.coverData.observe(this, Observer { upCover(it) })
viewModel.initData(intent) viewModel.initData(intent)
initView() initView()
@ -119,19 +120,17 @@ class AudioPlayActivity :
ImageLoader.load(this, path) ImageLoader.load(this, path)
.placeholder(R.drawable.image_cover_default) .placeholder(R.drawable.image_cover_default)
.error(R.drawable.image_cover_default) .error(R.drawable.image_cover_default)
.centerCrop()
.into(iv_cover) .into(iv_cover)
ImageLoader.load(this, path) ImageLoader.load(this, path)
.transition(DrawableTransitionOptions.withCrossFade(1500)) .transition(DrawableTransitionOptions.withCrossFade(1500))
.thumbnail(defaultCover()) .thumbnail(defaultCover())
.centerCrop() .apply(bitmapTransform(BlurTransformation(this, 25)))
.apply(RequestOptions.bitmapTransform(BlurTransformation(this, 25)))
.into(iv_bg) .into(iv_bg)
} }
private fun defaultCover(): RequestBuilder<Drawable> { private fun defaultCover(): RequestBuilder<Drawable> {
return ImageLoader.load(this, R.drawable.image_cover_default) return ImageLoader.load(this, R.drawable.image_cover_default)
.apply(RequestOptions.bitmapTransform(BlurTransformation(this, 25))) .apply(bitmapTransform(BlurTransformation(this, 25)))
} }
private fun playButton() { private fun playButton() {

@ -32,6 +32,7 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
get() = getViewModel(ArrangeBookViewModel::class.java) get() = getViewModel(ArrangeBookViewModel::class.java)
override val groupList: ArrayList<BookGroup> = arrayListOf() override val groupList: ArrayList<BookGroup> = arrayListOf()
private val groupRequestCode = 22 private val groupRequestCode = 22
private val addToGroupRequestCode = 34
private lateinit var adapter: ArrangeBookAdapter private lateinit var adapter: ArrangeBookAdapter
private var groupLiveData: LiveData<List<BookGroup>>? = null private var groupLiveData: LiveData<List<BookGroup>>? = null
private var booksLiveData: LiveData<List<Book>>? = null private var booksLiveData: LiveData<List<Book>>? = null
@ -99,10 +100,10 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
-1 -> App.db.bookDao().observeAll() -1 -> App.db.bookDao().observeAll()
-2 -> App.db.bookDao().observeLocal() -2 -> App.db.bookDao().observeLocal()
-3 -> App.db.bookDao().observeAudio() -3 -> App.db.bookDao().observeAudio()
-11 -> App.db.bookDao().observeNoGroup()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }
booksLiveData?.observe(this, Observer { booksLiveData?.observe(this, Observer {
adapter.selectedBooks.clear()
adapter.setItems(it) adapter.setItems(it)
upSelectCount() upSelectCount()
}) })
@ -112,28 +113,29 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
when (item.itemId) { when (item.itemId) {
R.id.menu_group_manage -> GroupManageDialog() R.id.menu_group_manage -> GroupManageDialog()
.show(supportFragmentManager, "groupManage") .show(supportFragmentManager, "groupManage")
R.id.menu_no_group -> {
title_bar.subtitle = getString(R.string.no_group)
groupId = -11
initBookData()
}
R.id.menu_all -> { R.id.menu_all -> {
title_bar.subtitle = item.title title_bar.subtitle = item.title
groupId = -1 groupId = -1
adapter.selectedBooks.clear()
initBookData() initBookData()
} }
R.id.menu_local -> { R.id.menu_local -> {
title_bar.subtitle = item.title title_bar.subtitle = item.title
groupId = -2 groupId = -2
adapter.selectedBooks.clear()
initBookData() initBookData()
} }
R.id.menu_audio -> { R.id.menu_audio -> {
title_bar.subtitle = item.title title_bar.subtitle = item.title
groupId = -3 groupId = -3
adapter.selectedBooks.clear()
initBookData() initBookData()
} }
else -> if (item.groupId == R.id.menu_group) { else -> if (item.groupId == R.id.menu_group) {
title_bar.subtitle = item.title title_bar.subtitle = item.title
groupId = item.itemId groupId = item.itemId
adapter.selectedBooks.clear()
initBookData() initBookData()
} }
} }
@ -144,9 +146,14 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
when (item?.itemId) { when (item?.itemId) {
R.id.menu_del_selection -> R.id.menu_del_selection ->
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) { alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
okButton { viewModel.deleteBook(*adapter.selectedBooks.toTypedArray()) } okButton { viewModel.deleteBook(*adapter.selectedBooks()) }
noButton { } noButton { }
}.show().applyTint() }.show().applyTint()
R.id.menu_update_enable ->
viewModel.upCanUpdate(adapter.selectedBooks(), true)
R.id.menu_update_disable ->
viewModel.upCanUpdate(adapter.selectedBooks(), false)
R.id.menu_add_to_group -> selectGroup(0, addToGroupRequestCode)
} }
return false return false
} }
@ -168,7 +175,7 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
when (requestCode) { when (requestCode) {
groupRequestCode -> { groupRequestCode -> {
val books = arrayListOf<Book>() val books = arrayListOf<Book>()
adapter.selectedBooks.forEach { adapter.selectedBooks().forEach {
books.add(it.copy(group = groupId)) books.add(it.copy(group = groupId))
} }
viewModel.updateBook(*books.toTypedArray()) viewModel.updateBook(*books.toTypedArray())
@ -178,11 +185,18 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
viewModel.updateBook(it.copy(group = groupId)) viewModel.updateBook(it.copy(group = groupId))
} }
} }
addToGroupRequestCode -> {
val books = arrayListOf<Book>()
adapter.selectedBooks().forEach {
books.add(it.copy(group = it.group or groupId))
}
viewModel.updateBook(*books.toTypedArray())
}
} }
} }
override fun upSelectCount() { override fun upSelectCount() {
select_action_bar.upCountView(adapter.selectedBooks.size, adapter.getItems().size) select_action_bar.upCountView(adapter.selectedBooks().size, adapter.getItems().size)
} }
override fun deleteBook(book: Book) { override fun deleteBook(book: Book) {

@ -13,7 +13,7 @@ import org.jetbrains.anko.sdk27.listeners.onClick
class ArrangeBookAdapter(context: Context, val callBack: CallBack) : class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<Book>(context, R.layout.item_arrange_book) { SimpleRecyclerAdapter<Book>(context, R.layout.item_arrange_book) {
val groupRequestCode = 12 val groupRequestCode = 12
val selectedBooks: HashSet<Book> = hashSetOf() private val selectedBooks: HashSet<Book> = hashSetOf()
var actionItem: Book? = null var actionItem: Book? = null
fun selectAll(selectAll: Boolean) { fun selectAll(selectAll: Boolean) {
@ -40,6 +40,16 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
callBack.upSelectCount() callBack.upSelectCount()
} }
fun selectedBooks(): Array<Book> {
val books = arrayListOf<Book>()
selectedBooks.forEach {
if (getItems().contains(it)) {
books.add(it)
}
}
return books.toTypedArray()
}
override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList<Any>) { override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList<Any>) {
with(holder.itemView) { with(holder.itemView) {
tv_name.text = if (item.author.isEmpty()) { tv_name.text = if (item.author.isEmpty()) {

@ -8,6 +8,15 @@ import io.legado.app.data.entities.Book
class ArrangeBookViewModel(application: Application) : BaseViewModel(application) { class ArrangeBookViewModel(application: Application) : BaseViewModel(application) {
fun upCanUpdate(books: Array<Book>, canUpdate: Boolean) {
execute {
books.forEach {
it.canUpdate = canUpdate
}
App.db.bookDao().update(*books)
}
}
fun updateBook(vararg book: Book) { fun updateBook(vararg book: Book) {
execute { execute {
App.db.bookDao().update(*book) App.db.bookDao().update(*book)

@ -1,21 +1,26 @@
package io.legado.app.ui.changecover package io.legado.app.ui.book.changecover
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.Theme
import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.dialog_change_source.* import kotlinx.android.synthetic.main.dialog_change_source.*
class ChangeCoverDialog : DialogFragment(), class ChangeCoverDialog : BaseDialogFragment(),
ChangeCoverViewModel.CallBack, Toolbar.OnMenuItemClickListener,
CoverAdapter.CallBack { CoverAdapter.CallBack {
companion object { companion object {
@ -35,7 +40,7 @@ class ChangeCoverDialog : DialogFragment(),
private var callBack: CallBack? = null private var callBack: CallBack? = null
private lateinit var viewModel: ChangeCoverViewModel private lateinit var viewModel: ChangeCoverViewModel
override lateinit var adapter: CoverAdapter lateinit var adapter: CoverAdapter
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
@ -51,30 +56,57 @@ class ChangeCoverDialog : DialogFragment(),
): View? { ): View? {
callBack = activity as? CallBack callBack = activity as? CallBack
viewModel = getViewModel(ChangeCoverViewModel::class.java) viewModel = getViewModel(ChangeCoverViewModel::class.java)
viewModel.callBack = this return inflater.inflate(R.layout.dialog_change_cover, container)
return inflater.inflate(R.layout.dialog_change_source, container)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.searchStateData.observe(this, Observer {
refresh_progress_bar.isAutoLoading = it
})
tool_bar.setTitle(R.string.change_cover_source) tool_bar.setTitle(R.string.change_cover_source)
arguments?.let { bundle -> viewModel.initData(arguments)
bundle.getString("name")?.let { initMenu()
viewModel.name = it initView()
} }
bundle.getString("author")?.let {
viewModel.author = it private fun initMenu() {
} tool_bar.inflateMenu(R.menu.change_cover)
} tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
tool_bar.setOnMenuItemClickListener(this)
}
private fun initView() {
recycler_view.layoutManager = GridLayoutManager(requireContext(), 3) recycler_view.layoutManager = GridLayoutManager(requireContext(), 3)
adapter = CoverAdapter(requireContext(), this) adapter = CoverAdapter(requireContext(), this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
viewModel.initData() viewModel.loadDbSearchBook()
} }
override fun observeLiveBus() {
super.observeLiveBus()
viewModel.searchStateData.observe(viewLifecycleOwner, Observer {
refresh_progress_bar.isAutoLoading = it
if (it) {
stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp)
} else {
stopMenuItem?.setIcon(R.drawable.ic_refresh_black_24dp)
}
tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
})
viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer {
val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it))
adapter.setItems(it)
diffResult.dispatchUpdatesTo(adapter)
})
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_stop -> viewModel.stopSearch()
}
return false
}
private val stopMenuItem: MenuItem?
get() = tool_bar.menu.findItem(R.id.menu_stop)
override fun changeTo(coverUrl: String) { override fun changeTo(coverUrl: String) {
callBack?.coverChangeTo(coverUrl) callBack?.coverChangeTo(coverUrl)
dismiss() dismiss()

@ -1,35 +1,51 @@
package io.legado.app.ui.changecover package io.legado.app.ui.book.changecover
import android.app.Application import android.app.Application
import android.os.Bundle
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.withContext
import java.util.concurrent.Executors import java.util.concurrent.Executors
class ChangeCoverViewModel(application: Application) : BaseViewModel(application) { class ChangeCoverViewModel(application: Application) : BaseViewModel(application) {
private var searchPool = private var searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
var callBack: CallBack? = null
var name: String = "" var name: String = ""
var author: String = "" var author: String = ""
private var task: Coroutine<*>? = null private var task: Coroutine<*>? = null
val searchStateData = MutableLiveData<Boolean>() val searchStateData = MutableLiveData<Boolean>()
val searchBooksLiveData = MutableLiveData<List<SearchBook>>()
private val searchBooks = ArrayList<SearchBook>()
fun initData() { fun initData(arguments: Bundle?) {
arguments?.let { bundle ->
bundle.getString("name")?.let {
name = it
}
bundle.getString("author")?.let {
author = it.replace(AppPattern.authorRegex, "")
}
}
}
fun loadDbSearchBook() {
execute { execute {
App.db.searchBookDao().getEnableHasCover(name, author) App.db.searchBookDao().getEnableHasCover(name, author).let {
}.onSuccess { searchBooks.addAll(it)
it?.let { if (it.size <= 1) {
callBack?.adapter?.setItems(it) searchBooksLiveData.postValue(searchBooks)
search()
} else {
searchBooksLiveData.postValue(searchBooks)
}
} }
}.onFinally {
search()
} }
} }
@ -48,12 +64,9 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
&& !searchBook.coverUrl.isNullOrEmpty() && !searchBook.coverUrl.isNullOrEmpty()
) { ) {
App.db.searchBookDao().insert(searchBook) App.db.searchBookDao().insert(searchBook)
callBack?.adapter?.let { adapter -> if (!searchBooks.contains(searchBook)) {
if (!adapter.getItems().contains(searchBook)) { searchBooks.add(searchBook)
withContext(Dispatchers.Main) { searchBooksLiveData.postValue(searchBooks)
adapter.addItem(searchBook)
}
}
} }
} }
} }
@ -66,12 +79,17 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
} }
} }
fun stopSearch() {
if (task?.isActive == true) {
task?.cancel()
} else {
search()
}
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
searchPool.close() searchPool.close()
} }
interface CallBack {
var adapter: CoverAdapter
}
} }

@ -1,4 +1,4 @@
package io.legado.app.ui.changecover package io.legado.app.ui.book.changecover
import android.content.Context import android.content.Context
import io.legado.app.R import io.legado.app.R

@ -0,0 +1,52 @@
package io.legado.app.ui.book.changecover
import android.os.Bundle
import androidx.recyclerview.widget.DiffUtil
import io.legado.app.data.entities.SearchBook
class DiffCallBack(private val oldItems: List<SearchBook>, private val newItems: List<SearchBook>) :
DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldItems.size
}
override fun getNewListSize(): Int {
return newItems.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return oldItem.bookUrl == newItem.bookUrl
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
if (oldItem.originName != newItem.originName) {
return false
}
if (oldItem.coverUrl != newItem.coverUrl) {
return false
}
return true
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
val payload = Bundle()
if (oldItem.originName != newItem.originName) {
payload.putString("name", newItem.originName)
}
if (oldItem.coverUrl != newItem.coverUrl) {
payload.putString("coverUrl", newItem.coverUrl)
}
if (payload.isEmpty) {
return null
}
return payload
}
}

@ -1,4 +1,4 @@
package io.legado.app.ui.changesource package io.legado.app.ui.book.changesource
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle

@ -1,4 +1,4 @@
package io.legado.app.ui.changesource package io.legado.app.ui.book.changesource
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -8,24 +8,26 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.constant.Theme
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.putPrefBoolean import io.legado.app.utils.putPrefBoolean
import kotlinx.android.synthetic.main.dialog_change_source.* import kotlinx.android.synthetic.main.dialog_change_source.*
class ChangeSourceDialog : DialogFragment(), class ChangeSourceDialog : BaseDialogFragment(),
Toolbar.OnMenuItemClickListener, Toolbar.OnMenuItemClickListener,
ChangeSourceAdapter.CallBack { ChangeSourceAdapter.CallBack {
@ -65,18 +67,14 @@ class ChangeSourceDialog : DialogFragment(),
return inflater.inflate(R.layout.dialog_change_source, container) return inflater.inflate(R.layout.dialog_change_source, container)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.initData(arguments) viewModel.initData(arguments)
showTitle() showTitle()
tool_bar.inflateMenu(R.menu.change_source)
tool_bar.setOnMenuItemClickListener(this)
initRecyclerView()
initMenu() initMenu()
initRecyclerView()
initSearchView() initSearchView()
initLiveData() initLiveData()
viewModel.loadDbSearchBook() viewModel.loadDbSearchBook()
viewModel.search()
} }
private fun showTitle() { private fun showTitle() {
@ -85,6 +83,9 @@ class ChangeSourceDialog : DialogFragment(),
} }
private fun initMenu() { private fun initMenu() {
tool_bar.inflateMenu(R.menu.change_source)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
tool_bar.setOnMenuItemClickListener(this)
tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked =
getPrefBoolean(PreferKey.changeSourceLoadToc) getPrefBoolean(PreferKey.changeSourceLoadToc)
} }
@ -110,7 +111,7 @@ class ChangeSourceDialog : DialogFragment(),
} }
private fun initSearchView() { private fun initSearchView() {
val searchView = tool_bar.menu.findItem(R.id.menu_search).actionView as SearchView val searchView = tool_bar.menu.findItem(R.id.menu_screen).actionView as SearchView
searchView.setOnCloseListener { searchView.setOnCloseListener {
showTitle() showTitle()
false false
@ -135,6 +136,12 @@ class ChangeSourceDialog : DialogFragment(),
private fun initLiveData() { private fun initLiveData() {
viewModel.searchStateData.observe(viewLifecycleOwner, Observer { viewModel.searchStateData.observe(viewLifecycleOwner, Observer {
refresh_progress_bar.isAutoLoading = it refresh_progress_bar.isAutoLoading = it
if (it) {
stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp)
} else {
stopMenuItem?.setIcon(R.drawable.ic_refresh_black_24dp)
}
tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
}) })
viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer { viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer {
val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it)) val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it))
@ -143,12 +150,16 @@ class ChangeSourceDialog : DialogFragment(),
}) })
} }
private val stopMenuItem: MenuItem?
get() = tool_bar.menu.findItem(R.id.menu_stop)
override fun onMenuItemClick(item: MenuItem?): Boolean { override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) { when (item?.itemId) {
R.id.menu_load_toc -> { R.id.menu_load_toc -> {
putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked) putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked)
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
} }
R.id.menu_stop -> viewModel.stopSearch()
} }
return false return false
} }

@ -1,4 +1,4 @@
package io.legado.app.ui.changesource package io.legado.app.ui.book.changesource
import android.app.Application import android.app.Application
import android.os.Bundle import android.os.Bundle
@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
@ -35,7 +36,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
name = it name = it
} }
bundle.getString("author")?.let { bundle.getString("author")?.let {
author = it author = it.replace(AppPattern.authorRegex, "")
} }
} }
} }
@ -44,7 +45,12 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
execute { execute {
App.db.searchBookDao().getByNameAuthorEnable(name, author).let { App.db.searchBookDao().getByNameAuthorEnable(name, author).let {
searchBooks.addAll(it) searchBooks.addAll(it)
upAdapter() if (it.size <= 1) {
upAdapter()
search()
} else {
upAdapter()
}
} }
} }
} }
@ -145,6 +151,14 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} }
} }
fun stopSearch() {
if (task?.isActive == true) {
task?.cancel()
} else {
search()
}
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
searchPool.close() searchPool.close()

@ -1,4 +1,4 @@
package io.legado.app.ui.changesource package io.legado.app.ui.book.changesource
import android.os.Bundle import android.os.Bundle
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil

@ -1,4 +1,4 @@
package io.legado.app.ui.chapterlist package io.legado.app.ui.book.chapterlist
import android.os.AsyncTask.execute import android.os.AsyncTask.execute
import android.view.LayoutInflater import android.view.LayoutInflater

@ -1,4 +1,4 @@
package io.legado.app.ui.chapterlist package io.legado.app.ui.book.chapterlist
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent

@ -1,4 +1,4 @@
package io.legado.app.ui.chapterlist package io.legado.app.ui.book.chapterlist
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu

@ -1,4 +1,4 @@
package io.legado.app.ui.chapterlist package io.legado.app.ui.book.chapterlist
import android.content.Context import android.content.Context
import android.widget.TextView import android.widget.TextView

@ -1,4 +1,4 @@
package io.legado.app.ui.chapterlist package io.legado.app.ui.book.chapterlist
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent

@ -1,4 +1,4 @@
package io.legado.app.ui.chapterlist package io.legado.app.ui.book.chapterlist
import android.app.Application import android.app.Application

@ -1,4 +1,4 @@
package io.legado.app.ui.download package io.legado.app.ui.book.download
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
@ -127,7 +127,7 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
FilePicker.selectFolder(this, exportRequestCode) { FilePicker.selectFolder(this, exportRequestCode) {
val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey) val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey)
if (path.isNullOrEmpty()) { if (path.isNullOrEmpty()) {
toast("没有默认路径") toast(R.string.no_default_path)
} else { } else {
startExport(path) startExport(path)
} }

@ -1,4 +1,4 @@
package io.legado.app.ui.download package io.legado.app.ui.book.download
import android.content.Context import android.content.Context
import android.widget.ImageView import android.widget.ImageView

@ -1,4 +1,4 @@
package io.legado.app.ui.download package io.legado.app.ui.book.download
import android.app.Application import android.app.Application
import android.net.Uri import android.net.Uri

@ -1,4 +1,4 @@
package io.legado.app.ui.explore package io.legado.app.ui.book.explore
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.Observer import androidx.lifecycle.Observer

@ -1,4 +1,4 @@
package io.legado.app.ui.explore package io.legado.app.ui.book.explore
import android.content.Context import android.content.Context
import io.legado.app.R import io.legado.app.R

@ -1,4 +1,4 @@
package io.legado.app.ui.explore package io.legado.app.ui.book.explore
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent

@ -20,9 +20,9 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.AppConst
import io.legado.app.constant.Theme import io.legado.app.constant.Theme
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.help.AppConfig
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
@ -42,7 +42,7 @@ import kotlin.collections.ArrayList
class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
private lateinit var viewModel: GroupViewModel private lateinit var viewModel: GroupViewModel
private lateinit var adapter: GroupAdapter private lateinit var adapter: GroupAdapter
private var callBack: CallBack? = null private val callBack: CallBack? get() = parentFragment as? CallBack
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
@ -62,19 +62,12 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
callBack = parentFragment as? CallBack tool_bar.title = getString(R.string.group_manage)
initData() initData()
initMenu()
} }
private fun initData() { private fun initData() {
tool_bar.title = getString(R.string.group_manage)
tool_bar.inflateMenu(R.menu.book_group_manage)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
tool_bar.setOnMenuItemClickListener(this)
tool_bar.menu.findItem(R.id.menu_group_local)
.isChecked = AppConst.bookGroupLocalShow
tool_bar.menu.findItem(R.id.menu_group_audio)
.isChecked = AppConst.bookGroupAudioShow
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
@ -90,17 +83,36 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view)
} }
private fun initMenu() {
tool_bar.setOnMenuItemClickListener(this)
tool_bar.inflateMenu(R.menu.book_group_manage)
tool_bar.menu.let {
it.applyTint(requireContext(), Theme.getTheme())
it.findItem(R.id.menu_group_all)
.isChecked = AppConfig.bookGroupAllShow
it.findItem(R.id.menu_group_local)
.isChecked = AppConfig.bookGroupLocalShow
it.findItem(R.id.menu_group_audio)
.isChecked = AppConfig.bookGroupAudioShow
}
}
override fun onMenuItemClick(item: MenuItem?): Boolean { override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) { when (item?.itemId) {
R.id.menu_add -> addGroup() R.id.menu_add -> addGroup()
R.id.menu_group_all -> {
item.isChecked = !item.isChecked
AppConfig.bookGroupAllShow = item.isChecked
callBack?.upGroup()
}
R.id.menu_group_local -> { R.id.menu_group_local -> {
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
AppConst.bookGroupLocalShow = item.isChecked AppConfig.bookGroupLocalShow = item.isChecked
callBack?.upGroup() callBack?.upGroup()
} }
R.id.menu_group_audio -> { R.id.menu_group_audio -> {
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
AppConst.bookGroupAudioShow = item.isChecked AppConfig.bookGroupAudioShow = item.isChecked
callBack?.upGroup() callBack?.upGroup()
} }
} }

@ -4,7 +4,6 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
@ -20,14 +19,15 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BlurTransformation import io.legado.app.help.BlurTransformation
import io.legado.app.help.ImageLoader import io.legado.app.help.ImageLoader
import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentDataHelp
import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.audio.AudioPlayActivity import io.legado.app.ui.audio.AudioPlayActivity
import io.legado.app.ui.book.changecover.ChangeCoverDialog
import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.book.group.GroupSelectDialog
import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.info.edit.BookInfoEditActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.changecover.ChangeCoverDialog
import io.legado.app.ui.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.gone import io.legado.app.utils.gone
import io.legado.app.utils.visible import io.legado.app.utils.visible
@ -52,8 +52,8 @@ class BookInfoActivity :
get() = getViewModel(BookInfoViewModel::class.java) get() = getViewModel(BookInfoViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.background.alpha = 0 setSupportActionBar(toolbar)
tv_intro.movementMethod = ScrollingMovementMethod.getInstance() supportActionBar?.setDisplayHomeAsUpEnabled(true)
viewModel.bookData.observe(this, Observer { showBook(it) }) viewModel.bookData.observe(this, Observer { showBook(it) })
viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) viewModel.chapterListData.observe(this, Observer { upLoading(false, it) })
viewModel.initData(intent) viewModel.initData(intent)
@ -86,8 +86,13 @@ class BookInfoActivity :
} }
} }
R.id.menu_can_update -> { R.id.menu_can_update -> {
viewModel.bookData.value?.let { if (viewModel.inBookshelf) {
it.canUpdate = !it.canUpdate viewModel.bookData.value?.let {
it.canUpdate = !it.canUpdate
viewModel.saveBook()
}
} else {
toast(R.string.after_add_bookshelf)
} }
} }
} }
@ -103,7 +108,7 @@ class BookInfoActivity :
private fun showBook(book: Book) { private fun showBook(book: Book) {
showCover(book) showCover(book)
tv_name.text = book.name tv_name.text = book.name
tv_author.text = getString(R.string.author_show, book.author) tv_author.text = getString(R.string.author_show, book.getRealAuthor())
tv_origin.text = getString(R.string.origin_show, book.originName) tv_origin.text = getString(R.string.origin_show, book.originName)
tv_lasted.text = getString(R.string.lasted_show, book.latestChapterTitle) tv_lasted.text = getString(R.string.lasted_show, book.latestChapterTitle)
tv_toc.text = getString(R.string.toc_s, getString(R.string.loading)) tv_toc.text = getString(R.string.toc_s, getString(R.string.loading))
@ -124,7 +129,6 @@ class BookInfoActivity :
ImageLoader.load(this, book.getDisplayCover()) ImageLoader.load(this, book.getDisplayCover())
.transition(DrawableTransitionOptions.withCrossFade(1500)) .transition(DrawableTransitionOptions.withCrossFade(1500))
.thumbnail(defaultCover()) .thumbnail(defaultCover())
.centerCrop()
.apply(bitmapTransform(BlurTransformation(this, 25))) .apply(bitmapTransform(BlurTransformation(this, 25)))
.into(bg_book) //模糊、渐变、缩小效果 .into(bg_book) //模糊、渐变、缩小效果
} }
@ -186,9 +190,7 @@ class BookInfoActivity :
} }
tv_shelf.onClick { tv_shelf.onClick {
if (viewModel.inBookshelf) { if (viewModel.inBookshelf) {
viewModel.delBook { deleteBook()
upTvBookshelf()
}
} else { } else {
viewModel.addToBookshelf { viewModel.addToBookshelf {
upTvBookshelf() upTvBookshelf()
@ -205,7 +207,7 @@ class BookInfoActivity :
ChangeSourceDialog.show(supportFragmentManager, it.name, it.author) ChangeSourceDialog.show(supportFragmentManager, it.name, it.author)
} }
} }
tv_toc.onClick { tv_toc_view.onClick {
if (!viewModel.inBookshelf) { if (!viewModel.inBookshelf) {
viewModel.saveBook { viewModel.saveBook {
viewModel.saveChapterList { viewModel.saveChapterList {
@ -216,13 +218,39 @@ class BookInfoActivity :
openChapterList() openChapterList()
} }
} }
tv_group.onClick { tv_change_group.onClick {
viewModel.bookData.value?.let { viewModel.bookData.value?.let {
GroupSelectDialog.show(supportFragmentManager, it.group) GroupSelectDialog.show(supportFragmentManager, it.group)
} }
} }
} }
private fun deleteBook() {
viewModel.bookData.value?.let {
if (it.isLocalBook()) {
alert(
titleResource = R.string.sure,
messageResource = R.string.sure_delete_book_file
) {
positiveButton(R.string.yes) {
viewModel.delBook(true) {
finish()
}
}
negativeButton(R.string.no) {
viewModel.delBook(false) {
finish()
}
}
}.show()
} else {
viewModel.delBook {
upTvBookshelf()
}
}
}
}
private fun openChapterList() { private fun openChapterList() {
if (viewModel.chapterListData.value.isNullOrEmpty()) { if (viewModel.chapterListData.value.isNullOrEmpty()) {
toast(R.string.chapter_list_empty) toast(R.string.chapter_list_empty)
@ -320,7 +348,7 @@ class BookInfoActivity :
} }
} else { } else {
if (!viewModel.inBookshelf) { if (!viewModel.inBookshelf) {
viewModel.delBook(null) viewModel.delBook()
} }
} }
} }

@ -11,6 +11,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.model.WebBook import io.legado.app.model.WebBook
import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.model.localBook.AnalyzeTxtFile
import io.legado.app.model.localBook.LocalBook
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
class BookInfoViewModel(application: Application) : BaseViewModel(application) { class BookInfoViewModel(application: Application) : BaseViewModel(application) {
@ -198,12 +199,15 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
} }
} }
fun delBook(success: (() -> Unit)?) { fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) {
execute { execute {
bookData.value?.let { bookData.value?.let {
App.db.bookDao().delete(it) App.db.bookDao().delete(it)
inBookshelf = false
if (it.isLocalBook()) {
LocalBook.deleteBook(it, deleteOriginal)
}
} }
inBookshelf = false
}.onSuccess { }.onSuccess {
success?.invoke() success?.invoke()
} }

@ -8,7 +8,7 @@ import androidx.lifecycle.Observer
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.ui.changecover.ChangeCoverDialog import io.legado.app.ui.book.changecover.ChangeCoverDialog
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_book_info_edit.* import kotlinx.android.synthetic.main.activity_book_info_edit.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick

@ -1,4 +1,4 @@
package io.legado.app.ui.importbook package io.legado.app.ui.book.local
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity

@ -1,4 +1,4 @@
package io.legado.app.ui.importbook package io.legado.app.ui.book.local
import android.content.Context import android.content.Context
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
@ -87,7 +87,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
ll_brief.visible() ll_brief.visible()
tv_tag.text = item.name.substringAfterLast(".") tv_tag.text = item.name.substringAfterLast(".")
tv_size.text = StringUtils.toSize(item.size) tv_size.text = StringUtils.toSize(item.size)
tv_date.text = AppConst.DATE_FORMAT.format(item.date) tv_date.text = AppConst.dateFormat.format(item.date)
cb_select.isChecked = selectedUris.contains(item.uri.toString()) cb_select.isChecked = selectedUris.contains(item.uri.toString())
} }
tv_name.text = item.name tv_name.text = item.name

@ -1,4 +1,4 @@
package io.legado.app.ui.importbook package io.legado.app.ui.book.local
import android.app.Application import android.app.Application
import android.net.Uri import android.net.Uri

@ -23,6 +23,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.SyncBookProgress
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.dialogs.okButton
@ -31,6 +32,8 @@ import io.legado.app.receiver.TimeBatteryReceiver
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.config.* import io.legado.app.ui.book.read.config.*
import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR import io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR
@ -40,8 +43,6 @@ import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.TextPageFactory
import io.legado.app.ui.book.read.page.delegate.PageDelegate import io.legado.app.ui.book.read.page.delegate.PageDelegate
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.replacerule.ReplaceRuleActivity
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
@ -215,6 +216,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
R.id.menu_enable_replace -> ReadBook.book?.let { R.id.menu_enable_replace -> ReadBook.book?.let {
it.useReplaceRule = !it.useReplaceRule it.useReplaceRule = !it.useReplaceRule
menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule
onReplaceRuleSave()
} }
R.id.menu_book_info -> ReadBook.book?.let { R.id.menu_book_info -> ReadBook.book?.let {
startActivity<BookInfoActivity>(Pair("bookUrl", it.bookUrl)) startActivity<BookInfoActivity>(Pair("bookUrl", it.bookUrl))
@ -270,18 +272,18 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
} }
} }
KeyEvent.KEYCODE_SPACE -> { KeyEvent.KEYCODE_SPACE -> {
page_view.moveToNextPage() page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true return true
} }
getPrefInt(PreferKey.prevKey) -> { getPrefInt(PreferKey.prevKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToPrevPage() page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV)
return true return true
} }
} }
getPrefInt(PreferKey.nextKey) -> { getPrefInt(PreferKey.nextKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToNextPage() page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true return true
} }
} }
@ -439,11 +441,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
if (getPrefBoolean("volumeKeyPageOnPlay") if (getPrefBoolean("volumeKeyPageOnPlay")
|| BaseReadAloudService.pause || BaseReadAloudService.pause
) { ) {
when (direction) { page_view.pageDelegate?.keyTurnPage(direction)
PageDelegate.Direction.PREV -> page_view.moveToPrevPage()
PageDelegate.Direction.NEXT -> page_view.moveToNextPage()
else -> return true
}
return true return true
} }
} }
@ -481,6 +479,8 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
if (!ReadBook.isLocalBook) { if (!ReadBook.isLocalBook) {
tv_chapter_url.text = it.url tv_chapter_url.text = it.url
tv_chapter_url.visible() tv_chapter_url.visible()
} else {
tv_chapter_url.gone()
} }
seek_read_page.max = it.pageSize().minus(1) seek_read_page.max = it.pageSize().minus(1)
seek_read_page.progress = ReadBook.durPageIndex seek_read_page.progress = ReadBook.durPageIndex
@ -663,6 +663,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
mHandler.removeCallbacks(keepScreenRunnable) mHandler.removeCallbacks(keepScreenRunnable)
textActionMenu?.dismiss() textActionMenu?.dismiss()
page_view.onDestroy() page_view.onDestroy()
SyncBookProgress.uploadBookProgress()
} }
override fun observeLiveBus() { override fun observeLiveBus() {

@ -12,8 +12,8 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.buttonDisabledColor import io.legado.app.lib.theme.buttonDisabledColor
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.utils.* import io.legado.app.utils.*
@ -49,7 +49,7 @@ class ReadMenu : FrameLayout {
fabNightTheme.setImageResource(R.drawable.ic_brightness) fabNightTheme.setImageResource(R.drawable.ic_brightness)
} }
initAnimation() initAnimation()
ATH.applyBackgroundTint(fl_bottom_bg) ll_bottom_bg.setBackgroundColor(context.bottomBackground)
vw_bg.onClick { } vw_bg.onClick { }
vwNavigationBar.onClick { } vwNavigationBar.onClick { }
seek_brightness.progress = context.getPrefInt("brightness", 100) seek_brightness.progress = context.getPrefInt("brightness", 100)

@ -6,6 +6,7 @@ import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity import android.view.Gravity
@ -13,11 +14,11 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.jaredrummler.android.colorpicker.ColorPickerDialog import com.jaredrummler.android.colorpicker.ColorPickerDialog
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
@ -31,9 +32,8 @@ import kotlinx.android.synthetic.main.dialog_read_bg_text.*
import kotlinx.android.synthetic.main.item_bg_image.view.* import kotlinx.android.synthetic.main.item_bg_image.view.*
import org.jetbrains.anko.sdk27.listeners.onCheckedChange import org.jetbrains.anko.sdk27.listeners.onCheckedChange
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import java.io.File
class BgTextConfigDialog : DialogFragment() { class BgTextConfigDialog : BaseDialogFragment() {
companion object { companion object {
const val TEXT_COLOR = 121 const val TEXT_COLOR = 121
@ -69,8 +69,7 @@ class BgTextConfigDialog : DialogFragment() {
return inflater.inflate(R.layout.dialog_read_bg_text, container) return inflater.inflate(R.layout.dialog_read_bg_text, container)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initData() initData()
initView() initView()
} }
@ -171,39 +170,44 @@ class BgTextConfigDialog : DialogFragment() {
resultSelectBg -> { resultSelectBg -> {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
if (uri.toString().isContentPath()) { setBgFromUri(uri)
val doc = DocumentFile.fromSingleUri(requireContext(), uri)
doc?.let {
var file = requireContext().getExternalFilesDir(null)
?: requireContext().filesDir
file =
FileUtils.createFileIfNotExist(file.absolutePath + File.separator + "bg" + File.separator + doc.name)
DocumentUtils.readBytes(requireContext(), uri)?.let {
file.writeBytes(it)
ReadBookConfig.durConfig.setBg(2, file.absolutePath)
ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false)
}
}
} else {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
RealPathUtil.getPath(requireContext(), uri)?.let { path ->
ReadBookConfig.durConfig.setBg(2, path)
ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false)
}
}
.request()
}
} }
} }
} }
} }
} }
private fun setBgFromUri(uri: Uri) {
if (uri.toString().isContentPath()) {
val doc = DocumentFile.fromSingleUri(requireContext(), uri)
doc?.name?.let {
var file = requireContext().getExternalFilesDir(null)
?: requireContext().filesDir
file = FileUtils.createFileIfNotExist(file, it, "bg")
kotlin.runCatching {
DocumentUtils.readBytes(requireContext(), doc.uri)
}.getOrNull()?.let { byteArray ->
file.writeBytes(byteArray)
ReadBookConfig.durConfig.setBg(2, file.absolutePath)
ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false)
} ?: toast("获取文件出错")
}
} else {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
RealPathUtil.getPath(requireContext(), uri)?.let { path ->
ReadBookConfig.durConfig.setBg(2, path)
ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false)
}
}
.request()
}
}
} }

@ -16,6 +16,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
@ -32,7 +33,7 @@ class MoreConfigDialog : DialogFragment() {
} }
dialog?.window?.let { dialog?.window?.let {
it.setBackgroundDrawableResource(R.color.background) it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 5, 0, 0) it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes val attr = it.attributes
attr.dimAmount = 0.0f attr.dimAmount = 0.0f
attr.gravity = Gravity.BOTTOM attr.gravity = Gravity.BOTTOM
@ -47,7 +48,7 @@ class MoreConfigDialog : DialogFragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
val view = LinearLayout(context) val view = LinearLayout(context)
view.setBackgroundResource(R.color.background) view.setBackgroundColor(requireContext().bottomBackground)
view.id = R.id.tag1 view.id = R.id.tag1
container?.addView(view) container?.addView(view)
return view return view

@ -1,5 +1,6 @@
package io.legado.app.ui.book.read.config package io.legado.app.ui.book.read.config
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
@ -11,6 +12,7 @@ import io.legado.app.constant.EventBus
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.utils.dp import io.legado.app.utils.dp
import io.legado.app.utils.gone
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.dialog_read_padding.* import kotlinx.android.synthetic.main.dialog_read_padding.*
@ -46,19 +48,18 @@ class PaddingConfigDialog : DialogFragment() {
initView() initView()
} }
override fun onDestroy() { override fun onDismiss(dialog: DialogInterface) {
super.onDestroy() super.onDismiss(dialog)
ReadBookConfig.save() ReadBookConfig.save()
} }
private fun initData() = with(ReadBookConfig) { private fun initData() = ReadBookConfig.apply {
if (hideStatusBar) { if (hideStatusBar) {
tv_header_padding.visible() ll_header_padding.visible()
dsb_header_padding_top.visible()
dsb_header_padding_bottom.visible()
dsb_header_padding_left.visible()
dsb_header_padding_right.visible()
tv_body_padding.setPadding(0, 10.dp, 0, 10.dp) tv_body_padding.setPadding(0, 10.dp, 0, 10.dp)
} else {
ll_header_padding.gone()
tv_body_padding.setPadding(0, 0.dp, 0, 10.dp)
} }
//正文 //正文
dsb_padding_top.progress = paddingTop dsb_padding_top.progress = paddingTop
@ -75,6 +76,8 @@ class PaddingConfigDialog : DialogFragment() {
dsb_footer_padding_bottom.progress = footerPaddingBottom dsb_footer_padding_bottom.progress = footerPaddingBottom
dsb_footer_padding_left.progress = footerPaddingLeft dsb_footer_padding_left.progress = footerPaddingLeft
dsb_footer_padding_right.progress = footerPaddingRight dsb_footer_padding_right.progress = footerPaddingRight
cb_show_top_line.isChecked = showHeaderLine
cb_show_bottom_line.isChecked = showFooterLine
} }
private fun initView() = with(ReadBookConfig) { private fun initView() = with(ReadBookConfig) {
@ -129,6 +132,18 @@ class PaddingConfigDialog : DialogFragment() {
footerPaddingRight = it footerPaddingRight = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
cb_show_top_line.onCheckedChangeListener = { cb, isChecked ->
if (cb.isPressed) {
showHeaderLine = isChecked
postEvent(EventBus.UP_CONFIG, true)
}
}
cb_show_bottom_line.onCheckedChangeListener = { cb, isChecked ->
if (cb.isPressed) {
showFooterLine = isChecked
postEvent(EventBus.UP_CONFIG, true)
}
}
} }
} }

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

Loading…
Cancel
Save