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. 80
      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
}
applicationIdSuffix '.release'
minifyEnabled false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
@ -75,8 +75,11 @@ android {
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
// Flag to enable support for the new language APIs
//coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
@ -103,7 +106,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
//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'
//androidX
@ -137,7 +140,7 @@ dependencies {
implementation "org.jetbrains.anko:anko-sdk27-listeners:$anko_version"
//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'

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

@ -133,7 +133,9 @@
android:name=".ui.about.DonateActivity"
android:launchMode="singleTask" />
<!--书源管理-->
<activity android:name=".ui.book.source.manage.BookSourceActivity">
<activity
android:name=".ui.book.source.manage.BookSourceActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@ -144,7 +146,9 @@
</intent-filter>
</activity>
<!--订阅源管理-->
<activity android:name=".ui.rss.source.manage.RssSourceActivity">
<activity
android:name=".ui.rss.source.manage.RssSourceActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -159,7 +163,7 @@
<!--替换规则界面-->
<activity
android:name=".ui.replacerule.ReplaceRuleActivity"
android:launchMode="singleTask">
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -171,16 +175,36 @@
android:scheme="yuedu" />
</intent-filter>
</activity>
<activity android:name=".ui.book.arrange.ArrangeBookActivity" />
<activity android:name=".ui.book.source.debug.BookSourceDebugActivity" />
<activity android:name=".ui.chapterlist.ChapterListActivity" />
<activity android:name=".ui.rss.read.ReadRssActivity" />
<activity android:name=".ui.importbook.ImportBookActivity" />
<activity android:name=".ui.explore.ExploreShowActivity" />
<activity android:name=".ui.rss.source.debug.RssSourceDebugActivity" />
<activity android:name=".ui.rss.article.RssArticlesActivity" />
<activity android:name=".ui.rss.favorites.RssFavoritesActivity" />
<activity android:name=".ui.download.DownloadActivity" />
<activity
android:name=".ui.book.arrange.ArrangeBookActivity"
android:launchMode="singleTop" />
<activity
android:name=".ui.book.source.debug.BookSourceDebugActivity"
android:launchMode="singleTop" />
<activity
android:name="io.legado.app.ui.book.chapterlist.ChapterListActivity"
android:launchMode="singleTop" />
<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
android:name=".receiver.SharedReceiverActivity"
android:label="@string/receiving_shared_label">

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

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

@ -48,13 +48,9 @@ class App : Application() {
versionCode = it.versionCode
versionName = it.versionName
}
if (!ThemeStore.isConfigured(this, versionCode)) applyTheme()
initNightMode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId()
LiveEventBus.get()
applyDayNight()
LiveEventBus
.config()
.supportBroadcast(this)
.lifecycleObserverAlwaysActive(true)

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

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

@ -2,7 +2,9 @@ package io.legado.app.constant
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 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 WEB_SERVICE_STOP = "webServiceStop"
const val UP_DOWNLOAD = "upDownload"
const val UP_TABS = "upTabs"
const val SAVE_CONTENT = "saveContent"
}

@ -32,6 +32,7 @@ object PreferKey {
const val webDavUrl = "web_dav_url"
const val webDavAccount = "web_dav_account"
const val webDavPassword = "web_dav_password"
const val webDavCreateDir = "webDavCreateDir"
const val changeSourceLoadToc = "changeSourceLoadToc"
const val chineseConverterType = "chineseConverterType"
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()
.addCallback(object : Callback() {
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
GlobalScope.launch { Restore.restore(Backup.backupPath) }
GlobalScope.launch { Restore.restoreDatabase(Backup.backupPath) }
}
})
.build()

@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.room.*
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookProgress
@Dao
interface BookDao {
@ -67,4 +68,23 @@ interface BookDao {
@Query("update books set `group` = :newGroupId where `group` = :oldGroupId")
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>
@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>
@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>

@ -5,6 +5,7 @@ import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.constant.AppPattern
import io.legado.app.constant.BookType
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
@ -86,7 +87,7 @@ data class Book(
@IgnoredOnParcel
override var tocHtml: String? = null
fun getRealAuthor() = author.replace("作者:", "")
fun getRealAuthor() = author.replace(AppPattern.authorRegex, "")
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.model.localBook.AnalyzeTxtFile
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.JaccardSimilarity
import org.jetbrains.anko.toast
import java.io.File
import kotlin.math.min
@ -177,7 +180,7 @@ object BookHelp {
fun formatAuthor(author: String?): String {
return author
?.replace("\\s*者[\\s::]*".toRegex(), "")
?.replace("\\s*者\\s*[::]\n*".toRegex(), "")
?.replace("\\s+".toRegex(), " ")
?.trim { it <= ' ' }
?: ""
@ -262,7 +265,7 @@ object BookHelp {
}
}
fun disposeContent(
suspend fun disposeContent(
title: String,
name: String,
origin: String?,
@ -272,14 +275,20 @@ object BookHelp {
var c = content
if (enableReplace) {
upReplaceRules(name, origin)
for (item in replaceRules) {
replaceRules.forEach { item ->
item.pattern.let {
if (it.isNotEmpty()) {
try {
c = if (item.isRegex) {
c.replace(it.toRegex(), item.replacement)
} else {
c.replace(it, item.replacement)
}
} catch (e: Exception) {
withContext(Main) {
App.INSTANCE.toast("${item.name}替换出错")
}
}
}
}
}

@ -1,13 +1,14 @@
package io.legado.app.help
import android.util.Base64
import androidx.annotation.Keep
import io.legado.app.constant.AppConst.DATE_FORMAT
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.MD5Utils
import java.util.*
@Keep
@Suppress("unused")
interface JsExtensions {

@ -4,6 +4,7 @@ import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import androidx.annotation.Keep
import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.PreferKey
@ -15,6 +16,7 @@ import java.io.File
/**
* 阅读界面配置
*/
@Keep
object ReadBookConfig {
const val readConfigFileName = "readConfig.json"
private val configFilePath =
@ -27,6 +29,7 @@ object ReadBookConfig {
val durConfig get() = getConfig(styleSelect)
private val shareConfig get() = getConfig(5)
var bg: Drawable? = null
var bgMeanColor: Int = 0
init {
upConfig()
@ -68,6 +71,13 @@ object ReadBookConfig {
val width = dm.widthPixels
val height = dm.heightPixels
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() {
@ -228,6 +238,7 @@ object ReadBookConfig {
if (shareLayout) shareConfig.footerPaddingTop = value
else durConfig.footerPaddingTop = value
@Keep
class Config(
private var bgStr: String = "#EEEEEE",//白天背景
private var bgStrNight: String = "#000000",//夜间背景
@ -239,7 +250,7 @@ object ReadBookConfig {
private var textColorNight: String = "#ADADAD",//夜间文字颜色
var textBold: Boolean = false,//是否粗体字
var textSize: Int = 20,//文字大小
var letterSpacing: Float = 1f,//字间距
var letterSpacing: Float = 0.5f,//字间距
var lineSpacingExtra: Int = 12,//行间距
var paragraphSpacing: Int = 12,//段距
var titleCenter: Boolean = true,//标题居中

@ -36,7 +36,7 @@ object ImportOldData {
try {// Book source
val sourceFile =
FileUtils.createFileIfNotExist(file, "myBookSource.json")
FileUtils.getFile(file, "myBookSource.json")
val json = sourceFile.readText()
val importCount = importOldSource(json)
withContext(Dispatchers.Main) {
@ -49,13 +49,18 @@ object ImportOldData {
}
try {// Replace rules
val ruleFile =
FileUtils.createFileIfNotExist(file, "myBookReplaceRule.json")
val ruleFile = FileUtils.getFile(file, "myBookReplaceRule.json")
if (ruleFile.exists()) {
val json = ruleFile.readText()
val importCount = importOldReplaceRule(json)
withContext(Dispatchers.Main) {
context.toast("成功导入替换规则${importCount}")
}
} else {
withContext(Dispatchers.Main) {
context.toast("未找到替换规则")
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
context.toast("导入替换规则失败\n${e.localizedMessage}")

@ -2,20 +2,25 @@ package io.legado.app.help.storage
import android.content.Context
import android.net.Uri
import androidx.appcompat.app.AppCompatDelegate
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.Configuration
import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.Option
import com.jayway.jsonpath.ParseContext
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.data.entities.*
import io.legado.app.help.AppConfig
import io.legado.app.help.LauncherIconHelp
import io.legado.app.help.ReadBookConfig
import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
import org.jetbrains.anko.defaultSharedPreferences
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) {
fileToListT<Book>(path, "bookshelf.json")?.let {
App.db.bookDao().insert(*it.toTypedArray())
@ -83,11 +89,16 @@ object Restore {
fileToListT<ReplaceRule>(path, "replaceRule.json")?.let {
App.db.replaceRuleDao().insert(*it.toTypedArray())
}
}
}
suspend fun restoreConfig(path: String = Backup.backupPath) {
withContext(IO) {
try {
val file =
FileUtils.createFileIfNotExist(path + File.separator + ReadBookConfig.readConfigFileName)
val configFile =
File(App.INSTANCE.filesDir.absolutePath + File.separator + ReadBookConfig.readConfigFileName)
FileUtils.getFile(App.INSTANCE.filesDir, ReadBookConfig.readConfigFileName)
if (file.exists()) {
file.copyTo(configFile, true)
ReadBookConfig.upConfig()
@ -119,8 +130,19 @@ object Restore {
ChapterProvider.upStyle()
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))
}
}
}
private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? {
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.utils.FileUtils
import io.legado.app.utils.ZipUtils
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
@ -25,31 +26,36 @@ object WebDavHelp {
private const val defaultWebDavUrl = "https://dav.jianguoyun.com/dav/"
private val zipFilePath = "${FileUtils.getCachePath()}${File.separator}backup.zip"
private fun getWebDavUrl(): String {
val rootWebDavUrl: String
get() {
var url = App.INSTANCE.getPrefString(PreferKey.webDavUrl)
if (url.isNullOrEmpty()) {
url = defaultWebDavUrl
}
if (!url.endsWith("/")) url += "/"
if (!url.endsWith("/")) url = "${url}/"
if (App.INSTANCE.getPrefBoolean(PreferKey.webDavCreateDir, true)) {
url = "${url}legado/"
}
return url
}
private fun initWebDav(): Boolean {
fun initWebDav(): Boolean {
val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount)
val password = App.INSTANCE.getPrefString(PreferKey.webDavPassword)
if (!account.isNullOrBlank() && !password.isNullOrBlank()) {
HttpAuth.auth = HttpAuth.Auth(account, password)
WebDav(rootWebDavUrl).makeAsDir()
return true
}
return false
}
private fun getWebDavFileNames(): ArrayList<String> {
val url = getWebDavUrl()
val url = rootWebDavUrl
val names = arrayListOf<String>()
if (initWebDav()) {
try {
var files = WebDav(url + "legado/").listFiles()
var files = WebDav(url).listFiles()
files = files.reversed()
for (index: Int in 0 until min(10, files.size)) {
files[index].displayName?.let {
@ -81,12 +87,17 @@ object WebDavHelp {
private fun restoreWebDav(name: String, success: () -> Unit) {
Coroutine.async {
getWebDavUrl().let {
val file = WebDav(it + "legado/" + name)
file.downloadTo(zipFilePath, true)
rootWebDavUrl.let {
if (name == SyncBookProgress.fileName) {
SyncBookProgress.downloadBookProgress()
} else {
val webDav = WebDav(it + name)
webDav.downloadTo(zipFilePath, true)
@Suppress("BlockingMethodInNonBlockingContext")
ZipUtils.unzipFile(zipFilePath, Backup.backupPath)
Restore.restore(Backup.backupPath)
Restore.restoreDatabase()
Restore.restoreConfig()
}
}
}.onSuccess {
success.invoke()
@ -102,10 +113,9 @@ object WebDavHelp {
}
FileUtils.deleteFile(zipFilePath)
if (ZipUtils.zipFiles(paths, zipFilePath)) {
WebDav(getWebDavUrl() + "legado").makeAsDir()
val putUrl = getWebDavUrl() + "legado/backup" +
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
.format(Date(System.currentTimeMillis())) + ".zip"
val backupDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
.format(Date(System.currentTimeMillis()))
val putUrl = "${rootWebDavUrl}backup${backupDate}.zip"
WebDav(putUrl).upload(zipFilePath)
}
}

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

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

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

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

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

@ -3,7 +3,7 @@ package io.legado.app.model.analyzeRule
import android.text.TextUtils
import androidx.annotation.Keep
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.BookChapter
import io.legado.app.help.JsExtensions

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

@ -20,7 +20,7 @@ object AnalyzeTxtFile {
private const val BUFFER_SIZE = 512 * 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)
?: App.INSTANCE.externalCacheDir
?: App.INSTANCE.cacheDir

@ -1,8 +1,10 @@
package io.legado.app.model.localBook
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.data.entities.Book
import io.legado.app.utils.FileUtils
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
import io.legado.app.constant.RSSKeywords
import io.legado.app.data.entities.RssArticle
import io.legado.app.model.Debug
import org.xmlpull.v1.XmlPullParser
@ -34,26 +33,26 @@ object RssParser {
// Start parsing the item
if (eventType == XmlPullParser.START_TAG) {
when {
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM, true) ->
xmlPullParser.name.equals(RSS_ITEM, 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()
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_LINK, true) ->
xmlPullParser.name.equals(RSS_ITEM_LINK, true) ->
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 =
xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL)
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_ENCLOSURE, true) ->
xmlPullParser.getAttributeValue(null, RSS_ITEM_URL)
xmlPullParser.name.equals(RSS_ITEM_ENCLOSURE, true) ->
if (insideItem) {
val type =
xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_TYPE)
xmlPullParser.getAttributeValue(null, RSS_ITEM_TYPE)
if (type != null && type.contains("image/")) {
currentArticle.image =
xmlPullParser.getAttributeValue(null, RSSKeywords.RSS_ITEM_URL)
xmlPullParser.getAttributeValue(null, RSS_ITEM_URL)
}
}
xmlPullParser.name
.equals(RSSKeywords.RSS_ITEM_DESCRIPTION, true) ->
.equals(RSS_ITEM_DESCRIPTION, true) ->
if (insideItem) {
val description = xmlPullParser.nextText()
currentArticle.description = description.trim()
@ -61,7 +60,7 @@ object RssParser {
currentArticle.image = getImageUrl(description)
}
}
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_CONTENT, true) ->
xmlPullParser.name.equals(RSS_ITEM_CONTENT, true) ->
if (insideItem) {
val content = xmlPullParser.nextText().trim()
currentArticle.content = content
@ -70,7 +69,7 @@ object RssParser {
}
}
xmlPullParser.name
.equals(RSSKeywords.RSS_ITEM_PUB_DATE, true) ->
.equals(RSS_ITEM_PUB_DATE, true) ->
if (insideItem) {
val nextTokenType = xmlPullParser.next()
if (nextTokenType == XmlPullParser.TEXT) {
@ -79,7 +78,7 @@ object RssParser {
// Skip to be able to find date inside 'tag' tag
continue@loop
}
xmlPullParser.name.equals(RSSKeywords.RSS_ITEM_TIME, true) ->
xmlPullParser.name.equals(RSS_ITEM_TIME, true) ->
if (insideItem) currentArticle.pubDate = xmlPullParser.nextText()
}
} else if (eventType == XmlPullParser.END_TAG
@ -129,4 +128,17 @@ object RssParser {
}
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
import androidx.annotation.Keep
import io.legado.app.App
import io.legado.app.R
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.utils.NetworkUtils
@Keep
object RssParserByRule {
@Throws(Exception::class)

@ -2,6 +2,7 @@ package io.legado.app.model.webBook
import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource
import io.legado.app.model.Debug
@ -38,7 +39,7 @@ object BookInfo {
Debug.log(bookSource.bookSourceUrl, "${book.name}")
Debug.log(bookSource.bookSourceUrl, "┌获取作者")
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, "┌获取分类")

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

@ -18,11 +18,12 @@ import org.jetbrains.anko.toast
import java.util.concurrent.Executors
class CheckSourceService : BaseService() {
private var searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
private val threadCount = AppConfig.threadCount
private var searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
private var task: Coroutine<*>? = null
private val allIds = LinkedHashSet<String>()
private val checkedIds = LinkedHashSet<String>()
private val allIds = ArrayList<String>()
private val checkedIds = ArrayList<String>()
private var processIndex = 0
override fun onCreate() {
super.onCreate()
@ -50,30 +51,44 @@ class CheckSourceService : BaseService() {
allIds.clear()
checkedIds.clear()
allIds.addAll(ids)
processIndex = 0
updateNotification(0, getString(R.string.progress_show, 0, allIds.size))
task = execute(context = searchPool) {
allIds.forEach { sourceUrl ->
task = execute {
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 ->
val webBook = WebBook(source)
webBook.searchBook("我的", scope = this, context = searchPool)
.onError(IO) {
source.addGroup("失效")
App.db.bookSourceDao().update(source)
}.onFinally {
}.onFinally(IO) {
check()
checkedIds.add(sourceUrl)
updateNotification(
checkedIds.size,
getString(R.string.progress_show, checkedIds.size, allIds.size)
)
synchronized(this) {
if (processIndex >= allIds.size + threadCount - 1) {
stopSelf()
}
}
}
}.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.postEvent
import kotlinx.coroutines.isActive
import org.jetbrains.anko.toast
import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream
@ -84,8 +83,6 @@ class HttpReadAloudService : BaseReadAloudService(),
break
}
}
}.onError {
toast("下载朗读文件出错:${it.localizedMessage}")
}
}

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

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

@ -25,8 +25,8 @@ import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.service.AudioPlayService
import io.legado.app.service.help.AudioPlay
import io.legado.app.ui.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_audio_play.*
import org.apache.commons.lang3.time.DateFormatUtils
@ -46,8 +46,9 @@ class AudioPlayActivity :
private var adjustProgress = false
override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.background.alpha = 0
AudioPlay.titleData.observe(this, Observer { title_bar.title = it })
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
AudioPlay.titleData.observe(this, Observer { toolbar.title = it })
AudioPlay.coverData.observe(this, Observer { upCover(it) })
viewModel.initData(intent)
initView()

@ -1,4 +1,4 @@
package io.legado.app.ui.changecover
package io.legado.app.ui.book.changecover
import android.os.Bundle
import android.util.DisplayMetrics
@ -52,27 +52,22 @@ class ChangeCoverDialog : DialogFragment(),
callBack = activity as? CallBack
viewModel = getViewModel(ChangeCoverViewModel::class.java)
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?) {
super.onViewCreated(view, savedInstanceState)
viewModel.searchStateData.observe(this, Observer {
viewModel.searchStateData.observe(viewLifecycleOwner, Observer {
refresh_progress_bar.isAutoLoading = it
})
tool_bar.setTitle(R.string.change_cover_source)
arguments?.let { bundle ->
bundle.getString("name")?.let {
viewModel.name = it
}
bundle.getString("author")?.let {
viewModel.author = it
}
viewModel.initData(bundle)
}
recycler_view.layoutManager = GridLayoutManager(requireContext(), 3)
adapter = CoverAdapter(requireContext(), this)
recycler_view.adapter = adapter
viewModel.initData()
viewModel.loadDbSearchBook()
}
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.os.Bundle
import androidx.lifecycle.MutableLiveData
import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook
@ -21,7 +23,16 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
private var task: Coroutine<*>? = null
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 {
App.db.searchBookDao().getEnableHasCover(name, author)
}.onSuccess {

@ -1,4 +1,4 @@
package io.legado.app.ui.changecover
package io.legado.app.ui.book.changecover
import android.content.Context
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.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.util.DisplayMetrics
@ -16,9 +16,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
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.SearchBook
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.getViewModel
import io.legado.app.utils.putPrefBoolean
@ -69,10 +71,8 @@ class ChangeSourceDialog : DialogFragment(),
super.onViewCreated(view, savedInstanceState)
viewModel.initData(arguments)
showTitle()
tool_bar.inflateMenu(R.menu.change_source)
tool_bar.setOnMenuItemClickListener(this)
initRecyclerView()
initMenu()
initRecyclerView()
initSearchView()
initLiveData()
viewModel.loadDbSearchBook()
@ -85,6 +85,9 @@ class ChangeSourceDialog : DialogFragment(),
}
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 =
getPrefBoolean(PreferKey.changeSourceLoadToc)
}
@ -110,7 +113,7 @@ class ChangeSourceDialog : DialogFragment(),
}
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 {
showTitle()
false
@ -149,6 +152,7 @@ class ChangeSourceDialog : DialogFragment(),
putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked)
item.isChecked = !item.isChecked
}
R.id.menu_stop -> viewModel.stopSearch()
}
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.os.Bundle
@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook
@ -35,7 +36,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
name = it
}
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() {
super.onCleared()
searchPool.close()

@ -1,4 +1,4 @@
package io.legado.app.ui.changesource
package io.legado.app.ui.book.changesource
import android.os.Bundle
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.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.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.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.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.content.Intent

@ -1,4 +1,4 @@
package io.legado.app.ui.chapterlist
package io.legado.app.ui.book.chapterlist
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.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.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.net.Uri

@ -1,4 +1,4 @@
package io.legado.app.ui.explore
package io.legado.app.ui.book.explore
import android.os.Bundle
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 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.content.Intent

@ -20,14 +20,15 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BlurTransformation
import io.legado.app.help.ImageLoader
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.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.info.edit.BookInfoEditActivity
import io.legado.app.ui.book.read.ReadBookActivity
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.gone
import io.legado.app.utils.visible
@ -52,7 +53,8 @@ class BookInfoActivity :
get() = getViewModel(BookInfoViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.background.alpha = 0
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
tv_intro.movementMethod = ScrollingMovementMethod.getInstance()
viewModel.bookData.observe(this, Observer { showBook(it) })
viewModel.chapterListData.observe(this, Observer { upLoading(false, it) })
@ -103,7 +105,7 @@ class BookInfoActivity :
private fun showBook(book: Book) {
showCover(book)
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_lasted.text = getString(R.string.lasted_show, book.latestChapterTitle)
tv_toc.text = getString(R.string.toc_s, getString(R.string.loading))
@ -186,9 +188,7 @@ class BookInfoActivity :
}
tv_shelf.onClick {
if (viewModel.inBookshelf) {
viewModel.delBook {
upTvBookshelf()
}
deleteBook()
} else {
viewModel.addToBookshelf {
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() {
if (viewModel.chapterListData.value.isNullOrEmpty()) {
toast(R.string.chapter_list_empty)
@ -320,7 +346,7 @@ class BookInfoActivity :
}
} else {
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.model.WebBook
import io.legado.app.model.localBook.AnalyzeTxtFile
import io.legado.app.model.localBook.LocalBook
import kotlinx.coroutines.Dispatchers.IO
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 {
bookData.value?.let {
App.db.bookDao().delete(it)
}
inBookshelf = false
if (it.isLocalBook()) {
LocalBook.deleteBook(it, deleteOriginal)
}
}
}.onSuccess {
success?.invoke()
}

@ -8,7 +8,7 @@ import androidx.lifecycle.Observer
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
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 kotlinx.android.synthetic.main.activity_book_info_edit.*
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.app.Activity

@ -1,4 +1,4 @@
package io.legado.app.ui.importbook
package io.legado.app.ui.book.local
import android.content.Context
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.net.Uri

@ -23,6 +23,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig
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.noButton
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.help.ReadAloud
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.read.config.*
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.delegate.PageDelegate
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.replacerule.ReplaceRuleActivity
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 {
it.useReplaceRule = !it.useReplaceRule
menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule
onReplaceRuleSave()
}
R.id.menu_book_info -> ReadBook.book?.let {
startActivity<BookInfoActivity>(Pair("bookUrl", it.bookUrl))
@ -270,18 +272,18 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
}
}
KeyEvent.KEYCODE_SPACE -> {
page_view.moveToNextPage()
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true
}
getPrefInt(PreferKey.prevKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToPrevPage()
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV)
return true
}
}
getPrefInt(PreferKey.nextKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToNextPage()
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true
}
}
@ -439,11 +441,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
if (getPrefBoolean("volumeKeyPageOnPlay")
|| BaseReadAloudService.pause
) {
when (direction) {
PageDelegate.Direction.PREV -> page_view.moveToPrevPage()
PageDelegate.Direction.NEXT -> page_view.moveToNextPage()
else -> return true
}
page_view.pageDelegate?.keyTurnPage(direction)
return true
}
}
@ -663,6 +661,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
mHandler.removeCallbacks(keepScreenRunnable)
textActionMenu?.dismiss()
page_view.onDestroy()
SyncBookProgress.uploadBookProgress()
}
override fun observeLiveBus() {

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

@ -33,7 +33,7 @@ class MoreConfigDialog : DialogFragment() {
}
dialog?.window?.let {
it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 5, 0, 0)
it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes
attr.dimAmount = 0.0f
attr.gravity = Gravity.BOTTOM

@ -7,8 +7,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar
import androidx.fragment.app.DialogFragment
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.EventBus
import io.legado.app.help.AppConfig
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.onLongClick
class ReadAloudDialog : DialogFragment() {
class ReadAloudDialog : BaseDialogFragment() {
var callBack: CallBack? = null
override fun onStart() {
@ -53,21 +53,16 @@ class ReadAloudDialog : DialogFragment() {
return inflater.inflate(R.layout.dialog_read_aloud, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground)
initData()
initOnChange()
initData()
initOnClick()
}
private fun initData() {
observeEvent<Int>(EventBus.ALOUD_STATE) { upPlayState() }
observeEvent<Int>(EventBus.TTS_DS) { seek_timer.progress = it }
upPlayState()
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)
seek_tts_SpeechRate.isEnabled = !cb_tts_follow_sys.isChecked
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 {
fun showMenuBar()
fun openChapterList()

@ -42,7 +42,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
}
dialog?.window?.let {
it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 5, 0, 0)
it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes
attr.dimAmount = 0.0f
attr.gravity = Gravity.BOTTOM

@ -61,8 +61,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
return inflater.inflate(R.layout.dialog_toc_regex, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
durRegex = arguments?.getString("tocRegex")
tool_bar.setTitle(R.string.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.getPrefString
import io.legado.app.utils.removePref
import io.legado.app.utils.toStringArray
@Suppress("DEPRECATION")
@ -28,7 +29,7 @@ object ChapterProvider {
var visibleHeight = 0
var visibleRight = 0
var visibleBottom = 0
private var lineSpacingExtra = 0f
private var lineSpacingExtra = 0
private var paragraphSpacing = 0
var typeface: Typeface = Typeface.SANS_SERIF
var titlePaint = TextPaint()
@ -51,7 +52,7 @@ object ChapterProvider {
val pageLengths = arrayListOf<Int>()
val stringBuilder = StringBuilder()
var surplusText = content
var durY = 0
var durY = 0f
textPages.add(TextPage())
while (surplusText.isNotEmpty()) {
if (textPages.first().textLines.isEmpty()) {
@ -60,7 +61,9 @@ object ChapterProvider {
if (end > 0) {
val title = surplusText.substring(0, end)
surplusText = surplusText.substring(end + 1)
durY = joinTitle(title, durY, textPages, pageLines, pageLengths, stringBuilder)
durY = setTypeText(
title, durY, textPages, pageLines, pageLengths, stringBuilder, true
)
}
} else {
//正文
@ -73,7 +76,8 @@ object ChapterProvider {
text = 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
@ -103,116 +107,59 @@ object ChapterProvider {
}
/**
* 标题
* 排版文字
*/
private fun joinTitle(
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(
private fun setTypeText(
text: String,
y: Int,
y: Float,
textPages: ArrayList<TextPage>,
pageLines: ArrayList<Int>,
pageLengths: ArrayList<Int>,
stringBuilder: StringBuilder
): Int {
stringBuilder: StringBuilder,
isTitle: Boolean
): Float {
var durY = y
val textPaint = if (isTitle) titlePaint else contentPaint
val layout = StaticLayout(
text, contentPaint, visibleWidth,
Layout.Alignment.ALIGN_NORMAL, 1f, lineSpacingExtra, true
text, textPaint, visibleWidth,
Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true
)
for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY
val textLine = TextLine()
if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex)
+ contentPaint.fontMetrics.descent < visibleHeight
) {
val textLine = TextLine(isTitle = isTitle)
if (durY + textPaint.textHeight < visibleHeight) {
textPages.last().textLines.add(textLine)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
durY += textPaint.textHeight + lineSpacingExtra
} 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)
durY = textPaint.textHeight + lineSpacingExtra
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
textLine.lineBottom = paddingTop + durY - lineSpacingExtra
textLine.lineBase = textLine.lineBottom - textPaint.fontMetrics.descent
textLine.lineTop = textLine.lineBottom - textPaint.textHeight
val words =
text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
stringBuilder.append(words)
textLine.text = words
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) {
//最后一行
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 {
//中间行
addCharsToLineMiddle(textLine, words, contentPaint, desiredWidth, 0f)
addCharsToLineMiddle(textLine, words, textPaint, desiredWidth, 0f)
}
}
durY += paragraphSpacing
@ -231,10 +178,10 @@ object ChapterProvider {
var x = 0f
val bodyIndent = ReadBookConfig.bodyIndent
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
for (i in 0..bodyIndent.lastIndex) {
bodyIndent.toStringArray().forEach {
val x1 = x + icw
textLine.addTextChar(
charData = bodyIndent[i].toString(),
charData = it,
start = paddingLeft + x,
end = paddingLeft + x1
)
@ -257,12 +204,11 @@ object ChapterProvider {
val gapCount: Int = words.length - 1
val d = (visibleWidth - desiredWidth) / gapCount
var x = startX
for (i in words.indices) {
val char = words[i]
val cw = StaticLayout.getDesiredWidth(char.toString(), textPaint)
for ((i, char) in words.toStringArray().withIndex()) {
val cw = StaticLayout.getDesiredWidth(char, textPaint)
val x1 = if (i != words.lastIndex) (x + cw + d) else (x + cw)
textLine.addTextChar(
charData = char.toString(),
charData = char,
start = paddingLeft + x,
end = paddingLeft + x1
)
@ -284,12 +230,11 @@ object ChapterProvider {
stringBuilder.append("\n")
textLine.text = "$words\n"
var x = startX
for (i in words.indices) {
val char = words[i].toString()
val cw = StaticLayout.getDesiredWidth(char, textPaint)
words.toStringArray().forEach {
val cw = StaticLayout.getDesiredWidth(it, textPaint)
val x1 = x + cw
textLine.addTextChar(
charData = char,
charData = it,
start = paddingLeft + x,
end = paddingLeft + x1
)
@ -346,7 +291,7 @@ object ChapterProvider {
val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL
contentPaint.typeface = Typeface.create(typeface, bold)
//间距
lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp.toFloat()
lineSpacingExtra = ReadBookConfig.lineSpacingExtra.dp
paragraphSpacing = ReadBookConfig.paragraphSpacing.dp
titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat()
contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat()
@ -366,4 +311,8 @@ object ChapterProvider {
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) {
pageOffset = 0f
} else if (pageOffset > 0) {
pageFactory.moveToPrev()
pageFactory.moveToPrev(false)
textPage = pageFactory.currentPage
pageOffset -= textPage.height
upView?.invoke(textPage)
} else if (pageOffset < -textPage.height) {
pageOffset += textPage.height
pageFactory.moveToNext()
pageFactory.moveToNext(false)
textPage = pageFactory.currentPage
upView?.invoke(textPage)
}

@ -16,4 +16,6 @@ interface DataSource {
fun hasNextChapter(): 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 moveToNext():Boolean
abstract fun moveToNext(upContent: Boolean): Boolean
abstract fun moveToPrev(): Boolean
abstract fun moveToPrev(upContent: Boolean): Boolean
abstract val nextPage: DATA

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

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

@ -6,9 +6,8 @@ import android.graphics.drawable.GradientDrawable
import io.legado.app.ui.book.read.page.PageView
class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
private val shadowDrawableR: GradientDrawable
private val bitmapMatrix = Matrix()
private val shadowDrawableR: GradientDrawable
init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
@ -18,37 +17,6 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
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) {
val offsetX = touchX - startX
@ -57,13 +25,16 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
bitmap?.let {
if (!isRunning) return
if (mDirection == Direction.PREV) {
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) {
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)
}
}
@ -77,4 +48,34 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
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
import android.graphics.Bitmap
import android.view.MotionEvent
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) {
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) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
abort()
}
MotionEvent.ACTION_MOVE -> {
if (isTextSelected) {
selectText(event)
@ -20,19 +51,37 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
}
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) {
isMoved = abs(startX - event.x) > slop
|| abs(startX - event.x) > abs(startY - event.y)
val deltaX = (focusX - startX).toInt()
val deltaY = (focusY - startY).toInt()
val distance = deltaX * deltaX + deltaY * deltaY
isMoved = distance > slopSquare
if (isMoved) {
if (event.x - startX > 0) {
if (sumX - startX > 0) {
//如果上一页不存在
if (!hasPrev()) {
noNext = true
return
}
setDirection(Direction.PREV)
setBitmap()
} else {
//如果不存在表示没有下一页了
if (!hasNext()) {
@ -40,7 +89,6 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
return
}
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
isRunning = true
//设置触摸点
setTouchPoint(event.x, event.y)
setTouchPoint(sumX, sumY)
}
}
override fun nextPageByAnim() {
super.nextPageByAnim()
if (!hasNext()) return
setDirection(Direction.NEXT)
setBitmap()
setTouchPoint(viewWidth.toFloat(), 0f)
onAnimStart()
}
override fun prevPageByAnim() {
super.prevPageByAnim()
if (!hasPrev()) return
setDirection(Direction.PREV)
setBitmap()
setTouchPoint(0f, 0f)
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
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.RectF
import android.view.GestureDetector
@ -15,7 +14,6 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.screenshot
import kotlin.math.abs
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
)
protected val context: Context = pageView.context
protected val slop = ViewConfiguration.get(context).scaledTouchSlop
//起始点
protected var startX: 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 prevPage: ContentView get() = pageView.prevPage
protected var bitmap: Bitmap? = null
protected var viewWidth: Int = pageView.width
protected var viewHeight: Int = pageView.height
private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
}
private val scroller: Scroller by lazy {
Scroller(pageView.context, DecelerateInterpolator())
}
protected val slopSquare by lazy {
val scaledTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
scaledTouchSlop * scaledTouchSlop
}
private val detector: GestureDetector by lazy {
GestureDetector(pageView.context, this)
}
private val snackBar: Snackbar by lazy {
Snackbar.make(pageView, "", Snackbar.LENGTH_SHORT)
}
var isMoved = false
var noNext = true
@ -126,11 +127,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
}
private fun stopScroll() {
isMoved = false
isRunning = false
isStarted = false
pageView.invalidate()
bitmap?.recycle()
bitmap = null
}
open fun setViewSize(width: Int, height: Int) {
@ -166,23 +166,28 @@ abstract class PageDelegate(protected val pageView: PageView) :
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
open fun setDirection(direction: 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 (!detector.onTouchEvent(event)) {
//GestureDetector.onFling小幅移动不会触发,所以要自己判断
if (event.action == MotionEvent.ACTION_UP && isMoved) {
when (event.action) {
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> if (isMoved) {
if (selectedOnDown) {
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
return true
}
if (isMoved) {
if (!noNext) onAnimStart()
return true
}
val x = e.x
val y = e.y
if (centerRectF.contains(x, y)) {
@ -321,7 +333,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
}
open fun onDestroy() {
bitmap?.recycle()
}
enum class Direction {

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

@ -28,7 +28,6 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
viewWidth - (touchX - startX)
}
}
startScroll(touchX.toInt(), 0, distanceX.toInt(), 0)
}
@ -38,27 +37,22 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
if ((mDirection == Direction.NEXT && offsetX > 0)
|| (mDirection == Direction.PREV && offsetX < 0)
) return
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())
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() {
curPage.x = 0.toFloat()
if (!isCancel) {
pageView.fillPage(mDirection)
}

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

@ -280,12 +280,16 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
*/
@Synchronized
private fun upSearchItems(items: List<SearchBook>, isMandatoryUpdate: Boolean) {
if (!isMandatoryUpdate && System.currentTimeMillis() - refreshTime < 1000) {
if (!isMandatoryUpdate && System.currentTimeMillis() - refreshTime < 500) {
return
}
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)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**

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

@ -3,7 +3,9 @@ package io.legado.app.ui.config
import android.os.Bundle
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.EventBus
import io.legado.app.utils.getViewModel
import io.legado.app.utils.observeEvent
import kotlinx.android.synthetic.main.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.ColorUtils
import io.legado.app.utils.*
import org.jetbrains.anko.defaultSharedPreferences
class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -27,7 +26,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_theme)
onSharedPreferenceChanged(requireContext().defaultSharedPreferences, "defaultTheme")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -49,9 +47,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
sharedPreferences ?: return
when (key) {
PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key))
"transparentStatusBar" -> {
recreateActivities()
}
"transparentStatusBar" -> recreateActivities()
"colorPrimary",
"colorAccent",
"colorBackground",
@ -100,7 +96,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(true)
}
}
"defaultTheme" -> findPreference<Preference>(key)?.summary = items[getPrefInt(key)]
}
}
@ -147,7 +142,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
AppConfig.isNightTheme = true
}
}
putPrefInt("defaultTheme", which)
App.INSTANCE.applyDayNight()
recreateActivities()
}

@ -34,14 +34,18 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
get() = getViewModel(MainViewModel::class.java)
private var pagePosition = 0
private val fragmentList = arrayListOf<Fragment>()
private var rssFragment: RssFragment? = null
private val fragmentId = arrayOf(0, 1, 2, 3)
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?) {
ATH.applyEdgeEffectColor(view_pager_main)
ATH.applyBottomNavigationColor(bottom_navigation_view)
view_pager_main.offscreenPageLimit = 3
upFragmentList()
view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager)
view_pager_main.addOnPageChangeListener(this)
bottom_navigation_view.setOnNavigationItemSelectedListener(this)
@ -69,22 +73,6 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
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() {
if (getPrefInt(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) {
bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS
upFragmentList()
view_pager_main.adapter?.notifyDataSetChanged()
if (AppConfig.isShowRSS) {
view_pager_main.setCurrentItem(3, false)
@ -160,7 +147,16 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
}
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 {
@ -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.base.VMBaseFragment
import io.legado.app.constant.AppConst
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.BookGroup
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.accentColor
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.local.ImportBookActivity
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.utils.*
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 ->
tab.text = bookGroups[position].groupName
}.attach()
observeEvent<Int>(EventBus.UP_TABS) {
tab_layout.getTabAt(it)?.select()
}
}
private fun initBookGroupData() {

@ -57,9 +57,6 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
}
initRecyclerView()
upRecyclerData()
observeEvent<String>(EventBus.UP_BOOK) {
booksAdapter.notification(it)
}
}
private fun initRecyclerView() {
@ -111,7 +108,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
else -> list.sortedByDescending { it.durChapterTime }
}
val diffResult = DiffUtil
.calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books))
.calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), books))
booksAdapter.setItems(books, diffResult)
})
}
@ -135,4 +132,10 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
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.primaryTextColor
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.splitNotBlank
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.filechooser.FileChooserDialog
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.utils.*
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) {
webServicePre?.isChecked = false
}
findPreference<NameListPreference>(PreferKey.themeMode)?.let {
it.setOnPreferenceChangeListener { _, _ ->
view?.post { App.INSTANCE.applyDayNight() }
true
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -96,7 +103,6 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog.
key: String?
) {
when (key) {
PreferKey.themeMode -> App.INSTANCE.applyDayNight()
PreferKey.webService -> {
if (requireContext().getPrefBoolean("webService")) {
WebService.start(requireContext())

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

@ -23,7 +23,7 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application
bundle.getString("pattern")?.let { pattern ->
val isRegex = bundle.getBoolean("isRegex")
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 -> {
viewModel.url?.let {
refresh_progress_bar.isAutoLoading = true
viewModel.clear()
viewModel.clearArticles()
}
}
}

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

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

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

Loading…
Cancel
Save