Merge pull request #20 from gedoor/master

up
pull/379/head
口口吕 5 years ago committed by GitHub
commit 3b25991a5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      app/build.gradle
  2. 50
      app/src/main/AndroidManifest.xml
  3. 30
      app/src/main/assets/txtTocRule.json
  4. 20
      app/src/main/assets/updateLog.md
  5. 22
      app/src/main/java/io/legado/app/App.kt
  6. 44
      app/src/main/java/io/legado/app/help/BookHelp.kt
  7. 2
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  8. 51
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  9. 9
      app/src/main/java/io/legado/app/help/http/AjaxWebView.kt
  10. 4
      app/src/main/java/io/legado/app/help/http/CookieStore.kt
  11. 5
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  12. 22
      app/src/main/java/io/legado/app/help/storage/ImportOldData.kt
  13. 1
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  14. 2
      app/src/main/java/io/legado/app/lib/theme/ATH.kt
  15. 6
      app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt
  16. 14
      app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
  17. 4
      app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.kt
  18. 1
      app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt
  19. 2
      app/src/main/java/io/legado/app/model/Rss.kt
  20. 8
      app/src/main/java/io/legado/app/model/WebBook.kt
  21. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  22. 18
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  23. 2
      app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt
  24. 14
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  25. 72
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  26. 20
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  27. 39
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  28. 14
      app/src/main/java/io/legado/app/ui/README.md
  29. 9
      app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt
  30. 6
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt
  31. 2
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
  32. 2
      app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt
  33. 2
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt
  34. 18
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
  35. 2
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  36. 2
      app/src/main/java/io/legado/app/ui/book/changesource/DiffCallBack.kt
  37. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkAdapter.kt
  38. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkFragment.kt
  39. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListActivity.kt
  40. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt
  41. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt
  42. 2
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListViewModel.kt
  43. 38
      app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt
  44. 2
      app/src/main/java/io/legado/app/ui/book/download/DownloadAdapter.kt
  45. 2
      app/src/main/java/io/legado/app/ui/book/download/DownloadViewModel.kt
  46. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt
  47. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt
  48. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
  49. 44
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  50. 6
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  51. 2
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  52. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  53. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt
  54. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt
  55. 48
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  56. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  57. 2
      app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt
  58. 5
      app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
  59. 2
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  60. 7
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  61. 19
      app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt
  62. 14
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  63. 1
      app/src/main/java/io/legado/app/ui/book/read/page/PageFactory.kt
  64. 21
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  65. 7
      app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt
  66. 73
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt
  67. 10
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  68. 34
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  69. 7
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt
  70. 23
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt
  71. 35
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt
  72. 8
      app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
  73. 2
      app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt
  74. 23
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  75. 92
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  76. 21
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  77. 111
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  78. 35
      app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt
  79. 18
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  80. 4
      app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt
  81. 191
      app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt
  82. 4
      app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt
  83. 2
      app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt
  84. 2
      app/src/main/java/io/legado/app/ui/filechooser/utils/FilePickerIcon.java
  85. 16
      app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt
  86. 65
      app/src/main/java/io/legado/app/ui/login/SourceLogin.kt
  87. 11
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  88. 8
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  89. 4
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  90. 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt
  91. 2
      app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt
  92. 97
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  93. 28
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt
  94. 15
      app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditDialog.kt
  95. 3
      app/src/main/java/io/legado/app/ui/replacerule/edit/ReplaceEditViewModel.kt
  96. 45
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  97. 75
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  98. 21
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  99. 46
      app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt
  100. 5
      app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -75,8 +75,11 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility = '1.8' // Flag to enable support for the new language APIs
targetCompatibility = '1.8' //coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions { kotlinOptions {

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

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

@ -1,6 +1,22 @@
## 更新日志 ## 更新日志
* 旧版数据导入教程: * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
**2020/03/02**
* 添加书源登录
* 替换规则实时生效
* 页面最后一行计算是否能放下时不计算行距
* 优化翻页动画
* 优化书源校验
* 按键翻页有动画了
**2020/03/01**
* 修复书源解析的一个bug
* 添加底部操作栏颜色配置
* 修复滚动点击翻页,修复滚动最后一页显示加载中
* 去除备份恢复默认路径
* 尝试修复部分手机一键导入书源报错
* 翻页还有些bug不用反馈了,我已经知道,会修复的
**2020/02/29** **2020/02/29**
* 添加书源一键导入 * 添加书源一键导入

@ -78,26 +78,24 @@ class App : Application() {
ThemeStore.editTheme(this) ThemeStore.editTheme(this)
.primaryColor( .primaryColor(
getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600)) getPrefInt("colorPrimaryNight", getCompatColor(R.color.md_blue_grey_600))
) ).accentColor(
.accentColor(
getPrefInt("colorAccentNight", getCompatColor(R.color.md_brown_800)) getPrefInt("colorAccentNight", getCompatColor(R.color.md_brown_800))
) ).backgroundColor(
.backgroundColor(
getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color)) getPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color))
) ).bottomBackground(
.apply() getPrefInt("colorBottomBackgroundNight", getCompatColor(R.color.md_grey_850))
).apply()
} else { } else {
ThemeStore.editTheme(this) ThemeStore.editTheme(this)
.primaryColor( .primaryColor(
getPrefInt("colorPrimary", getCompatColor(R.color.md_indigo_800)) getPrefInt("colorPrimary", getCompatColor(R.color.md_indigo_800))
) ).accentColor(
.accentColor(
getPrefInt("colorAccent", getCompatColor(R.color.md_red_600)) getPrefInt("colorAccent", getCompatColor(R.color.md_red_600))
) ).backgroundColor(
.backgroundColor(
getPrefInt("colorBackground", getCompatColor(R.color.md_grey_100)) getPrefInt("colorBackground", getCompatColor(R.color.md_grey_100))
) ).bottomBackground(
.apply() getPrefInt("colorBottomBackground", getCompatColor(R.color.md_grey_200))
).apply()
} }
} }

@ -237,14 +237,30 @@ object BookHelp {
private var bookName: String? = null private var bookName: String? = null
private var bookOrigin: String? = null private var bookOrigin: String? = null
private var replaceRules: List<ReplaceRule> = arrayListOf() private var replaceRules: List<ReplaceRule> = arrayListOf()
var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)
set(value) {
field = value
App.INSTANCE.putPrefInt(PreferKey.bodyIndent, value)
bodyIndent = " ".repeat(value)
}
var bodyIndent = " ".repeat(bodyIndentCount)
@Synchronized
fun upReplaceRules(name: String? = null, origin: String? = null) {
if (name != null) {
if (bookName != name || bookOrigin != origin) {
replaceRules = if (origin.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(name)
} else {
App.db.replaceRuleDao().findEnabledByScope(name, origin)
}
bookName = name
bookOrigin = origin
}
} else {
val o = bookOrigin
bookName?.let {
replaceRules = if (o.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(it)
} else {
App.db.replaceRuleDao().findEnabledByScope(it, o)
}
}
}
}
fun disposeContent( fun disposeContent(
title: String, title: String,
@ -253,16 +269,9 @@ object BookHelp {
content: String, content: String,
enableReplace: Boolean enableReplace: Boolean
): String { ): String {
synchronized(this) {
if (enableReplace && (bookName != name || bookOrigin != origin)) {
replaceRules = if (origin.isNullOrEmpty()) {
App.db.replaceRuleDao().findEnabledByScope(name)
} else {
App.db.replaceRuleDao().findEnabledByScope(name, origin)
}
}
}
var c = content var c = content
if (enableReplace) {
upReplaceRules(name, origin)
for (item in replaceRules) { for (item in replaceRules) {
item.pattern.let { item.pattern.let {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
@ -274,6 +283,7 @@ object BookHelp {
} }
} }
} }
}
if (!c.substringBefore("\n").contains(title)) { if (!c.substringBefore("\n").contains(title)) {
c = "$title\n$c" c = "$title\n$c"
} }
@ -281,6 +291,6 @@ object BookHelp {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(c) 1 -> c = ZhConvertBootstrap.newInstance().toSimple(c)
2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c) 2 -> c = ZhConvertBootstrap.newInstance().toTraditional(c)
} }
return c.replace("\\s*\\n+\\s*".toRegex(), "\n$bodyIndent") return c.replace("\\s*\\n+\\s*".toRegex(), "\n${ReadBookConfig.bodyIndent}")
} }
} }

@ -17,7 +17,7 @@ interface JsExtensions {
fun ajax(urlStr: String): String? { fun ajax(urlStr: String): String? {
return try { return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, null, null) val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, null, null)
val call = analyzeUrl.getResponse() val call = analyzeUrl.getResponse(urlStr)
val response = call.execute() val response = call.execute()
response.body() response.body()
} catch (e: Exception) { } catch (e: Exception) {

@ -26,24 +26,6 @@ object ReadBookConfig {
} }
val durConfig get() = getConfig(styleSelect) val durConfig get() = getConfig(styleSelect)
private val shareConfig get() = getConfig(5) private val shareConfig get() = getConfig(5)
var styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect)
set(value) {
field = value
App.INSTANCE.putPrefInt(PreferKey.readStyleSelect, value)
}
var shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout)
set(value) {
field = value
App.INSTANCE.putPrefBoolean(PreferKey.shareLayout, value)
}
var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
set(value) {
field = value
isScroll = value == 3
App.INSTANCE.putPrefInt(PreferKey.pageAnim, value)
}
var isScroll = pageAnim == 3
val clickTurnPage get() = App.INSTANCE.getPrefBoolean(PreferKey.clickTurnPage, true)
var bg: Drawable? = null var bg: Drawable? = null
init { init {
@ -113,6 +95,39 @@ object ReadBookConfig {
} }
//配置写入读取 //配置写入读取
var styleSelect = App.INSTANCE.getPrefInt(PreferKey.readStyleSelect)
set(value) {
field = value
if (App.INSTANCE.getPrefInt(PreferKey.readStyleSelect) != value) {
App.INSTANCE.putPrefInt(PreferKey.readStyleSelect, value)
}
}
var shareLayout = App.INSTANCE.getPrefBoolean(PreferKey.shareLayout)
set(value) {
field = value
if (App.INSTANCE.getPrefBoolean(PreferKey.shareLayout) != value) {
App.INSTANCE.putPrefBoolean(PreferKey.shareLayout, value)
}
}
var pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
set(value) {
field = value
isScroll = value == 3
if (App.INSTANCE.getPrefInt(PreferKey.pageAnim) != value) {
App.INSTANCE.putPrefInt(PreferKey.pageAnim, value)
}
}
var isScroll = pageAnim == 3
val clickTurnPage get() = App.INSTANCE.getPrefBoolean(PreferKey.clickTurnPage, true)
var bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)
set(value) {
field = value
bodyIndent = " ".repeat(value)
if (App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2) != value) {
App.INSTANCE.putPrefInt(PreferKey.bodyIndent, value)
}
}
var bodyIndent = " ".repeat(bodyIndentCount)
var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) var hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) var hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
var textBold: Boolean var textBold: Boolean

@ -1,7 +1,6 @@
package io.legado.app.help.http package io.legado.app.help.http
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.http.SslError
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -167,10 +166,6 @@ class AjaxWebView {
).sendToTarget() ).sendToTarget()
} }
} }
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
handler.proceed()
}
} }
private class EvalJsRunnable( private class EvalJsRunnable(
@ -242,10 +237,6 @@ class AjaxWebView {
} }
} }
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
handler.proceed()
}
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
params.setCookie(url) params.setCookie(url)
if (params.hasJavaScript()) { if (params.hasJavaScript()) {

@ -3,15 +3,15 @@ package io.legado.app.help.http
import android.text.TextUtils import android.text.TextUtils
import io.legado.app.App import io.legado.app.App
import io.legado.app.data.entities.Cookie import io.legado.app.data.entities.Cookie
import io.legado.app.help.coroutine.Coroutine
object CookieStore { object CookieStore {
fun setCookie(url: String, cookie: String?) { fun setCookie(url: String, cookie: String?) {
try { Coroutine.async {
val cookieBean = Cookie(url, cookie ?: "") val cookieBean = Cookie(url, cookie ?: "")
App.db.cookieDao().insert(cookieBean) App.db.cookieDao().insert(cookieBean)
} catch (ignore: Exception) {
} }
} }

@ -101,9 +101,8 @@ object Backup {
val file = File(backupPath + File.separator + fileName) val file = File(backupPath + File.separator + fileName)
if (file.exists()) { if (file.exists()) {
treeDoc.findFile(fileName)?.delete() treeDoc.findFile(fileName)?.delete()
treeDoc.createFile("", fileName)?.let { treeDoc.createFile("", fileName)
DocumentUtils.writeBytes(context, file.readBytes(), it.uri) ?.writeBytes(context, file.readBytes())
}
} }
} }
} }

@ -17,15 +17,12 @@ import org.jetbrains.anko.toast
import java.io.File import java.io.File
object ImportOldData { object ImportOldData {
val yueDuPath by lazy {
FileUtils.getSdCardPath() + File.separator + "YueDu"
}
fun import(context: Context) { fun import(context: Context, file: File) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try {// 导入书架 try {// 导入书架
val shelfFile = val shelfFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookShelf.json") FileUtils.createFileIfNotExist(file, "myBookShelf.json")
val json = shelfFile.readText() val json = shelfFile.readText()
val importCount = importOldBookshelf(json) val importCount = importOldBookshelf(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -39,7 +36,7 @@ object ImportOldData {
try {// Book source try {// Book source
val sourceFile = val sourceFile =
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookSource.json") FileUtils.getFile(file, "myBookSource.json")
val json = sourceFile.readText() val json = sourceFile.readText()
val importCount = importOldSource(json) val importCount = importOldSource(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -52,13 +49,18 @@ object ImportOldData {
} }
try {// Replace rules try {// Replace rules
val ruleFile = val ruleFile = FileUtils.getFile(file, "myBookReplaceRule.json")
FileUtils.createFileIfNotExist(yueDuPath + File.separator + "myBookReplaceRule.json") if (ruleFile.exists()) {
val json = ruleFile.readText() val json = ruleFile.readText()
val importCount = importOldReplaceRule(json) val importCount = importOldReplaceRule(json)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
context.toast("成功导入替换规则${importCount}") context.toast("成功导入替换规则${importCount}")
} }
} else {
withContext(Dispatchers.Main) {
context.toast("未找到替换规则")
}
}
} catch (e: Exception) { } catch (e: Exception) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
context.toast("导入替换规则失败\n${e.localizedMessage}") context.toast("导入替换规则失败\n${e.localizedMessage}")
@ -115,13 +117,13 @@ object ImportOldData {
} }
} }
fun importOldBookshelf(json: String): Int { private fun importOldBookshelf(json: String): Int {
val books = OldBook.toNewBook(json) val books = OldBook.toNewBook(json)
App.db.bookDao().insert(*books.toTypedArray()) App.db.bookDao().insert(*books.toTypedArray())
return books.size return books.size
} }
fun importOldSource(json: String): Int { private fun importOldSource(json: String): Int {
val bookSources = mutableListOf<BookSource>() val bookSources = mutableListOf<BookSource>()
val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$") val items: List<Map<String, Any>> = Restore.jsonPath.parse(json).read("$")
for (item in items) { for (item in items) {

@ -114,6 +114,7 @@ object Restore {
pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim) pageAnim = App.INSTANCE.getPrefInt(PreferKey.pageAnim)
hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar) hideStatusBar = App.INSTANCE.getPrefBoolean(PreferKey.hideStatusBar)
hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar) hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2)
} }
ChapterProvider.upStyle() ChapterProvider.upStyle()
ReadBook.loadContent() ReadBook.loadContent()

@ -205,7 +205,7 @@ object ATH {
.setSelectedColor(ThemeStore.accentColor(bottom_navigation_view.context)).create() .setSelectedColor(ThemeStore.accentColor(bottom_navigation_view.context)).create()
itemIconTintList = colorStateList itemIconTintList = colorStateList
itemTextColor = colorStateList itemTextColor = colorStateList
itemBackgroundResource = R.color.background_menu setBackgroundColor(ThemeStore.bottomBackground(context))
} }
} }

@ -54,6 +54,9 @@ val Context.accentColor: Int
val Context.backgroundColor: Int val Context.backgroundColor: Int
get() = ThemeStore.backgroundColor(this) get() = ThemeStore.backgroundColor(this)
val Context.bottomBackground: Int
get() = ThemeStore.bottomBackground(this)
val Context.primaryTextColor: Int val Context.primaryTextColor: Int
get() = getPrimaryTextColor(isDarkTheme) get() = getPrimaryTextColor(isDarkTheme)
@ -78,6 +81,9 @@ val Fragment.accentColor: Int
val Fragment.backgroundColor: Int val Fragment.backgroundColor: Int
get() = ThemeStore.backgroundColor(requireContext()) get() = ThemeStore.backgroundColor(requireContext())
val Fragment.bottomBackground: Int
get() = ThemeStore.bottomBackground(requireContext())
val Fragment.primaryTextColor: Int val Fragment.primaryTextColor: Int
get() = requireContext().getPrimaryTextColor(isDarkTheme) get() = requireContext().getPrimaryTextColor(isDarkTheme)

@ -147,6 +147,11 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
return this return this
} }
override fun bottomBackground(color: Int): ThemeStore {
mEditor.putInt(ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND, color)
return this
}
override fun coloredStatusBar(colored: Boolean): ThemeStore { override fun coloredStatusBar(colored: Boolean): ThemeStore {
mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored) mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored)
return this return this
@ -279,6 +284,15 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
) )
} }
@CheckResult
@ColorInt
fun bottomBackground(context: Context): Int {
return prefs(context).getInt(
ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND,
ATHUtils.resolveColor(context, android.R.attr.colorBackground)
)
}
@CheckResult @CheckResult
fun coloredStatusBar(context: Context): Boolean { fun coloredStatusBar(context: Context): Boolean {
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true) return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true)

@ -78,8 +78,12 @@ internal interface ThemeStoreInterface {
fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore
// Background
fun backgroundColor(@ColorInt color: Int): ThemeStore fun backgroundColor(@ColorInt color: Int): ThemeStore
fun bottomBackground(@ColorInt color: Int): ThemeStore
// Toggle configurations // Toggle configurations
fun coloredStatusBar(colored: Boolean): ThemeStore fun coloredStatusBar(colored: Boolean): ThemeStore

@ -22,6 +22,7 @@ object ThemeStorePrefKeys {
const val KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse" const val KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse"
const val KEY_BACKGROUND_COLOR = "backgroundColor" const val KEY_BACKGROUND_COLOR = "backgroundColor"
const val KEY_BOTTOM_BACKGROUND = "bottomBackground"
const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar" const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"
const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar" const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"

@ -35,7 +35,7 @@ object Rss {
): Coroutine<String> { ): Coroutine<String> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
val body = AnalyzeUrl(rssArticle.link, baseUrl = rssArticle.origin) val body = AnalyzeUrl(rssArticle.link, baseUrl = rssArticle.origin)
.getResponseAwait() .getResponseAwait(rssArticle.origin)
.body .body
val analyzeRule = AnalyzeRule() val analyzeRule = AnalyzeRule()
analyzeRule.setContent( analyzeRule.setContent(

@ -37,7 +37,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl, baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
) )
val res = analyzeUrl.getResponseAwait() val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList( BookList.analyzeBookList(
res.body, res.body,
bookSource, bookSource,
@ -65,7 +65,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl, baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
) )
val res = analyzeUrl.getResponseAwait() val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList( BookList.analyzeBookList(
res.body, res.body,
bookSource, bookSource,
@ -96,7 +96,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl, baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
) )
analyzeUrl.getResponseAwait().body analyzeUrl.getResponseAwait(bookSource.bookSourceUrl).body
} }
BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl) BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl)
book book
@ -122,7 +122,7 @@ class WebBook(val bookSource: BookSource) {
ruleUrl = book.tocUrl, ruleUrl = book.tocUrl,
baseUrl = book.bookUrl, baseUrl = book.bookUrl,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait().body ).getResponseAwait(bookSource.bookSourceUrl).body
} }
BookChapterList.analyzeChapterList(this, book, body, bookSource, book.tocUrl) BookChapterList.analyzeChapterList(this, book, body, bookSource, book.tocUrl)
} }

@ -610,7 +610,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
override fun ajax(urlStr: String): String? { override fun ajax(urlStr: String): String? {
return try { return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, baseUrl, book) val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, baseUrl, book)
val call = analyzeUrl.getResponse() val call = analyzeUrl.getResponse(urlStr)
val response = call.execute() val response = call.execute()
response.body() response.body()
} catch (e: Exception) { } catch (e: Exception) {

@ -8,10 +8,7 @@ import io.legado.app.constant.Pattern.EXP_PATTERN
import io.legado.app.constant.Pattern.JS_PATTERN import io.legado.app.constant.Pattern.JS_PATTERN
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.BaseBook
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.http.AjaxWebView import io.legado.app.help.http.*
import io.legado.app.help.http.HttpHelper
import io.legado.app.help.http.RequestMethod
import io.legado.app.help.http.Res
import io.legado.app.help.http.api.HttpGetApi import io.legado.app.help.http.api.HttpGetApi
import io.legado.app.help.http.api.HttpPostApi import io.legado.app.help.http.api.HttpPostApi
import io.legado.app.utils.* import io.legado.app.utils.*
@ -243,7 +240,11 @@ class AnalyzeUrl(
} }
@Throws(Exception::class) @Throws(Exception::class)
fun getResponse(): Call<String> { fun getResponse(tag: String): Call<String> {
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
headerMap["Cookie"] = cookie
}
return when { return when {
method == RequestMethod.POST -> { method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) { if (fieldMap.isNotEmpty()) {
@ -267,7 +268,7 @@ class AnalyzeUrl(
@Throws(Exception::class) @Throws(Exception::class)
suspend fun getResponseAwait( suspend fun getResponseAwait(
tag: String? = null, tag: String,
jsStr: String? = null, jsStr: String? = null,
sourceRegex: String? = null sourceRegex: String? = null
): Res { ): Res {
@ -278,8 +279,13 @@ class AnalyzeUrl(
params.javaScript = jsStr params.javaScript = jsStr
params.sourceRegex = sourceRegex params.sourceRegex = sourceRegex
params.postData = bodyTxt?.toByteArray() params.postData = bodyTxt?.toByteArray()
params.tag = tag
return HttpHelper.ajax(params) return HttpHelper.ajax(params)
} }
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
headerMap["Cookie"] = cookie
}
val res = when { val res = when {
method == RequestMethod.POST -> { method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) { if (fieldMap.isNotEmpty()) {

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

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

@ -31,7 +31,7 @@ object BookChapterList {
App.INSTANCE.getString(R.string.error_get_web_content, baseUrl) App.INSTANCE.getString(R.string.error_get_web_content, baseUrl)
) )
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
val analyzeRule = AnalyzeRule(book)
val tocRule = bookSource.getTocRule() val tocRule = bookSource.getTocRule()
val nextUrlList = arrayListOf(baseUrl) val nextUrlList = arrayListOf(baseUrl)
var reverse = false var reverse = false
@ -45,8 +45,7 @@ object BookChapterList {
} }
var chapterData = var chapterData =
analyzeChapterList( analyzeChapterList(
analyzeRule.setContent(body, baseUrl), book, baseUrl, body, tocRule, listRule, bookSource, log = true
book.bookUrl, baseUrl, tocRule, listRule, bookSource, log = true
) )
chapterData.chapterList?.let { chapterData.chapterList?.let {
chapterList.addAll(it) chapterList.addAll(it)
@ -64,11 +63,10 @@ object BookChapterList {
ruleUrl = nextUrl, ruleUrl = nextUrl,
book = book, book = book,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait() ).getResponseAwait(bookSource.bookSourceUrl)
.body?.let { nextBody -> .body?.let { nextBody ->
chapterData = analyzeChapterList( chapterData = analyzeChapterList(
analyzeRule.setContent(nextBody, nextUrl), book, nextUrl, nextBody, tocRule, listRule, bookSource
book.bookUrl, nextUrl, tocRule, listRule, bookSource
) )
nextUrl = if (chapterData.nextUrl.isNotEmpty()) { nextUrl = if (chapterData.nextUrl.isNotEmpty()) {
chapterData.nextUrl[0] chapterData.nextUrl[0]
@ -95,21 +93,55 @@ object BookChapterList {
} }
Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}") Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}")
for (item in chapterDataList) { for (item in chapterDataList) {
downloadToc(
coroutineScope,
item,
book,
bookSource,
tocRule,
listRule,
chapterList,
chapterDataList,
{
block.resume(finish(book, chapterList, reverse))
}, {
block.cancel(it)
})
}
}
}
} catch (e: Exception) {
block.resumeWithException(e)
}
}
private fun downloadToc(
coroutineScope: CoroutineScope,
chapterData: ChapterData<String>,
book: Book,
bookSource: BookSource,
tocRule: TocRule,
listRule: String,
chapterList: ArrayList<BookChapter>,
chapterDataList: ArrayList<ChapterData<String>>,
onFinish: () -> Unit,
onError: (e: Throwable) -> Unit
) {
Coroutine.async(scope = coroutineScope) { Coroutine.async(scope = coroutineScope) {
val nextBody = AnalyzeUrl( val nextBody = AnalyzeUrl(
ruleUrl = item.nextUrl, ruleUrl = chapterData.nextUrl,
book = book, book = book,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait().body ).getResponseAwait(bookSource.bookSourceUrl).body
?: throw Exception("${chapterData.nextUrl}, 下载失败")
val nextChapterData = analyzeChapterList( val nextChapterData = analyzeChapterList(
analyzeRule.setContent(nextBody, item.nextUrl), book, chapterData.nextUrl, nextBody, tocRule, listRule, bookSource,
book.bookUrl, item.nextUrl, tocRule, listRule, bookSource,
false false
) )
synchronized(chapterDataList) { synchronized(chapterDataList) {
val isFinished = addChapterListIsFinish( val isFinished = addChapterListIsFinish(
chapterDataList, chapterDataList,
item, chapterData,
nextChapterData.chapterList nextChapterData.chapterList
) )
if (isFinished) { if (isFinished) {
@ -118,17 +150,11 @@ object BookChapterList {
chapterList.addAll(it) chapterList.addAll(it)
} }
} }
block.resume(finish(book, chapterList, reverse)) onFinish()
} }
} }
}.onError { }.onError {
block.resumeWithException(it) onError(it)
}
}
}
}
} catch (e: Exception) {
block.resumeWithException(e)
} }
} }
@ -173,15 +199,17 @@ object BookChapterList {
} }
private fun analyzeChapterList( private fun analyzeChapterList(
analyzeRule: AnalyzeRule, book: Book,
bookUrl: String,
baseUrl: String, baseUrl: String,
body: String,
tocRule: TocRule, tocRule: TocRule,
listRule: String, listRule: String,
bookSource: BookSource, bookSource: BookSource,
getNextUrl: Boolean = true, getNextUrl: Boolean = true,
log: Boolean = false log: Boolean = false
): ChapterData<List<String>> { ): ChapterData<List<String>> {
val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body, baseUrl)
val chapterList = arrayListOf<BookChapter>() val chapterList = arrayListOf<BookChapter>()
val nextUrlList = arrayListOf<String>() val nextUrlList = arrayListOf<String>()
val nextTocRule = tocRule.nextTocUrl val nextTocRule = tocRule.nextTocUrl
@ -212,7 +240,7 @@ object BookChapterList {
var isVip: String? var isVip: String?
for (item in elements) { for (item in elements) {
analyzeRule.setContent(item) analyzeRule.setContent(item)
val bookChapter = BookChapter(bookUrl = bookUrl) val bookChapter = BookChapter(bookUrl = book.bookUrl)
analyzeRule.chapter = bookChapter analyzeRule.chapter = bookChapter
bookChapter.title = analyzeRule.getString(nameRule) bookChapter.title = analyzeRule.getString(nameRule)
bookChapter.url = analyzeRule.getString(urlRule, true) bookChapter.url = analyzeRule.getString(urlRule, true)

@ -33,13 +33,11 @@ object BookContent {
) )
) )
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
val analyzeRule = AnalyzeRule(book)
val content = StringBuilder() val content = StringBuilder()
val nextUrlList = arrayListOf(baseUrl) val nextUrlList = arrayListOf(baseUrl)
val contentRule = bookSource.getContentRule() val contentRule = bookSource.getContentRule()
var contentData = analyzeContent( var contentData = analyzeContent(
analyzeRule.setContent(body, baseUrl), book, baseUrl, body, contentRule, bookChapter, bookSource
contentRule, bookChapter, bookSource
) )
content.append(contentData.content.replace(bookChapter.title, "")) content.append(contentData.content.replace(bookChapter.title, ""))
if (contentData.nextUrl.size == 1) { if (contentData.nextUrl.size == 1) {
@ -58,12 +56,11 @@ object BookContent {
ruleUrl = nextUrl, ruleUrl = nextUrl,
book = book, book = book,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait() ).getResponseAwait(bookSource.bookSourceUrl)
.body?.let { nextBody -> .body?.let { nextBody ->
contentData = contentData =
analyzeContent( analyzeContent(
analyzeRule.setContent(nextBody, nextUrl), book, nextUrl, nextBody, contentRule, bookChapter, bookSource, false
contentRule, bookChapter, bookSource, false
) )
nextUrl = nextUrl =
if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else "" if (contentData.nextUrl.isNotEmpty()) contentData.nextUrl[0] else ""
@ -83,12 +80,11 @@ object BookContent {
ruleUrl = item.nextUrl, ruleUrl = item.nextUrl,
book = book, book = book,
headerMapF = bookSource.getHeaderMap() headerMapF = bookSource.getHeaderMap()
).getResponseAwait() ).getResponseAwait(bookSource.bookSourceUrl)
.body?.let { .body?.let {
contentData = contentData =
analyzeContent( analyzeContent(
analyzeRule.setContent(it, item.nextUrl), book, item.nextUrl, it, contentRule, bookChapter, bookSource, false
contentRule, bookChapter, bookSource, false
) )
item.content = contentData.content item.content = contentData.content
} }
@ -108,12 +104,16 @@ object BookContent {
@Throws(Exception::class) @Throws(Exception::class)
private fun analyzeContent( private fun analyzeContent(
analyzeRule: AnalyzeRule, book: Book,
baseUrl: String,
body: String,
contentRule: ContentRule, contentRule: ContentRule,
chapter: BookChapter, chapter: BookChapter,
bookSource: BookSource, bookSource: BookSource,
printLog: Boolean = true printLog: Boolean = true
): ContentData<List<String>> { ): ContentData<List<String>> {
val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body, baseUrl)
val nextUrlList = arrayListOf<String>() val nextUrlList = arrayListOf<String>()
analyzeRule.chapter = chapter analyzeRule.chapter = chapter
val nextUrlRule = contentRule.nextContentUrl val nextUrlRule = contentRule.nextContentUrl

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

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

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

@ -1,4 +1,4 @@
package io.legado.app.ui.changecover package io.legado.app.ui.book.changecover
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -52,12 +52,12 @@ class ChangeCoverDialog : DialogFragment(),
callBack = activity as? CallBack callBack = activity as? CallBack
viewModel = getViewModel(ChangeCoverViewModel::class.java) viewModel = getViewModel(ChangeCoverViewModel::class.java)
viewModel.callBack = this viewModel.callBack = this
return inflater.inflate(R.layout.dialog_change_source, container) return inflater.inflate(R.layout.dialog_change_cover, container)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.searchStateData.observe(this, Observer { viewModel.searchStateData.observe(viewLifecycleOwner, Observer {
refresh_progress_bar.isAutoLoading = it refresh_progress_bar.isAutoLoading = it
}) })
tool_bar.setTitle(R.string.change_cover_source) tool_bar.setTitle(R.string.change_cover_source)

@ -1,4 +1,4 @@
package io.legado.app.ui.changecover package io.legado.app.ui.book.changecover
import android.app.Application import android.app.Application
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData

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

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

@ -1,4 +1,4 @@
package io.legado.app.ui.changesource package io.legado.app.ui.book.changesource
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
@ -16,9 +16,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.constant.Theme
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.putPrefBoolean import io.legado.app.utils.putPrefBoolean
@ -69,10 +71,8 @@ class ChangeSourceDialog : DialogFragment(),
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.initData(arguments) viewModel.initData(arguments)
showTitle() showTitle()
tool_bar.inflateMenu(R.menu.change_source)
tool_bar.setOnMenuItemClickListener(this)
initRecyclerView()
initMenu() initMenu()
initRecyclerView()
initSearchView() initSearchView()
initLiveData() initLiveData()
viewModel.loadDbSearchBook() viewModel.loadDbSearchBook()
@ -85,6 +85,9 @@ class ChangeSourceDialog : DialogFragment(),
} }
private fun initMenu() { private fun initMenu() {
tool_bar.inflateMenu(R.menu.change_source)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
tool_bar.setOnMenuItemClickListener(this)
tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked =
getPrefBoolean(PreferKey.changeSourceLoadToc) getPrefBoolean(PreferKey.changeSourceLoadToc)
} }
@ -110,16 +113,15 @@ class ChangeSourceDialog : DialogFragment(),
} }
private fun initSearchView() { private fun initSearchView() {
val searchView = tool_bar.menu.findItem(R.id.menu_search).actionView as SearchView search_view.setOnCloseListener {
searchView.setOnCloseListener {
showTitle() showTitle()
false false
} }
searchView.setOnSearchClickListener { search_view.setOnSearchClickListener {
tool_bar.title = "" tool_bar.title = ""
tool_bar.subtitle = "" tool_bar.subtitle = ""
} }
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean { override fun onQueryTextSubmit(query: String?): Boolean {
return false return false
} }

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

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

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

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

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

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

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

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

@ -1,4 +1,4 @@
package io.legado.app.ui.download package io.legado.app.ui.book.download
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
@ -16,17 +16,15 @@ import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.service.help.Download import io.legado.app.service.help.Download
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_download.* import kotlinx.android.synthetic.main.activity_download.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.anko.alert
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
@ -126,11 +124,7 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
override fun export(position: Int) { override fun export(position: Int) {
exportPosition = position exportPosition = position
alert { FilePicker.selectFolder(this, exportRequestCode) {
titleResource = R.string.select_folder
items(resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> {
val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey) val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey)
if (path.isNullOrEmpty()) { if (path.isNullOrEmpty()) {
toast("没有默认路径") toast("没有默认路径")
@ -138,32 +132,6 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
startExport(path) startExport(path)
} }
} }
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, exportRequestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
PermissionsCompat.Builder(this@DownloadActivity)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
supportFragmentManager,
exportRequestCode,
mode = FileChooserDialog.DIRECTORY
)
}
.request()
}
}
}
}.show()
} }
private fun startExport(path: String) { private fun startExport(path: String) {

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

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

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

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

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

@ -20,14 +20,17 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BlurTransformation import io.legado.app.help.BlurTransformation
import io.legado.app.help.ImageLoader import io.legado.app.help.ImageLoader
import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentDataHelp
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.ui.audio.AudioPlayActivity import io.legado.app.ui.audio.AudioPlayActivity
import io.legado.app.ui.book.changecover.ChangeCoverDialog
import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.book.group.GroupSelectDialog
import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.info.edit.BookInfoEditActivity
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.changecover.ChangeCoverDialog
import io.legado.app.ui.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.gone import io.legado.app.utils.gone
import io.legado.app.utils.visible import io.legado.app.utils.visible
@ -52,7 +55,8 @@ class BookInfoActivity :
get() = getViewModel(BookInfoViewModel::class.java) get() = getViewModel(BookInfoViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.background.alpha = 0 setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
tv_intro.movementMethod = ScrollingMovementMethod.getInstance() tv_intro.movementMethod = ScrollingMovementMethod.getInstance()
viewModel.bookData.observe(this, Observer { showBook(it) }) viewModel.bookData.observe(this, Observer { showBook(it) })
viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) viewModel.chapterListData.observe(this, Observer { upLoading(false, it) })
@ -186,9 +190,7 @@ class BookInfoActivity :
} }
tv_shelf.onClick { tv_shelf.onClick {
if (viewModel.inBookshelf) { if (viewModel.inBookshelf) {
viewModel.delBook { deleteBook()
upTvBookshelf()
}
} else { } else {
viewModel.addToBookshelf { viewModel.addToBookshelf {
upTvBookshelf() upTvBookshelf()
@ -223,6 +225,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
) {
okButton {
viewModel.delBook(true) {
finish()
}
}
noButton {
viewModel.delBook(false) {
finish()
}
}
}
} else {
viewModel.delBook {
upTvBookshelf()
}
}
}
}
private fun openChapterList() { private fun openChapterList() {
if (viewModel.chapterListData.value.isNullOrEmpty()) { if (viewModel.chapterListData.value.isNullOrEmpty()) {
toast(R.string.chapter_list_empty) toast(R.string.chapter_list_empty)
@ -320,7 +348,7 @@ class BookInfoActivity :
} }
} else { } else {
if (!viewModel.inBookshelf) { if (!viewModel.inBookshelf) {
viewModel.delBook(null) viewModel.delBook()
} }
} }
} }

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

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

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

@ -1,4 +1,4 @@
package io.legado.app.ui.importbook package io.legado.app.ui.book.local
import android.content.Context import android.content.Context
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile

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

@ -20,7 +20,9 @@ import io.legado.app.constant.PreferKey
import io.legado.app.constant.Status import io.legado.app.constant.Status
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.dialogs.okButton
@ -38,8 +40,9 @@ import io.legado.app.ui.book.read.page.PageView
import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.TextPageFactory
import io.legado.app.ui.book.read.page.delegate.PageDelegate import io.legado.app.ui.book.read.page.delegate.PageDelegate
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.changesource.ChangeSourceDialog import io.legado.app.ui.book.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity import io.legado.app.ui.book.chapterlist.ChapterListActivity
import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.replacerule.ReplaceRuleActivity
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
@ -63,6 +66,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
ChangeSourceDialog.CallBack, ChangeSourceDialog.CallBack,
ReadBook.CallBack, ReadBook.CallBack,
TocRegexDialog.CallBack, TocRegexDialog.CallBack,
ReplaceEditDialog.CallBack,
ColorPickerDialogListener { ColorPickerDialogListener {
private val requestCodeChapterList = 568 private val requestCodeChapterList = 568
private val requestCodeEditSource = 111 private val requestCodeEditSource = 111
@ -211,6 +215,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
R.id.menu_enable_replace -> ReadBook.book?.let { R.id.menu_enable_replace -> ReadBook.book?.let {
it.useReplaceRule = !it.useReplaceRule it.useReplaceRule = !it.useReplaceRule
menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule
onReplaceRuleSave()
} }
R.id.menu_book_info -> ReadBook.book?.let { R.id.menu_book_info -> ReadBook.book?.let {
startActivity<BookInfoActivity>(Pair("bookUrl", it.bookUrl)) startActivity<BookInfoActivity>(Pair("bookUrl", it.bookUrl))
@ -219,6 +224,12 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
supportFragmentManager, supportFragmentManager,
ReadBook.book?.tocUrl ReadBook.book?.tocUrl
) )
R.id.menu_login -> ReadBook.webBook?.bookSource?.let {
startActivity<SourceLogin>(
Pair("sourceUrl", it.bookSourceUrl),
Pair("loginUrl", it.loginUrl)
)
}
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
@ -260,18 +271,18 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
} }
} }
KeyEvent.KEYCODE_SPACE -> { KeyEvent.KEYCODE_SPACE -> {
page_view.moveToNextPage() page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true return true
} }
getPrefInt(PreferKey.prevKey) -> { getPrefInt(PreferKey.prevKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToPrevPage() page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV)
return true return true
} }
} }
getPrefInt(PreferKey.nextKey) -> { getPrefInt(PreferKey.nextKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToNextPage() page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true return true
} }
} }
@ -429,11 +440,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
if (getPrefBoolean("volumeKeyPageOnPlay") if (getPrefBoolean("volumeKeyPageOnPlay")
|| BaseReadAloudService.pause || BaseReadAloudService.pause
) { ) {
when (direction) { page_view.pageDelegate?.keyTurnPage(direction)
PageDelegate.Direction.PREV -> page_view.moveToPrevPage()
PageDelegate.Direction.NEXT -> page_view.moveToNextPage()
else -> return true
}
return true return true
} }
} }
@ -544,14 +551,33 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
} }
} }
/**
* 替换规则变化
*/
override fun onReplaceRuleSave() {
Coroutine.async {
BookHelp.upReplaceRules()
ReadBook.loadContent()
}
}
/**
* 显示阅读样式配置
*/
override fun showReadStyle() { override fun showReadStyle() {
ReadStyleDialog().show(supportFragmentManager, "readStyle") ReadStyleDialog().show(supportFragmentManager, "readStyle")
} }
/**
* 显示更多设置
*/
override fun showMoreSetting() { override fun showMoreSetting() {
MoreConfigDialog().show(supportFragmentManager, "moreConfig") MoreConfigDialog().show(supportFragmentManager, "moreConfig")
} }
/**
* 更新状态栏,导航栏
*/
override fun upSystemUiVisibility() { override fun upSystemUiVisibility() {
Help.upSystemUiVisibility(this, !read_menu.isVisible) Help.upSystemUiVisibility(this, !read_menu.isVisible)
} }
@ -610,7 +636,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
viewModel.openChapter(index) viewModel.openChapter(index)
} }
} }
requestCodeReplace -> ReadBook.loadContent() requestCodeReplace -> onReplaceRuleSave()
} }
} }
} }

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

@ -95,6 +95,7 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
onMenuItemSelected(it) onMenuItemSelected(it)
} }
} }
callBack.onMenuActionFinally()
} }
} }
} }
@ -121,7 +122,6 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
} }
} }
} }
callBack.onMenuActionFinally()
} }
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)

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

@ -11,6 +11,7 @@ import androidx.fragment.app.DialogFragment
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
@ -54,6 +55,7 @@ class ReadAloudDialog : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground)
initData() initData()
initOnChange() initOnChange()
initOnClick() initOnClick()

@ -11,10 +11,10 @@ import androidx.fragment.app.DialogFragment
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.Help import io.legado.app.ui.book.read.Help
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
@ -42,7 +42,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
dialog?.window?.let { dialog?.window?.let {
it.setBackgroundDrawableResource(R.color.background) it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 5, 0, 0) it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes val attr = it.attributes
attr.dimAmount = 0.0f attr.dimAmount = 0.0f
attr.gravity = Gravity.BOTTOM attr.gravity = Gravity.BOTTOM
@ -72,6 +72,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
} }
private fun initView() { private fun initView() {
root_view.setBackgroundColor(requireContext().bottomBackground)
dsb_text_size.valueFormat = { dsb_text_size.valueFormat = {
(it + 5).toString() (it + 5).toString()
} }
@ -118,7 +119,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack {
title = getString(R.string.text_indent), title = getString(R.string.text_indent),
items = resources.getStringArray(R.array.indent).toList() items = resources.getStringArray(R.array.indent).toList()
) { _, index -> ) { _, index ->
BookHelp.bodyIndentCount = index ReadBookConfig.bodyIndentCount = index
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
} }

@ -9,7 +9,6 @@ import io.legado.app.App
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextLine import io.legado.app.ui.book.read.page.entities.TextLine
@ -34,7 +33,6 @@ object ChapterProvider {
var typeface: Typeface = Typeface.SANS_SERIF var typeface: Typeface = Typeface.SANS_SERIF
var titlePaint = TextPaint() var titlePaint = TextPaint()
var contentPaint = TextPaint() var contentPaint = TextPaint()
private var bodyIndent = BookHelp.bodyIndent
init { init {
upStyle() upStyle()
@ -122,10 +120,12 @@ object ChapterProvider {
) )
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY textPages.last().height = durY
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
val textLine = TextLine(isTitle = true) val textLine = TextLine(isTitle = true)
if (durY < visibleHeight) { if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex)
+ contentPaint.fontMetrics.descent < visibleHeight
) {
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
} else { } else {
textPages.last().text = stringBuilder.toString() textPages.last().text = stringBuilder.toString()
stringBuilder.clear() stringBuilder.clear()
@ -140,7 +140,7 @@ object ChapterProvider {
(layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat() (layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex))).toFloat()
textLine.lineBase = (paddingTop + durY - textLine.lineBase = (paddingTop + durY -
(layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat() (layout.getLineBottom(lineIndex) - layout.getLineBaseline(lineIndex))).toFloat()
textLine.lineBottom = textLine.lineBase + titlePaint.fontMetrics.descent textLine.lineBottom = textLine.lineBase + contentPaint.fontMetrics.descent
val words = val words =
title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex)) title.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))
stringBuilder.append(words) stringBuilder.append(words)
@ -178,10 +178,12 @@ object ChapterProvider {
) )
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
textPages.last().height = durY textPages.last().height = durY
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
val textLine = TextLine() val textLine = TextLine()
if (durY < visibleHeight) { if (durY + layout.getLineBaseline(lineIndex) - layout.getLineTop(lineIndex)
+ contentPaint.fontMetrics.descent < visibleHeight
) {
textPages.last().textLines.add(textLine) textPages.last().textLines.add(textLine)
durY = durY + layout.getLineBottom(lineIndex) - layout.getLineTop(lineIndex)
} else { } else {
textPages.last().text = stringBuilder.toString() textPages.last().text = stringBuilder.toString()
stringBuilder.clear() stringBuilder.clear()
@ -227,6 +229,7 @@ object ChapterProvider {
desiredWidth: Float desiredWidth: Float
) { ) {
var x = 0f var x = 0f
val bodyIndent = ReadBookConfig.bodyIndent
val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length val icw = StaticLayout.getDesiredWidth(bodyIndent, textPaint) / bodyIndent.length
for (i in 0..bodyIndent.lastIndex) { for (i in 0..bodyIndent.lastIndex) {
val x1 = x + icw val x1 = x + icw
@ -348,8 +351,6 @@ object ChapterProvider {
titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat() titlePaint.textSize = (ReadBookConfig.textSize + 2).dp.toFloat()
contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat() contentPaint.textSize = ReadBookConfig.textSize.dp.toFloat()
bodyIndent = BookHelp.bodyIndent
upSize() upSize()
} }

@ -92,6 +92,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
if (!ReadBookConfig.isScroll) return if (!ReadBookConfig.isScroll) return
//滚动翻页 //滚动翻页
if (!pageFactory.hasNext()) return
val nextPage = relativePage(1) val nextPage = relativePage(1)
relativeOffset = relativeOffset(1) relativeOffset = relativeOffset(1)
nextPage.textLines.forEach { textLine -> nextPage.textLines.forEach { textLine ->
@ -108,6 +109,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
textLine.isReadAloud textLine.isReadAloud
) )
} }
if (!pageFactory.hasNextPlus()) return
relativeOffset = relativeOffset(2) relativeOffset = relativeOffset(2)
if (relativeOffset < ChapterProvider.visibleHeight) { if (relativeOffset < ChapterProvider.visibleHeight) {
relativePage(2).textLines.forEach { textLine -> relativePage(2).textLines.forEach { textLine ->
@ -163,25 +165,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
} }
pageOffset += offset pageOffset += offset
if (pageOffset > 0) { if (!pageFactory.hasPrev() && pageOffset > 0) {
if (!pageFactory.hasPrev()) {
pageOffset = 0f pageOffset = 0f
} else { } else if (!pageFactory.hasNext() && pageOffset < 0) {
pageOffset = 0f
} else if (pageOffset > 0) {
pageFactory.moveToPrev() pageFactory.moveToPrev()
textPage = pageFactory.currentPage textPage = pageFactory.currentPage
pageOffset -= textPage.height pageOffset -= textPage.height
upView?.invoke(textPage) upView?.invoke(textPage)
}
} else if (pageOffset < -textPage.height) { } else if (pageOffset < -textPage.height) {
if (!pageFactory.hasNext()) {
pageOffset = -textPage.height.toFloat()
} else {
pageOffset += textPage.height pageOffset += textPage.height
pageFactory.moveToNext() pageFactory.moveToNext()
textPage = pageFactory.currentPage textPage = pageFactory.currentPage
upView?.invoke(textPage) upView?.invoke(textPage)
} }
}
invalidate() invalidate()
} }

@ -22,4 +22,5 @@ abstract class PageFactory<DATA>(protected val dataSource: DataSource) {
abstract fun hasPrev(): Boolean abstract fun hasPrev(): Boolean
abstract fun hasNextPlus(): Boolean
} }

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

@ -13,6 +13,10 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
return hasNextChapter() || currentChapter?.isLastIndex(pageIndex) != true return hasNextChapter() || currentChapter?.isLastIndex(pageIndex) != true
} }
override fun hasNextPlus(): Boolean = with(dataSource) {
return hasNextChapter() || pageIndex < (currentChapter?.pageSize() ?: 1) - 2
}
override fun moveToFirst() { override fun moveToFirst() {
ReadBook.setPageIndex(0) ReadBook.setPageIndex(0)
} }
@ -68,6 +72,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource
?: TextPage(title = it.title).format() ?: TextPage(title = it.title).format()
} }
} }
if (!hasNextChapter()) {
return@with TextPage(text = "")
}
nextChapter?.let { nextChapter?.let {
return@with it.page(0)?.removePageAloudSpan() return@with it.page(0)?.removePageAloudSpan()
?: TextPage(title = it.title).format() ?: TextPage(title = it.title).format()

@ -1,14 +1,12 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
private val shadowDrawableR: GradientDrawable private val shadowDrawableR: GradientDrawable
private val bitmapMatrix = Matrix()
init { init {
val shadowColors = intArrayOf(0x66111111, 0x00000000) val shadowColors = intArrayOf(0x66111111, 0x00000000)
@ -18,35 +16,11 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT
} }
override fun onAnimStart() { override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) {
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() curPage.x = 0.toFloat()
if (!isCancel) { prevPage.x = -viewWidth.toFloat()
pageView.fillPage(mDirection) nextPage.x = 0.toFloat()
} super.setStartPoint(x, y, invalidate)
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
@ -57,13 +31,12 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
) return ) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
bitmap?.let { if (!isRunning) return
if (mDirection == Direction.PREV) { if (mDirection == Direction.PREV) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat()) prevPage.translationX = offsetX - viewWidth
canvas.drawBitmap(it, bitmapMatrix, null) addShadow(distanceX.toInt(), canvas)
} else if (mDirection == Direction.NEXT) { } else if (mDirection == Direction.NEXT) {
curPage.translationX = offsetX curPage.translationX = offsetX
}
addShadow(distanceX.toInt(), canvas) addShadow(distanceX.toInt(), canvas)
} }
} }
@ -77,4 +50,36 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
shadowDrawableR.draw(canvas) shadowDrawableR.draw(canvas)
} }
} }
override fun onAnimStop() {
curPage.x = 0.toFloat()
prevPage.x = -viewWidth.toFloat()
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)
}
} }

@ -8,6 +8,9 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
override fun onTouch(event: MotionEvent) { override fun onTouch(event: MotionEvent) {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> {
abort()
}
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
if (isTextSelected) { if (isTextSelected) {
selectText(event) selectText(event)
@ -32,7 +35,6 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
return return
} }
setDirection(Direction.PREV) setDirection(Direction.PREV)
setBitmap()
} else { } else {
//如果不存在表示没有下一页了 //如果不存在表示没有下一页了
if (!hasNext()) { if (!hasNext()) {
@ -40,7 +42,6 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
return return
} }
setDirection(Direction.NEXT) setDirection(Direction.NEXT)
setBitmap()
} }
} }
} }
@ -53,18 +54,19 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
} }
override fun nextPageByAnim() { override fun nextPageByAnim() {
super.nextPageByAnim()
if (!hasNext()) return if (!hasNext()) return
setDirection(Direction.NEXT) setDirection(Direction.NEXT)
setBitmap()
setTouchPoint(viewWidth.toFloat(), 0f) setTouchPoint(viewWidth.toFloat(), 0f)
onAnimStart() onAnimStart()
} }
override fun prevPageByAnim() { override fun prevPageByAnim() {
super.prevPageByAnim()
if (!hasPrev()) return if (!hasPrev()) return
setDirection(Direction.PREV) setDirection(Direction.PREV)
setBitmap()
setTouchPoint(0f, 0f) setTouchPoint(0f, 0f)
onAnimStart() onAnimStart()
} }
} }

@ -1,7 +1,6 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.RectF import android.graphics.RectF
import android.view.GestureDetector import android.view.GestureDetector
@ -15,7 +14,6 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.ContentView
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import io.legado.app.utils.screenshot
import kotlin.math.abs import kotlin.math.abs
abstract class PageDelegate(protected val pageView: PageView) : abstract class PageDelegate(protected val pageView: PageView) :
@ -40,8 +38,6 @@ abstract class PageDelegate(protected val pageView: PageView) :
protected val curPage: ContentView get() = pageView.curPage protected val curPage: ContentView get() = pageView.curPage
protected val prevPage: ContentView get() = pageView.prevPage protected val prevPage: ContentView get() = pageView.prevPage
protected var bitmap: Bitmap? = null
protected var viewWidth: Int = pageView.width protected var viewWidth: Int = pageView.width
protected var viewHeight: Int = pageView.height protected var viewHeight: Int = pageView.height
@ -126,11 +122,10 @@ abstract class PageDelegate(protected val pageView: PageView) :
} }
private fun stopScroll() { private fun stopScroll() {
isMoved = false
isRunning = false isRunning = false
isStarted = false isStarted = false
pageView.invalidate() pageView.invalidate()
bitmap?.recycle()
bitmap = null
} }
open fun setViewSize(width: Int, height: Int) { open fun setViewSize(width: Int, height: Int) {
@ -166,23 +161,28 @@ abstract class PageDelegate(protected val pageView: PageView) :
open fun onScroll() {}//移动contentView, slidePage open fun onScroll() {}//移动contentView, slidePage
abstract fun nextPageByAnim() open fun nextPageByAnim() {
abort()
}
abstract fun prevPageByAnim() open fun prevPageByAnim() {
abort()
}
open fun keyTurnPage(direction: Direction) {
if (isRunning) return
when (direction) {
Direction.NEXT -> nextPageByAnim()
Direction.PREV -> prevPageByAnim()
else -> return
}
}
@CallSuper @CallSuper
open fun setDirection(direction: Direction) { open fun setDirection(direction: Direction) {
mDirection = direction mDirection = direction
} }
open fun setBitmap() {
bitmap = when (mDirection) {
Direction.NEXT -> nextPage.screenshot()
Direction.PREV -> prevPage.screenshot()
else -> null
}
}
/** /**
* 触摸事件处理 * 触摸事件处理
*/ */
@ -321,7 +321,7 @@ abstract class PageDelegate(protected val pageView: PageView) :
} }
open fun onDestroy() { open fun onDestroy() {
bitmap?.recycle()
} }
enum class Direction { enum class Direction {

@ -2,6 +2,7 @@ package io.legado.app.ui.book.read.page.delegate
import android.view.MotionEvent import android.view.MotionEvent
import android.view.VelocityTracker import android.view.VelocityTracker
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
import kotlin.math.abs import kotlin.math.abs
@ -60,10 +61,12 @@ class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) {
} }
override fun nextPageByAnim() { override fun nextPageByAnim() {
super.nextPageByAnim()
startScroll(0, 0, 0, -ChapterProvider.visibleHeight)
} }
override fun prevPageByAnim() { override fun prevPageByAnim() {
super.prevPageByAnim()
startScroll(0, 0, 0, ChapterProvider.visibleHeight)
} }
} }

@ -118,6 +118,16 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat() mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat()
} }
override fun onDestroy() {
super.onDestroy()
prevBitmap?.recycle()
prevBitmap = null
curBitmap?.recycle()
curBitmap = null
nextBitmap?.recycle()
nextBitmap = null
}
override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) { override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) {
super.setStartPoint(x, y, invalidate) super.setStartPoint(x, y, invalidate)
calcCornerXY(x, y) calcCornerXY(x, y)
@ -126,13 +136,13 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) { override fun setTouchPoint(x: Float, y: Float, invalidate: Boolean) {
super.setTouchPoint(x, y, false) super.setTouchPoint(x, y, false)
//触摸y中间位置吧y变成屏幕高度 //触摸y中间位置吧y变成屏幕高度
if ((startY > viewHeight * 0.33 && startY < viewHeight * 0.66) if ((startY > viewHeight / 3.0 && startY < viewHeight * 2 / 3.0)
|| mDirection == Direction.PREV || mDirection == Direction.PREV
) { ) {
touchY = viewHeight.toFloat() touchY = viewHeight.toFloat()
} }
if (startY > viewHeight * 0.33 && startY < viewHeight / 2.0 if (startY > viewHeight / 3.0 && startY < viewHeight / 2.0
&& mDirection == Direction.NEXT && mDirection == Direction.NEXT
) { ) {
touchY = 1f touchY = 1f
@ -142,6 +152,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
override fun setDirection(direction: Direction) { override fun setDirection(direction: Direction) {
super.setDirection(direction) super.setDirection(direction)
setBitmap()
when (direction) { when (direction) {
Direction.PREV -> Direction.PREV ->
//上一页滑动不出现对角 //上一页滑动不出现对角
@ -158,7 +169,7 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
} }
} }
override fun setBitmap() { private fun setBitmap() {
when (mDirection) { when (mDirection) {
Direction.PREV -> { Direction.PREV -> {
prevBitmap = prevPage.screenshot() prevBitmap = prevPage.screenshot()
@ -209,12 +220,6 @@ class SimulationPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageVi
if (!isCancel) { if (!isCancel) {
pageView.fillPage(mDirection) pageView.fillPage(mDirection)
} }
prevBitmap?.recycle()
prevBitmap = null
nextBitmap?.recycle()
nextBitmap = null
curBitmap?.recycle()
curBitmap = null
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {

@ -1,12 +1,16 @@
package io.legado.app.ui.book.read.page.delegate package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Matrix
import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.PageView
class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) { class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
private val bitmapMatrix = Matrix() override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) {
curPage.x = 0f
prevPage.x = -viewWidth.toFloat()
nextPage.x = viewWidth.toFloat()
super.setStartPoint(x, y, invalidate)
}
override fun onAnimStart() { override fun onAnimStart() {
val distanceX: Float val distanceX: Float
@ -28,7 +32,6 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
viewWidth - (touchX - startX) viewWidth - (touchX - startX)
} }
} }
startScroll(touchX.toInt(), 0, distanceX.toInt(), 0) startScroll(touchX.toInt(), 0, distanceX.toInt(), 0)
} }
@ -39,26 +42,20 @@ class SlidePageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
|| (mDirection == Direction.PREV && offsetX < 0) || (mDirection == Direction.PREV && offsetX < 0)
) return ) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth if (!isRunning) return
bitmap?.let { if (mDirection == Direction.PREV) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat())
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 curPage.translationX = offsetX
prevPage.translationX = offsetX - viewWidth
} else if (mDirection == Direction.NEXT) {
curPage.translationX = offsetX
nextPage.translationX = offsetX + viewWidth
}
} }
override fun onAnimStop() { override fun onAnimStop() {
curPage.x = 0.toFloat() curPage.x = 0f
prevPage.x = -viewWidth.toFloat()
nextPage.x = viewWidth.toFloat()
if (!isCancel) { if (!isCancel) {
pageView.fillPage(mDirection) pageView.fillPage(mDirection)
} }

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

@ -83,7 +83,7 @@ class BookSourceDebugActivity :
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_scan -> { R.id.menu_scan -> {
startActivityForResult<QrCodeActivity>(qrRequestCode) startActivityForResult<QrCodeActivity>(qrRequestCode)
} }
} }

@ -24,6 +24,7 @@ import io.legado.app.data.entities.rule.*
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.book.source.debug.BookSourceDebugActivity
import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.widget.KeyboardToolPop import io.legado.app.ui.widget.KeyboardToolPop
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
@ -67,22 +68,20 @@ class BookSourceEditActivity :
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_save -> { R.id.menu_save -> getSource().let { source ->
val source = getSource()
if (checkSource(source)) { if (checkSource(source)) {
viewModel.save(source) { setResult(Activity.RESULT_OK); finish() } viewModel.save(source) { setResult(Activity.RESULT_OK); finish() }
} }
} }
R.id.menu_debug_source -> { R.id.menu_debug_source -> getSource().let { source ->
val source = getSource()
if (checkSource(source)) { if (checkSource(source)) {
viewModel.save(source) { viewModel.save(source) {
startActivity<BookSourceDebugActivity>(Pair("key", source.bookSourceUrl)) startActivity<BookSourceDebugActivity>(Pair("key", source.bookSourceUrl))
} }
} }
} }
R.id.menu_copy_source -> { R.id.menu_copy_source -> getSource().let { source ->
GSON.toJson(getSource())?.let { sourceStr -> GSON.toJson(source)?.let { sourceStr ->
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr)) clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr))
} }
@ -101,6 +100,18 @@ class BookSourceEditActivity :
toast(R.string.can_not_open) toast(R.string.can_not_open)
} }
} }
R.id.menu_login -> getSource().let {
if (checkSource(it)) {
if (it.loginUrl.isNullOrEmpty()) {
toast(R.string.source_no_login)
} else {
startActivity<SourceLogin>(
Pair("sourceUrl", it.bookSourceUrl),
Pair("loginUrl", it.loginUrl)
)
}
}
}
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -20,14 +21,13 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.* import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.service.help.CheckSource import io.legado.app.service.help.CheckSource
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
@ -39,7 +39,7 @@ import kotlinx.android.synthetic.main.view_search.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult import org.jetbrains.anko.startActivityForResult
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.FileNotFoundException import java.io.File
class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity_book_source), class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity_book_source),
PopupMenu.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener,
@ -50,7 +50,8 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
get() = getViewModel(BookSourceViewModel::class.java) get() = getViewModel(BookSourceViewModel::class.java)
private val importRecordKey = "bookSourceRecordKey" private val importRecordKey = "bookSourceRecordKey"
private val qrRequestCode = 101 private val qrRequestCode = 101
private val importSource = 132 private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: BookSourceAdapter private lateinit var adapter: BookSourceAdapter
private var bookSourceLiveDate: LiveData<List<BookSource>>? = null private var bookSourceLiveDate: LiveData<List<BookSource>>? = null
private var groups = linkedSetOf<String>() private var groups = linkedSetOf<String>()
@ -82,7 +83,8 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode) R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_group_manage -> R.id.menu_group_manage ->
GroupManageDialog().show(supportFragmentManager, "groupManage") GroupManageDialog().show(supportFragmentManager, "groupManage")
R.id.menu_import_source_local -> selectFileSys() R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json"))
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
} }
if (item.groupId == R.id.source_group) { if (item.groupId == R.id.source_group) {
@ -187,7 +189,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelection()) R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelection())
R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelection()) R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelection())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
R.id.menu_check_source -> CheckSource.start(this, adapter.getSelection()) R.id.menu_check_source -> CheckSource.start(this, adapter.getSelection())
} }
return true return true
@ -235,47 +237,10 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
}.show().applyTint() }.show().applyTint()
} }
private fun selectFileSys() {
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFile()
}
.request()
}
}
private fun selectFile() {
FileChooserDialog.show(
supportFragmentManager, importSource,
allowExtensions = arrayOf("txt", "json")
)
}
override fun upCountView() { override fun upCountView() {
select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount()) select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount())
} }
override fun onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == importSource) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
}
}
}
override fun onQueryTextChange(newText: String?): Boolean { override fun onQueryTextChange(newText: String?): Boolean {
newText?.let { newText?.let {
initLiveDataBookSource(it) initLiveDataBookSource(it)
@ -307,6 +272,21 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
viewModel.topSource(bookSource) viewModel.topSource(bookSource)
} }
override fun onFilePicked(requestCode: Int, currentPath: String) {
when (requestCode) {
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
@ -318,7 +298,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
} }
} }
} }
importSource -> if (resultCode == Activity.RESULT_OK) { importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
try { try {
uri.readText(this)?.let { uri.readText(this)?.let {
@ -328,22 +308,24 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
} catch (e: FileNotFoundException) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFileSys()
}
.request()
} catch (e: Exception) { } catch (e: Exception) {
toast(e.localizedMessage ?: "ERROR") toast(e.localizedMessage ?: "ERROR")
} }
} }
} }
exportRequestCode -> {
data?.data?.let { uri ->
if (uri.toString().isContentPath()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
} }
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.book.source.manage
import android.app.Application import android.app.Application
import android.text.TextUtils import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
@ -89,12 +90,24 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun exportSelection(sources: LinkedHashSet<BookSource>) { fun exportSelection(sources: LinkedHashSet<BookSource>, file: File) {
execute { execute {
val json = GSON.toJson(sources) val json = GSON.toJson(sources)
val file = FileUtils.createFileIfNotExist(file, "exportBookSource.json")
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportBookSource.json") .writeText(json)
file.writeText(json) }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun exportSelection(sources: LinkedHashSet<BookSource>, doc: DocumentFile) {
execute {
val json = GSON.toJson(sources)
doc.findFile("exportBookSource.json")?.delete()
doc.createFile("", "exportBookSource.json")
?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${Backup.exportPath}")
}.onError { }.onError {

@ -3,7 +3,6 @@ package io.legado.app.ui.config
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import io.legado.app.App import io.legado.app.App
@ -17,13 +16,13 @@ import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.ImportOldData import io.legado.app.help.storage.ImportOldData
import io.legado.app.help.storage.Restore import io.legado.app.help.storage.Restore
import io.legado.app.help.storage.WebDavHelp import io.legado.app.help.storage.WebDavHelp
import io.legado.app.lib.dialogs.alert import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.isContentPath import io.legado.app.utils.isContentPath
import io.legado.app.utils.toast import io.legado.app.utils.toast
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File
object BackupRestoreUi { object BackupRestoreUi {
private const val selectFolderRequestCode = 21 private const val selectFolderRequestCode = 21
@ -79,31 +78,7 @@ object BackupRestoreUi {
} }
fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) { fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) {
fragment.alert { FilePicker.selectFolder(fragment, requestCode)
titleResource = R.string.select_folder
items(fragment.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> backupUsePermission(fragment, requestCode = requestCode)
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
FileChooserDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
} }
fun restore(fragment: Fragment) { fun restore(fragment: Fragment) {
@ -120,13 +95,13 @@ object BackupRestoreUi {
Restore.restore(fragment.requireContext(), backupPath) Restore.restore(fragment.requireContext(), backupPath)
fragment.toast(R.string.restore_success) fragment.toast(R.string.restore_success)
} else { } else {
selectRestoreFolder(fragment) selectBackupFolder(fragment, restoreSelectRequestCode)
} }
} else { } else {
restoreUsePermission(fragment, backupPath) restoreUsePermission(fragment, backupPath)
} }
} else { } else {
selectRestoreFolder(fragment) selectBackupFolder(fragment, restoreSelectRequestCode)
} }
} }
} }
@ -147,79 +122,8 @@ object BackupRestoreUi {
.request() .request()
} }
private fun selectRestoreFolder(fragment: Fragment) {
fragment.alert {
titleResource = R.string.select_folder
items(fragment.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> restoreUsePermission(fragment)
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragment.startActivityForResult(intent, restoreSelectRequestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
FileChooserDialog.show(
fragment.childFragmentManager,
restoreSelectRequestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
}
fun importOldData(fragment: Fragment) { fun importOldData(fragment: Fragment) {
fragment.alert { FilePicker.selectFolder(fragment, oldDataRequestCode)
titleResource = R.string.select_folder
items(fragment.resources.getStringArray(R.array.select_folder).toList()) { _, index ->
when (index) {
0 -> importOldUsePermission(fragment)
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
fragment.startActivityForResult(intent, oldDataRequestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
fragment.childFragmentManager,
oldDataRequestCode,
mode = FileChooserDialog.DIRECTORY
)
}
.request()
}
}
}
}.show()
}
private fun importOldUsePermission(fragment: Fragment) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
fragment.toast(R.string.a10_permission_toast)
}
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
ImportOldData.import(fragment.requireContext())
}
.request()
} }
fun onFilePicked(requestCode: Int, currentPath: String) { fun onFilePicked(requestCode: Int, currentPath: String) {
@ -243,6 +147,9 @@ object BackupRestoreUi {
selectFolderRequestCode -> { selectFolderRequestCode -> {
AppConfig.backupPath = currentPath AppConfig.backupPath = currentPath
} }
oldDataRequestCode -> {
ImportOldData.import(App.INSTANCE, File(currentPath))
}
} }
} }

@ -16,12 +16,10 @@ import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.receiver.SharedReceiverActivity import io.legado.app.receiver.SharedReceiverActivity
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.widget.number.NumberPickerDialog import io.legado.app.ui.widget.number.NumberPickerDialog
import io.legado.app.utils.* import io.legado.app.utils.*
@ -124,38 +122,9 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
} }
private fun selectDownloadPath() { private fun selectDownloadPath() {
alert { FilePicker.selectFolder(this, requestCodeDownloadPath) {
titleResource = R.string.select_folder
items(resources.getStringArray(R.array.select_folder).toList()) { _, i ->
when (i) {
0 -> {
removePref(PreferKey.downloadPath) removePref(PreferKey.downloadPath)
} }
1 -> {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, requestCodeDownloadPath)
} catch (e: Exception) {
e.printStackTrace()
toast(e.localizedMessage ?: "ERROR")
}
}
2 -> PermissionsCompat.Builder(this@OtherConfigFragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
childFragmentManager,
requestCodeDownloadPath,
mode = FileChooserDialog.DIRECTORY,
initPath = BookHelp.downloadPath
)
}
.request()
}
}
}.show()
} }
override fun onFilePicked(requestCode: Int, currentPath: String) { override fun onFilePicked(requestCode: Int, currentPath: String) {

@ -18,7 +18,6 @@ import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ColorUtils import io.legado.app.lib.theme.ColorUtils
import io.legado.app.utils.* import io.legado.app.utils.*
import org.jetbrains.anko.defaultSharedPreferences
class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -27,7 +26,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_theme) addPreferencesFromResource(R.xml.pref_config_theme)
onSharedPreferenceChanged(requireContext().defaultSharedPreferences, "defaultTheme")
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -49,10 +47,11 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
sharedPreferences ?: return sharedPreferences ?: return
when (key) { when (key) {
PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key)) PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key))
"transparentStatusBar" -> { "transparentStatusBar" -> recreateActivities()
recreateActivities() "colorPrimary",
} "colorAccent",
"colorPrimary", "colorAccent", "colorBackground" -> { "colorBackground",
"colorBottomBackground" -> {
if (backgroundIsDark(sharedPreferences)) { if (backgroundIsDark(sharedPreferences)) {
alert { alert {
title = "白天背景太暗" title = "白天背景太暗"
@ -73,7 +72,10 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(false) upTheme(false)
} }
} }
"colorPrimaryNight", "colorAccentNight", "colorBackgroundNight" -> { "colorPrimaryNight",
"colorAccentNight",
"colorBackgroundNight",
"colorBottomBackgroundNight" -> {
if (backgroundIsLight(sharedPreferences)) { if (backgroundIsLight(sharedPreferences)) {
alert { alert {
title = "夜间背景太亮" title = "夜间背景太亮"
@ -94,7 +96,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(true) upTheme(true)
} }
} }
"defaultTheme" -> findPreference<Preference>(key)?.summary = items[getPrefInt(key)]
} }
} }
@ -141,7 +142,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
AppConfig.isNightTheme = true AppConfig.isNightTheme = true
} }
} }
putPrefInt("defaultTheme", which)
App.INSTANCE.applyDayNight() App.INSTANCE.applyDayNight()
recreateActivities() recreateActivities()
} }

@ -42,7 +42,7 @@ class FileChooserDialog : DialogFragment(),
isShowHomeDir: Boolean = false, isShowHomeDir: Boolean = false,
isShowUpDir: Boolean = true, isShowUpDir: Boolean = true,
isShowHideDir: Boolean = false, isShowHideDir: Boolean = false,
allowExtensions: Array<String?>? = null, allowExtensions: Array<String>? = null,
menus: Array<String>? = null menus: Array<String>? = null
) { ) {
FileChooserDialog().apply { FileChooserDialog().apply {
@ -61,7 +61,7 @@ class FileChooserDialog : DialogFragment(),
} }
} }
override var allowExtensions: Array<String?>? = null override var allowExtensions: Array<String>? = null
override val isOnlyListDir: Boolean override val isOnlyListDir: Boolean
get() = mode == DIRECTORY get() = mode == DIRECTORY
override var isShowHomeDir: Boolean = false override var isShowHomeDir: Boolean = false

@ -0,0 +1,191 @@
package io.legado.app.ui.filechooser
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.alert
import io.legado.app.utils.toast
import org.jetbrains.anko.toast
@Suppress("unused")
object FilePicker {
fun selectFolder(activity: AppCompatActivity, requestCode: Int, default: (() -> Unit)? = null) {
activity.alert(titleResource = R.string.select_folder) {
val selectList =
activity.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectDirIntent()
activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
activity.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(activity) {
FileChooserDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
}
fun selectFolder(fragment: Fragment, requestCode: Int, default: (() -> Unit)? = null) {
fragment.requireContext()
.alert(titleResource = R.string.select_folder) {
val selectList =
fragment.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectDirIntent()
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(fragment) {
FileChooserDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FileChooserDialog.DIRECTORY
)
}
}
}
}.show()
}
fun selectFile(
activity: BaseActivity,
requestCode: Int,
type: String,
allowExtensions: Array<String>?,
default: (() -> Unit)? = null
) {
activity.alert(titleResource = R.string.select_file) {
val selectList =
activity.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectFileIntent()
intent.type = type//设置类型
activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
activity.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(activity) {
FileChooserDialog.show(
activity.supportFragmentManager,
requestCode,
mode = FileChooserDialog.FILE,
allowExtensions = allowExtensions
)
}
}
}
}.show()
}
fun selectFile(
fragment: Fragment,
requestCode: Int,
type: String,
allowExtensions: Array<String>,
default: (() -> Unit)? = null
) {
fragment.requireContext()
.alert(titleResource = R.string.select_file) {
val selectList =
fragment.resources.getStringArray(R.array.select_folder).toMutableList()
default ?: let {
selectList.removeAt(0)
}
items(selectList) { _, index ->
when (if (default == null) index + 1 else index) {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectFileIntent()
intent.type = type//设置类型
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
fragment.toast(e.localizedMessage ?: "ERROR")
}
}
2 -> checkPermissions(fragment) {
FileChooserDialog.show(
fragment.childFragmentManager,
requestCode,
mode = FileChooserDialog.FILE,
allowExtensions = allowExtensions
)
}
}
}
}.show()
}
private fun getSelectFileIntent(): Intent {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent
}
private fun getSelectDirIntent(): Intent {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent
}
private fun checkPermissions(fragment: Fragment, success: (() -> Unit)? = null) {
PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
}
.request()
}
private fun checkPermissions(activity: AppCompatActivity, success: (() -> Unit)? = null) {
PermissionsCompat.Builder(activity)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
success?.invoke()
}
.request()
}
}

@ -6,9 +6,9 @@ import android.graphics.drawable.Drawable
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.entity.FileItem import io.legado.app.ui.filechooser.entity.FileItem
import io.legado.app.ui.filechooser.utils.ConvertUtils import io.legado.app.ui.filechooser.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FilePickerIcon
import io.legado.app.ui.filechooser.utils.FileUtils import io.legado.app.ui.filechooser.utils.FileUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.* import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
@ -122,7 +122,7 @@ class FileAdapter(context: Context, val callBack: CallBack) :
interface CallBack { interface CallBack {
fun onFileClick(position: Int) fun onFileClick(position: Int)
//允许的扩展名 //允许的扩展名
var allowExtensions: Array<String?>? var allowExtensions: Array<String>?
/** /**
* 是否仅仅读取目录 * 是否仅仅读取目录
*/ */

@ -5,8 +5,8 @@ import android.os.Environment
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.ui.filechooser.FilePickerIcon
import io.legado.app.ui.filechooser.utils.ConvertUtils import io.legado.app.ui.filechooser.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FilePickerIcon
import kotlinx.android.synthetic.main.item_path_filepicker.view.* import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import java.util.* import java.util.*

@ -1,4 +1,4 @@
package io.legado.app.ui.filechooser; package io.legado.app.ui.filechooser.utils;
/** /**
* Generated by https://github.com/gzu-liyujiang/Image2ByteVar * Generated by https://github.com/gzu-liyujiang/Image2ByteVar

@ -60,7 +60,7 @@ object FileUtils {
@JvmOverloads @JvmOverloads
fun listDirs( fun listDirs(
startDirPath: String, startDirPath: String,
excludeDirs: Array<String?>? = null, @SortType sortType: Int = BY_NAME_ASC excludeDirs: Array<String>? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> { ): Array<File?> {
var excludeDirs1 = excludeDirs var excludeDirs1 = excludeDirs
val dirList = ArrayList<File>() val dirList = ArrayList<File>()
@ -76,7 +76,7 @@ object FileUtils {
}) })
?: return arrayOfNulls(0) ?: return arrayOfNulls(0)
if (excludeDirs1 == null) { if (excludeDirs1 == null) {
excludeDirs1 = arrayOfNulls(0) excludeDirs1 = arrayOf()
} }
for (dir in dirs) { for (dir in dirs) {
val file = dir.absoluteFile val file = dir.absoluteFile
@ -115,7 +115,7 @@ object FileUtils {
@JvmOverloads @JvmOverloads
fun listDirsAndFiles( fun listDirsAndFiles(
startDirPath: String, startDirPath: String,
allowExtensions: Array<String?>? = null allowExtensions: Array<String>? = null
): Array<File?>? { ): Array<File?>? {
val dirs: Array<File?>? val dirs: Array<File?>?
val files: Array<File?>? = if (allowExtensions == null) { val files: Array<File?>? = if (allowExtensions == null) {
@ -189,12 +189,13 @@ object FileUtils {
/** /**
* 列出指定目录下的所有文件 * 列出指定目录下的所有文件
*/ */
fun listFiles(startDirPath: String, allowExtensions: Array<String?>): Array<File?>? { fun listFiles(startDirPath: String, allowExtensions: Array<String>?): Array<File?>? {
val file = File(startDirPath) val file = File(startDirPath)
return file.listFiles { _, name -> return file.listFiles { _, name ->
//返回当前目录所有以某些扩展名结尾的文件 //返回当前目录所有以某些扩展名结尾的文件
val extension = getExtension(name) val extension = getExtension(name)
allowExtensions.contentDeepToString().contains(extension) allowExtensions?.contentDeepToString()?.contains(extension) == true
|| allowExtensions == null
} }
} }
@ -202,7 +203,10 @@ object FileUtils {
* 列出指定目录下的所有文件 * 列出指定目录下的所有文件
*/ */
fun listFiles(startDirPath: String, allowExtension: String?): Array<File?>? { fun listFiles(startDirPath: String, allowExtension: String?): Array<File?>? {
return listFiles(startDirPath, arrayOf(allowExtension)) return if (allowExtension == null)
listFiles(startDirPath, allowExtension = null)
else
listFiles(startDirPath, arrayOf(allowExtension))
} }
/** /**

@ -1,14 +1,79 @@
package io.legado.app.ui.login package io.legado.app.ui.login
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.help.http.CookieStore
import io.legado.app.utils.snackbar
import kotlinx.android.synthetic.main.activity_source_login.*
class SourceLogin : BaseActivity(R.layout.activity_source_login) { class SourceLogin : BaseActivity(R.layout.activity_source_login) {
var sourceUrl: String? = null
var loginUrl: String? = null
var checking = false
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
sourceUrl = intent.getStringExtra("sourceUrl")
loginUrl = intent.getStringExtra("loginUrl")
title = getString(R.string.login_source, sourceUrl)
initWebView()
}
@SuppressLint("SetJavaScriptEnabled")
private fun initWebView() {
val settings = web_view.settings
settings.setSupportZoom(true)
settings.builtInZoomControls = true
settings.javaScriptEnabled = true
val cookieManager = CookieManager.getInstance()
web_view.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
val cookie = cookieManager.getCookie(url)
sourceUrl?.let {
CookieStore.setCookie(it, cookie)
}
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
val cookie = cookieManager.getCookie(url)
sourceUrl?.let {
CookieStore.setCookie(it, cookie)
}
if (checking) {
finish()
}
super.onPageFinished(view, url)
}
}
web_view.loadUrl(loginUrl)
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.source_login, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_success -> {
if (!checking) {
checking = true
title_bar.snackbar(R.string.check_host_cookie)
web_view.loadUrl(sourceUrl)
}
}
}
return super.onCompatOptionsItemSelected(item)
} }
} }

@ -7,7 +7,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager import androidx.viewpager.widget.ViewPager
import com.github.houbb.opencc4j.util.ZhConverterUtil
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import io.legado.app.App import io.legado.app.App
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
@ -16,7 +15,6 @@ import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
@ -53,21 +51,12 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
override fun onPostCreate(savedInstanceState: Bundle?) { override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState) super.onPostCreate(savedInstanceState)
upVersion() upVersion()
//初始化简繁转换引擎
when (AppConfig.chineseConverterType) {
1 -> Coroutine.async { ZhConverterUtil.toSimple("初始化") }
2 -> Coroutine.async { ZhConverterUtil.toTraditional("初始化") }
}
//自动更新书籍 //自动更新书籍
if (AppConfig.autoRefreshBook) { if (AppConfig.autoRefreshBook) {
view_pager_main.postDelayed({ view_pager_main.postDelayed({
viewModel.upChapterList() viewModel.upChapterList()
}, 1000) }, 1000)
} }
//清楚过期数据
view_pager_main.postDelayed({
viewModel.clearExpiredData()
}, 3000)
} }
override fun onNavigationItemSelected(item: MenuItem): Boolean { override fun onNavigationItemSelected(item: MenuItem): Boolean {

@ -13,7 +13,6 @@ import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
class MainViewModel(application: Application) : BaseViewModel(application) { class MainViewModel(application: Application) : BaseViewModel(application) {
val updateList = hashSetOf<String>() val updateList = hashSetOf<String>()
@ -54,13 +53,6 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
} }
} }
fun clearExpiredData() {
execute {
App.db.searchBookDao()
.clearExpired(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
}
}
fun initRss() { fun initRss() {
execute { execute {
val url = "https://gitee.com/alanskycn/yuedu/raw/master/JS/RSS/rssSource" val url = "https://gitee.com/alanskycn/yuedu/raw/master/JS/RSS/rssSource"

@ -27,8 +27,8 @@ import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.book.arrange.ArrangeBookActivity import io.legado.app.ui.book.arrange.ArrangeBookActivity
import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.group.GroupManageDialog
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.download.DownloadActivity import io.legado.app.ui.book.download.DownloadActivity
import io.legado.app.ui.importbook.ImportBookActivity import io.legado.app.ui.book.local.ImportBookActivity
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.* import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.*

@ -111,7 +111,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books),
else -> list.sortedByDescending { it.durChapterTime } else -> list.sortedByDescending { it.durChapterTime }
} }
val diffResult = DiffUtil val diffResult = DiffUtil
.calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books)) .calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), books))
booksAdapter.setItems(books, diffResult) booksAdapter.setItems(books, diffResult)
}) })
} }

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

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -19,13 +20,14 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.BookHelp
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.* import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.replacerule.edit.ReplaceEditDialog import io.legado.app.ui.replacerule.edit.ReplaceEditDialog
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
@ -36,7 +38,6 @@ import kotlinx.android.synthetic.main.dialog_edit_text.view.*
import kotlinx.android.synthetic.main.view_search.* import kotlinx.android.synthetic.main.view_search.*
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File import java.io.File
import java.io.FileNotFoundException
class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activity_replace_rule), class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activity_replace_rule),
@ -47,7 +48,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
override val viewModel: ReplaceRuleViewModel override val viewModel: ReplaceRuleViewModel
get() = getViewModel(ReplaceRuleViewModel::class.java) get() = getViewModel(ReplaceRuleViewModel::class.java)
private val importRecordKey = "replaceRuleRecordKey" private val importRecordKey = "replaceRuleRecordKey"
private val importSource = 132 private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: ReplaceRuleAdapter private lateinit var adapter: ReplaceRuleAdapter
private var groups = hashSetOf<String>() private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null private var groupMenu: SubMenu? = null
@ -139,8 +141,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
} }
private fun observeReplaceRuleData(key: String? = null) { private fun observeReplaceRuleData(key: String? = null) {
replaceRuleLiveData?.removeObservers(this)
dataInit = false dataInit = false
replaceRuleLiveData?.removeObservers(this)
replaceRuleLiveData = if (key.isNullOrEmpty()) { replaceRuleLiveData = if (key.isNullOrEmpty()) {
App.db.replaceRuleDao().liveDataAll() App.db.replaceRuleDao().liveDataAll()
} else { } else {
@ -177,7 +179,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection())
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_local -> selectFileSys() R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json"))
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
@ -186,7 +189,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
when (item?.itemId) { when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection()) R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
} }
return false return false
} }
@ -233,56 +236,34 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
}.show().applyTint() }.show().applyTint()
} }
private fun selectFileSys() { override fun onQueryTextChange(newText: String?): Boolean {
try { observeReplaceRuleData("%$newText%")
val intent = Intent(Intent.ACTION_GET_CONTENT) return false
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFile()
}
.request()
}
} }
private fun selectFile() { override fun onQueryTextSubmit(query: String?): Boolean {
FileChooserDialog.show( return false
supportFragmentManager, importSource,
allowExtensions = arrayOf("txt", "json")
)
} }
override fun onFilePicked(requestCode: Int, currentPath: String) { override fun onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == importSource) { when (requestCode) {
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSource(File(currentPath).readText()) { msg -> viewModel.importSource(File(currentPath).readText()) { msg ->
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
} }
override fun onQueryTextChange(newText: String?): Boolean {
observeReplaceRuleData("%$newText%")
return false
}
override fun onQueryTextSubmit(query: String?): Boolean {
return false
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) { importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
try { try {
uri.readText(this)?.let { uri.readText(this)?.let {
@ -292,46 +273,58 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
} catch (e: FileNotFoundException) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFileSys()
}
.request()
} catch (e: Exception) { } catch (e: Exception) {
toast(e.localizedMessage ?: "ERROR") toast(e.localizedMessage ?: "ERROR")
} }
} }
} }
exportRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.toString().isContentPath()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
} }
} }
override fun onDestroy() {
super.onDestroy()
Coroutine.async { BookHelp.upReplaceRules() }
}
override fun upCountView() { override fun upCountView() {
select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount()) select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount())
} }
override fun update(vararg rule: ReplaceRule) { override fun update(vararg rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.update(*rule) viewModel.update(*rule)
} }
override fun delete(rule: ReplaceRule) { override fun delete(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.delete(rule) viewModel.delete(rule)
} }
override fun edit(rule: ReplaceRule) { override fun edit(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
ReplaceEditDialog.show(supportFragmentManager, rule.id) ReplaceEditDialog.show(supportFragmentManager, rule.id)
} }
override fun toTop(rule: ReplaceRule) { override fun toTop(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.toTop(rule) viewModel.toTop(rule)
} }
override fun upOrder() { override fun upOrder() {
setResult(Activity.RESULT_OK)
viewModel.upOrder() viewModel.upOrder()
} }
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.replacerule
import android.app.Application import android.app.Application
import android.text.TextUtils import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
@ -9,10 +10,7 @@ import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.ImportOldData import io.legado.app.help.storage.ImportOldData
import io.legado.app.utils.FileUtils import io.legado.app.utils.*
import io.legado.app.utils.GSON
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.splitNotBlank
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File import java.io.File
@ -89,12 +87,24 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
} }
} }
fun exportSelection(rules: LinkedHashSet<ReplaceRule>) { fun exportSelection(sources: LinkedHashSet<ReplaceRule>, file: File) {
execute { execute {
val json = GSON.toJson(rules) val json = GSON.toJson(sources)
val file = FileUtils.createFileIfNotExist(file, "exportReplaceRule.json")
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportReplaceRule.json") .writeText(json)
file.writeText(json) }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun exportSelection(sources: LinkedHashSet<ReplaceRule>, doc: DocumentFile) {
execute {
val json = GSON.toJson(sources)
doc.findFile("exportReplaceRule.json")?.delete()
doc.createFile("", "exportReplaceRule.json")
?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${Backup.exportPath}")
}.onError { }.onError {

@ -23,11 +23,17 @@ class ReplaceEditDialog : DialogFragment(),
companion object { companion object {
fun show(fragmentManager: FragmentManager, id: Long = -1, pattern: String? = null) { fun show(
fragmentManager: FragmentManager,
id: Long = -1,
pattern: String? = null,
isRegex: Boolean = false
) {
val dialog = ReplaceEditDialog() val dialog = ReplaceEditDialog()
val bundle = Bundle() val bundle = Bundle()
bundle.putLong("id", id) bundle.putLong("id", id)
bundle.putString("pattern", pattern) bundle.putString("pattern", pattern)
bundle.putBoolean("isRegex", isRegex)
dialog.arguments = bundle dialog.arguments = bundle
dialog.show(fragmentManager, "editReplace") dialog.show(fragmentManager, "editReplace")
} }
@ -68,6 +74,7 @@ class ReplaceEditDialog : DialogFragment(),
when (item?.itemId) { when (item?.itemId) {
R.id.menu_save -> { R.id.menu_save -> {
viewModel.save(getReplaceRule()) { viewModel.save(getReplaceRule()) {
callBack?.onReplaceRuleSave()
dismiss() dismiss()
} }
} }
@ -94,4 +101,10 @@ class ReplaceEditDialog : DialogFragment(),
replaceRule.scope = et_scope.text.toString() replaceRule.scope = et_scope.text.toString()
return replaceRule return replaceRule
} }
val callBack get() = activity as? CallBack
interface CallBack {
fun onReplaceRuleSave()
}
} }

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

@ -65,7 +65,7 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
} }
private fun initWebView() { private fun initWebView() {
webView.webViewClient = object : WebViewClient() { web_view.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading( override fun shouldOverrideUrlLoading(
view: WebView?, view: WebView?,
request: WebResourceRequest? request: WebResourceRequest?
@ -78,8 +78,19 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
} }
return true return true
} }
@Suppress("DEPRECATION")
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
if (url?.startsWith("http", true) == true) {
return false
}
url?.let {
openUrl(it)
}
return true
}
} }
webView.settings.apply { web_view.settings.apply {
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
domStorageEnabled = true domStorageEnabled = true
allowContentAccess = true allowContentAccess = true
@ -94,23 +105,35 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
val url = NetworkUtils.getAbsoluteURL(it.origin, it.link) val url = NetworkUtils.getAbsoluteURL(it.origin, it.link)
val html = viewModel.clHtml(content) val html = viewModel.clHtml(content)
if (viewModel.rssSource?.loadWithBaseUrl == true) { if (viewModel.rssSource?.loadWithBaseUrl == true) {
webView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url)//不想用baseUrl进else web_view.loadDataWithBaseURL(
url,
html,
"text/html",
"utf-8",
url
)//不想用baseUrl进else
} else { } else {
//webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码 //webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码
webView.loadDataWithBaseURL(null, html, "text/html;charset=utf-8", "utf-8", url) web_view.loadDataWithBaseURL(
null,
html,
"text/html;charset=utf-8",
"utf-8",
url
)
} }
} }
}) })
viewModel.urlLiveData.observe(this, Observer { viewModel.urlLiveData.observe(this, Observer {
upJavaScriptEnable() upJavaScriptEnable()
webView.loadUrl(it.url, it.headerMap) web_view.loadUrl(it.url, it.headerMap)
}) })
} }
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
private fun upJavaScriptEnable() { private fun upJavaScriptEnable() {
if (viewModel.rssSource?.enableJs == true) { if (viewModel.rssSource?.enableJs == true) {
webView.settings.javaScriptEnabled = true web_view.settings.javaScriptEnabled = true
} }
} }
@ -151,9 +174,9 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
event?.let { event?.let {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && webView.canGoBack()) { KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && web_view.canGoBack()) {
if (webView.copyBackForwardList().size > 1) { if (web_view.copyBackForwardList().size > 1) {
webView.goBack() web_view.goBack()
return true return true
} }
} }
@ -168,8 +191,8 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
viewModel.textToSpeech.stop() viewModel.textToSpeech.stop()
upTtsMenu(false) upTtsMenu(false)
} else { } else {
webView.settings.javaScriptEnabled = true web_view.settings.javaScriptEnabled = true
webView.evaluateJavascript("document.documentElement.outerHTML") { web_view.evaluateJavascript("document.documentElement.outerHTML") {
val html = StringEscapeUtils.unescapeJson(it) val html = StringEscapeUtils.unescapeJson(it)
val text = Jsoup.clean(html, Whitelist.none()) val text = Jsoup.clean(html, Whitelist.none())
.replace(Regex("""&\w+;"""), "") .replace(Regex("""&\w+;"""), "")

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -20,12 +21,11 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.* import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
@ -38,7 +38,7 @@ import kotlinx.android.synthetic.main.view_search.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult import org.jetbrains.anko.startActivityForResult
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.FileNotFoundException import java.io.File
class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_rss_source), class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_rss_source),
@ -50,7 +50,8 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
get() = getViewModel(RssSourceViewModel::class.java) get() = getViewModel(RssSourceViewModel::class.java)
private val importRecordKey = "rssSourceRecordKey" private val importRecordKey = "rssSourceRecordKey"
private val qrRequestCode = 101 private val qrRequestCode = 101
private val importSource = 124 private val importRequestCode = 124
private val exportRequestCode = 65
private lateinit var adapter: RssSourceAdapter private lateinit var adapter: RssSourceAdapter
private var sourceLiveData: LiveData<List<RssSource>>? = null private var sourceLiveData: LiveData<List<RssSource>>? = null
private var groups = hashSetOf<String>() private var groups = hashSetOf<String>()
@ -79,7 +80,8 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_add -> startActivity<RssSourceEditActivity>() R.id.menu_add -> startActivity<RssSourceEditActivity>()
R.id.menu_import_source_local -> selectFileSys() R.id.menu_import_source_local -> FilePicker
.selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json"))
R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_onLine -> showImportDialog()
R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode) R.id.menu_import_source_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
R.id.menu_group_manage -> GroupManageDialog() R.id.menu_group_manage -> GroupManageDialog()
@ -96,7 +98,7 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection()) R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection())
R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection())
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection())
R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelection()) R.id.menu_export_selection -> FilePicker.selectFolder(this, exportRequestCode)
R.id.menu_check_source -> { R.id.menu_check_source -> {
} }
} }
@ -248,46 +250,25 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
}.show().applyTint() }.show().applyTint()
} }
private fun selectFileSys() {
try {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "text/*"//设置类型
startActivityForResult(intent, importSource)
} catch (e: Exception) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFile()
}
.request()
}
}
private fun selectFile() {
FileChooserDialog.show(
supportFragmentManager, importSource,
allowExtensions = arrayOf("txt", "json")
)
}
override fun onFilePicked(requestCode: Int, currentPath: String) { override fun onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == importSource) { when (requestCode) {
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg -> viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
}
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) { importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri -> data?.data?.let { uri ->
try { try {
uri.readText(this)?.let { uri.readText(this)?.let {
@ -297,17 +278,6 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
} catch (e: FileNotFoundException) {
PermissionsCompat.Builder(this)
.addPermissions(
Permissions.READ_EXTERNAL_STORAGE,
Permissions.WRITE_EXTERNAL_STORAGE
)
.rationale(R.string.bg_image_per)
.onGranted {
selectFileSys()
}
.request()
} catch (e: Exception) { } catch (e: Exception) {
toast(e.localizedMessage ?: "ERROR") toast(e.localizedMessage ?: "ERROR")
} }
@ -321,6 +291,19 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
} }
} }
} }
exportRequestCode -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
if (uri.toString().isContentPath()) {
DocumentFile.fromTreeUri(this, uri)?.let {
viewModel.exportSelection(adapter.getSelection(), it)
}
} else {
uri.path?.let {
viewModel.exportSelection(adapter.getSelection(), File(it))
}
}
}
}
} }
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.rss.source.manage
import android.app.Application import android.app.Application
import android.text.TextUtils import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.JsonPath
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
@ -67,12 +68,24 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
} }
} }
fun exportSelection(sources: LinkedHashSet<RssSource>) { fun exportSelection(sources: LinkedHashSet<RssSource>, file: File) {
execute { execute {
val json = GSON.toJson(sources) val json = GSON.toJson(sources)
val file = FileUtils.createFileIfNotExist(file, "exportRssSource.json")
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportRssSource.json") .writeText(json)
file.writeText(json) }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {
context.toast("导出失败\n${it.localizedMessage}")
}
}
fun exportSelection(sources: LinkedHashSet<RssSource>, doc: DocumentFile) {
execute {
val json = GSON.toJson(sources)
doc.findFile("exportRssSource.json")?.delete()
doc.createFile("", "exportRssSource.json")
?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${Backup.exportPath}")
}.onError { }.onError {

@ -1,35 +1,50 @@
package io.legado.app.ui.welcome package io.legado.app.ui.welcome
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import com.github.houbb.opencc4j.util.ZhConverterUtil
import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import kotlinx.android.synthetic.main.activity_welcome.* import kotlinx.android.synthetic.main.activity_welcome.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
import java.util.concurrent.TimeUnit
open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) { open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) {
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
iv_bg.setColorFilter(accentColor) iv_book.setColorFilter(accentColor)
vw_title_line.setBackgroundColor(accentColor)
// 避免从桌面启动程序后,会重新实例化入口类的activity // 避免从桌面启动程序后,会重新实例化入口类的activity
if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) { if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) {
finish() finish()
return } else {
init()
} }
val welAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(800)
welAnimator.startDelay = 100
welAnimator.addUpdateListener { animation ->
val alpha = animation.animatedValue as Float
iv_bg.alpha = alpha
} }
welAnimator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) { private fun init() {
Coroutine.async {
//清楚过期数据
App.db.searchBookDao()
.clearExpired(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
//初始化简繁转换引擎
when (AppConfig.chineseConverterType) {
1 -> ZhConverterUtil.toSimple("初始化")
2 -> ZhConverterUtil.toTraditional("初始化")
else -> null
}
}
root_view.postDelayed({ startMainActivity() }, 300)
}
private fun startMainActivity() {
startActivity<MainActivity>() startActivity<MainActivity>()
if (getPrefBoolean(getString(R.string.pk_default_read))) { if (getPrefBoolean(getString(R.string.pk_default_read))) {
startActivity<ReadBookActivity>() startActivity<ReadBookActivity>()
@ -37,15 +52,6 @@ open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) {
finish() finish()
} }
override fun onAnimationEnd(animation: Animator) = Unit
override fun onAnimationCancel(animation: Animator) = Unit
override fun onAnimationRepeat(animation: Animator) = Unit
})
welAnimator.start()
}
} }
class Launcher1 : WelcomeActivity() class Launcher1 : WelcomeActivity()

@ -9,6 +9,8 @@ import androidx.annotation.MenuRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import io.legado.app.R import io.legado.app.R
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.utils.dp
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.view_select_action_bar.view.* import kotlinx.android.synthetic.main.view_select_action_bar.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
@ -18,7 +20,8 @@ class SelectActionBar(context: Context, attrs: AttributeSet?) : FrameLayout(cont
private var selMenu: PopupMenu? = null private var selMenu: PopupMenu? = null
init { init {
setBackgroundResource(R.color.background_menu) setBackgroundColor(context.bottomBackground)
elevation = 10.dp.toFloat()
View.inflate(context, R.layout.view_select_action_bar, this) View.inflate(context, R.layout.view_select_action_bar, this)
cb_selected_all.setOnCheckedChangeListener { buttonView, isChecked -> cb_selected_all.setOnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed) { if (buttonView.isPressed) {

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

Loading…
Cancel
Save