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. 58
      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. 30
      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. 104
      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. 59
      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. 50
      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. 32
      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. 79
      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. 10
      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. 39
      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. 105
      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. 81
      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. 54
      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 {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
// Flag to enable support for the new language APIs
//coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {

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

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

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

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

@ -237,14 +237,30 @@ object BookHelp {
private var bookName: String? = null
private var bookOrigin: String? = null
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(
title: String,
@ -253,23 +269,17 @@ object BookHelp {
content: String,
enableReplace: Boolean
): 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
for (item in replaceRules) {
item.pattern.let {
if (it.isNotEmpty()) {
c = if (item.isRegex) {
c.replace(it.toRegex(), item.replacement)
} else {
c.replace(it, item.replacement)
if (enableReplace) {
upReplaceRules(name, origin)
for (item in replaceRules) {
item.pattern.let {
if (it.isNotEmpty()) {
c = if (item.isRegex) {
c.replace(it.toRegex(), item.replacement)
} else {
c.replace(it, item.replacement)
}
}
}
}
@ -281,6 +291,6 @@ object BookHelp {
1 -> c = ZhConvertBootstrap.newInstance().toSimple(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? {
return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, null, null)
val call = analyzeUrl.getResponse()
val call = analyzeUrl.getResponse(urlStr)
val response = call.execute()
response.body()
} catch (e: Exception) {

@ -26,24 +26,6 @@ object ReadBookConfig {
}
val durConfig get() = getConfig(styleSelect)
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
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 hideNavigationBar = App.INSTANCE.getPrefBoolean(PreferKey.hideNavigationBar)
var textBold: Boolean

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

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

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

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

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

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

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

@ -147,6 +147,11 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
return this
}
override fun bottomBackground(color: Int): ThemeStore {
mEditor.putInt(ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND, color)
return this
}
override fun coloredStatusBar(colored: Boolean): ThemeStore {
mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored)
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
fun coloredStatusBar(context: Context): Boolean {
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true)

@ -78,8 +78,12 @@ internal interface ThemeStoreInterface {
fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore
// Background
fun backgroundColor(@ColorInt color: Int): ThemeStore
fun bottomBackground(@ColorInt color: Int): ThemeStore
// Toggle configurations
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_BACKGROUND_COLOR = "backgroundColor"
const val KEY_BOTTOM_BACKGROUND = "bottomBackground"
const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"
const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"

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

@ -37,7 +37,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap()
)
val res = analyzeUrl.getResponseAwait()
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList(
res.body,
bookSource,
@ -65,7 +65,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap()
)
val res = analyzeUrl.getResponseAwait()
val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl)
BookList.analyzeBookList(
res.body,
bookSource,
@ -96,7 +96,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap()
)
analyzeUrl.getResponseAwait().body
analyzeUrl.getResponseAwait(bookSource.bookSourceUrl).body
}
BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl)
book
@ -122,7 +122,7 @@ class WebBook(val bookSource: BookSource) {
ruleUrl = book.tocUrl,
baseUrl = book.bookUrl,
headerMapF = bookSource.getHeaderMap()
).getResponseAwait().body
).getResponseAwait(bookSource.bookSourceUrl).body
}
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? {
return try {
val analyzeUrl = AnalyzeUrl(urlStr, null, null, null, baseUrl, book)
val call = analyzeUrl.getResponse()
val call = analyzeUrl.getResponse(urlStr)
val response = call.execute()
response.body()
} 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.data.entities.BaseBook
import io.legado.app.help.JsExtensions
import io.legado.app.help.http.AjaxWebView
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.*
import io.legado.app.help.http.api.HttpGetApi
import io.legado.app.help.http.api.HttpPostApi
import io.legado.app.utils.*
@ -243,7 +240,11 @@ class AnalyzeUrl(
}
@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 {
method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {
@ -267,7 +268,7 @@ class AnalyzeUrl(
@Throws(Exception::class)
suspend fun getResponseAwait(
tag: String? = null,
tag: String,
jsStr: String? = null,
sourceRegex: String? = null
): Res {
@ -278,8 +279,13 @@ class AnalyzeUrl(
params.javaScript = jsStr
params.sourceRegex = sourceRegex
params.postData = bodyTxt?.toByteArray()
params.tag = tag
return HttpHelper.ajax(params)
}
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
headerMap["Cookie"] = cookie
}
val res = when {
method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {

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

@ -1,8 +1,10 @@
package io.legado.app.model.localBook
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.data.entities.Book
import io.legado.app.utils.FileUtils
object LocalBook {
@ -27,4 +29,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)
)
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
val analyzeRule = AnalyzeRule(book)
val tocRule = bookSource.getTocRule()
val nextUrlList = arrayListOf(baseUrl)
var reverse = false
@ -45,8 +45,7 @@ object BookChapterList {
}
var chapterData =
analyzeChapterList(
analyzeRule.setContent(body, baseUrl),
book.bookUrl, baseUrl, tocRule, listRule, bookSource, log = true
book, baseUrl, body, tocRule, listRule, bookSource, log = true
)
chapterData.chapterList?.let {
chapterList.addAll(it)
@ -64,11 +63,10 @@ object BookChapterList {
ruleUrl = nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getResponseAwait()
).getResponseAwait(bookSource.bookSourceUrl)
.body?.let { nextBody ->
chapterData = analyzeChapterList(
analyzeRule.setContent(nextBody, nextUrl),
book.bookUrl, nextUrl, tocRule, listRule, bookSource
book, nextUrl, nextBody, tocRule, listRule, bookSource
)
nextUrl = if (chapterData.nextUrl.isNotEmpty()) {
chapterData.nextUrl[0]
@ -95,35 +93,20 @@ object BookChapterList {
}
Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}")
for (item in chapterDataList) {
Coroutine.async(scope = coroutineScope) {
val nextBody = AnalyzeUrl(
ruleUrl = item.nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getResponseAwait().body
val nextChapterData = analyzeChapterList(
analyzeRule.setContent(nextBody, item.nextUrl),
book.bookUrl, item.nextUrl, tocRule, listRule, bookSource,
false
)
synchronized(chapterDataList) {
val isFinished = addChapterListIsFinish(
chapterDataList,
item,
nextChapterData.chapterList
)
if (isFinished) {
chapterDataList.forEach { item ->
item.chapterList?.let {
chapterList.addAll(it)
}
}
block.resume(finish(book, chapterList, reverse))
}
}
}.onError {
block.resumeWithException(it)
}
downloadToc(
coroutineScope,
item,
book,
bookSource,
tocRule,
listRule,
chapterList,
chapterDataList,
{
block.resume(finish(book, chapterList, reverse))
}, {
block.cancel(it)
})
}
}
}
@ -132,6 +115,49 @@ object BookChapterList {
}
}
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) {
val nextBody = AnalyzeUrl(
ruleUrl = chapterData.nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getResponseAwait(bookSource.bookSourceUrl).body
?: throw Exception("${chapterData.nextUrl}, 下载失败")
val nextChapterData = analyzeChapterList(
book, chapterData.nextUrl, nextBody, tocRule, listRule, bookSource,
false
)
synchronized(chapterDataList) {
val isFinished = addChapterListIsFinish(
chapterDataList,
chapterData,
nextChapterData.chapterList
)
if (isFinished) {
chapterDataList.forEach { item ->
item.chapterList?.let {
chapterList.addAll(it)
}
}
onFinish()
}
}
}.onError {
onError(it)
}
}
private fun addChapterListIsFinish(
chapterDataList: ArrayList<ChapterData<String>>,
chapterData: ChapterData<String>,
@ -173,15 +199,17 @@ object BookChapterList {
}
private fun analyzeChapterList(
analyzeRule: AnalyzeRule,
bookUrl: String,
book: Book,
baseUrl: String,
body: String,
tocRule: TocRule,
listRule: String,
bookSource: BookSource,
getNextUrl: Boolean = true,
log: Boolean = false
): ChapterData<List<String>> {
val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body, baseUrl)
val chapterList = arrayListOf<BookChapter>()
val nextUrlList = arrayListOf<String>()
val nextTocRule = tocRule.nextTocUrl
@ -212,7 +240,7 @@ object BookChapterList {
var isVip: String?
for (item in elements) {
analyzeRule.setContent(item)
val bookChapter = BookChapter(bookUrl = bookUrl)
val bookChapter = BookChapter(bookUrl = book.bookUrl)
analyzeRule.chapter = bookChapter
bookChapter.title = analyzeRule.getString(nameRule)
bookChapter.url = analyzeRule.getString(urlRule, true)

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

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

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

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

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

@ -1,4 +1,4 @@
package io.legado.app.ui.changecover
package io.legado.app.ui.book.changecover
import android.app.Application
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 io.legado.app.R

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

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

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

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

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

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

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

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

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

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

@ -1,4 +1,4 @@
package io.legado.app.ui.download
package io.legado.app.ui.book.download
import android.app.Activity
import android.content.Intent
@ -16,17 +16,15 @@ import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
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.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.activity_download.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jetbrains.anko.alert
import org.jetbrains.anko.toast
@ -126,44 +124,14 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
override fun export(position: Int) {
exportPosition = position
alert {
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)
if (path.isNullOrEmpty()) {
toast("没有默认路径")
} else {
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()
}
}
FilePicker.selectFolder(this, exportRequestCode) {
val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey)
if (path.isNullOrEmpty()) {
toast("没有默认路径")
} else {
startExport(path)
}
}.show()
}
}
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.widget.ImageView

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

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

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

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

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

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

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

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

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

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

@ -20,7 +20,9 @@ import io.legado.app.constant.PreferKey
import io.legado.app.constant.Status
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
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.delegate.PageDelegate
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.ui.book.changesource.ChangeSourceDialog
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.edit.ReplaceEditDialog
import io.legado.app.ui.widget.dialog.TextDialog
@ -63,6 +66,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
ChangeSourceDialog.CallBack,
ReadBook.CallBack,
TocRegexDialog.CallBack,
ReplaceEditDialog.CallBack,
ColorPickerDialogListener {
private val requestCodeChapterList = 568
private val requestCodeEditSource = 111
@ -211,6 +215,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
R.id.menu_enable_replace -> ReadBook.book?.let {
it.useReplaceRule = !it.useReplaceRule
menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule
onReplaceRuleSave()
}
R.id.menu_book_info -> ReadBook.book?.let {
startActivity<BookInfoActivity>(Pair("bookUrl", it.bookUrl))
@ -219,6 +224,12 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
supportFragmentManager,
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)
}
@ -260,18 +271,18 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
}
}
KeyEvent.KEYCODE_SPACE -> {
page_view.moveToNextPage()
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true
}
getPrefInt(PreferKey.prevKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToPrevPage()
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.PREV)
return true
}
}
getPrefInt(PreferKey.nextKey) -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
page_view.moveToNextPage()
page_view.pageDelegate?.keyTurnPage(PageDelegate.Direction.NEXT)
return true
}
}
@ -429,11 +440,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
if (getPrefBoolean("volumeKeyPageOnPlay")
|| BaseReadAloudService.pause
) {
when (direction) {
PageDelegate.Direction.PREV -> page_view.moveToPrevPage()
PageDelegate.Direction.NEXT -> page_view.moveToNextPage()
else -> return true
}
page_view.pageDelegate?.keyTurnPage(direction)
return true
}
}
@ -544,14 +551,33 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
}
}
/**
* 替换规则变化
*/
override fun onReplaceRuleSave() {
Coroutine.async {
BookHelp.upReplaceRules()
ReadBook.loadContent()
}
}
/**
* 显示阅读样式配置
*/
override fun showReadStyle() {
ReadStyleDialog().show(supportFragmentManager, "readStyle")
}
/**
* 显示更多设置
*/
override fun showMoreSetting() {
MoreConfigDialog().show(supportFragmentManager, "moreConfig")
}
/**
* 更新状态栏,导航栏
*/
override fun upSystemUiVisibility() {
Help.upSystemUiVisibility(this, !read_menu.isVisible)
}
@ -610,7 +636,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
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.ReadBookConfig
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.service.help.ReadBook
import io.legado.app.utils.*
@ -48,6 +49,7 @@ class ReadMenu : FrameLayout {
fabNightTheme.setImageResource(R.drawable.ic_brightness)
}
initAnimation()
ll_bottom_bg.setBackgroundColor(context.bottomBackground)
vw_bg.onClick { }
vwNavigationBar.onClick { }
seek_brightness.progress = context.getPrefInt("brightness", 100)

@ -95,6 +95,7 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
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)

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

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

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

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

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

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

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

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

@ -1,14 +1,12 @@
package io.legado.app.ui.book.read.page.delegate
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.drawable.GradientDrawable
import io.legado.app.ui.book.read.page.PageView
class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
private val shadowDrawableR: GradientDrawable
private val bitmapMatrix = Matrix()
init {
val shadowColors = intArrayOf(0x66111111, 0x00000000)
@ -18,35 +16,11 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT
}
override fun onAnimStart() {
val distanceX: Float
when (mDirection) {
Direction.NEXT -> distanceX =
if (isCancel) {
var dis = viewWidth - startX + touchX
if (dis > viewWidth) {
dis = viewWidth.toFloat()
}
viewWidth - dis
} else {
-(touchX + (viewWidth - startX))
}
else -> distanceX =
if (isCancel) {
-(touchX - startX)
} else {
viewWidth - (touchX - startX)
}
}
startScroll(touchX.toInt(), 0, distanceX.toInt(), 0)
}
override fun onAnimStop() {
override fun setStartPoint(x: Float, y: Float, invalidate: Boolean) {
curPage.x = 0.toFloat()
if (!isCancel) {
pageView.fillPage(mDirection)
}
prevPage.x = -viewWidth.toFloat()
nextPage.x = 0.toFloat()
super.setStartPoint(x, y, invalidate)
}
override fun onDraw(canvas: Canvas) {
@ -57,13 +31,12 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
) return
val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth
bitmap?.let {
if (mDirection == Direction.PREV) {
bitmapMatrix.setTranslate(distanceX, 0.toFloat())
canvas.drawBitmap(it, bitmapMatrix, null)
} else if (mDirection == Direction.NEXT) {
curPage.translationX = offsetX
}
if (!isRunning) return
if (mDirection == Direction.PREV) {
prevPage.translationX = offsetX - viewWidth
addShadow(distanceX.toInt(), canvas)
} else if (mDirection == Direction.NEXT) {
curPage.translationX = offsetX
addShadow(distanceX.toInt(), canvas)
}
}
@ -77,4 +50,36 @@ class CoverPageDelegate(pageView: PageView) : HorizontalPageDelegate(pageView) {
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) {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
abort()
}
MotionEvent.ACTION_MOVE -> {
if (isTextSelected) {
selectText(event)
@ -32,7 +35,6 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
return
}
setDirection(Direction.PREV)
setBitmap()
} else {
//如果不存在表示没有下一页了
if (!hasNext()) {
@ -40,7 +42,6 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
return
}
setDirection(Direction.NEXT)
setBitmap()
}
}
}
@ -53,18 +54,19 @@ abstract class HorizontalPageDelegate(pageView: PageView) : PageDelegate(pageVie
}
override fun nextPageByAnim() {
super.nextPageByAnim()
if (!hasNext()) return
setDirection(Direction.NEXT)
setBitmap()
setTouchPoint(viewWidth.toFloat(), 0f)
onAnimStart()
}
override fun prevPageByAnim() {
super.prevPageByAnim()
if (!hasPrev()) return
setDirection(Direction.PREV)
setBitmap()
setTouchPoint(0f, 0f)
onAnimStart()
}
}

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

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

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

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

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

@ -83,7 +83,7 @@ class BookSourceDebugActivity :
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_scan -> {
R.id.menu_scan -> {
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.theme.ATH
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.utils.GSON
import io.legado.app.utils.applyTint
@ -67,22 +68,20 @@ class BookSourceEditActivity :
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_save -> {
val source = getSource()
R.id.menu_save -> getSource().let { source ->
if (checkSource(source)) {
viewModel.save(source) { setResult(Activity.RESULT_OK); finish() }
}
}
R.id.menu_debug_source -> {
val source = getSource()
R.id.menu_debug_source -> getSource().let { source ->
if (checkSource(source)) {
viewModel.save(source) {
startActivity<BookSourceDebugActivity>(Pair("key", source.bookSourceUrl))
}
}
}
R.id.menu_copy_source -> {
GSON.toJson(getSource())?.let { sourceStr ->
R.id.menu_copy_source -> getSource().let { source ->
GSON.toJson(source)?.let { sourceStr ->
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
clipboard?.setPrimaryClip(ClipData.newPlainText(null, sourceStr))
}
@ -101,6 +100,18 @@ class BookSourceEditActivity :
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)
}

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
@ -20,14 +21,13 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.BookSource
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.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
import io.legado.app.service.help.CheckSource
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
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.widget.SelectActionBar
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.startActivityForResult
import org.jetbrains.anko.toast
import java.io.FileNotFoundException
import java.io.File
class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity_book_source),
PopupMenu.OnMenuItemClickListener,
@ -50,7 +50,8 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
get() = getViewModel(BookSourceViewModel::class.java)
private val importRecordKey = "bookSourceRecordKey"
private val qrRequestCode = 101
private val importSource = 132
private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: BookSourceAdapter
private var bookSourceLiveDate: LiveData<List<BookSource>>? = null
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_group_manage ->
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()
}
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_enable_explore -> viewModel.enableSelectExplore(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())
}
return true
@ -235,47 +237,10 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
}.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() {
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 {
newText?.let {
initLiveDataBookSource(it)
@ -307,6 +272,21 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
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?) {
super.onActivityResult(requestCode, resultCode, data)
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 ->
try {
uri.readText(this)?.let {
@ -328,22 +308,24 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
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) {
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.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath
import io.legado.app.App
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 {
val json = GSON.toJson(sources)
val file =
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportBookSource.json")
file.writeText(json)
FileUtils.createFileIfNotExist(file, "exportBookSource.json")
.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 {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {

@ -3,7 +3,6 @@ package io.legado.app.ui.config
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
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.Restore
import io.legado.app.help.storage.WebDavHelp
import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.getPrefString
import io.legado.app.utils.isContentPath
import io.legado.app.utils.toast
import kotlinx.coroutines.Dispatchers.Main
import org.jetbrains.anko.toast
import java.io.File
object BackupRestoreUi {
private const val selectFolderRequestCode = 21
@ -79,31 +78,7 @@ object BackupRestoreUi {
}
fun selectBackupFolder(fragment: Fragment, requestCode: Int = selectFolderRequestCode) {
fragment.alert {
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()
FilePicker.selectFolder(fragment, requestCode)
}
fun restore(fragment: Fragment) {
@ -120,13 +95,13 @@ object BackupRestoreUi {
Restore.restore(fragment.requireContext(), backupPath)
fragment.toast(R.string.restore_success)
} else {
selectRestoreFolder(fragment)
selectBackupFolder(fragment, restoreSelectRequestCode)
}
} else {
restoreUsePermission(fragment, backupPath)
}
} else {
selectRestoreFolder(fragment)
selectBackupFolder(fragment, restoreSelectRequestCode)
}
}
}
@ -147,79 +122,8 @@ object BackupRestoreUi {
.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) {
fragment.alert {
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()
FilePicker.selectFolder(fragment, oldDataRequestCode)
}
fun onFilePicked(requestCode: Int, currentPath: String) {
@ -243,6 +147,9 @@ object BackupRestoreUi {
selectFolderRequestCode -> {
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.help.AppConfig
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.receiver.SharedReceiverActivity
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.utils.*
@ -124,38 +122,9 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
}
private fun selectDownloadPath() {
alert {
titleResource = R.string.select_folder
items(resources.getStringArray(R.array.select_folder).toList()) { _, i ->
when (i) {
0 -> {
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()
FilePicker.selectFolder(this, requestCodeDownloadPath) {
removePref(PreferKey.downloadPath)
}
}
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.ColorUtils
import io.legado.app.utils.*
import org.jetbrains.anko.defaultSharedPreferences
class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
@ -27,7 +26,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_theme)
onSharedPreferenceChanged(requireContext().defaultSharedPreferences, "defaultTheme")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -49,10 +47,11 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
sharedPreferences ?: return
when (key) {
PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key))
"transparentStatusBar" -> {
recreateActivities()
}
"colorPrimary", "colorAccent", "colorBackground" -> {
"transparentStatusBar" -> recreateActivities()
"colorPrimary",
"colorAccent",
"colorBackground",
"colorBottomBackground" -> {
if (backgroundIsDark(sharedPreferences)) {
alert {
title = "白天背景太暗"
@ -73,7 +72,10 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(false)
}
}
"colorPrimaryNight", "colorAccentNight", "colorBackgroundNight" -> {
"colorPrimaryNight",
"colorAccentNight",
"colorBackgroundNight",
"colorBottomBackgroundNight" -> {
if (backgroundIsLight(sharedPreferences)) {
alert {
title = "夜间背景太亮"
@ -94,7 +96,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(true)
}
}
"defaultTheme" -> findPreference<Preference>(key)?.summary = items[getPrefInt(key)]
}
}
@ -141,7 +142,6 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
AppConfig.isNightTheme = true
}
}
putPrefInt("defaultTheme", which)
App.INSTANCE.applyDayNight()
recreateActivities()
}

@ -42,7 +42,7 @@ class FileChooserDialog : DialogFragment(),
isShowHomeDir: Boolean = false,
isShowUpDir: Boolean = true,
isShowHideDir: Boolean = false,
allowExtensions: Array<String?>? = null,
allowExtensions: Array<String>? = null,
menus: Array<String>? = null
) {
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
get() = mode == DIRECTORY
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.base.adapter.ItemViewHolder
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.utils.ConvertUtils
import io.legado.app.ui.filechooser.utils.FilePickerIcon
import io.legado.app.ui.filechooser.utils.FileUtils
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
@ -122,7 +122,7 @@ class FileAdapter(context: Context, val callBack: CallBack) :
interface CallBack {
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.base.adapter.ItemViewHolder
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.FilePickerIcon
import kotlinx.android.synthetic.main.item_path_filepicker.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
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

@ -60,7 +60,7 @@ object FileUtils {
@JvmOverloads
fun listDirs(
startDirPath: String,
excludeDirs: Array<String?>? = null, @SortType sortType: Int = BY_NAME_ASC
excludeDirs: Array<String>? = null, @SortType sortType: Int = BY_NAME_ASC
): Array<File?> {
var excludeDirs1 = excludeDirs
val dirList = ArrayList<File>()
@ -76,7 +76,7 @@ object FileUtils {
})
?: return arrayOfNulls(0)
if (excludeDirs1 == null) {
excludeDirs1 = arrayOfNulls(0)
excludeDirs1 = arrayOf()
}
for (dir in dirs) {
val file = dir.absoluteFile
@ -115,7 +115,7 @@ object FileUtils {
@JvmOverloads
fun listDirsAndFiles(
startDirPath: String,
allowExtensions: Array<String?>? = null
allowExtensions: Array<String>? = null
): Array<File?>? {
val dirs: Array<File?>?
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)
return file.listFiles { _, 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?>? {
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
import android.annotation.SuppressLint
import android.graphics.Bitmap
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.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) {
var sourceUrl: String? = null
var loginUrl: String? = null
var checking = false
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.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import com.github.houbb.opencc4j.util.ZhConverterUtil
import com.google.android.material.bottomnavigation.BottomNavigationView
import io.legado.app.App
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.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.storage.Backup
import io.legado.app.lib.theme.ATH
import io.legado.app.service.BaseReadAloudService
@ -53,21 +51,12 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
upVersion()
//初始化简繁转换引擎
when (AppConfig.chineseConverterType) {
1 -> Coroutine.async { ZhConverterUtil.toSimple("初始化") }
2 -> Coroutine.async { ZhConverterUtil.toTraditional("初始化") }
}
//自动更新书籍
if (AppConfig.autoRefreshBook) {
view_pager_main.postDelayed({
viewModel.upChapterList()
}, 1000)
}
//清楚过期数据
view_pager_main.postDelayed({
viewModel.clearExpiredData()
}, 3000)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {

@ -13,7 +13,6 @@ import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
class MainViewModel(application: Application) : BaseViewModel(application) {
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() {
execute {
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.group.GroupManageDialog
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.download.DownloadActivity
import io.legado.app.ui.importbook.ImportBookActivity
import io.legado.app.ui.book.download.DownloadActivity
import io.legado.app.ui.book.local.ImportBookActivity
import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.*
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 }
}
val diffResult = DiffUtil
.calculateDiff(BooksDiffCallBack(ArrayList(booksAdapter.getItems()), books))
.calculateDiff(BooksDiffCallBack(booksAdapter.getItems(), books))
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.primaryTextColor
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.explore.ExploreShowActivity
import io.legado.app.ui.book.explore.ExploreShowActivity
import io.legado.app.utils.getViewModel
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.startActivity

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
@ -19,13 +20,14 @@ import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.BookHelp
import io.legado.app.help.ItemTouchCallback
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.*
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
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.widget.SelectActionBar
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 org.jetbrains.anko.toast
import java.io.File
import java.io.FileNotFoundException
class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activity_replace_rule),
@ -47,7 +48,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
override val viewModel: ReplaceRuleViewModel
get() = getViewModel(ReplaceRuleViewModel::class.java)
private val importRecordKey = "replaceRuleRecordKey"
private val importSource = 132
private val importRequestCode = 132
private val exportRequestCode = 65
private lateinit var adapter: ReplaceRuleAdapter
private var groups = hashSetOf<String>()
private var groupMenu: SubMenu? = null
@ -139,8 +141,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
}
private fun observeReplaceRuleData(key: String? = null) {
replaceRuleLiveData?.removeObservers(this)
dataInit = false
replaceRuleLiveData?.removeObservers(this)
replaceRuleLiveData = if (key.isNullOrEmpty()) {
App.db.replaceRuleDao().liveDataAll()
} else {
@ -177,7 +179,8 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection())
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)
}
@ -186,7 +189,7 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
when (item?.itemId) {
R.id.menu_enable_selection -> viewModel.enableSelection(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
}
@ -233,43 +236,6 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
}.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 onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == importSource) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSource(File(currentPath).readText()) { msg ->
title_bar.snackbar(msg)
}
}
}
override fun onQueryTextChange(newText: String?): Boolean {
observeReplaceRuleData("%$newText%")
return false
@ -279,10 +245,25 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
return false
}
override fun onFilePicked(requestCode: Int, currentPath: String) {
when (requestCode) {
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSource(File(currentPath).readText()) { msg ->
title_bar.snackbar(msg)
}
}
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) {
importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
try {
uri.readText(this)?.let {
@ -292,46 +273,58 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
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) {
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() {
select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount())
}
override fun update(vararg rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.update(*rule)
}
override fun delete(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.delete(rule)
}
override fun edit(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
ReplaceEditDialog.show(supportFragmentManager, rule.id)
}
override fun toTop(rule: ReplaceRule) {
setResult(Activity.RESULT_OK)
viewModel.toTop(rule)
}
override fun upOrder() {
setResult(Activity.RESULT_OK)
viewModel.upOrder()
}
}

@ -2,6 +2,7 @@ package io.legado.app.ui.replacerule
import android.app.Application
import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.R
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.storage.Backup
import io.legado.app.help.storage.ImportOldData
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.*
import org.jetbrains.anko.toast
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 {
val json = GSON.toJson(rules)
val file =
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportReplaceRule.json")
file.writeText(json)
val json = GSON.toJson(sources)
FileUtils.createFileIfNotExist(file, "exportReplaceRule.json")
.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 {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {

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

@ -21,8 +21,9 @@ class ReplaceEditViewModel(application: Application) : BaseViewModel(application
}
} else {
bundle.getString("pattern")?.let { pattern ->
val isRegex = bundle.getBoolean("isRegex")
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() {
webView.webViewClient = object : WebViewClient() {
web_view.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
@ -78,8 +78,19 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
}
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
domStorageEnabled = 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 html = viewModel.clHtml(content)
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 {
//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 {
upJavaScriptEnable()
webView.loadUrl(it.url, it.headerMap)
web_view.loadUrl(it.url, it.headerMap)
})
}
@SuppressLint("SetJavaScriptEnabled")
private fun upJavaScriptEnable() {
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 {
event?.let {
when (keyCode) {
KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && webView.canGoBack()) {
if (webView.copyBackForwardList().size > 1) {
webView.goBack()
KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && web_view.canGoBack()) {
if (web_view.copyBackForwardList().size > 1) {
web_view.goBack()
return true
}
}
@ -168,8 +191,8 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
viewModel.textToSpeech.stop()
upTtsMenu(false)
} else {
webView.settings.javaScriptEnabled = true
webView.evaluateJavascript("document.documentElement.outerHTML") {
web_view.settings.javaScriptEnabled = true
web_view.evaluateJavascript("document.documentElement.outerHTML") {
val html = StringEscapeUtils.unescapeJson(it)
val text = Jsoup.clean(html, Whitelist.none())
.replace(Regex("""&\w+;"""), "")

@ -9,6 +9,7 @@ import android.view.MenuItem
import android.view.SubMenu
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil
@ -20,12 +21,11 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.RssSource
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.theme.ATH
import io.legado.app.lib.theme.primaryTextColor
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.rss.source.edit.RssSourceEditActivity
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.startActivityForResult
import org.jetbrains.anko.toast
import java.io.FileNotFoundException
import java.io.File
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)
private val importRecordKey = "rssSourceRecordKey"
private val qrRequestCode = 101
private val importSource = 124
private val importRequestCode = 124
private val exportRequestCode = 65
private lateinit var adapter: RssSourceAdapter
private var sourceLiveData: LiveData<List<RssSource>>? = null
private var groups = hashSetOf<String>()
@ -79,7 +80,8 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
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_qr -> startActivityForResult<QrCodeActivity>(qrRequestCode)
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_disable_selection -> viewModel.disableSelection(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 -> {
}
}
@ -248,46 +250,25 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
}.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) {
if (requestCode == importSource) {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
when (requestCode) {
importRequestCode -> {
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
viewModel.importSourceFromFilePath(currentPath) { msg ->
title_bar.snackbar(msg)
}
}
exportRequestCode -> viewModel.exportSelection(
adapter.getSelection(),
File(currentPath)
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
importSource -> if (resultCode == Activity.RESULT_OK) {
importRequestCode -> if (resultCode == Activity.RESULT_OK) {
data?.data?.let { uri ->
try {
uri.readText(this)?.let {
@ -297,17 +278,6 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
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) {
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.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import com.jayway.jsonpath.JsonPath
import io.legado.app.App
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 {
val json = GSON.toJson(sources)
val file =
FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportRssSource.json")
file.writeText(json)
FileUtils.createFileIfNotExist(file, "exportRssSource.json")
.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 {
context.toast("成功导出至\n${Backup.exportPath}")
}.onError {

@ -1,49 +1,55 @@
package io.legado.app.ui.welcome
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Intent
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.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.ui.book.read.ReadBookActivity
import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getPrefBoolean
import kotlinx.android.synthetic.main.activity_welcome.*
import org.jetbrains.anko.startActivity
import java.util.concurrent.TimeUnit
open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) {
override fun onActivityCreated(savedInstanceState: Bundle?) {
iv_bg.setColorFilter(accentColor)
iv_book.setColorFilter(accentColor)
vw_title_line.setBackgroundColor(accentColor)
// 避免从桌面启动程序后,会重新实例化入口类的activity
if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) {
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) {
startActivity<MainActivity>()
if (getPrefBoolean(getString(R.string.pk_default_read))) {
startActivity<ReadBookActivity>()
}
finish()
}
override fun onAnimationEnd(animation: Animator) = Unit
}
override fun onAnimationCancel(animation: Animator) = Unit
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)
}
override fun onAnimationRepeat(animation: Animator) = Unit
})
welAnimator.start()
private fun startMainActivity() {
startActivity<MainActivity>()
if (getPrefBoolean(getString(R.string.pk_default_read))) {
startActivity<ReadBookActivity>()
}
finish()
}
}

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

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

Loading…
Cancel
Save