Merge pull request #4 from gedoor/master

update
pull/127/head
Celeter 5 years ago committed by GitHub
commit 05b6a9354f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      app/build.gradle
  2. 11
      app/proguard-rules.pro
  3. 50
      app/src/main/AndroidManifest.xml
  4. 30
      app/src/main/assets/txtTocRule.json
  5. 17
      app/src/main/assets/updateLog.md
  6. 8
      app/src/main/java/io/legado/app/App.kt
  7. 12
      app/src/main/java/io/legado/app/base/BaseDialogFragment.kt
  8. 2
      app/src/main/java/io/legado/app/base/BaseFragment.kt
  9. 4
      app/src/main/java/io/legado/app/constant/AppPattern.kt
  10. 1
      app/src/main/java/io/legado/app/constant/EventBus.kt
  11. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  12. 17
      app/src/main/java/io/legado/app/constant/RSSKeywords.kt
  13. 2
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  14. 20
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  15. 14
      app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt
  16. 3
      app/src/main/java/io/legado/app/data/entities/Book.kt
  17. 9
      app/src/main/java/io/legado/app/data/entities/BookProgress.kt
  18. 15
      app/src/main/java/io/legado/app/help/BookHelp.kt
  19. 3
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  20. 13
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  21. 11
      app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
  22. 28
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  23. 50
      app/src/main/java/io/legado/app/help/storage/SyncBookProgress.kt
  24. 36
      app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt
  25. 1
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
  26. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt
  27. 3
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt
  28. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt
  29. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt
  30. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  31. 4
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  32. 2
      app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt
  33. 15
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  34. 38
      app/src/main/java/io/legado/app/model/rss/RssParser.kt
  35. 2
      app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt
  36. 3
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  37. 4
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  38. 39
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  39. 3
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  40. 24
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  41. 14
      app/src/main/java/io/legado/app/ui/README.md
  42. 9
      app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt
  43. 15
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt
  44. 15
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
  45. 2
      app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt
  46. 2
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt
  47. 14
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
  48. 9
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  49. 2
      app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt
  50. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt
  51. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkFragment.kt
  52. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt
  53. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt
  54. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt
  55. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt
  56. 2
      app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt
  57. 2
      app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt
  58. 2
      app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt
  59. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt
  60. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt
  61. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
  62. 44
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  63. 8
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  64. 2
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  65. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  66. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt
  67. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt
  68. 19
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  69. 34
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  70. 2
      app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
  71. 18
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  72. 2
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  73. 3
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt
  74. 139
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  75. 4
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  76. 2
      app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt
  77. 4
      app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt
  78. 29
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  79. 10
      app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt
  80. 75
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt
  81. 77
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  82. 58
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  83. 26
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  84. 78
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt
  85. 26
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt
  86. 4
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt
  87. 8
      app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
  88. 3
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  89. 8
      app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt
  90. 8
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  91. 39
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  92. 8
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  93. 11
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt
  94. 2
      app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt
  95. 8
      app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
  96. 7
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  97. 2
      app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt
  98. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt
  99. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt
  100. 2
      app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -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'

@ -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,7 +133,9 @@
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" />
@ -144,7 +146,9 @@
</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 +163,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 +175,36 @@
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" /> <activity
android:name=".ui.rss.read.ReadRssActivity"
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 <activity
android:name=".receiver.SharedReceiverActivity" android:name=".receiver.SharedReceiverActivity"
android:label="@string/receiving_shared_label"> android:label="@string/receiving_shared_label">

@ -2,67 +2,73 @@
{ {
"enable": true, "enable": true,
"name": "目录", "name": "目录",
"rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", "rule": "^[  \\t]{0,4}(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": 0 "serialNumber": 0
}, },
{ {
"enable": false, "enable": false,
"name": "目录(不匹配行前空白)", "name": "目录(不匹配行前空白)",
"rule": "^(?<=\\s)(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", "rule": "^(?<= |\\s)(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$",
"serialNumber": 1 "serialNumber": 1
}, },
{ {
"enable": false, "enable": false,
"name": "目录(去简介)", "name": "目录(去简介)",
"rule": "^(?<=\\s)(?:前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", "rule": "^(?<= |\\s)(?:前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$",
"serialNumber": 2 "serialNumber": 2
}, },
{ {
"enable": false, "enable": false,
"name": "目录(古典、轻小说备用)", "name": "目录(古典、轻小说备用)",
"rule": "^[ \\t]{0,4}(?:前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$", "rule": "^[  \\t]{0,4}(?:前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$",
"serialNumber": 3 "serialNumber": 3
}, },
{ {
"enable": true, "enable": true,
"name": "数字 分隔符 标题名称", "name": "数字 分隔符 标题名称",
"rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$", "rule": "^[  \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$",
"serialNumber": 4 "serialNumber": 4
}, },
{ {
"enable": true, "enable": true,
"name": "正文 标题/序号", "name": "正文 标题/序号",
"rule": "^[ \\t]{0,4}正文\\s{1,4}.{0,20}$", "rule": "^[  \\t]{0,4}正文\\s{1,4}.{0,20}$",
"serialNumber": 5 "serialNumber": 5
}, },
{ {
"enable": true, "enable": true,
"name": "Chapter/Section/Part 序号 标题", "name": "Chapter/Section/Part/Episode 序号 标题",
"rule": "^[ \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art)\\s{0,4}\\d{1,4}.{0,30}$", "rule": "^[  \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art|PART|[Ee]pisode)\\s{0,4}\\d{1,4}.{0,30}$",
"serialNumber": 6 "serialNumber": 6
}, },
{ {
"enable": true, "enable": true,
"name": "特殊符号 序号 标题", "name": "特殊符号 序号 标题",
"rule": "^[ \\t]{0,4}[〈〖〔【][第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节][\\.:: \f\t].{0,30}$", "rule": "^[  \\t]{0,4}[〈〖〔【][第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节][\\.:: \f\t].{0,30}$",
"serialNumber": 7 "serialNumber": 7
}, },
{ {
"enable": true, "enable": true,
"name": "特殊符号 标题", "name": "特殊符号 标题",
"rule": "^[ \\t]{0,4}[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$", "rule": "^[  \\t]{0,4}[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$",
"serialNumber": 8 "serialNumber": 8
}, },
{ {
"enable":false, "enable":false,
"name": "特殊符号 标题(不匹配空白字符)", "name": "特殊符号 标题(不匹配空白字符)",
"rule": "^(?<=\\s)[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$", "rule": "^(?<= |\\s)[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$",
"serialNumber": 9 "serialNumber": 9
}, },
{
"enable": true,
"name": "章/卷 序号 标题",
"rule": "^[  \\t]{0,4}(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完)|终章|后记|尾声|番外|[卷章][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8})\\s{0,4}.{0,30}$",
"serialNumber": 10
},
{ {
"enable":false, "enable":false,
"name": "顶格标题", "name": "顶格标题",
"rule": "^\\S.{1,20}$", "rule": "^\\S.{1,20}$",
"serialNumber": 10 "serialNumber": 11
} }
] ]

@ -2,10 +2,27 @@
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 * 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
**2020/03/05**
* 修复翻页动画
* 修复主题模式跟随
* 修复滚动翻页切换章节时跳动
**2020/03/04**
* 修复仿真翻页动画
* 添加阅读记录同步,正常退出进入软件时同步阅读记录
**2020/03/03**
* 修复bug
* 优化排版,确保段距为0时每行在相同的位置
* 修复底部遮挡
**2020/03/02** **2020/03/02**
* 添加书源登录 * 添加书源登录
* 替换规则实时生效 * 替换规则实时生效
* 页面最后一行计算是否能放下时不计算行距 * 页面最后一行计算是否能放下时不计算行距
* 优化翻页动画
* 优化书源校验
* 按键翻页有动画了
**2020/03/01** **2020/03/01**
* 修复书源解析的一个bug * 修复书源解析的一个bug

@ -48,13 +48,9 @@ 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)

@ -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

@ -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"
} }

@ -32,6 +32,7 @@ object PreferKey {
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 {
@ -67,4 +68,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>

@ -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?
)

@ -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,14 +275,20 @@ 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()) {
try {
c = if (item.isRegex) { c = if (item.isRegex) {
c.replace(it.toRegex(), item.replacement) c.replace(it.toRegex(), item.replacement)
} else { } else {
c.replace(it, item.replacement) c.replace(it, item.replacement)
} }
} catch (e: Exception) {
withContext(Main) {
App.INSTANCE.toast("${item.name}替换出错")
}
}
} }
} }
} }

@ -1,13 +1,14 @@
package io.legado.app.help package io.legado.app.help
import android.util.Base64 import android.util.Base64
import androidx.annotation.Keep
import io.legado.app.constant.AppConst.DATE_FORMAT import io.legado.app.constant.AppConst.DATE_FORMAT
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 {

@ -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()
@ -68,6 +71,13 @@ object ReadBookConfig {
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)
bg?.let {
if (it is BitmapDrawable) {
bgMeanColor = BitmapUtils.getMeanColor(it.bitmap)
} else if (it is ColorDrawable) {
bgMeanColor = it.color
}
}
} }
fun save() { fun save() {
@ -228,6 +238,7 @@ object ReadBookConfig {
if (shareLayout) shareConfig.footerPaddingTop = value if (shareLayout) shareConfig.footerPaddingTop = value
else durConfig.footerPaddingTop = value else durConfig.footerPaddingTop = 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,7 +250,7 @@ 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 titleCenter: Boolean = true,//标题居中

@ -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,13 +49,18 @@ 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) {
context.toast("导入替换规则失败\n${e.localizedMessage}") context.toast("导入替换规则失败\n${e.localizedMessage}")

@ -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,8 +130,19 @@ object Restore {
ChapterProvider.upStyle() ChapterProvider.upStyle()
ReadBook.loadContent() ReadBook.loadContent()
} }
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)) 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>? {
try { try {

@ -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()
} else {
val webDav = WebDav(it + name)
webDav.downloadTo(zipFilePath, true)
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
ZipUtils.unzipFile(zipFilePath, Backup.backupPath) ZipUtils.unzipFile(zipFilePath, Backup.backupPath)
Restore.restore(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)
} }
} }

@ -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

@ -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)

@ -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) {
check()
}
}.onError {
toast("校验书源出错:${it.localizedMessage}")
}
}
private fun check() {
synchronized(this) {
processIndex++
}
if (processIndex < allIds.size) {
val sourceUrl = allIds[processIndex]
App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source ->
val webBook = WebBook(source) val webBook = WebBook(source)
webBook.searchBook("我的", scope = this, context = searchPool) webBook.searchBook("我的", scope = this, context = searchPool)
.onError(IO) { .onError(IO) {
source.addGroup("失效") source.addGroup("失效")
App.db.bookSourceDao().update(source) App.db.bookSourceDao().update(source)
}.onFinally { }.onFinally(IO) {
check()
checkedIds.add(sourceUrl) checkedIds.add(sourceUrl)
updateNotification( updateNotification(
checkedIds.size, checkedIds.size,
getString(R.string.progress_show, checkedIds.size, allIds.size) getString(R.string.progress_show, checkedIds.size, allIds.size)
) )
synchronized(this) {
if (processIndex >= allIds.size + threadCount - 1) {
stopSelf()
} }
} }
} }
}.onError {
toast("校验书源出错:${it.localizedMessage}")
} }
task?.invokeOnCompletion {
stopSelf()
} }
} }

@ -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}")
} }
} }

@ -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 替换净化界面

@ -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()

@ -1,4 +1,4 @@
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
@ -52,27 +52,22 @@ class ChangeCoverDialog : DialogFragment(),
callBack = activity as? CallBack callBack = activity as? CallBack
viewModel = getViewModel(ChangeCoverViewModel::class.java) viewModel = getViewModel(ChangeCoverViewModel::class.java)
viewModel.callBack = this viewModel.callBack = this
return inflater.inflate(R.layout.dialog_change_source, container) return inflater.inflate(R.layout.dialog_change_cover, container)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.searchStateData.observe(this, Observer { viewModel.searchStateData.observe(viewLifecycleOwner, Observer {
refresh_progress_bar.isAutoLoading = it refresh_progress_bar.isAutoLoading = it
}) })
tool_bar.setTitle(R.string.change_cover_source) tool_bar.setTitle(R.string.change_cover_source)
arguments?.let { bundle -> arguments?.let { bundle ->
bundle.getString("name")?.let { viewModel.initData(bundle)
viewModel.name = it
}
bundle.getString("author")?.let {
viewModel.author = it
}
} }
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 changeTo(coverUrl: String) { override fun changeTo(coverUrl: String) {

@ -1,9 +1,11 @@
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.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
@ -21,7 +23,16 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
private var task: Coroutine<*>? = null private var task: Coroutine<*>? = null
val searchStateData = MutableLiveData<Boolean>() val searchStateData = MutableLiveData<Boolean>()
fun initData() { fun initData(bundle: 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)
}.onSuccess { }.onSuccess {

@ -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

@ -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
@ -16,9 +16,11 @@ 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.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
@ -69,10 +71,8 @@ class ChangeSourceDialog : DialogFragment(),
super.onViewCreated(view, savedInstanceState) 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()
@ -85,6 +85,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 +113,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
@ -149,6 +152,7 @@ class ChangeSourceDialog : DialogFragment(),
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, "")
} }
} }
} }
@ -145,6 +146,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} }
} }
fun stopSearch() {
task?.cancel()
}
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

@ -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,14 +20,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,7 +53,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)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
tv_intro.movementMethod = ScrollingMovementMethod.getInstance() tv_intro.movementMethod = ScrollingMovementMethod.getInstance()
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) })
@ -103,7 +105,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))
@ -186,9 +188,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()
@ -223,6 +223,32 @@ class BookInfoActivity :
} }
} }
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 +346,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 inBookshelf = false
if (it.isLocalBook()) {
LocalBook.deleteBook(it, deleteOriginal)
}
}
}.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

@ -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
} }
} }
@ -663,6 +661,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() {

@ -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,19 +170,28 @@ class BgTextConfigDialog : DialogFragment() {
resultSelectBg -> { resultSelectBg -> {
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
setBgFromUri(uri)
}
}
}
}
}
private fun setBgFromUri(uri: Uri) {
if (uri.toString().isContentPath()) { if (uri.toString().isContentPath()) {
val doc = DocumentFile.fromSingleUri(requireContext(), uri) val doc = DocumentFile.fromSingleUri(requireContext(), uri)
doc?.let { doc?.name?.let {
var file = requireContext().getExternalFilesDir(null) var file = requireContext().getExternalFilesDir(null)
?: requireContext().filesDir ?: requireContext().filesDir
file = file = FileUtils.createFileIfNotExist(file, it, "bg")
FileUtils.createFileIfNotExist(file.absolutePath + File.separator + "bg" + File.separator + doc.name) kotlin.runCatching {
DocumentUtils.readBytes(requireContext(), uri)?.let { DocumentUtils.readBytes(requireContext(), doc.uri)
file.writeBytes(it) }.getOrNull()?.let { byteArray ->
file.writeBytes(byteArray)
ReadBookConfig.durConfig.setBg(2, file.absolutePath) ReadBookConfig.durConfig.setBg(2, file.absolutePath)
ReadBookConfig.upBg() ReadBookConfig.upBg()
postEvent(EventBus.UP_CONFIG, false) postEvent(EventBus.UP_CONFIG, false)
} } ?: toast("获取文件出错")
} }
} else { } else {
PermissionsCompat.Builder(this) PermissionsCompat.Builder(this)
@ -203,7 +211,3 @@ class BgTextConfigDialog : DialogFragment() {
} }
} }
} }
}
}
}
}

@ -33,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

@ -7,8 +7,8 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.SeekBar import android.widget.SeekBar
import androidx.fragment.app.DialogFragment
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
@ -23,7 +23,7 @@ import kotlinx.android.synthetic.main.dialog_read_aloud.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.sdk27.listeners.onLongClick import org.jetbrains.anko.sdk27.listeners.onLongClick
class ReadAloudDialog : DialogFragment() { class ReadAloudDialog : BaseDialogFragment() {
var callBack: CallBack? = null var callBack: CallBack? = null
override fun onStart() { override fun onStart() {
@ -53,21 +53,16 @@ class ReadAloudDialog : DialogFragment() {
return inflater.inflate(R.layout.dialog_read_aloud, container) return inflater.inflate(R.layout.dialog_read_aloud, container)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground) ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground)
initData()
initOnChange() initOnChange()
initData()
initOnClick() initOnClick()
} }
private fun initData() { private fun initData() {
observeEvent<Int>(EventBus.ALOUD_STATE) { upPlayState() }
observeEvent<Int>(EventBus.TTS_DS) { seek_timer.progress = it }
upPlayState() upPlayState()
seek_timer.progress = BaseReadAloudService.timeMinute seek_timer.progress = BaseReadAloudService.timeMinute
tv_timer.text =
requireContext().getString(R.string.timer_m, BaseReadAloudService.timeMinute)
cb_tts_follow_sys.isChecked = requireContext().getPrefBoolean("ttsFollowSys", true) cb_tts_follow_sys.isChecked = requireContext().getPrefBoolean("ttsFollowSys", true)
seek_tts_SpeechRate.isEnabled = !cb_tts_follow_sys.isChecked seek_tts_SpeechRate.isEnabled = !cb_tts_follow_sys.isChecked
seek_tts_SpeechRate.progress = AppConfig.ttsSpeechRate seek_tts_SpeechRate.progress = AppConfig.ttsSpeechRate
@ -139,6 +134,11 @@ class ReadAloudDialog : DialogFragment() {
} }
} }
override fun observeLiveBus() {
observeEvent<Int>(EventBus.ALOUD_STATE) { upPlayState() }
observeEvent<Int>(EventBus.TTS_DS) { seek_timer.progress = it }
}
interface CallBack { interface CallBack {
fun showMenuBar() fun showMenuBar()
fun openChapterList() fun openChapterList()

@ -42,7 +42,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
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

@ -61,8 +61,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
return inflater.inflate(R.layout.dialog_toc_regex, container) return inflater.inflate(R.layout.dialog_toc_regex, container)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
durRegex = arguments?.getString("tocRegex") durRegex = arguments?.getString("tocRegex")
tool_bar.setTitle(R.string.txt_toc_regex) tool_bar.setTitle(R.string.txt_toc_regex)
tool_bar.inflateMenu(R.menu.txt_toc_regex) tool_bar.inflateMenu(R.menu.txt_toc_regex)

@ -16,6 +16,7 @@ import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.dp import io.legado.app.utils.dp
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.removePref import io.legado.app.utils.removePref
import io.legado.app.utils.toStringArray
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -28,7 +29,7 @@ object ChapterProvider {
var visibleHeight = 0 var visibleHeight = 0
var visibleRight = 0 var visibleRight = 0
var visibleBottom = 0 var visibleBottom = 0
private var lineSpacingExtra = 0f private var lineSpacingExtra = 0
private var paragraphSpacing = 0 private var paragraphSpacing = 0
var typeface: Typeface = Typeface.SANS_SERIF var typeface: Typeface = Typeface.SANS_SERIF
var titlePaint = TextPaint() var titlePaint = TextPaint()
@ -51,7 +52,7 @@ object ChapterProvider {
val pageLengths = arrayListOf<Int>() val pageLengths = arrayListOf<Int>()
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
var surplusText = content var surplusText = content
var durY = 0 var durY = 0f
textPages.add(TextPage()) textPages.add(TextPage())
while (surplusText.isNotEmpty()) { while (surplusText.isNotEmpty()) {
if (textPages.first().textLines.isEmpty()) { if (textPages.first().textLines.isEmpty()) {
@ -60,7 +61,9 @@ object ChapterProvider {
if (end > 0) { if (end > 0) {
val title = surplusText.substring(0, end) val title = surplusText.substring(0, end)
surplusText = surplusText.substring(end + 1) surplusText = surplusText.substring(end + 1)
durY = joinTitle(title, durY, textPages, pageLines, pageLengths, stringBuilder) durY = setTypeText(
title, durY, textPages, pageLines, pageLengths, stringBuilder, true
)
} }
} else { } else {
//正文 //正文
@ -73,7 +76,8 @@ object ChapterProvider {
text = surplusText text = surplusText
surplusText = "" surplusText = ""
} }
durY = joinBody(text, durY, textPages, pageLines, pageLengths, stringBuilder) durY =
setTypeText(text, durY, textPages, pageLines, pageLengths, stringBuilder, false)
} }
} }
textPages.last().height = durY + 20.dp textPages.last().height = durY + 20.dp
@ -103,116 +107,59 @@ object ChapterProvider {
} }
/** /**
* 标题 * 排版文字
*/ */
private fun joinTitle( private fun setTypeText(
title: String,
y: Int,
textPages: ArrayList<TextPage>,
pageLines: ArrayList<Int>,
pageLengths: ArrayList<Int>,
stringBuilder: StringBuilder
): Int {
var durY = y
val layout = StaticLayout(
title, titlePaint, visibleWidth,
Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true
)
for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY
val textLine = TextLine(isTitle = true)
if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex)
+ contentPaint.fontMetrics.descent < visibleHeight
) {
textPages.last().textLines.add(textLine)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
} else {
textPages.last().text = stringBuilder.toString()
stringBuilder.clear()
pageLines.add(textPages.last().textLines.size)
pageLengths.add(textPages.last().text.length)
//新页面
durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
textPages.add(TextPage())
textPages.last().textLines.add(textLine)
}
textLine.lineTop = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat()
textLine.lineBase = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent
val words =
title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
stringBuilder.append(words)
textLine.text = words
val desiredWidth = layout.getLineWidth(lineIndex)
if (lineIndex != layout.lineCount - 1) {
addCharsToLineMiddle(textLine, words, titlePaint, desiredWidth, 0f)
} else {
//最后一行
val x = if (ReadBookConfig.titleCenter)
(visibleWidth - layout.getLineWidth(lineIndex)) / 2
else 0f
addCharsToLineLast(textLine, words, stringBuilder, titlePaint, x)
}
}
durY += paragraphSpacing
return durY
}
/**
* 正文
*/
private fun joinBody(
text: String, text: String,
y: Int, y: Float,
textPages: ArrayList<TextPage>, textPages: ArrayList<TextPage>,
pageLines: ArrayList<Int>, pageLines: ArrayList<Int>,
pageLengths: ArrayList<Int>, pageLengths: ArrayList<Int>,
stringBuilder: StringBuilder stringBuilder: StringBuilder,
): Int { isTitle: Boolean
): Float {
var durY = y var durY = y
val textPaint = if (isTitle) titlePaint else contentPaint
val layout = StaticLayout( val layout = StaticLayout(
text, contentPaint, visibleWidth, text, textPaint, visibleWidth,
Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true
) )
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY textPages.last().height = durY
val textLine = TextLine() val textLine = TextLine(isTitle = isTitle)
if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex) if (durY + textPaint.textHeight < visibleHeight) {
+ contentPaint.fontMetrics.descent < visibleHeight
) {
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) durY += textPaint.textHeight + lineSpacingExtra
} else { } else {
textPages.last().text = stringBuilder.toString() textPages.last().text = stringBuilder.toString()
stringBuilder.clear() stringBuilder.clear()
pageLines.add(textPages.last().textLines.size) pageLines.add(textPages.last().textLines.size)
pageLengths.add(textPages.last().text.length) pageLengths.add(textPages.last().text.length)
//新页面 //新页面
durY = layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex) durY = textPaint.textHeight + lineSpacingExtra
textPages.add(TextPage()) textPages.add(TextPage())
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
} }
textLine.lineTop = (paddingTop + durY - textLine.lineBottom = paddingTop + durY - lineSpacingExtra
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() textLine.lineBase = textLine.lineBottom - textPaint.fontMetrics.descent
textLine.lineBase = (paddingTop + durY - textLine.lineTop = textLine.lineBottom - textPaint.textHeight
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent
val words = val words =
text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
stringBuilder.append(words) stringBuilder.append(words)
textLine.text = words textLine.text = words
val desiredWidth = layout.getLineWidth(lineIndex) val desiredWidth = layout.getLineWidth(lineIndex)
if (lineIndex == 0 && layout.lineCount > 1) { if (lineIndex == 0 && layout.lineCount > 1 && !isTitle) {
//第一行 //第一行
addCharsToLineFirst(textLine, words, contentPaint, desiredWidth) addCharsToLineFirst(textLine, words, textPaint, desiredWidth)
} else if (lineIndex == layout.lineCount - 1) { } else if (lineIndex == layout.lineCount - 1) {
//最后一行 //最后一行
addCharsToLineLast(textLine, words, stringBuilder, contentPaint, 0f) val x = if (isTitle && ReadBookConfig.titleCenter)
(visibleWidth - layout.getLineWidth(lineIndex)) / 2
else 0f
addCharsToLineLast(textLine, words, stringBuilder, textPaint, x)
} else { } else {
//中间行 //中间行
addCharsToLineMiddle(textLine, words, contentPaint, desiredWidth, 0f) addCharsToLineMiddle(textLine, words, textPaint, desiredWidth, 0f)
} }
} }
durY += paragraphSpacing durY += paragraphSpacing
@ -231,10 +178,10 @@ object ChapterProvider {
var x = 0f var x = 0f
val bodyIndent = ReadBookConfig.bodyIndent val bodyIndent = ReadBookConfig.bodyIndent
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
for (i in 0..bodyIndent.lastIndex) { bodyIndent.toStringArray().forEach {
val x1 = x + icw val x1 = x + icw
textLine.addTextChar( textLine.addTextChar(
charData = bodyIndent[i].toString(), charData = it,
start = paddingLeft + x, start = paddingLeft + x,
end = paddingLeft + x1 end = paddingLeft + x1
) )
@ -257,12 +204,11 @@ object ChapterProvider {
val gapCount: Int = words.length - 1 val gapCount: Int = words.length - 1
val d = (visibleWidth - desiredWidth) / gapCount val d = (visibleWidth - desiredWidth) / gapCount
var x = startX var x = startX
for (i in words.indices) { for ((i, char) in words.toStringArray().withIndex()) {
val char = words[i] val cw = StaticLayout.getDesiredWidth(char, textPaint)
val cw = StaticLayout.getDesiredWidth(char.toString(), textPaint)
val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw) val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw)
textLine.addTextChar( textLine.addTextChar(
charData = char.toString(), charData = char,
start = paddingLeft + x, start = paddingLeft + x,
end = paddingLeft + x1 end = paddingLeft + x1
) )
@ -284,12 +230,11 @@ object ChapterProvider {
stringBuilder.append("\n") stringBuilder.append("\n")
textLine.text = "$words\n" textLine.text = "$words\n"
var x = startX var x = startX
for (i in words.indices) { words.toStringArray().forEach {
val char = words[i].toString() val cw = StaticLayout.getDesiredWidth(it, textPaint)
val cw = StaticLayout.getDesiredWidth(char, textPaint)
val x1 = x + cw val x1 = x + cw
textLine.addTextChar( textLine.addTextChar(
charData = char, charData = it,
start = paddingLeft + x, start = paddingLeft + x,
end = paddingLeft + x1 end = paddingLeft + x1
) )
@ -346,7 +291,7 @@ object ChapterProvider {
val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL
contentPaint.typeface = Typeface.create(typeface, bold) contentPaint.typeface = Typeface.create(typeface, bold)
//间距 //间距
lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp.toFloat() lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp
paragraphSpacing = ReadBookConfig.paragraphSpacing.dp paragraphSpacing = ReadBookConfig.paragraphSpacing.dp
titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat() titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat()
contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat() contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat()
@ -366,4 +311,8 @@ object ChapterProvider {
visibleBottom = paddingTop + visibleHeight visibleBottom = paddingTop + visibleHeight
} }
private val TextPaint.textHeight: Float
get() {
return this.fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading
}
} }

@ -170,13 +170,13 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} else if (!pageFactory.hasNext() && pageOffset < 0) { } else if (!pageFactory.hasNext() && pageOffset < 0) {
pageOffset = 0f pageOffset = 0f
} else if (pageOffset > 0) { } else if (pageOffset > 0) {
pageFactory.moveToPrev() pageFactory.moveToPrev(false)
textPage = pageFactory.currentPage textPage = pageFactory.currentPage
pageOffset -= textPage.height pageOffset -= textPage.height
upView?.invoke(textPage) upView?.invoke(textPage)
} else if (pageOffset < -textPage.height) { } else if (pageOffset < -textPage.height) {
pageOffset += textPage.height pageOffset += textPage.height
pageFactory.moveToNext() pageFactory.moveToNext(false)
textPage = pageFactory.currentPage textPage = pageFactory.currentPage
upView?.invoke(textPage) upView?.invoke(textPage)
} }

@ -16,4 +16,6 @@ interface DataSource {
fun hasNextChapter(): Boolean fun hasNextChapter(): Boolean
fun hasPrevChapter(): Boolean fun hasPrevChapter(): Boolean
fun upContent(relativePosition: Int = 0)
} }

@ -6,9 +6,9 @@ abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun moveToLast() abstract fun moveToLast()
abstract fun moveToNext():Boolean abstract fun moveToNext(upContent: Boolean): Boolean
abstract fun moveToPrev(): Boolean abstract fun moveToPrev(upContent: Boolean): Boolean
abstract val nextPage: DATA abstract val nextPage: DATA

@ -26,12 +26,12 @@ class PageView(context: Context, attrs: AttributeSet) :
init { init {
callBack = activity as CallBack callBack = activity as CallBack
prevPage = ContentView(context)
addView(prevPage)
nextPage = ContentView(context) nextPage = ContentView(context)
addView(nextPage) addView(nextPage)
curPage = ContentView(context) curPage = ContentView(context)
addView(curPage) addView(curPage)
prevPage = ContentView(context)
addView(prevPage)
upBg() upBg()
setWillNotDraw(false) setWillNotDraw(false)
pageFactory = TextPageFactory(this) pageFactory = TextPageFactory(this)
@ -40,6 +40,7 @@ class PageView(context: Context, attrs: AttributeSet) :
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh) super.onSizeChanged(w, h, oldw, oldh)
prevPage.x = -w.toFloat()
pageDelegate?.setViewSize(w, h) pageDelegate?.setViewSize(w, h)
if (oldw != 0 && oldh != 0) { if (oldw != 0 && oldh != 0) {
ReadBook.loadContent() ReadBook.loadContent()
@ -75,12 +76,10 @@ class PageView(context: Context, attrs: AttributeSet) :
fun fillPage(direction: PageDelegate.Direction) { fun fillPage(direction: PageDelegate.Direction) {
when (direction) { when (direction) {
PageDelegate.Direction.PREV -> { PageDelegate.Direction.PREV -> {
pageFactory.moveToPrev() pageFactory.moveToPrev(true)
upContent()
} }
PageDelegate.Direction.NEXT -> { PageDelegate.Direction.NEXT -> {
pageFactory.moveToNext() pageFactory.moveToNext(true)
upContent()
} }
else -> Unit else -> Unit
} }
@ -99,7 +98,7 @@ class PageView(context: Context, attrs: AttributeSet) :
upContent() upContent()
} }
fun upContent(relativePosition: Int = 0) { override fun upContent(relativePosition: Int) {
if (ReadBookConfig.isScroll) { if (ReadBookConfig.isScroll) {
curPage.setContent(pageFactory.currentPage) curPage.setContent(pageFactory.currentPage)
} else { } else {
@ -116,22 +115,6 @@ class PageView(context: Context, attrs: AttributeSet) :
callBack.screenOffTimerStart() callBack.screenOffTimerStart()
} }
fun moveToPrevPage(noAnim: Boolean = true) {
if (noAnim) {
fillPage(PageDelegate.Direction.PREV)
} else {
pageDelegate?.prevPageByAnim()
}
}
fun moveToNextPage(noAnim: Boolean = true) {
if (noAnim) {
fillPage(PageDelegate.Direction.NEXT)
} else {
pageDelegate?.nextPageByAnim()
}
}
fun upStyle() { fun upStyle() {
ChapterProvider.upStyle() ChapterProvider.upStyle()
curPage.upStyle() curPage.upStyle()

@ -31,25 +31,27 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
} ?: ReadBook.setPageIndex(0) } ?: ReadBook.setPageIndex(0)
} }
override fun moveToNext(): Boolean = with(dataSource) { override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) {
return if (hasNext()) { return if (hasNext()) {
if (currentChapter?.isLastIndex(pageIndex) == true) { if (currentChapter?.isLastIndex(pageIndex) == true) {
ReadBook.moveToNextChapter(false) ReadBook.moveToNextChapter(upContent)
} else { } else {
ReadBook.setPageIndex(pageIndex.plus(1)) ReadBook.setPageIndex(pageIndex.plus(1))
} }
if (upContent) upContent()
true true
} else } else
false false
} }
override fun moveToPrev(): Boolean = with(dataSource) { override fun moveToPrev(upContent: Boolean): Boolean = with(dataSource) {
return if (hasPrev()) { return if (hasPrev()) {
if (pageIndex <= 0) { if (pageIndex <= 0) {
ReadBook.moveToPrevChapter(false) ReadBook.moveToPrevChapter(upContent)
} else { } else {
ReadBook.setPageIndex(pageIndex.minus(1)) ReadBook.setPageIndex(pageIndex.minus(1))
} }
if (upContent) upContent()
true true
} else } else
false false

@ -6,9 +6,8 @@ import android.graphics.drawable.GradientDrawable
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
private val shadowDrawableR: GradientDrawable
private val bitmapMatrix = Matrix() private val bitmapMatrix = Matrix()
private val shadowDrawableR: GradientDrawable
init { init {
val shadowColors = intArrayOf(0x66111111, 0x00000000) val shadowColors = intArrayOf(0x66111111, 0x00000000)
@ -18,37 +17,6 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT
} }
override fun onAnimStart() {
val distanceX: Float
when (mDirection) {
Direction.NEXT -> distanceX =
if (isCancel) {
var dis = viewWidth - startX + touchX
if (dis > viewWidth) {
dis = viewWidth.toFloat()
}
viewWidth - dis
} else {
-(touchX + (viewWidth - startX))
}
else -> distanceX =
if (isCancel) {
-(touchX - startX)
} else {
viewWidth - (touchX - startX)
}
}
startScroll(touchX.toInt(), 0, distanceX.toInt(), 0)
}
override fun onAnimStop() {
curPage.x = 0.toFloat()
if (!isCancel) {
pageView.fillPage(mDirection)
}
}
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
val offsetX = touchX - startX val offsetX = touchX - startX
@ -57,13 +25,16 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
) return ) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
bitmap?.let { if (!isRunning) return
if (mDirection == Direction.PREV) { if (mDirection == Direction.PREV) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat()) bitmapMatrix.setTranslate(distanceX, 0.toFloat())
canvas.drawBitmap(it, bitmapMatrix, null) curBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
addShadow(distanceX.toInt(), canvas)
} else if (mDirection == Direction.NEXT) { } else if (mDirection == Direction.NEXT) {
curPage.translationX = offsetX bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat())
} nextBitmap?.let { canvas.drawBitmap(it, 0f, 0f, null) }
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
addShadow(distanceX.toInt(), canvas) addShadow(distanceX.toInt(), canvas)
} }
} }
@ -77,4 +48,34 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
shadowDrawableR.draw(canvas) shadowDrawableR.draw(canvas)
} }
} }
override fun onAnimStop() {
if (!isCancel) {
pageView.fillPage(mDirection)
}
}
override fun onAnimStart() {
val distanceX: Float
when (mDirection) {
Direction.NEXT -> distanceX =
if (isCancel) {
var dis = viewWidth - startX + touchX
if (dis > viewWidth) {
dis = viewWidth.toFloat()
}
viewWidth - dis
} else {
-(touchX + (viewWidth - startX))
}
else -> distanceX =
if (isCancel) {
-(touchX - startX)
} else {
viewWidth - (touchX - startX)
}
}
startScroll(touchX.toInt(), 0, distanceX.toInt(), 0)
}
} }

@ -1,13 +1,44 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.Bitmap
import android.view.MotionEvent import android.view.MotionEvent
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import kotlin.math.abs import io.legado.app.utils.screenshot
abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) { abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageView) {
protected var curBitmap: Bitmap? = null
protected var prevBitmap: Bitmap? = null
protected var nextBitmap: Bitmap? = null
override fun setDirection(direction: Direction) {
super.setDirection(direction)
setBitmap()
}
private fun setBitmap() {
when (mDirection) {
Direction.PREV -> {
prevBitmap?.recycle()
prevBitmap = prevPage.screenshot()
curBitmap?.recycle()
curBitmap = curPage.screenshot()
}
Direction.NEXT -> {
nextBitmap?.recycle()
nextBitmap = nextPage.screenshot()
curBitmap?.recycle()
curBitmap = curPage.screenshot()
}
else -> Unit
}
}
override fun onTouch(event: MotionEvent) { override fun onTouch(event: MotionEvent) {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> {
abort()
}
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
if (isTextSelected) { if (isTextSelected) {
selectText(event) selectText(event)
@ -20,19 +51,37 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
} }
private fun onScroll(event: MotionEvent) { private fun onScroll(event: MotionEvent) {
val action: Int = event.action
val pointerUp =
action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP
val skipIndex = if (pointerUp) event.actionIndex else -1
// Determine focal point
var sumX = 0f
var sumY = 0f
val count: Int = event.pointerCount
for (i in 0 until count) {
if (skipIndex == i) continue
sumX += event.getX(i)
sumY += event.getY(i)
}
val div = if (pointerUp) count - 1 else count
val focusX = sumX / div
val focusY = sumY / div
//判断是否移动了 //判断是否移动了
if (!isMoved) { if (!isMoved) {
isMoved = abs(startX - event.x) > slop val deltaX = (focusX - startX).toInt()
|| abs(startX - event.x) > abs(startY - event.y) val deltaY = (focusY - startY).toInt()
val distance = deltaX * deltaX + deltaY * deltaY
isMoved = distance > slopSquare
if (isMoved) { if (isMoved) {
if (event.x - startX > 0) { if (sumX - startX > 0) {
//如果上一页不存在 //如果上一页不存在
if (!hasPrev()) { if (!hasPrev()) {
noNext = true noNext = true
return return
} }
setDirection(Direction.PREV) setDirection(Direction.PREV)
setBitmap()
} else { } else {
//如果不存在表示没有下一页了 //如果不存在表示没有下一页了
if (!hasNext()) { if (!hasNext()) {
@ -40,7 +89,6 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
return return
} }
setDirection(Direction.NEXT) setDirection(Direction.NEXT)
setBitmap()
} }
} }
} }
@ -48,23 +96,34 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
isCancel = if (mDirection == Direction.NEXT) touchX > lastX else touchX < lastX isCancel = if (mDirection == Direction.NEXT) touchX > lastX else touchX < lastX
isRunning = true isRunning = true
//设置触摸点 //设置触摸点
setTouchPoint(event.x, event.y) setTouchPoint(sumX, sumY)
} }
} }
override fun nextPageByAnim() { override fun nextPageByAnim() {
super.nextPageByAnim()
if (!hasNext()) return if (!hasNext()) return
setDirection(Direction.NEXT) setDirection(Direction.NEXT)
setBitmap()
setTouchPoint(viewWidth.toFloat(), 0f) setTouchPoint(viewWidth.toFloat(), 0f)
onAnimStart() onAnimStart()
} }
override fun prevPageByAnim() { override fun prevPageByAnim() {
super.prevPageByAnim()
if (!hasPrev()) return if (!hasPrev()) return
setDirection(Direction.PREV) setDirection(Direction.PREV)
setBitmap()
setTouchPoint(0f, 0f) setTouchPoint(0f, 0f)
onAnimStart() onAnimStart()
} }
override fun onDestroy() {
super.onDestroy()
prevBitmap?.recycle()
prevBitmap = null
curBitmap?.recycle()
curBitmap = null
nextBitmap?.recycle()
nextBitmap = null
}
} }

@ -1,7 +1,6 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.RectF import android.graphics.RectF
import android.view.GestureDetector import android.view.GestureDetector
@ -15,7 +14,6 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.screenshot
import kotlin.math.abs import kotlin.math.abs
abstract class PageDelegate(protected val pageView: PageView) : abstract class PageDelegate(protected val pageView: PageView) :
@ -25,7 +23,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
pageView.width * 0.66f, pageView.height * 0.66f pageView.width * 0.66f, pageView.height * 0.66f
) )
protected val context: Context = pageView.context protected val context: Context = pageView.context
protected val slop = ViewConfiguration.get(context).scaledTouchSlop
//起始点 //起始点
protected var startX: Float = 0f protected var startX: Float = 0f
protected var startY: Float = 0f protected var startY: Float = 0f
@ -40,23 +38,26 @@ abstract class PageDelegate(protected val pageView: PageView) :
protected val curPage: ContentView get() = pageView.curPage protected val curPage: ContentView get() = pageView.curPage
protected val prevPage: ContentView get() = pageView.prevPage protected val prevPage: ContentView get() = pageView.prevPage
protected var bitmap: Bitmap? = null
protected var viewWidth: Int = pageView.width protected var viewWidth: Int = pageView.width
protected var viewHeight: Int = pageView.height protected var viewHeight: Int = pageView.height
private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
}
private val scroller: Scroller by lazy { private val scroller: Scroller by lazy {
Scroller(pageView.context, DecelerateInterpolator()) Scroller(pageView.context, DecelerateInterpolator())
} }
protected val slopSquare by lazy {
val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
scaledTouchSlop * scaledTouchSlop
}
private val detector: GestureDetector by lazy { private val detector: GestureDetector by lazy {
GestureDetector(pageView.context, this) GestureDetector(pageView.context, this)
} }
private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
}
var isMoved = false var isMoved = false
var noNext = true var noNext = true
@ -126,11 +127,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
} }
private fun stopScroll() { private fun stopScroll() {
isMoved = false
isRunning = false isRunning = false
isStarted = false isStarted = false
pageView.invalidate() pageView.invalidate()
bitmap?.recycle()
bitmap = null
} }
open fun setViewSize(width: Int, height: Int) { open fun setViewSize(width: Int, height: Int) {
@ -166,23 +166,28 @@ abstract class PageDelegate(protected val pageView: PageView) :
open fun onScroll() {}//移动contentView, slidePage open fun onScroll() {}//移动contentView, slidePage
abstract fun nextPageByAnim() open fun nextPageByAnim() {
abort()
}
abstract fun prevPageByAnim() open fun prevPageByAnim() {
abort()
}
open fun keyTurnPage(direction: Direction) {
if (isRunning) return
when (direction) {
Direction.NEXT -> nextPageByAnim()
Direction.PREV -> prevPageByAnim()
else -> return
}
}
@CallSuper @CallSuper
open fun setDirection(direction: Direction) { open fun setDirection(direction: Direction) {
mDirection = direction mDirection = direction
} }
open fun setBitmap() {
bitmap = when (mDirection) {
Direction.NEXT -> nextPage.screenshot()
Direction.PREV -> prevPage.screenshot()
else -> null
}
}
/** /**
* 触摸事件处理 * 触摸事件处理
*/ */
@ -191,7 +196,9 @@ abstract class PageDelegate(protected val pageView: PageView) :
if (isStarted) return if (isStarted) return
if (!detector.onTouchEvent(event)) { if (!detector.onTouchEvent(event)) {
//GestureDetector.onFling小幅移动不会触发,所以要自己判断 //GestureDetector.onFling小幅移动不会触发,所以要自己判断
if (event.action == MotionEvent.ACTION_UP && isMoved) { when (event.action) {
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> if (isMoved) {
if (selectedOnDown) { if (selectedOnDown) {
selectedOnDown = false selectedOnDown = false
} }
@ -199,6 +206,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
} }
} }
} }
}
/** /**
* 按下 * 按下
@ -232,6 +240,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
selectedOnDown = false selectedOnDown = false
return true return true
} }
if (isMoved) {
if (!noNext) onAnimStart()
return true
}
val x = e.x val x = e.x
val y = e.y val y = e.y
if (centerRectF.contains(x, y)) { if (centerRectF.contains(x, y)) {
@ -321,7 +333,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
} }
open fun onDestroy() { open fun onDestroy() {
bitmap?.recycle()
} }
enum class Direction { enum class Direction {

@ -4,7 +4,6 @@ import android.view.MotionEvent
import android.view.VelocityTracker import android.view.VelocityTracker
import io.legado.app.ui.book.read.page.ChapterProvider import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import kotlin.math.abs
class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
@ -46,9 +45,28 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
private fun onScroll(event: MotionEvent) { private fun onScroll(event: MotionEvent) {
mVelocity.addMovement(event) mVelocity.addMovement(event)
mVelocity.computeCurrentVelocity(velocityDuration) mVelocity.computeCurrentVelocity(velocityDuration)
setTouchPoint(event.x, event.y) val action: Int = event.action
val pointerUp =
action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP
val skipIndex = if (pointerUp) event.actionIndex else -1
// Determine focal point
var sumX = 0f
var sumY = 0f
val count: Int = event.pointerCount
for (i in 0 until count) {
if (skipIndex == i) continue
sumX += event.getX(i)
sumY += event.getY(i)
}
val div = if (pointerUp) count - 1 else count
val focusX = sumX / div
val focusY = sumY / div
setTouchPoint(sumX, sumY)
if (!isMoved) { if (!isMoved) {
isMoved = abs(startX - event.x) > slop || abs(startY - event.y) > slop val deltaX = (focusX - startX).toInt()
val deltaY = (focusY - startY).toInt()
val distance = deltaX * deltaX + deltaY * deltaY
isMoved = distance > slopSquare
} }
if (isMoved) { if (isMoved) {
isRunning = true isRunning = true
@ -61,10 +79,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
} }
override fun nextPageByAnim() { override fun nextPageByAnim() {
super.nextPageByAnim()
startScroll(0, 0, 0, -ChapterProvider.visibleHeight) startScroll(0, 0, 0, -ChapterProvider.visibleHeight)
} }
override fun prevPageByAnim() { override fun prevPageByAnim() {
super.prevPageByAnim()
startScroll(0, 0, 0, ChapterProvider.visibleHeight) startScroll(0, 0, 0, ChapterProvider.visibleHeight)
} }
} }

@ -3,8 +3,8 @@ package io.legado.app.ui.book.read.page.delegate
import android.graphics.* import android.graphics.*
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.os.Build import android.os.Build
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.screenshot
import kotlin.math.* import kotlin.math.*
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -73,10 +73,6 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL } private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL }
private var curBitmap: Bitmap? = null
private var prevBitmap: Bitmap? = null
private var nextBitmap: Bitmap? = null
init { init {
//设置颜色数组 //设置颜色数组
val color = intArrayOf(0x333333, -0x4fcccccd) val color = intArrayOf(0x333333, -0x4fcccccd)
@ -119,20 +115,20 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
} }
override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) { override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) {
super.setStartPoint(x, y, invalidate) super.setStartPoint(x, y, false)
calcCornerXY(x, y) calcCornerXY(x, y)
} }
override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) { override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) {
super.setTouchPoint(x, y, false) super.setTouchPoint(x, y, false)
//触摸y中间位置吧y变成屏幕高度 //触摸y中间位置吧y变成屏幕高度
if ((startY > viewHeight * 0.33 && startY < viewHeight * 0.66) if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3)
|| mDirection == Direction.PREV || mDirection == Direction.PREV
) { ) {
touchY = viewHeight.toFloat() touchY = viewHeight.toFloat()
} }
if (startY > viewHeight * 0.33 && startY < viewHeight / 2.0 if (startY > viewHeight / 3 && startY < viewHeight / 2
&& mDirection == Direction.NEXT && mDirection == Direction.NEXT
) { ) {
touchY = 1f touchY = 1f
@ -145,33 +141,19 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
when (direction) { when (direction) {
Direction.PREV -> Direction.PREV ->
//上一页滑动不出现对角 //上一页滑动不出现对角
if (startX > viewWidth / 2.0) { if (startX > viewWidth / 2) {
calcCornerXY(startX, viewHeight.toFloat()) calcCornerXY(startX, viewHeight.toFloat())
} else { } else {
calcCornerXY(viewWidth - startX, viewHeight.toFloat()) calcCornerXY(viewWidth - startX, viewHeight.toFloat())
} }
Direction.NEXT -> Direction.NEXT ->
if (viewWidth / 2.0 > startX) { if (viewWidth / 2 > startX) {
calcCornerXY(viewWidth - startX, startY) calcCornerXY(viewWidth - startX, startY)
} }
else -> Unit else -> Unit
} }
} }
override fun setBitmap() {
when (mDirection) {
Direction.PREV -> {
prevBitmap = prevPage.screenshot()
curBitmap = curPage.screenshot()
}
Direction.NEXT -> {
nextBitmap = nextPage.screenshot()
curBitmap = curPage.screenshot()
}
else -> Unit
}
}
override fun onAnimStart() { override fun onAnimStart() {
var dx: Float var dx: Float
val dy: Float val dy: Float
@ -209,15 +191,10 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
if (!isCancel) { if (!isCancel) {
pageView.fillPage(mDirection) pageView.fillPage(mDirection)
} }
prevBitmap?.recycle()
prevBitmap = null
nextBitmap?.recycle()
nextBitmap = null
curBitmap?.recycle()
curBitmap = null
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
if (!isRunning) return
when (mDirection) { when (mDirection) {
Direction.NEXT -> { Direction.NEXT -> {
calcPoints() calcPoints()
@ -292,6 +269,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
mMatrix.setValues(mMatrixArray) mMatrix.setValues(mMatrixArray)
mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y) mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y)
mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y) mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y)
canvas.drawColor(ReadBookConfig.bgMeanColor)
canvas.drawBitmap(bitmap, mMatrix, mPaint) canvas.drawBitmap(bitmap, mMatrix, mPaint)
mPaint.colorFilter = null mPaint.colorFilter = null
canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y) canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y)
@ -480,8 +458,8 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
* 计算拖拽点对应的拖拽脚 * 计算拖拽点对应的拖拽脚
*/ */
private fun calcCornerXY(x: Float, y: Float) { private fun calcCornerXY(x: Float, y: Float) {
mCornerX = if (x <= viewWidth / 2.0) 0 else viewWidth mCornerX = if (x <= viewWidth / 2) 0 else viewWidth
mCornerY = if (y <= viewHeight / 2.0) 0 else viewHeight mCornerY = if (y <= viewHeight / 2) 0 else viewHeight
mIsRtOrLb = (mCornerX == 0 && mCornerY == viewHeight) mIsRtOrLb = (mCornerX == 0 && mCornerY == viewHeight)
|| (mCornerY == 0 && mCornerX == viewWidth) || (mCornerY == 0 && mCornerX == viewWidth)
} }
@ -492,20 +470,22 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
mMiddleX = (mTouchX + mCornerX) / 2 mMiddleX = (mTouchX + mCornerX) / 2
mMiddleY = (mTouchY + mCornerY) / 2 mMiddleY = (mTouchY + mCornerY) / 2
mBezierControl1.x = mBezierControl1.x =
mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX) mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX)
mBezierControl1.y = mCornerY.toFloat() mBezierControl1.y = mCornerY.toFloat()
mBezierControl2.x = mCornerX.toFloat() mBezierControl2.x = mCornerX.toFloat()
mBezierControl2.y = if ((mCornerY - mMiddleY).toInt() == 0) {
mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f val f4 = mCornerY - mMiddleY
if (f4 == 0f) {
mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f
} else { } else {
mBezierControl2.y =
mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY)
} }
mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2 mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2
mBezierStart1.y = mCornerY.toFloat() mBezierStart1.y = mCornerY.toFloat()
// 固定左边上下两个点 // 固定左边上下两个点
if (mTouchX > 0 && mTouchX < viewWidth) { if (mTouchX > 0 && mTouchX < viewWidth) {
if (mBezierStart1.x < 0 || mBezierStart1.x > viewWidth) { if (mBezierStart1.x < 0 || mBezierStart1.x > viewWidth) {
@ -515,6 +495,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
val f1 = abs(mCornerX - mTouchX) val f1 = abs(mCornerX - mTouchX)
val f2 = viewWidth * f1 / mBezierStart1.x val f2 = viewWidth * f1 / mBezierStart1.x
mTouchX = abs(mCornerX - f2) mTouchX = abs(mCornerX - f2)
val f3 = abs(mCornerX - mTouchX) * abs(mCornerY - mTouchY) / f1 val f3 = abs(mCornerX - mTouchX) * abs(mCornerY - mTouchY) / f1
mTouchY = abs(mCornerY - f3) mTouchY = abs(mCornerY - f3)
@ -526,9 +507,13 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
mBezierControl1.y = mCornerY.toFloat() mBezierControl1.y = mCornerY.toFloat()
mBezierControl2.x = mCornerX.toFloat() mBezierControl2.x = mCornerX.toFloat()
mBezierControl2.y = if ((mCornerY - mMiddleY).toInt() == 0) {
val f5 = mCornerY - mMiddleY
if (f5 == 0f) {
mBezierControl2.y =
mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f
} else { } else {
mBezierControl2.y =
mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY) mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY)
} }
@ -538,12 +523,19 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
mBezierStart2.x = mCornerX.toFloat() mBezierStart2.x = mCornerX.toFloat()
mBezierStart2.y = mBezierControl2.y - (mCornerY - mBezierControl2.y) / 2 mBezierStart2.y = mBezierControl2.y - (mCornerY - mBezierControl2.y) / 2
mTouchToCornerDis = hypot(mTouchX - mCornerX, touchY - mCornerY) mTouchToCornerDis = hypot(
(mTouchX - mCornerX).toDouble(),
(mTouchY - mCornerY).toDouble()
).toFloat()
mBezierEnd1 = mBezierEnd1 = getCross(
getCross(PointF(mTouchX, mTouchY), mBezierControl1, mBezierStart1, mBezierStart2) PointF(mTouchX, mTouchY), mBezierControl1, mBezierStart1,
mBezierEnd2 = mBezierStart2
getCross(PointF(mTouchX, mTouchY), mBezierControl2, mBezierStart1, mBezierStart2) )
mBezierEnd2 = getCross(
PointF(mTouchX, mTouchY), mBezierControl2, mBezierStart1,
mBezierStart2
)
mBezierVertex1.x = (mBezierStart1.x + 2 * mBezierControl1.x + mBezierEnd1.x) / 4 mBezierVertex1.x = (mBezierStart1.x + 2 * mBezierControl1.x + mBezierEnd1.x) / 4
mBezierVertex1.y = (2 * mBezierControl1.y + mBezierStart1.y + mBezierEnd1.y) / 4 mBezierVertex1.y = (2 * mBezierControl1.y + mBezierStart1.y + mBezierEnd1.y) / 4

@ -28,7 +28,6 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
viewWidth - (touchX - startX) viewWidth - (touchX - startX)
} }
} }
startScroll(touchX.toInt(), 0, distanceX.toInt(), 0) startScroll(touchX.toInt(), 0, distanceX.toInt(), 0)
} }
@ -38,27 +37,22 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
if ((mDirection == Direction.NEXT && offsetX > 0) if ((mDirection == Direction.NEXT && offsetX > 0)
|| (mDirection == Direction.PREV && offsetX < 0) || (mDirection == Direction.PREV && offsetX < 0)
) return ) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
bitmap?.let { if (!isRunning) return
if (mDirection == Direction.PREV) {
bitmapMatrix.setTranslate(distanceX + viewWidth, 0.toFloat())
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
bitmapMatrix.setTranslate(distanceX, 0.toFloat()) bitmapMatrix.setTranslate(distanceX, 0.toFloat())
canvas.drawBitmap(it, bitmapMatrix, null) prevBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
} } else if (mDirection == Direction.NEXT) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat())
nextBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
bitmapMatrix.setTranslate(distanceX - viewWidth, 0.toFloat())
curBitmap?.let { canvas.drawBitmap(it, bitmapMatrix, null) }
} }
override fun onScroll() {
val offsetX = touchX - startX
if ((mDirection == Direction.NEXT && offsetX > 0)
|| (mDirection == Direction.PREV && offsetX < 0)
) return
curPage.translationX = offsetX
} }
override fun onAnimStop() { override fun onAnimStop() {
curPage.x = 0.toFloat()
if (!isCancel) { if (!isCancel) {
pageView.fillPage(mDirection) pageView.fillPage(mDirection)
} }

@ -14,7 +14,7 @@ data class TextPage(
var pageSize: Int = 0, var pageSize: Int = 0,
var chapterSize: Int = 0, var chapterSize: Int = 0,
var chapterIndex: Int = 0, var chapterIndex: Int = 0,
var height: Int = 0 var height: Float = 0f
) { ) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -46,7 +46,7 @@ data class TextPage(
} }
textLines.add(textLine) textLines.add(textLine)
} }
height = ChapterProvider.visibleHeight height = ChapterProvider.visibleHeight.toFloat()
} }
return this return this
} }

@ -280,12 +280,16 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
*/ */
@Synchronized @Synchronized
private fun upSearchItems(items: List<SearchBook>, isMandatoryUpdate: Boolean) { private fun upSearchItems(items: List<SearchBook>, isMandatoryUpdate: Boolean) {
if (!isMandatoryUpdate && System.currentTimeMillis() - refreshTime < 1000) { if (!isMandatoryUpdate && System.currentTimeMillis() - refreshTime < 500) {
return return
} }
refreshTime = System.currentTimeMillis() refreshTime = System.currentTimeMillis()
val diffResult = DiffUtil.calculateDiff(DiffCallBack(ArrayList(adapter.getItems()), items)) try {
val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), items))
adapter.setItems(items, diffResult) adapter.setItems(items, diffResult)
} catch (e: Exception) {
e.printStackTrace()
}
} }
/** /**

@ -114,7 +114,8 @@ object BackupRestoreUi {
.onGranted { .onGranted {
Coroutine.async { Coroutine.async {
AppConfig.backupPath = path AppConfig.backupPath = path
Restore.restore(path) Restore.restoreDatabase(path)
Restore.restoreConfig(path)
}.onSuccess { }.onSuccess {
fragment.toast(R.string.restore_success) fragment.toast(R.string.restore_success)
} }

@ -3,7 +3,9 @@ package io.legado.app.ui.config
import android.os.Bundle import android.os.Bundle
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.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.observeEvent
import kotlinx.android.synthetic.main.activity_config.* import kotlinx.android.synthetic.main.activity_config.*
class ConfigActivity : VMBaseActivity<ConfigViewModel>(R.layout.activity_config) { class ConfigActivity : VMBaseActivity<ConfigViewModel>(R.layout.activity_config) {
@ -47,4 +49,10 @@ class ConfigActivity : VMBaseActivity<ConfigViewModel>(R.layout.activity_config)
} }
override fun observeLiveBus() {
super.observeLiveBus()
observeEvent<String>(EventBus.RECREATE) {
recreate()
}
}
} }

@ -18,7 +18,6 @@ import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.ColorUtils
import io.legado.app.utils.* import io.legado.app.utils.*
import org.jetbrains.anko.defaultSharedPreferences
class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -27,7 +26,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_theme) addPreferencesFromResource(R.xml.pref_config_theme)
onSharedPreferenceChanged(requireContext().defaultSharedPreferences, "defaultTheme")
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -49,9 +47,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
sharedPreferences ?: return sharedPreferences ?: return
when (key) { when (key) {
PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key)) PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key))
"transparentStatusBar" -> { "transparentStatusBar" -> recreateActivities()
recreateActivities()
}
"colorPrimary", "colorPrimary",
"colorAccent", "colorAccent",
"colorBackground", "colorBackground",
@ -100,7 +96,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(true) upTheme(true)
} }
} }
"defaultTheme" -> findPreference<Preference>(key)?.summary = items[getPrefInt(key)]
} }
} }
@ -147,7 +142,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
AppConfig.isNightTheme = true AppConfig.isNightTheme = true
} }
} }
putPrefInt("defaultTheme", which)
App.INSTANCE.applyDayNight() App.INSTANCE.applyDayNight()
recreateActivities() recreateActivities()
} }

@ -34,14 +34,18 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
get() = getViewModel(MainViewModel::class.java) get() = getViewModel(MainViewModel::class.java)
private var pagePosition = 0 private var pagePosition = 0
private val fragmentList = arrayListOf<Fragment>() private val fragmentId = arrayOf(0, 1, 2, 3)
private var rssFragment: RssFragment? = null private val fragmentMap = mapOf<Int, Fragment>(
Pair(fragmentId[0], BookshelfFragment()),
Pair(fragmentId[1], ExploreFragment()),
Pair(fragmentId[2], RssFragment()),
Pair(fragmentId[3], MyFragment())
)
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
ATH.applyEdgeEffectColor(view_pager_main) ATH.applyEdgeEffectColor(view_pager_main)
ATH.applyBottomNavigationColor(bottom_navigation_view) ATH.applyBottomNavigationColor(bottom_navigation_view)
view_pager_main.offscreenPageLimit = 3 view_pager_main.offscreenPageLimit = 3
upFragmentList()
view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager) view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager)
view_pager_main.addOnPageChangeListener(this) view_pager_main.addOnPageChangeListener(this)
bottom_navigation_view.setOnNavigationItemSelectedListener(this) bottom_navigation_view.setOnNavigationItemSelectedListener(this)
@ -69,22 +73,6 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
return false return false
} }
private fun upFragmentList() {
if (fragmentList.isEmpty()) {
fragmentList.add(BookshelfFragment())
fragmentList.add(ExploreFragment())
fragmentList.add(RssFragment())
fragmentList.add(MyFragment())
}
if (AppConfig.isShowRSS && fragmentList.size < 4) {
fragmentList.add(2,
rssFragment ?: RssFragment().apply { rssFragment = this })
}
if (!AppConfig.isShowRSS && fragmentList.size == 4) {
fragmentList.removeAt(2)
}
}
private fun upVersion() { private fun upVersion() {
if (getPrefInt(PreferKey.versionCode) != App.INSTANCE.versionCode) { if (getPrefInt(PreferKey.versionCode) != App.INSTANCE.versionCode) {
putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode)
@ -144,7 +132,6 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
} }
observeEvent<String>(EventBus.SHOW_RSS) { observeEvent<String>(EventBus.SHOW_RSS) {
bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS
upFragmentList()
view_pager_main.adapter?.notifyDataSetChanged() view_pager_main.adapter?.notifyDataSetChanged()
if (AppConfig.isShowRSS) { if (AppConfig.isShowRSS) {
view_pager_main.setCurrentItem(3, false) view_pager_main.setCurrentItem(3, false)
@ -160,7 +147,16 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
} }
override fun getItem(position: Int): Fragment { override fun getItem(position: Int): Fragment {
return fragmentList[position] return when (position) {
0 -> fragmentMap.getValue(fragmentId[0])
1 -> fragmentMap.getValue(fragmentId[1])
2 -> if (AppConfig.isShowRSS) {
fragmentMap.getValue(fragmentId[2])
} else {
fragmentMap.getValue(fragmentId[3])
}
else -> fragmentMap.getValue(fragmentId[3])
}
} }
override fun getCount(): Int { override fun getCount(): Int {
@ -168,4 +164,5 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
} }
} }
} }

@ -15,7 +15,6 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
@ -25,10 +24,10 @@ import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.theme.ATH 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.ui.book.arrange.ArrangeBookActivity import io.legado.app.ui.book.arrange.ArrangeBookActivity
import io.legado.app.ui.book.download.DownloadActivity
import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.group.GroupManageDialog
import io.legado.app.ui.book.local.ImportBookActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.download.DownloadActivity
import io.legado.app.ui.importbook.ImportBookActivity
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.* import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.*
@ -99,9 +98,6 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
TabLayoutMediator(tab_layout, view_pager_bookshelf) { tab, position -> TabLayoutMediator(tab_layout, view_pager_bookshelf) { tab, position ->
tab.text = bookGroups[position].groupName tab.text = bookGroups[position].groupName
}.attach() }.attach()
observeEvent<Int>(EventBus.UP_TABS) {
tab_layout.getTabAt(it)?.select()
}
} }
private fun initBookGroupData() { private fun initBookGroupData() {

@ -57,9 +57,6 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
} }
initRecyclerView() initRecyclerView()
upRecyclerData() upRecyclerData()
observeEvent<String>(EventBus.UP_BOOK) {
booksAdapter.notification(it)
}
} }
private fun initRecyclerView() { private fun initRecyclerView() {
@ -111,7 +108,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
else -> list.sortedByDescending { it.durChapterTime } else -> list.sortedByDescending { it.durChapterTime }
} }
val diffResult = DiffUtil val diffResult = DiffUtil
.calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books)) .calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), books))
booksAdapter.setItems(books, diffResult) booksAdapter.setItems(books, diffResult)
}) })
} }
@ -135,4 +132,10 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
return bookUrl in activityViewModel.updateList return bookUrl in activityViewModel.updateList
} }
override fun observeLiveBus() {
super.observeLiveBus()
observeEvent<String>(EventBus.UP_BOOK) {
booksAdapter.notification(it)
}
}
} }

@ -18,7 +18,7 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.explore.ExploreShowActivity import io.legado.app.ui.book.explore.ExploreShowActivity
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity

@ -23,6 +23,7 @@ import io.legado.app.ui.config.ConfigActivity
import io.legado.app.ui.config.ConfigViewModel import io.legado.app.ui.config.ConfigViewModel
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.replacerule.ReplaceRuleActivity
import io.legado.app.ui.widget.prefs.NameListPreference
import io.legado.app.ui.widget.prefs.SwitchPreference import io.legado.app.ui.widget.prefs.SwitchPreference
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.view_title_bar.* import kotlinx.android.synthetic.main.view_title_bar.*
@ -74,6 +75,12 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog.
observeEvent<Boolean>(EventBus.WEB_SERVICE_STOP) { observeEvent<Boolean>(EventBus.WEB_SERVICE_STOP) {
webServicePre?.isChecked = false webServicePre?.isChecked = false
} }
findPreference<NameListPreference>(PreferKey.themeMode)?.let {
it.setOnPreferenceChangeListener { _, _ ->
view?.post { App.INSTANCE.applyDayNight() }
true
}
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -96,7 +103,6 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog.
key: String? key: String?
) { ) {
when (key) { when (key) {
PreferKey.themeMode -> App.INSTANCE.applyDayNight()
PreferKey.webService -> { PreferKey.webService -> {
if (requireContext().getPrefBoolean("webService")) { if (requireContext().getPrefBoolean("webService")) {
WebService.start(requireContext()) WebService.start(requireContext())

@ -141,8 +141,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
} }
private fun observeReplaceRuleData(key: String? = null) { private fun observeReplaceRuleData(key: String? = null) {
replaceRuleLiveData?.removeObservers(this)
dataInit = false dataInit = false
replaceRuleLiveData?.removeObservers(this)
replaceRuleLiveData = if (key.isNullOrEmpty()) { replaceRuleLiveData = if (key.isNullOrEmpty()) {
App.db.replaceRuleDao().liveDataAll() App.db.replaceRuleDao().liveDataAll()
} else { } else {
@ -304,22 +304,27 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
} }
override fun update(vararg rule: ReplaceRule) { override fun update(vararg rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.update(*rule) viewModel.update(*rule)
} }
override fun delete(rule: ReplaceRule) { override fun delete(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.delete(rule) viewModel.delete(rule)
} }
override fun edit(rule: ReplaceRule) { override fun edit(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
ReplaceEditDialog.show(supportFragmentManager, rule.id) ReplaceEditDialog.show(supportFragmentManager, rule.id)
} }
override fun toTop(rule: ReplaceRule) { override fun toTop(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.toTop(rule) viewModel.toTop(rule)
} }
override fun upOrder() { override fun upOrder() {
setResult(Activity.RESULT_OK)
viewModel.upOrder() viewModel.upOrder()
} }
} }

@ -23,7 +23,7 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application
bundle.getString("pattern")?.let { pattern -> bundle.getString("pattern")?.let { pattern ->
val isRegex = bundle.getBoolean("isRegex") val isRegex = bundle.getBoolean("isRegex")
replaceRuleData.postValue( replaceRuleData.postValue(
ReplaceRule(pattern = pattern, isRegex = isRegex) ReplaceRule(name = pattern, pattern = pattern, isRegex = isRegex)
) )
} }
} }

@ -60,7 +60,7 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
R.id.menu_clear -> { R.id.menu_clear -> {
viewModel.url?.let { viewModel.url?.let {
refresh_progress_bar.isAutoLoading = true refresh_progress_bar.isAutoLoading = true
viewModel.clear() viewModel.clearArticles()
} }
} }
} }

@ -106,7 +106,7 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
} }
} }
fun clear() { fun clearArticles() {
execute { execute {
url?.let { url?.let {
App.db.rssArticleDao().delete(it) App.db.rssArticleDao().delete(it)

@ -8,6 +8,7 @@ import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
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.help.storage.SyncBookProgress
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity import io.legado.app.ui.main.MainActivity
@ -41,6 +42,7 @@ open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) {
else -> null else -> null
} }
} }
SyncBookProgress.downloadBookProgress()
root_view.postDelayed({ startMainActivity() }, 300) root_view.postDelayed({ startMainActivity() }, 300)
} }

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

Loading…
Cancel
Save