Merge remote-tracking branch 'origin/master' into master

pull/1114/head
bushixuanqi 4 years ago
commit 3712feff2e
  1. 3
      README.md
  2. 6
      app/build.gradle
  3. 25
      app/src/main/assets/defaultData/rssSources.json
  4. 17
      app/src/main/assets/help/ruleHelp.md
  5. 64
      app/src/main/assets/updateLog.md
  6. 40
      app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
  7. 72
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  8. 39
      app/src/main/java/io/legado/app/data/entities/ExploreKind.kt
  9. 2
      app/src/main/java/io/legado/app/help/LocalConfig.kt
  10. 263
      app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt
  11. 11
      app/src/main/java/io/legado/app/service/CheckSourceService.kt
  12. 2
      app/src/main/java/io/legado/app/ui/about/ReadRecordActivity.kt
  13. 4
      app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt
  14. 2
      app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt
  15. 4
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  16. 4
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  17. 4
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  18. 6
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  19. 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt
  20. 27
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  21. 6
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  22. 72
      app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt
  23. 24
      app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt
  24. 2
      app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt
  25. 2
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  26. 18
      app/src/main/java/io/legado/app/ui/book/read/config/ClickActionConfigDialog.kt
  27. 88
      app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt
  28. 2
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt
  29. 6
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  30. 6
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  31. 4
      app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
  32. 4
      app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt
  33. 2
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt
  34. 6
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  35. 2
      app/src/main/java/io/legado/app/ui/book/search/BookAdapter.kt
  36. 2
      app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt
  37. 8
      app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt
  38. 2
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentAdapter.kt
  39. 2
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditAdapter.kt
  40. 2
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
  41. 2
      app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt
  42. 2
      app/src/main/java/io/legado/app/ui/book/toc/ChapterListAdapter.kt
  43. 4
      app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt
  44. 4
      app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt
  45. 4
      app/src/main/java/io/legado/app/ui/dict/DictDialog.kt
  46. 2
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  47. 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterGrid.kt
  48. 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterList.kt
  49. 81
      app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt
  50. 3
      app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt
  51. 4
      app/src/main/java/io/legado/app/ui/replace/GroupManageDialog.kt
  52. 2
      app/src/main/java/io/legado/app/ui/replace/ReplaceRuleAdapter.kt
  53. 4
      app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt
  54. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter.kt
  55. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter1.kt
  56. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter2.kt
  57. 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt
  58. 2
      app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesAdapter.kt
  59. 2
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditAdapter.kt
  60. 4
      app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt
  61. 2
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt
  62. 2
      app/src/main/java/io/legado/app/ui/widget/KeyboardToolPop.kt
  63. 8
      app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt
  64. 2
      app/src/main/java/io/legado/app/ui/widget/dialog/TextListDialog.kt
  65. 2
      app/src/main/java/io/legado/app/ui/widget/font/FontAdapter.kt
  66. 2
      app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt
  67. 13
      app/src/main/res/layout/dialog_dict.xml
  68. 2
      app/src/main/res/layout/item_find_book.xml
  69. 47
      app/src/main/res/layout/popup_action_menu.xml

@ -12,9 +12,10 @@
[![GitHub contributors](https://img.shields.io/github/contributors/gedoor/legado)](https://github.com/gedoor/legado/graphs/contributors) [![GitHub contributors](https://img.shields.io/github/contributors/gedoor/legado)](https://github.com/gedoor/legado/graphs/contributors)
## 阅读3.0 ## 阅读3.0
* [书源规则](https://alanskycn.gitee.io/teachme/) * [书源规则](https://alanskycn.gitee.io/teachme)
* [更新日志](/app/src/main/assets/updateLog.md) * [更新日志](/app/src/main/assets/updateLog.md)
* [帮助文档](/app/src/main/assets/help/appHelp.md) * [帮助文档](/app/src/main/assets/help/appHelp.md)
* [web端](https://github.com/celetor/web-yuedu3)
## 下载 ## 下载
<a href="https://play.google.com/store/apps/details?id=io.legado.play.release"><img width="200px" alt="Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"/></a> or <a href="https://www.coolapk.com/apk/io.legado.app.release"><img width="100px" height="100px" alt="CoolApk" src="https://github.com/Celeter/build/raw/master/.github/scripts/coolapk.png"/></a> or [Releases](https://github.com/gedoor/legado/releases/latest) <a href="https://play.google.com/store/apps/details?id=io.legado.play.release"><img width="200px" alt="Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"/></a> or <a href="https://www.coolapk.com/apk/io.legado.app.release"><img width="100px" height="100px" alt="CoolApk" src="https://github.com/Celeter/build/raw/master/.github/scripts/coolapk.png"/></a> or [Releases](https://github.com/gedoor/legado/releases/latest)

@ -133,9 +133,9 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.viewpager2:viewpager2:1.0.0' implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
implementation 'com.google.android:flexbox:1.1.0' implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.code.gson:gson:2.8.7'
implementation 'androidx.webkit:webkit:1.4.0' implementation 'androidx.webkit:webkit:1.4.0'
//media //media

@ -9,31 +9,22 @@
"sourceUrl": "https://www.yuque.com/legado" "sourceUrl": "https://www.yuque.com/legado"
}, },
{ {
"articleStyle": 0,
"customOrder": 2, "customOrder": 2,
"enableJs": true, "enableJs": true,
"enabled": true, "enabled": true,
"loadWithBaseUrl": false, "singleUrl": true,
"ruleArticles": "class.release-main-section||tbody@tr", "sourceIcon": "http:\/\/mmbiz.qpic.cn\/mmbiz_png\/hpfMV8hEuL2eS6vnCxvTzoOiaCAibV6exBzJWq9xMic9xDg3YXAick87tsfafic0icRwkQ5ibV0bJ84JtSuxhPuEDVquA\/0?wx_fmt=png",
"ruleContent": "@js:\nif(baseUrl.match(\/90pan\/)){\nresult=result.replace(\/true\\||<a href=\"([^\"]+)\".*?><.*?>([^<]+)<\\\/span><\\\/a>\/g,'<h4><a href=\"$1\" style=\"color:#000\">$2<\/a><\/h4><p style=\"font-size:10px\">$1<\/p>\\n\\n')}\nelse{\nhtml=String(java.getString(\"@@class.markdown-body@html\",false)+java.getString(\"@@class.flex-justify-between.-1@html\",false));\nhref='https:\/\/github.com'+String(java.getString(\"@@class.flex-justify-between.-1@class.Box-body.0@a@href\",false))\nhtml+'<br><p>下载地址:<\/p><a style=\"font-size:13px\">'+href+'<\/a>'\n}", "sourceName": "小说拾遗",
"ruleImage": "@js:if(baseUrl.match(\/legado|704\/g)){\nresult='https:\/\/urlmin.cn\/034'\n}else{result=\"https:\/\/urlmin.cn\/15c\"}", "sourceUrl": "snssdk1128:\/\/user\/profile\/562564899806367"
"ruleLink": "tag.a.0@href\n@js:if(baseUrl.match(\/90pan\/)){\nresult='https:\/\/www.90pan.com\/ajax.php,{\"method\": \"POST\",\"body\": \"action=load_down_addr1&file_id='+result.match(\/b(\\d+)\/)[1]+'\"}'}else{\nresult='https:\/\/github.com'+result\n}",
"ruleNextPage": "text.Next@href",
"rulePubDate": "tag.td!0@text||class.markdown-body@text##\\n##·",
"ruleTitle": "tag.a.0@text",
"sortUrl": "阅读2.0·GitHub::https:\/\/github.com\/gedoor\/MyBookshelf\/releases\n阅读3.0·GitHub::https:\/\/github.com\/gedoor\/legado\/releases\n阅读2.0·90盘::https:\/\/www.90pan.com\/o138703\n阅读3.0·90盘::https:\/\/www.90pan.com\/o138704",
"sourceIcon": "http:\/\/ku.mumuceo.com\/static\/images\/applogo\/yuedu.png",
"sourceName": "阅读下载",
"sourceUrl": "https:\/\/github.com\/gedoor\/legado\/releases",
"style": ""
}, },
{ {
"customOrder": 3, "customOrder": 3,
"enableJs": true, "enableJs": true,
"enabled": true, "enabled": true,
"loadWithBaseUrl": false,
"singleUrl": true, "singleUrl": true,
"sourceIcon": "http:\/\/mmbiz.qpic.cn\/mmbiz_png\/hpfMV8hEuL2eS6vnCxvTzoOiaCAibV6exBzJWq9xMic9xDg3YXAick87tsfafic0icRwkQ5ibV0bJ84JtSuxhPuEDVquA\/0?wx_fmt=png", "sourceIcon": "https://Cloud.miaogongzi.net/images/icon.png",
"sourceName": "小说拾遗", "sourceName": "Meow云",
"sourceUrl": "snssdk1128:\/\/user\/profile\/562564899806367" "sourceUrl": "https://pan.miaogongzi.net"
} }
] ]

@ -11,6 +11,23 @@
: regex规则,不可省略,只可以用在书籍列表和目录列表 : regex规则,不可省略,只可以用在书籍列表和目录列表
``` ```
* 发现url格式
```json
[
{
"title": "xxx",
"url": "",
"style": {
"layout_flexGrow": 0,
"layout_flexShrink": 1,
"layout_alignSelf": "auto",
"layout_flexBasisPercent": -1,
"layout_wrapBefore": false
}
}
]
```
* 获取登录后的cookie * 获取登录后的cookie
``` ```
java.getCookie("http://baidu.com", null) => userid=1234;pwd=adbcd java.getCookie("http://baidu.com", null) => userid=1234;pwd=adbcd

@ -8,7 +8,27 @@
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则出现问题。先关闭替换净化并刷新,再观察是否正常。如果正常说明净化规则存在误杀,如果关闭后仍然出现相关问题,请点击源链接查看原文与正文是否相同,如果不同,再进行反馈。 * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则出现问题。先关闭替换净化并刷新,再观察是否正常。如果正常说明净化规则存在误杀,如果关闭后仍然出现相关问题,请点击源链接查看原文与正文是否相同,如果不同,再进行反馈。
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
**2021/07/05** **2021/07/09**
1. 发现url添加json格式, 支持设置标签样式
* 样式属性可以搜索 [FleboxLayout子元素支持的属性介绍](https://www.jianshu.com/p/3c471953e36d)
* 样式属性可省略,有默认值
```json
[
{
"title": "xxx",
"url": "",
"style": {
"layout_flexGrow": 0,
"layout_flexShrink": 1,
"layout_alignSelf": "auto",
"layout_flexBasisPercent": -1,
"layout_wrapBefore": false
}
}
]
```
**2021/07/07**
1. 默认规则新增类似`jsonPath`的索引写法 by bushixuanqi 1. 默认规则新增类似`jsonPath`的索引写法 by bushixuanqi
* 格式形如 `[index,index, ...]``[!index,index, ...]` 其中`[!`开头表示筛选方式为排除,`index`可以是单个索引,也可以是区间。 * 格式形如 `[index,index, ...]``[!index,index, ...]` 其中`[!`开头表示筛选方式为排除,`index`可以是单个索引,也可以是区间。
* 区间格式为 `start:end``start:end:step`,其中`start`为`0`可省略,`end`为`-1`可省略。 * 区间格式为 `start:end``start:end:step`,其中`start`为`0`可省略,`end`为`-1`可省略。
@ -20,6 +40,7 @@
3. 添加Umd格式支持 by ag2s20150909 3. 添加Umd格式支持 by ag2s20150909
4. 修复web页面按键重复监听的bug 4. 修复web页面按键重复监听的bug
5. 亮度条往中间移了一点,防止误触 5. 亮度条往中间移了一点,防止误触
6. 添加内置字典
**2021/06/29** **2021/06/29**
* 修复html格式化bug * 修复html格式化bug
@ -428,44 +449,3 @@ getTxtInFolder(unzipPath: String): String
``` ```
* 增加type字段,返回16进制字符串,例:`https://www.baidu.com,{"type":"zip"}` * 增加type字段,返回16进制字符串,例:`https://www.baidu.com,{"type":"zip"}`
* 底部操作栏阴影跟随设置调节 * 底部操作栏阴影跟随设置调节
**2020/09/24**
* 修复规则解析bug
**2020/09/21**
* 修复规则解析bug
* 换源时无最新章节信息时可加载详情页来获取(默认关闭)
**2020/09/20**
* 优化正文搜索
* 阅读界面信息添加书名
**2020/09/18**
* 解决正文替换{{title}}问题
* 修复共用布局配置不能读取的问题
* 添加自定义源分组功能 by KKL369
* 解决跨进程调用ReaderProvider出现CursorIndexOutOfBoundsException问题
**2020/09/17**
* 优化正文搜索文字颜色
* 优化书源校验 by KKL369
* 缓存导出到webDav by 10bits
* 导入的字体在字体选择界面显示
**2020/09/15**
* 修复导入排版字体重复报错的bug
* 添加正文搜索 by [h11128](https://github.com/h11128)
**2020/09/12**
* web看书同步最新章
* web写源增加图片样式等规则
* 正文规则可以使用@get:{title}获取目录标题,js里使用title
**2020/09/11**
* 修复一些bug
* 背景配置自由添加
**2020/09/10**
* 修复自动换源的bug
* 修复保存主题的bug
* 书源排序,分享,注释优化 by [h11128](https://github.com/h11128)

@ -10,7 +10,16 @@ interface BookSourceDao {
@Query("select * from book_sources order by customOrder asc") @Query("select * from book_sources order by customOrder asc")
fun liveDataAll(): LiveData<List<BookSource>> fun liveDataAll(): LiveData<List<BookSource>>
@Query("select * from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey or bookSourceComment like :searchKey order by customOrder asc") @Query(
"""
select * from book_sources
where bookSourceName like :searchKey
or bookSourceGroup like :searchKey
or bookSourceUrl like :searchKey
or bookSourceComment like :searchKey
order by customOrder asc
"""
)
fun liveDataSearch(searchKey: String): LiveData<List<BookSource>> fun liveDataSearch(searchKey: String): LiveData<List<BookSource>>
@Query("select * from book_sources where bookSourceGroup like :searchKey order by customOrder asc") @Query("select * from book_sources where bookSourceGroup like :searchKey order by customOrder asc")
@ -25,10 +34,26 @@ interface BookSourceDao {
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc") @Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' order by customOrder asc")
fun liveExplore(): LiveData<List<BookSource>> fun liveExplore(): LiveData<List<BookSource>>
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and (bookSourceGroup like :key or bookSourceName like :key) order by customOrder asc") @Query(
"""
select * from book_sources
where enabledExplore = 1
and trim(exploreUrl) <> ''
and (bookSourceGroup like :key or bookSourceName like :key)
order by customOrder asc
"""
)
fun liveExplore(key: String): LiveData<List<BookSource>> fun liveExplore(key: String): LiveData<List<BookSource>>
@Query("select * from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and (bookSourceGroup like :key) order by customOrder asc") @Query(
"""
select * from book_sources
where enabledExplore = 1
and trim(exploreUrl) <> ''
and (bookSourceGroup like :key)
order by customOrder asc
"""
)
fun liveGroupExplore(key: String): LiveData<List<BookSource>> fun liveGroupExplore(key: String): LiveData<List<BookSource>>
@Query("select distinct bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''") @Query("select distinct bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''")
@ -37,7 +62,14 @@ interface BookSourceDao {
@Query("select distinct bookSourceGroup from book_sources where enabled = 1 and trim(bookSourceGroup) <> ''") @Query("select distinct bookSourceGroup from book_sources where enabled = 1 and trim(bookSourceGroup) <> ''")
fun liveGroupEnabled(): LiveData<List<String>> fun liveGroupEnabled(): LiveData<List<String>>
@Query("select distinct bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''") @Query(
"""
select distinct bookSourceGroup from book_sources
where enabledExplore = 1
and trim(exploreUrl) <> ''
and trim(bookSourceGroup) <> ''
"""
)
fun liveExploreGroup(): LiveData<List<String>> fun liveExploreGroup(): LiveData<List<String>>
@Query("select * from book_sources where bookSourceGroup like '%' || :group || '%'") @Query("select * from book_sources where bookSourceGroup like '%' || :group || '%'")

@ -10,10 +10,7 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.CacheManager import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.http.CookieStore import io.legado.app.help.http.CookieStore
import io.legado.app.utils.ACache import io.legado.app.utils.*
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.splitNotBlank
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import splitties.init.appCtx import splitties.init.appCtx
import javax.script.SimpleBindings import javax.script.SimpleBindings
@ -100,42 +97,48 @@ data class BookSource(
} }
} }
fun getExploreKinds() = arrayListOf<ExploreKind>().apply { fun getExploreKinds(): List<ExploreKind> {
exploreUrl?.let { urlRule -> val exploreUrl = exploreUrl ?: return emptyList()
var a = urlRule val kinds = arrayListOf<ExploreKind>()
if (a.isNotBlank()) { var ruleStr = exploreUrl
kotlin.runCatching { if (ruleStr.isNotBlank()) {
if (urlRule.startsWith("<js>", false) kotlin.runCatching {
|| urlRule.startsWith("@js", false) if (exploreUrl.startsWith("<js>", false)
) { || exploreUrl.startsWith("@js", false)
val aCache = ACache.get(appCtx, "explore") ) {
a = aCache.getAsString(bookSourceUrl) ?: "" val aCache = ACache.get(appCtx, "explore")
if (a.isBlank()) { ruleStr = aCache.getAsString(bookSourceUrl) ?: ""
val bindings = SimpleBindings() if (ruleStr.isBlank()) {
bindings["baseUrl"] = bookSourceUrl val bindings = SimpleBindings()
bindings["java"] = this bindings["baseUrl"] = bookSourceUrl
bindings["cookie"] = CookieStore bindings["java"] = this
bindings["cache"] = CacheManager bindings["cookie"] = CookieStore
val jsStr = if (urlRule.startsWith("@")) { bindings["cache"] = CacheManager
urlRule.substring(3) val jsStr = if (exploreUrl.startsWith("@")) {
} else { exploreUrl.substring(3)
urlRule.substring(4, urlRule.lastIndexOf("<")) } else {
} exploreUrl.substring(4, exploreUrl.lastIndexOf("<"))
a = AppConst.SCRIPT_ENGINE.eval(jsStr, bindings).toString()
aCache.put(bookSourceUrl, a)
} }
ruleStr = AppConst.SCRIPT_ENGINE.eval(jsStr, bindings).toString().trim()
aCache.put(bookSourceUrl, ruleStr)
} }
val b = a.split("(&&|\n)+".toRegex()) }
b.forEach { c -> if (ruleStr.isJsonArray()) {
GSON.fromJsonArray<ExploreKind>(ruleStr)?.let {
kinds.addAll(it)
}
} else {
ruleStr.split("(&&|\n)+".toRegex()).forEach { c ->
val d = c.split("::") val d = c.split("::")
if (d.size > 1) if (d.size > 1)
add(ExploreKind(d[0], d[1])) kinds.add(ExploreKind(d[0], d[1]))
} }
}.onFailure {
add(ExploreKind(it.localizedMessage ?: ""))
} }
}.onFailure {
kinds.add(ExploreKind(it.localizedMessage ?: ""))
} }
} }
return kinds
} }
/** /**
@ -171,11 +174,6 @@ data class BookSource(
private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty()) private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())
data class ExploreKind(
var title: String,
var url: String? = null
)
class Converters { class Converters {
@TypeConverter @TypeConverter
fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule) fun exploreRuleToString(exploreRule: ExploreRule?): String = GSON.toJson(exploreRule)

@ -0,0 +1,39 @@
package io.legado.app.data.entities
data class ExploreKind(
val title: String,
val url: String? = null,
val style: Style? = null
) {
companion object {
val defaultStyle = Style()
}
fun style(): Style {
return style ?: defaultStyle
}
data class Style(
val layout_flexGrow: Float = 0F,
val layout_flexShrink: Float = 1F,
val layout_alignSelf: String = "auto",
val layout_flexBasisPercent: Float = -1F,
val layout_wrapBefore: Boolean = false,
) {
fun alignSelf(): Int {
return when (layout_alignSelf) {
"auto" -> -1
"flex_start" -> 0
"flex_end" -> 1
"center" -> 2
"baseline" -> 3
"stretch" -> 4
else -> -1
}
}
}
}

@ -35,7 +35,7 @@ object LocalConfig {
get() = !isLastVersion(1, "txtTocRuleVersion") get() = !isLastVersion(1, "txtTocRuleVersion")
val hasUpRssSources: Boolean val hasUpRssSources: Boolean
get() = !isLastVersion(3, "rssSourceVersion") get() = !isLastVersion(4, "rssSourceVersion")
var versionCode var versionCode
get() = localConfig.getLong(versionCodeKey, 0) get() = localConfig.getLong(versionCodeKey, 0)

@ -2,27 +2,21 @@ package io.legado.app.model.analyzeRule
//通用的规则切分处理 //通用的规则切分处理
class RuleAnalyzer(data: String, code:Boolean = false) { class RuleAnalyzer(data: String, code: Boolean = false) {
private var queue: String = data //被处理字符串 private var queue: String = data //被处理字符串
private var pos = 0 //处理到的位置 private var pos = 0 //当前处理到的位置
private var rule = arrayOf<String>() private var start = 0 //当前处理字段的开始
private var startX = 0 //当前规则的开始
private var start = 0 //每次处理字段的开始 private var rule = arrayOf<String>() //分割出的规则列表
private var startX = 0 //规则的开始 private var step: Int = 0 //分割字符的长度
private var step:Int = 0 //分割字符的长度 var elementsType = "" //当前分割字符串
val chompBalanced = if(code) ::chompCodeBalanced else ::chompRuleBalanced //设置平衡组函数,json或JavaScript时设置成chompCodeBalanced,否则为chompRuleBalanced //设置平衡组函数,json或JavaScript时设置成chompCodeBalanced,否则为chompRuleBalanced
val chompBalanced = if (code) ::chompCodeBalanced else ::chompRuleBalanced
var elementsType = "" fun trim() { // 修剪当前规则之前的"@"或者空白符
//当前平衡字段
fun currBalancedString( stepStart:Int = 1 , stepEnd:Int = 1): String { //stepStart平衡字符的起始分隔字串长度,stepEnd平衡字符的结束分隔字串长度
return queue.substring(startX+stepStart,pos-stepEnd) //当前平衡字段
}
fun trim(){ // 修剪当前规则之前的"@"或者空白符
while (queue[pos] == '@' || queue[pos] < '!') pos++ while (queue[pos] == '@' || queue[pos] < '!') pos++
} }
@ -33,14 +27,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
//当前拉取字段 //当前拉取字段
fun currString(): String { fun currString(): String {
return queue.substring(start,pos) //当前拉取到的字段 return queue.substring(start, pos) //当前拉取到的字段
}
//剩余字串
private fun remainingString(): String {
start = pos
pos = queue.length
return queue.substring(start)
} }
/** /**
@ -48,7 +35,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @return 若剩余字串中已无字符则返回true * @return 若剩余字串中已无字符则返回true
*/ */
val isEmpty: Boolean val isEmpty: Boolean
get() = queue.length - pos == 0 //是否处理到最后 get() = queue.length - pos == 0 //是否处理到最后
/** /**
* 消耗剩余字串中一个字符 * 消耗剩余字串中一个字符
@ -72,15 +59,13 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @param seq 分隔字符 **区分大小写** * @param seq 分隔字符 **区分大小写**
* @return 是否找到相应字段 * @return 是否找到相应字段
*/ */
fun consumeTo(seq: String,setStartPos:Boolean = true): Boolean { fun consumeTo(seq: String): Boolean {
start = pos //将处理到的位置设置为规则起点
if(setStartPos)start = pos //将处理到的位置设置为规则起点
val offset = queue.indexOf(seq, pos) val offset = queue.indexOf(seq, pos)
return if (offset != -1) { return if (offset != -1) {
pos = offset pos = offset
true true
} else false } else false
} }
/** /**
@ -88,7 +73,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @param seq 匹配字符串序列 * @param seq 匹配字符串序列
* @return 成功返回true并设置间隔失败则直接返回fasle * @return 成功返回true并设置间隔失败则直接返回fasle
*/ */
fun consumeToAny(vararg seq:String): Boolean { fun consumeToAny(vararg seq: String): Boolean {
var pos = pos //声明新变量记录匹配位置,不更改类本身的位置 var pos = pos //声明新变量记录匹配位置,不更改类本身的位置
@ -113,13 +98,13 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @param seq 匹配字符序列 * @param seq 匹配字符序列
* @return 返回匹配位置 * @return 返回匹配位置
*/ */
private fun findToAny(vararg seq:Char): Int { private fun findToAny(vararg seq: Char): Int {
var pos = pos //声明新变量记录匹配位置,不更改类本身的位置 var pos = pos //声明新变量记录匹配位置,不更改类本身的位置
while (pos != queue.length) { while (pos != queue.length) {
for (s in seq) if(queue[pos] == s) return pos //匹配则返回位置 for (s in seq) if (queue[pos] == s) return pos //匹配则返回位置
pos++ //逐个试探 pos++ //逐个试探
@ -129,13 +114,15 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
} }
//其中js只要符合语法,就不用避开任何阅读关键字,自由发挥 //其中js只要符合语法,就不用避开任何阅读关键字,自由发挥
fun chompJsBalanced(f: ((Char) -> Boolean?) = { fun chompJsBalanced(
when (it) { f: ((Char) -> Boolean?) = {
'{' -> true //开始嵌套一层 when (it) {
'}' -> false //闭合一层嵌套 '{' -> true //开始嵌套一层
else -> null '}' -> false //闭合一层嵌套
} else -> null
} ): Boolean { }
}
): Boolean {
var pos = pos //声明变量记录临时处理位置 var pos = pos //声明变量记录临时处理位置
var depth = 0 //嵌套深度 var depth = 0 //嵌套深度
var bracketsDepth = 0 //[]嵌套深度 var bracketsDepth = 0 //[]嵌套深度
@ -151,22 +138,23 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
if (pos == queue.length) break if (pos == queue.length) break
var c = queue[pos++] var c = queue[pos++]
if (c != '\\') { //非转义字符 if (c != '\\') { //非转义字符
if (c == '\'' && !commits && !commit && !regex && !inDoubleQuote && !inOtherQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号 if (c == '\'' && !commits && !commit && !regex && !inDoubleQuote && !inOtherQuote) inSingleQuote =
else if (c == '"' && !commits && !commit && !regex && !inSingleQuote && !inOtherQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号 !inSingleQuote //匹配具有语法功能的单引号
else if (c == '`' && !commits && !commit && !regex && !inSingleQuote && !inDoubleQuote) inOtherQuote = !inOtherQuote //匹配具有语法功能的'`' else if (c == '"' && !commits && !commit && !regex && !inSingleQuote && !inOtherQuote) inDoubleQuote =
!inDoubleQuote //匹配具有语法功能的双引号
else if (c == '`' && !commits && !commit && !regex && !inSingleQuote && !inDoubleQuote) inOtherQuote =
!inOtherQuote //匹配具有语法功能的'`'
else if (c == '/' && !commits && !commit && !regex && !inSingleQuote && !inDoubleQuote && !inOtherQuote) { //匹配注释或正则起点 else if (c == '/' && !commits && !commit && !regex && !inSingleQuote && !inDoubleQuote && !inOtherQuote) { //匹配注释或正则起点
c = queue[pos++] c = queue[pos++]
when(c){ when (c) {
'/'->commit=true //匹配单行注释起点 '/' -> commit = true //匹配单行注释起点
'*'->commits=true //匹配多行注释起点 '*' -> commits = true //匹配多行注释起点
else ->regex=true //匹配正则起点 else -> regex = true //匹配正则起点
} }
} } else if (commits && c == '*') { //匹配多行注释终点
else if(commits && c == '*') { //匹配多行注释终点
c = queue[pos++] c = queue[pos++]
if(c == '/')commits = false if (c == '/') commits = false
} } else if (regex && c == '/') { //正则的终点或[]平衡
else if(regex && c == '/') { //正则的终点或[]平衡
when (c) { when (c) {
'/' -> regex = false//匹配正则终点 '/' -> regex = false//匹配正则终点
@ -177,25 +165,25 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
']' -> bracketsDepth-- //闭合一层嵌套[] ']' -> bracketsDepth-- //闭合一层嵌套[]
} }
}else if(c == '\n') commit = false } else if (c == '\n') commit = false
if (commits || commit || regex || inSingleQuote || inDoubleQuote || inOtherQuote) continue //语法单元未匹配结束,直接进入下个循环 if (commits || commit || regex || inSingleQuote || inDoubleQuote || inOtherQuote) continue //语法单元未匹配结束,直接进入下个循环
val fn = f(c) ?: continue val fn = f(c) ?: continue
if (fn) depth++ else depth-- //嵌套或者闭合 if (fn) depth++ else depth-- //嵌套或者闭合
}else pos++ } else pos++
} while (depth > 0 || bracketsDepth >0) //拉出全部符合js语法的字段 } while (depth > 0 || bracketsDepth > 0) //拉出全部符合js语法的字段
return if(depth > 0 || bracketsDepth > 0) false else { return if (depth > 0 || bracketsDepth > 0) false else {
this.pos = pos //同步位置 this.pos = pos //同步位置
true true
} }
} }
/** /**
* 拉出一个代码平衡组存在转义文本没有实体字符通常以{}作为模块 * 拉出一个非内嵌代码平衡组存在转义文本
*/ */
fun chompCodeBalanced(open: Char, close: Char): Boolean { fun chompCodeBalanced(open: Char, close: Char): Boolean {
@ -214,22 +202,21 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
if (c == '\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号 if (c == '\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号
else if (c == '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号 else if (c == '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号
if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环 if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环
if ( c == '[' )depth++ //开始嵌套一层 if (c == '[') depth++ //开始嵌套一层
else if ( c== ']') depth-- //闭合一层嵌套 else if (c == ']') depth-- //闭合一层嵌套
else if(depth == 0 ) { else if (depth == 0) {
//处于默认嵌套中的非默认字符不需要平衡,仅depth为0时默认嵌套全部闭合,此字符才进行嵌套 //处于默认嵌套中的非默认字符不需要平衡,仅depth为0时默认嵌套全部闭合,此字符才进行嵌套
if(c == open)otherDepth++ if (c == open) otherDepth++
else if(c == close)otherDepth-- else if (c == close) otherDepth--
} }
}else pos++ } else pos++
} while (depth > 0 || otherDepth > 0) //拉出一个平衡字串 } while (depth > 0 || otherDepth > 0) //拉出一个平衡字串
return if(depth > 0 || otherDepth > 0) false else { return if (depth > 0 || otherDepth > 0) false else {
startX = this.pos //内嵌规则起始
this.pos = pos //同步位置 this.pos = pos //同步位置
true true
} }
@ -251,18 +238,18 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
if (c == '\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号 if (c == '\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号
else if (c == '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号 else if (c == '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号
if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环 if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环
else if( c=='\\' ){ //不在引号中的转义字符才将下个字符转义 else if (c == '\\') { //不在引号中的转义字符才将下个字符转义
pos++ pos++
continue continue
} }
if ( c == open )depth++ //开始嵌套一层 if (c == open) depth++ //开始嵌套一层
else if ( c== close) depth-- //闭合一层嵌套 else if (c == close) depth-- //闭合一层嵌套
} while (depth > 0 ) //拉出一个平衡字串 } while (depth > 0) //拉出一个平衡字串
return if(depth > 0) false else { return if (depth > 0) false else {
this.pos = pos //同步位置 this.pos = pos //同步位置
true true
} }
@ -272,18 +259,18 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则 * 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则
* 解决jsonPath自带的"&&""||"与阅读的规则冲突,以及规则正则或字符串中包含"&&""||""%%""@"导致的冲突 * 解决jsonPath自带的"&&""||"与阅读的规则冲突,以及规则正则或字符串中包含"&&""||""%%""@"导致的冲突
*/ */
tailrec fun splitRule(vararg split: String): Array<String>{ //首段匹配,elementsType为空 tailrec fun splitRule(vararg split: String): Array<String> { //首段匹配,elementsType为空
if(split.size == 1) { if (split.size == 1) {
elementsType = split[0] //设置分割字串 elementsType = split[0] //设置分割字串
return if(!consumeTo(elementsType)) { return if (!consumeTo(elementsType)) {
rule += queue.substring(startX) rule += queue.substring(startX)
rule rule
}else { } else {
step = elementsType.length //设置分隔符长度 step = elementsType.length //设置分隔符长度
splitRule() splitRule()
} //递归匹配 } //递归匹配
}else if (!consumeToAny(* split)) { //未找到分隔符 } else if (!consumeToAny(* split)) { //未找到分隔符
rule += queue.substring(startX) rule += queue.substring(startX)
return rule return rule
} }
@ -291,7 +278,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
val end = pos //记录分隔位置 val end = pos //记录分隔位置
pos = start //重回开始,启动另一种查找 pos = start //重回开始,启动另一种查找
do{ do {
val st = findToAny('[', '(') //查找筛选器位置 val st = findToAny('[', '(') //查找筛选器位置
if (st == -1) { if (st == -1) {
@ -323,11 +310,10 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
pos += step //跳过分隔符 pos += step //跳过分隔符
} }
return if(pos > st) { return if (pos > st) {
startX = start startX = start
splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配 splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配
} } else { //执行到此,证明后面再无分隔字符
else { //执行到此,证明后面再无分隔字符
rule += queue.substring(pos) //将剩余字段压入数组末尾 rule += queue.substring(pos) //将剩余字段压入数组末尾
rule rule
} }
@ -337,13 +323,13 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符
if (!chompBalanced(queue[pos], next)) throw Error( if (!chompBalanced(queue[pos], next)) throw Error(
queue.substring( queue.substring(
0, 0,
start start
) + "后未平衡" ) + "后未平衡"
) //拉出一个筛选器,不平衡则报错 ) //拉出一个筛选器,不平衡则报错
}while( end > pos ) } while (end > pos)
start = pos //设置开始查找筛选器位置的起始位置 start = pos //设置开始查找筛选器位置的起始位置
@ -351,12 +337,12 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
} }
@JvmName("splitRuleNext") @JvmName("splitRuleNext")
private tailrec fun splitRule(): Array<String>{ //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快 private tailrec fun splitRule(): Array<String> { //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快
val end = pos //记录分隔位置 val end = pos //记录分隔位置
pos = start //重回开始,启动另一种查找 pos = start //重回开始,启动另一种查找
do{ do {
val st = findToAny('[', '(') //查找筛选器位置 val st = findToAny('[', '(') //查找筛选器位置
if (st == -1) { if (st == -1) {
@ -384,11 +370,10 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
pos += step //跳过分隔符 pos += step //跳过分隔符
} }
return if(pos > st) { return if (pos > st) {
startX = start startX = start
splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配 splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配
} } else { //执行到此,证明后面再无分隔字符
else { //执行到此,证明后面再无分隔字符
rule += queue.substring(pos) //将剩余字段压入数组末尾 rule += queue.substring(pos) //将剩余字段压入数组末尾
rule rule
} }
@ -398,24 +383,23 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符 val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符
if (!chompBalanced(queue[pos], next)) throw Error( if (!chompBalanced(queue[pos], next)) throw Error(
queue.substring( queue.substring(
0, 0,
start start
) + "后未平衡" ) + "后未平衡"
) //拉出一个筛选器,不平衡则报错 ) //拉出一个筛选器,不平衡则报错
}while( end > pos ) } while (end > pos)
start = pos //设置开始查找筛选器位置的起始位置 start = pos //设置开始查找筛选器位置的起始位置
return if(!consumeTo(elementsType)) { return if (!consumeTo(elementsType)) {
rule += queue.substring(startX) rule += queue.substring(startX)
rule rule
}else splitRule() //递归匹配 } else splitRule() //递归匹配
} }
/** /**
* 替换内嵌规则 * 替换内嵌规则
* @param inner 起始标志,{$. {{ * @param inner 起始标志,{$. {{
@ -424,67 +408,32 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @param fr 查找到内嵌规则时用于解析的函数 * @param fr 查找到内嵌规则时用于解析的函数
* *
* */ * */
fun innerRule( inner:String,startStep:Int = 1,endStep:Int = 1,fr:(String)->String?): String { fun innerRule(
inner: String,
val start0 = pos //规则匹配前起点 startStep: Int = 1,
endStep: Int = 1,
fr: (String) -> String?
): String {
startX = pos //设置规则起点
val st = StringBuilder() val st = StringBuilder()
while (!isEmpty && consumeTo(inner)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true while (consumeTo(inner)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true
val posPre = pos //记录上次结束位置 if (chompCodeBalanced('{', '}')) {
if (chompBalanced('{', '}')) { val frv = fr(queue.substring(start + startStep, pos - endStep))
val frv= fr(currBalancedString(startStep,endStep)) if (frv != null) {
if(frv != null) { st.append(queue.substring(startX, start) + frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串
st.append(queue.substring(start,posPre)+frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串 startX = pos //记录下次规则起点
continue //获取内容成功,继续选择下个内嵌规则 continue //获取内容成功,继续选择下个内嵌规则
} }
} }
pos += inner.length //拉出字段不平衡,inner只是个普通字串,跳到此inner后继续匹配 pos += inner.length //拉出字段不平衡,inner只是个普通字串,跳到此inner后继续匹配
} }
//匹配前起点与当前规则起点相同,证明无替换成功的内嵌规则,返回空字符串。否则返回替换后的字符串 st.append(queue.substring(startX)) //压入剩余字符串
return if(start0 == start) "" else { return st.toString()
st.append(remainingString()) //压入剩余字符串
st.toString()
}
} }
// /**
// * 匹配并返回标签中的属性键字串(字母、数字、-、_、:)
// * @return 属性键字串
// */
// fun consumeAttributeKey(start:Int = pos): String {
// while (!isEmpty && (Character.isLetterOrDigit(queue[pos]) || matchesAny('-', '_', ':'))) pos++
// return queue.substring(start, pos)
// }
// fun splitRule(query:String,item:String = "other",listItem:String = "allInOne"):String{
//
// val cuurItem = item //当前项类型,list->列表项 mulu->章节列表项 url->链接项 search->搜索链接项 find发现链接列表项 other->其他项
// val cuurList = listItem//当前界面总列表项类型,allInOne,json,xml,kotin,java
// var Reverse = false //是否反转列表
//
// consumeWhitespace() //消耗开头空白
// var fisrt = consume() //拉出并消费首字符
//
// when(item){
// "search" ->
// "find" ->
// "mulu" -> if(fisrt == '-'){
// Reverse=true //开启反转
// consumeWhitespace() //拉出所有空白符
// fisrt = consume() //首字符后移
// }
// else ->
//
// }
//
// return query
// }
companion object { companion object {
/** /**
* 转义字符 * 转义字符
@ -495,7 +444,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* 阅读共有分隔字串起始部分 * 阅读共有分隔字串起始部分
* "##","@@","{{","{[","<js>", "@js:" * "##","@@","{{","{[","<js>", "@js:"
*/ */
val splitList =arrayOf("##","@@","{{","{[","<js>", "@js:") val splitList = arrayOf("##", "@@", "{{", "{[", "<js>", "@js:")
/** /**
* 发现名称-链接分隔字串 * 发现名称-链接分隔字串
@ -519,7 +468,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* 结果为元素列表的项的同规则组合结构 * 结果为元素列表的项的同规则组合结构
* "||","&&","%%" * "||","&&","%%"
*/ */
val splitListReSplit = arrayOf("||","&&","%%") val splitListReSplit = arrayOf("||", "&&", "%%")
/** /**
* js脚本结束字串 * js脚本结束字串
@ -542,27 +491,27 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
/** /**
* '[', ']', '(', ')','{','}' * '[', ']', '(', ')','{','}'
*/ */
val splitListPublic = charArrayOf('[', ']', '(', ')','{','}') val splitListPublic = charArrayOf('[', ']', '(', ')', '{', '}')
/** /**
* '*',"/","//",":","::","@","|","@xpath:" * '*',"/","//",":","::","@","|","@xpath:"
*/ */
val splitListXpath = arrayOf("*","/","//",":","::","@","|","@xpath:") val splitListXpath = arrayOf("*", "/", "//", ":", "::", "@", "|", "@xpath:")
/** /**
* '*','$',".","..", "@json:" * '*','$',".","..", "@json:"
*/ */
val splitListJson = arrayOf('*','$',".","..", "@json:") val splitListJson = arrayOf('*', '$', ".", "..", "@json:")
/** /**
* '*',"+","~",".",",","|","@","@css:",":" * '*',"+","~",".",",","|","@","@css:",":"
*/ */
val splitListCss = arrayOf('*',"+","~",".",",","|","@","@css:",":") val splitListCss = arrayOf('*', "+", "~", ".", ",", "|", "@", "@css:", ":")
/** /**
* "-",".","!","@","@@" * "-",".","!","@","@@"
*/ */
val splitListDefault = arrayOf("-",".","!","@","@@") val splitListDefault = arrayOf("-", ".", "!", "@", "@@")
} }
} }

@ -131,11 +131,18 @@ class CheckSourceService : BaseService() {
}.timeout(180000L) }.timeout(180000L)
.onError { .onError {
source.addGroup("失效") source.addGroup("失效")
source.bookSourceComment = source.bookSourceComment = """
"error:${it.localizedMessage}\n${source.bookSourceComment}" "error:${it.localizedMessage}
${source.bookSourceComment}"
""".trimIndent()
appDb.bookSourceDao.update(source) appDb.bookSourceDao.update(source)
}.onSuccess { }.onSuccess {
source.removeGroup("失效") source.removeGroup("失效")
source.bookSourceComment = source.bookSourceComment
?.split("\n")
?.filterNot {
it.startsWith("error:")
}?.joinToString("\n")
appDb.bookSourceDao.update(source) appDb.bookSourceDao.update(source)
}.onFinally { }.onFinally {
onNext(source.bookSourceUrl, source.bookSourceName) onNext(source.bookSourceUrl, source.bookSourceName)

@ -56,7 +56,7 @@ class ReadRecordActivity : BaseActivity<ActivityReadRecordBinding>() {
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
readRecord.tvBookName.setText(R.string.all_read_time) readRecord.tvBookName.setText(R.string.all_read_time)
adapter = RecordAdapter(this@ReadRecordActivity) adapter = RecordAdapter(this@ReadRecordActivity)
recyclerView.adapter = adapter recyclerView.adapter = adapter

@ -31,7 +31,7 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
item: Book, item: Book,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
tvName.text = item.name tvName.text = item.name
tvAuthor.text = context.getString(R.string.author_show, item.getRealAuthor()) tvAuthor.text = context.getString(R.string.author_show, item.getRealAuthor())
@ -53,7 +53,7 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
} }
override fun registerListener(holder: ItemViewHolder, binding: ItemDownloadBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemDownloadBinding) {
with(binding) { binding.run {
ivDownload.setOnClickListener { ivDownload.setOnClickListener {
getItem(holder.layoutPosition)?.let { getItem(holder.layoutPosition)?.let {
if (downloadMap?.containsKey(it.bookUrl) == true) { if (downloadMap?.containsKey(it.bookUrl) == true) {

@ -34,7 +34,7 @@ class CoverAdapter(context: Context, val callBack: CallBack) :
binding: ItemCoverBinding, binding: ItemCoverBinding,
item: SearchBook, item: SearchBook,
payloads: MutableList<Any> payloads: MutableList<Any>
) = with(binding) { ) = binding.run {
ivCover.load(item.coverUrl, item.name, item.author) ivCover.load(item.coverUrl, item.name, item.author)
tvSource.text = item.originName tvSource.text = item.originName
} }

@ -160,7 +160,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
item: BookGroup, item: BookGroup,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
root.setBackgroundColor(context.backgroundColor) root.setBackgroundColor(context.backgroundColor)
tvGroup.text = item.getManageName(context) tvGroup.text = item.getManageName(context)
swShow.isChecked = item.show swShow.isChecked = item.show
@ -168,7 +168,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
} }
override fun registerListener(holder: ItemViewHolder, binding: ItemBookGroupManageBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemBookGroupManageBinding) {
with(binding) { binding.run {
tvEdit.setOnClickListener { getItem(holder.layoutPosition)?.let { editGroup(it) } } tvEdit.setOnClickListener { getItem(holder.layoutPosition)?.let { editGroup(it) } }
swShow.setOnCheckedChangeListener { buttonView, isChecked -> swShow.setOnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed) { if (buttonView.isPressed) {

@ -168,7 +168,7 @@ class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
item: BookGroup, item: BookGroup,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
root.setBackgroundColor(context.backgroundColor) root.setBackgroundColor(context.backgroundColor)
cbGroup.text = item.groupName cbGroup.text = item.groupName
cbGroup.isChecked = (groupId and item.groupId) > 0 cbGroup.isChecked = (groupId and item.groupId) > 0
@ -176,7 +176,7 @@ class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
} }
override fun registerListener(holder: ItemViewHolder, binding: ItemGroupSelectBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemGroupSelectBinding) {
with(binding) { binding.run {
cbGroup.setOnCheckedChangeListener { buttonView, isChecked -> cbGroup.setOnCheckedChangeListener { buttonView, isChecked ->
if (buttonView.isPressed) { if (buttonView.isPressed) {
getItem(holder.layoutPosition)?.let { getItem(holder.layoutPosition)?.let {

@ -172,7 +172,7 @@ class BookInfoActivity :
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
private fun showBook(book: Book) = with(binding) { private fun showBook(book: Book) = binding.run {
showCover(book) showCover(book)
tvName.text = book.name tvName.text = book.name
tvAuthor.text = getString(R.string.author_show, book.getRealAuthor()) tvAuthor.text = getString(R.string.author_show, book.getRealAuthor())
@ -243,7 +243,7 @@ class BookInfoActivity :
} }
} }
private fun initOnClick() = with(binding) { private fun initOnClick() = binding.run {
ivCover.setOnClickListener { ivCover.setOnClickListener {
viewModel.bookData.value?.let { viewModel.bookData.value?.let {
ChangeCoverDialog.show(supportFragmentManager, it.name, it.author) ChangeCoverDialog.show(supportFragmentManager, it.name, it.author)

@ -55,7 +55,7 @@ class BookInfoEditActivity :
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
private fun initEvent() = with(binding) { private fun initEvent() = binding.run {
tvChangeCover.setOnClickListener { tvChangeCover.setOnClickListener {
viewModel.bookData.value?.let { viewModel.bookData.value?.let {
ChangeCoverDialog.show(supportFragmentManager, it.name, it.author) ChangeCoverDialog.show(supportFragmentManager, it.name, it.author)
@ -70,7 +70,7 @@ class BookInfoEditActivity :
} }
} }
private fun upView(book: Book) = with(binding) { private fun upView(book: Book) = binding.run {
tieBookName.setText(book.name) tieBookName.setText(book.name)
tieBookAuthor.setText(book.author) tieBookAuthor.setText(book.author)
tieCoverUrl.setText(book.getDisplayCover()) tieCoverUrl.setText(book.getDisplayCover())
@ -84,7 +84,7 @@ class BookInfoEditActivity :
} }
} }
private fun saveData() = with(binding) { private fun saveData() = binding.run {
viewModel.book?.let { book -> viewModel.book?.let { book ->
book.name = tieBookName.text?.toString() ?: "" book.name = tieBookName.text?.toString() ?: ""
book.author = tieBookAuthor.text?.toString() ?: "" book.author = tieBookAuthor.text?.toString() ?: ""

@ -31,7 +31,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
item: DocItem, item: DocItem,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
if (item.isDir) { if (item.isDir) {
ivIcon.setImageResource(R.drawable.ic_folder) ivIcon.setImageResource(R.drawable.ic_folder)

@ -105,7 +105,12 @@ class ReadBookActivity : ReadBookBaseActivity(),
} }
private var menu: Menu? = null private var menu: Menu? = null
private val textActionMenu: TextActionMenu by lazy { private val textActionMenu: TextActionMenu by lazy {
TextActionMenu(this, this) TextActionMenu(this, this).apply {
contentView.measure(
View.MeasureSpec.UNSPECIFIED,
View.MeasureSpec.UNSPECIFIED
)
}
} }
override val scope: CoroutineScope get() = this override val scope: CoroutineScope get() = this
@ -416,7 +421,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
* view触摸,文字选择 * view触摸,文字选择
*/ */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View, event: MotionEvent): Boolean = with(binding) { override fun onTouch(v: View, event: MotionEvent): Boolean = binding.run {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> textActionMenu.dismiss() MotionEvent.ACTION_DOWN -> textActionMenu.dismiss()
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
@ -439,7 +444,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/** /**
* 更新文字选择开始位置 * 更新文字选择开始位置
*/ */
override fun upSelectedStart(x: Float, y: Float, top: Float) = with(binding) { override fun upSelectedStart(x: Float, y: Float, top: Float) = binding.run {
cursorLeft.x = x - cursorLeft.width cursorLeft.x = x - cursorLeft.width
cursorLeft.y = y cursorLeft.y = y
cursorLeft.visible(true) cursorLeft.visible(true)
@ -450,7 +455,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/** /**
* 更新文字选择结束位置 * 更新文字选择结束位置
*/ */
override fun upSelectedEnd(x: Float, y: Float) = with(binding) { override fun upSelectedEnd(x: Float, y: Float) = binding.run {
cursorRight.x = x cursorRight.x = x
cursorRight.y = y cursorRight.y = y
cursorRight.visible(true) cursorRight.visible(true)
@ -459,7 +464,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/** /**
* 取消文字选择 * 取消文字选择
*/ */
override fun onCancelSelect() = with(binding) { override fun onCancelSelect() = binding.run {
cursorLeft.invisible() cursorLeft.invisible()
cursorRight.invisible() cursorRight.invisible()
textActionMenu.dismiss() textActionMenu.dismiss()
@ -468,11 +473,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/** /**
* 显示文本操作菜单 * 显示文本操作菜单
*/ */
override fun showTextActionMenu() = with(binding) { override fun showTextActionMenu() = binding.run {
textActionMenu.contentView.measure(
View.MeasureSpec.UNSPECIFIED,
View.MeasureSpec.UNSPECIFIED
)
val popupHeight = textActionMenu.contentView.measuredHeight val popupHeight = textActionMenu.contentView.measuredHeight
val x = textMenuPosition.x.toInt() val x = textMenuPosition.x.toInt()
var y = textMenuPosition.y.toInt() - popupHeight var y = textMenuPosition.y.toInt() - popupHeight
@ -543,7 +544,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/** /**
* 文本选择菜单操作完成 * 文本选择菜单操作完成
*/ */
override fun onMenuActionFinally() = with(binding) { override fun onMenuActionFinally() = binding.run {
textActionMenu.dismiss() textActionMenu.dismiss()
readView.curPage.cancelSelect() readView.curPage.cancelSelect()
readView.isTextSelected = false readView.isTextSelected = false
@ -796,7 +797,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/** /**
* colorSelectDialog * colorSelectDialog
*/ */
override fun onColorSelected(dialogId: Int, color: Int) = with(ReadBookConfig.durConfig) { override fun onColorSelected(dialogId: Int, color: Int) = ReadBookConfig.durConfig.run {
when (dialogId) { when (dialogId) {
TEXT_COLOR -> { TEXT_COLOR -> {
setCurTextColor(color) setCurTextColor(color)
@ -899,7 +900,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
} }
} }
override fun observeLiveBus() = with(binding) { override fun observeLiveBus() = binding.run {
super.observeLiveBus() super.observeLiveBus()
observeEvent<String>(EventBus.TIME_CHANGED) { readView.upTime() } observeEvent<String>(EventBus.TIME_CHANGED) { readView.upTime() }
observeEvent<Int>(EventBus.BATTERY_CHANGED) { readView.upBattery(it) } observeEvent<Int>(EventBus.BATTERY_CHANGED) { readView.upBattery(it) }

@ -52,7 +52,7 @@ class ReadMenu @JvmOverloads constructor(
bindEvent() bindEvent()
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
if (AppConfig.isNightTheme) { if (AppConfig.isNightTheme) {
fabNightTheme.setImageResource(R.drawable.ic_daytime) fabNightTheme.setImageResource(R.drawable.ic_daytime)
} else { } else {
@ -134,7 +134,7 @@ class ReadMenu @JvmOverloads constructor(
return context.getPrefBoolean("brightnessAuto", true) || !showBrightnessView return context.getPrefBoolean("brightnessAuto", true) || !showBrightnessView
} }
private fun bindEvent() = with(binding) { private fun bindEvent() = binding.run {
tvChapterName.setOnClickListener { tvChapterName.setOnClickListener {
callBack.openSourceEditActivity() callBack.openSourceEditActivity()
} }
@ -304,7 +304,7 @@ class ReadMenu @JvmOverloads constructor(
binding.seekReadPage.progress = seek binding.seekReadPage.progress = seek
} }
fun setAutoPage(autoPage: Boolean) = with(binding) { fun setAutoPage(autoPage: Boolean) = binding.run {
if (autoPage) { if (autoPage) {
fabAutoPage.setImageResource(R.drawable.ic_auto_page_stop) fabAutoPage.setImageResource(R.drawable.ic_auto_page_stop)
fabAutoPage.contentDescription = context.getString(R.string.auto_next_page_stop) fabAutoPage.contentDescription = context.getString(R.string.auto_next_page_stop)

@ -17,22 +17,23 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.view.SupportMenuInflater import androidx.appcompat.view.SupportMenuInflater
import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.view.menu.MenuItemImpl import androidx.appcompat.view.menu.MenuItemImpl
import androidx.core.view.isVisible
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.databinding.ItemTextBinding import io.legado.app.databinding.ItemTextBinding
import io.legado.app.databinding.PopupActionMenuBinding import io.legado.app.databinding.PopupActionMenuBinding
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.utils.* import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.sendToClip
import io.legado.app.utils.share
import io.legado.app.utils.toastOnUi
import splitties.views.onClick
import java.util.* import java.util.*
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
class TextActionMenu(private val context: Context, private val callBack: CallBack) : class TextActionMenu(private val context: Context, private val callBack: CallBack) :
PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT), PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT),
TextToSpeech.OnInitListener { TextToSpeech.OnInitListener {
private val binding = PopupActionMenuBinding.inflate(LayoutInflater.from(context)) private val binding = PopupActionMenuBinding.inflate(LayoutInflater.from(context))
private val adapter = Adapter(context)
private val menu = MenuBuilder(context) private val menu = MenuBuilder(context)
private val moreMenu = MenuBuilder(context) private val moreMenu = MenuBuilder(context)
private val ttsListener by lazy { private val ttsListener by lazy {
@ -47,68 +48,23 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
isOutsideTouchable = false isOutsideTouchable = false
isFocusable = false isFocusable = false
initRecyclerView()
setOnDismissListener {
binding.ivMenuMore.setImageResource(R.drawable.ic_more_vert)
binding.recyclerViewMore.gone()
adapter.setItems(menu.visibleItems)
binding.recyclerView.visible()
}
}
private fun initRecyclerView() = with(binding) {
recyclerView.adapter = adapter
recyclerViewMore.adapter = adapter
SupportMenuInflater(context).inflate(R.menu.content_select_action, menu) SupportMenuInflater(context).inflate(R.menu.content_select_action, menu)
adapter.setItems(menu.visibleItems)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
onInitializeMenu(moreMenu) onInitializeMenu(moreMenu)
} }
if (moreMenu.size() > 0) { val menuItems = menu.visibleItems + moreMenu.visibleItems
ivMenuMore.visible() menuItems.forEach {
} val textView = ItemTextBinding.inflate(LayoutInflater.from(context)).root.apply {
ivMenuMore.setOnClickListener { tag = it
if (recyclerView.isVisible) { text = it.title
ivMenuMore.setImageResource(R.drawable.ic_arrow_back) onClick {
adapter.setItems(moreMenu.visibleItems)
recyclerView.gone()
recyclerViewMore.visible()
} else {
ivMenuMore.setImageResource(R.drawable.ic_more_vert)
recyclerViewMore.gone()
adapter.setItems(menu.visibleItems)
recyclerView.visible()
}
}
}
inner class Adapter(context: Context) :
RecyclerAdapter<MenuItemImpl, ItemTextBinding>(context) {
override fun getViewBinding(parent: ViewGroup): ItemTextBinding {
return ItemTextBinding.inflate(inflater, parent, false)
}
override fun convert(
holder: ItemViewHolder,
binding: ItemTextBinding,
item: MenuItemImpl,
payloads: MutableList<Any>
) {
with(binding) {
textView.text = item.title
}
}
override fun registerListener(holder: ItemViewHolder, binding: ItemTextBinding) {
holder.itemView.setOnClickListener {
getItem(holder.layoutPosition)?.let {
if (!callBack.onMenuItemSelected(it.itemId)) { if (!callBack.onMenuItemSelected(it.itemId)) {
onMenuItemSelected(it) onMenuItemSelected(it)
} }
callBack.onMenuActionFinally()
} }
callBack.onMenuActionFinally()
} }
binding.root.addView(textView)
} }
} }

@ -52,21 +52,21 @@ class AutoReadDialog : BaseDialogFragment() {
return inflater.inflate(R.layout.dialog_auto_read, container) return inflater.inflate(R.layout.dialog_auto_read, container)
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
val bg = requireContext().bottomBackground val bg = requireContext().bottomBackground
val isLight = ColorUtils.isColorLight(bg) val isLight = ColorUtils.isColorLight(bg)
val textColor = requireContext().getPrimaryTextColor(isLight) val textColor = requireContext().getPrimaryTextColor(isLight)
binding.root.setBackgroundColor(bg) root.setBackgroundColor(bg)
binding.tvReadSpeedTitle.setTextColor(textColor) tvReadSpeedTitle.setTextColor(textColor)
binding.tvReadSpeed.setTextColor(textColor) tvReadSpeed.setTextColor(textColor)
binding.ivCatalog.setColorFilter(textColor) ivCatalog.setColorFilter(textColor)
binding.tvCatalog.setTextColor(textColor) tvCatalog.setTextColor(textColor)
binding.ivMainMenu.setColorFilter(textColor) ivMainMenu.setColorFilter(textColor)
binding.tvMainMenu.setTextColor(textColor) tvMainMenu.setTextColor(textColor)
binding.ivAutoPageStop.setColorFilter(textColor) ivAutoPageStop.setColorFilter(textColor)
binding.tvAutoPageStop.setTextColor(textColor) tvAutoPageStop.setTextColor(textColor)
binding.ivSetting.setColorFilter(textColor) ivSetting.setColorFilter(textColor)
binding.tvSetting.setTextColor(textColor) tvSetting.setTextColor(textColor)
initOnChange() initOnChange()
initData() initData()
initEvent() initEvent()

@ -25,7 +25,7 @@ class BgAdapter(context: Context, val textColor: Int) :
item: String, item: String,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
ImageLoader.load( ImageLoader.load(
context, context,
context.assets.open("bg${File.separator}$item").readBytes() context.assets.open("bg${File.separator}$item").readBytes()

@ -98,7 +98,7 @@ class BgTextConfigDialog : BaseDialogFragment() {
(activity as ReadBookActivity).bottomDialog-- (activity as ReadBookActivity).bottomDialog--
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
val bg = requireContext().bottomBackground val bg = requireContext().bottomBackground
val isLight = ColorUtils.isColorLight(bg) val isLight = ColorUtils.isColorLight(bg)
primaryTextColor = requireContext().getPrimaryTextColor(isLight) primaryTextColor = requireContext().getPrimaryTextColor(isLight)

@ -56,15 +56,15 @@ class ClickActionConfigDialog : BaseDialogFragment() {
initViewEvent() initViewEvent()
} }
private fun initData() = with(AppConfig) { private fun initData() = binding.run {
binding.tvTopLeft.text = actions[clickActionTL] tvTopLeft.text = actions[AppConfig.clickActionTL]
binding.tvTopCenter.text = actions[clickActionTC] tvTopCenter.text = actions[AppConfig.clickActionTC]
binding.tvTopRight.text = actions[clickActionTR] tvTopRight.text = actions[AppConfig.clickActionTR]
binding.tvMiddleLeft.text = actions[clickActionML] tvMiddleLeft.text = actions[AppConfig.clickActionML]
binding.tvMiddleRight.text = actions[clickActionMR] tvMiddleRight.text = actions[AppConfig.clickActionMR]
binding.tvBottomLeft.text = actions[clickActionBL] tvBottomLeft.text = actions[AppConfig.clickActionBL]
binding.tvBottomCenter.text = actions[clickActionBC] tvBottomCenter.text = actions[AppConfig.clickActionBC]
binding.tvBottomRight.text = actions[clickActionBR] tvBottomRight.text = actions[AppConfig.clickActionBR]
} }
private fun initViewEvent() { private fun initViewEvent() {

@ -49,84 +49,84 @@ class PaddingConfigDialog : BaseDialogFragment() {
ReadBookConfig.save() ReadBookConfig.save()
} }
private fun initData() = ReadBookConfig.apply { private fun initData() = binding.run {
//正文 //正文
binding.dsbPaddingTop.progress = paddingTop dsbPaddingTop.progress = ReadBookConfig.paddingTop
binding.dsbPaddingBottom.progress = paddingBottom dsbPaddingBottom.progress = ReadBookConfig.paddingBottom
binding.dsbPaddingLeft.progress = paddingLeft dsbPaddingLeft.progress = ReadBookConfig.paddingLeft
binding.dsbPaddingRight.progress = paddingRight dsbPaddingRight.progress = ReadBookConfig.paddingRight
//页眉 //页眉
binding.dsbHeaderPaddingTop.progress = headerPaddingTop dsbHeaderPaddingTop.progress = ReadBookConfig.headerPaddingTop
binding.dsbHeaderPaddingBottom.progress = headerPaddingBottom dsbHeaderPaddingBottom.progress = ReadBookConfig.headerPaddingBottom
binding.dsbHeaderPaddingLeft.progress = headerPaddingLeft dsbHeaderPaddingLeft.progress = ReadBookConfig.headerPaddingLeft
binding.dsbHeaderPaddingRight.progress = headerPaddingRight dsbHeaderPaddingRight.progress = ReadBookConfig.headerPaddingRight
//页脚 //页脚
binding.dsbFooterPaddingTop.progress = footerPaddingTop dsbFooterPaddingTop.progress = ReadBookConfig.footerPaddingTop
binding.dsbFooterPaddingBottom.progress = footerPaddingBottom dsbFooterPaddingBottom.progress = ReadBookConfig.footerPaddingBottom
binding.dsbFooterPaddingLeft.progress = footerPaddingLeft dsbFooterPaddingLeft.progress = ReadBookConfig.footerPaddingLeft
binding.dsbFooterPaddingRight.progress = footerPaddingRight dsbFooterPaddingRight.progress = ReadBookConfig.footerPaddingRight
binding.cbShowTopLine.isChecked = showHeaderLine cbShowTopLine.isChecked = ReadBookConfig.showHeaderLine
binding.cbShowBottomLine.isChecked = showFooterLine cbShowBottomLine.isChecked = ReadBookConfig.showFooterLine
} }
private fun initView() = with(ReadBookConfig) { private fun initView() = binding.run {
//正文 //正文
binding.dsbPaddingTop.onChanged = { dsbPaddingTop.onChanged = {
paddingTop = it ReadBookConfig.paddingTop = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbPaddingBottom.onChanged = { dsbPaddingBottom.onChanged = {
paddingBottom = it ReadBookConfig.paddingBottom = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbPaddingLeft.onChanged = { dsbPaddingLeft.onChanged = {
paddingLeft = it ReadBookConfig.paddingLeft = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbPaddingRight.onChanged = { dsbPaddingRight.onChanged = {
paddingRight = it ReadBookConfig.paddingRight = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
//页眉 //页眉
binding.dsbHeaderPaddingTop.onChanged = { dsbHeaderPaddingTop.onChanged = {
headerPaddingTop = it ReadBookConfig.headerPaddingTop = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbHeaderPaddingBottom.onChanged = { dsbHeaderPaddingBottom.onChanged = {
headerPaddingBottom = it ReadBookConfig.headerPaddingBottom = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbHeaderPaddingLeft.onChanged = { dsbHeaderPaddingLeft.onChanged = {
headerPaddingLeft = it ReadBookConfig.headerPaddingLeft = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbHeaderPaddingRight.onChanged = { dsbHeaderPaddingRight.onChanged = {
headerPaddingRight = it ReadBookConfig.headerPaddingRight = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
//页脚 //页脚
binding.dsbFooterPaddingTop.onChanged = { dsbFooterPaddingTop.onChanged = {
footerPaddingTop = it ReadBookConfig.footerPaddingTop = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbFooterPaddingBottom.onChanged = { dsbFooterPaddingBottom.onChanged = {
footerPaddingBottom = it ReadBookConfig.footerPaddingBottom = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbFooterPaddingLeft.onChanged = { dsbFooterPaddingLeft.onChanged = {
footerPaddingLeft = it ReadBookConfig.footerPaddingLeft = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.dsbFooterPaddingRight.onChanged = { dsbFooterPaddingRight.onChanged = {
footerPaddingRight = it ReadBookConfig.footerPaddingRight = it
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.cbShowTopLine.onCheckedChangeListener = { _, isChecked -> cbShowTopLine.onCheckedChangeListener = { _, isChecked ->
showHeaderLine = isChecked ReadBookConfig.showHeaderLine = isChecked
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
binding.cbShowBottomLine.onCheckedChangeListener = { _, isChecked -> cbShowBottomLine.onCheckedChangeListener = { _, isChecked ->
showFooterLine = isChecked ReadBookConfig.showFooterLine = isChecked
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
} }

@ -39,7 +39,7 @@ class ReadAloudConfigDialog : DialogFragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
val view = LinearLayout(requireContext()) val view = LinearLayout(requireContext())
view.setBackgroundColor(requireContext().backgroundColor) view.setBackgroundColor(requireContext().backgroundColor)
view.id = R.id.tag1 view.id = R.id.tag1

@ -60,7 +60,7 @@ class ReadAloudDialog : BaseDialogFragment() {
val bg = requireContext().bottomBackground val bg = requireContext().bottomBackground
val isLight = ColorUtils.isColorLight(bg) val isLight = ColorUtils.isColorLight(bg)
val textColor = requireContext().getPrimaryTextColor(isLight) val textColor = requireContext().getPrimaryTextColor(isLight)
with(binding) { binding.run {
rootView.setBackgroundColor(bg) rootView.setBackgroundColor(bg)
tvPre.setTextColor(textColor) tvPre.setTextColor(textColor)
tvNext.setTextColor(textColor) tvNext.setTextColor(textColor)
@ -85,7 +85,7 @@ class ReadAloudDialog : BaseDialogFragment() {
initEvent() initEvent()
} }
private fun initData() = with(binding) { private fun initData() = binding.run {
upPlayState() upPlayState()
upTimerText(BaseReadAloudService.timeMinute) upTimerText(BaseReadAloudService.timeMinute)
seekTimer.progress = BaseReadAloudService.timeMinute seekTimer.progress = BaseReadAloudService.timeMinute
@ -94,7 +94,7 @@ class ReadAloudDialog : BaseDialogFragment() {
seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate
} }
private fun initEvent() = with(binding) { private fun initEvent() = binding.run {
llMainMenu.setOnClickListener { llMainMenu.setOnClickListener {
callBack?.showMenuBar() callBack?.showMenuBar()
dismissAllowingStateLoss() dismissAllowingStateLoss()

@ -66,7 +66,7 @@ class ReadStyleDialog : BaseDialogFragment(), FontSelectDialog.CallBack {
(activity as ReadBookActivity).bottomDialog-- (activity as ReadBookActivity).bottomDialog--
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
val bg = requireContext().bottomBackground val bg = requireContext().bottomBackground
val isLight = ColorUtils.isColorLight(bg) val isLight = ColorUtils.isColorLight(bg)
val textColor = requireContext().getPrimaryTextColor(isLight) val textColor = requireContext().getPrimaryTextColor(isLight)
@ -105,7 +105,7 @@ class ReadStyleDialog : BaseDialogFragment(), FontSelectDialog.CallBack {
styleAdapter.setItems(ReadBookConfig.configList) styleAdapter.setItems(ReadBookConfig.configList)
} }
private fun initViewEvent() = with(binding) { private fun initViewEvent() = binding.run {
chineseConverter.onChanged { chineseConverter.onChanged {
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
@ -178,7 +178,7 @@ class ReadStyleDialog : BaseDialogFragment(), FontSelectDialog.CallBack {
return true return true
} }
private fun upView() = with(binding) { private fun upView() = binding.run {
ReadBook.pageAnim().let { ReadBook.pageAnim().let {
if (it >= 0 && it < rgPageAnim.childCount) { if (it >= 0 && it < rgPageAnim.childCount) {
rgPageAnim.check(rgPageAnim[it].id) rgPageAnim.check(rgPageAnim[it].id)

@ -72,7 +72,7 @@ class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
initData() initData()
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
toolBar.setBackgroundColor(primaryColor) toolBar.setBackgroundColor(primaryColor)
toolBar.setTitle(R.string.speak_engine) toolBar.setTitle(R.string.speak_engine)
ATH.applyEdgeEffectColor(recyclerView) ATH.applyEdgeEffectColor(recyclerView)
@ -96,7 +96,7 @@ class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
} }
} }
private fun initMenu() = with(binding) { private fun initMenu() = binding.run {
toolBar.inflateMenu(R.menu.speak_engine) toolBar.inflateMenu(R.menu.speak_engine)
toolBar.menu.applyTint(requireContext()) toolBar.menu.applyTint(requireContext())
toolBar.setOnMenuItemClickListener(this@SpeakEngineDialog) toolBar.setOnMenuItemClickListener(this@SpeakEngineDialog)

@ -46,7 +46,7 @@ class TipConfigDialog : BaseDialogFragment() {
} }
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
rgTitleMode.checkByIndex(ReadBookConfig.titleMode) rgTitleMode.checkByIndex(ReadBookConfig.titleMode)
dsbTitleSize.progress = ReadBookConfig.titleSize dsbTitleSize.progress = ReadBookConfig.titleSize
dsbTitleTop.progress = ReadBookConfig.titleTopSpacing dsbTitleTop.progress = ReadBookConfig.titleTopSpacing
@ -74,7 +74,7 @@ class TipConfigDialog : BaseDialogFragment() {
} }
} }
private fun initEvent() = with(binding) { private fun initEvent() = binding.run {
rgTitleMode.setOnCheckedChangeListener { _, checkedId -> rgTitleMode.setOnCheckedChangeListener { _, checkedId ->
ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId) ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId)
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)

@ -69,7 +69,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
initData() initData()
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
adapter = TocRegexAdapter(requireContext()) adapter = TocRegexAdapter(requireContext())
recyclerView.addItemDecoration(VerticalDivider(requireContext())) recyclerView.addItemDecoration(VerticalDivider(requireContext()))
recyclerView.adapter = adapter recyclerView.adapter = adapter

@ -55,7 +55,7 @@ class PageView(context: Context) : FrameLayout(context) {
} }
} }
fun upStyle() = with(binding) { fun upStyle() = binding.run {
upTipStyle() upTipStyle()
ReadBookConfig.let { ReadBookConfig.let {
val tipColor = with(ReadTipConfig) { val tipColor = with(ReadTipConfig) {
@ -102,7 +102,7 @@ class PageView(context: Context) : FrameLayout(context) {
ReadBookConfig.hideStatusBar || (activity as? BaseActivity<*>)?.isInMultiWindow == true ReadBookConfig.hideStatusBar || (activity as? BaseActivity<*>)?.isInMultiWindow == true
} }
private fun upTipStyle() = with(binding) { private fun upTipStyle() = binding.run {
ReadTipConfig.apply { ReadTipConfig.apply {
tvHeaderLeft.isInvisible = tipHeaderLeft != chapterTitle tvHeaderLeft.isInvisible = tipHeaderLeft != chapterTitle
bvHeaderLeft.isInvisible = bvHeaderLeft.isInvisible =
@ -173,7 +173,7 @@ class PageView(context: Context) : FrameLayout(context) {
} }
} }
private fun getTipView(tip: Int): BatteryView? = with(binding) { private fun getTipView(tip: Int): BatteryView? = binding.run {
return when (tip) { return when (tip) {
ReadTipConfig.tipHeaderLeft -> ReadTipConfig.tipHeaderLeft ->
if (tip == ReadTipConfig.chapterTitle) tvHeaderLeft else bvHeaderLeft if (tip == ReadTipConfig.chapterTitle) tvHeaderLeft else bvHeaderLeft

@ -21,7 +21,7 @@ class BookAdapter(context: Context, val callBack: CallBack) :
item: Book, item: Book,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
textView.text = item.name textView.text = item.name
} }
} }

@ -27,7 +27,7 @@ class HistoryKeyAdapter(activity: SearchActivity, val callBack: CallBack) :
item: SearchKeyword, item: SearchKeyword,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
textView.text = item.word textView.text = item.word
} }
} }

@ -74,7 +74,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) :
} }
private fun bind(binding: ItemSearchBinding, searchBook: SearchBook) { private fun bind(binding: ItemSearchBinding, searchBook: SearchBook) {
with(binding) { binding.run {
tvName.text = searchBook.name tvName.text = searchBook.name
tvAuthor.text = context.getString(R.string.author_show, searchBook.author) tvAuthor.text = context.getString(R.string.author_show, searchBook.author)
bvOriginCount.setBadgeCount(searchBook.origins.size) bvOriginCount.setBadgeCount(searchBook.origins.size)
@ -87,7 +87,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) :
} }
private fun bindChange(binding: ItemSearchBinding, searchBook: SearchBook, bundle: Bundle) { private fun bindChange(binding: ItemSearchBinding, searchBook: SearchBook, bundle: Bundle) {
with(binding) { binding.run {
bundle.keySet().map { bundle.keySet().map {
when (it) { when (it) {
"origins" -> bvOriginCount.setBadgeCount(searchBook.origins.size) "origins" -> bvOriginCount.setBadgeCount(searchBook.origins.size)
@ -105,7 +105,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) :
} }
private fun upLasted(binding: ItemSearchBinding, latestChapterTitle: String?) { private fun upLasted(binding: ItemSearchBinding, latestChapterTitle: String?) {
with(binding) { binding.run {
if (latestChapterTitle.isNullOrEmpty()) { if (latestChapterTitle.isNullOrEmpty()) {
tvLasted.gone() tvLasted.gone()
} else { } else {
@ -116,7 +116,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) :
} }
} }
private fun upKind(binding: ItemSearchBinding, kinds: List<String>) = with(binding) { private fun upKind(binding: ItemSearchBinding, kinds: List<String>) = binding.run {
if (kinds.isEmpty()) { if (kinds.isEmpty()) {
llKind.gone() llKind.gone()
} else { } else {

@ -28,7 +28,7 @@ class SearchContentAdapter(context: Context, val callback: Callback) :
item: SearchResult, item: SearchResult,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
val isDur = callback.durChapterIndex() == item.chapterIndex val isDur = callback.durChapterIndex() == item.chapterIndex
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
tvSearchResult.text = item.getHtmlCompat(textColor, accentColor) tvSearchResult.text = item.getHtmlCompat(textColor, accentColor)

@ -33,7 +33,7 @@ class BookSourceEditAdapter : RecyclerView.Adapter<BookSourceEditAdapter.MyViewH
class MyViewHolder(val binding: ItemSourceEditBinding) : RecyclerView.ViewHolder(binding.root) { class MyViewHolder(val binding: ItemSourceEditBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(editEntity: EditEntity) = with(binding) { fun bind(editEntity: EditEntity) = binding.run {
if (editText.getTag(R.id.tag1) == null) { if (editText.getTag(R.id.tag1) == null) {
val listener = object : View.OnAttachStateChangeListener { val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) { override fun onViewAttachedToWindow(v: View) {

@ -84,7 +84,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
item: BookSource, item: BookSource,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
val payload = payloads.getOrNull(0) as? Bundle val payload = payloads.getOrNull(0) as? Bundle
if (payload == null) { if (payload == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f)) root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))

@ -125,7 +125,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
item: String, item: String,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
root.setBackgroundColor(context.backgroundColor) root.setBackgroundColor(context.backgroundColor)
tvGroup.text = item tvGroup.text = item
} }

@ -27,7 +27,7 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
item: BookChapter, item: BookChapter,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
val isDur = callback.durChapterIndex() == item.index val isDur = callback.durChapterIndex() == item.index
val cached = callback.isLocalBook || cacheFileNames.contains(item.getFileName()) val cached = callback.isLocalBook || cacheFileNames.contains(item.getFileName())
if (payloads.isEmpty()) { if (payloads.isEmpty()) {

@ -39,7 +39,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
private var tocLiveData: LiveData<List<BookChapter>>? = null private var tocLiveData: LiveData<List<BookChapter>>? = null
private var scrollToDurChapter = false private var scrollToDurChapter = false
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = with(binding) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
viewModel.chapterCallBack = this@ChapterListFragment viewModel.chapterCallBack = this@ChapterListFragment
val bbg = bottomBackground val bbg = bottomBackground
val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg)) val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg))
@ -62,7 +62,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
ivChapterTop.setOnClickListener { mLayoutManager.scrollToPositionWithOffset(0, 0) } ivChapterTop.setOnClickListener { mLayoutManager.scrollToPositionWithOffset(0, 0) }
ivChapterBottom.setOnClickListener { ivChapterBottom.setOnClickListener {
if (adapter.itemCount > 0) { if (adapter.itemCount > 0) {

@ -46,14 +46,14 @@ class ThemeListDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
initData() initData()
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
adapter = Adapter() adapter = Adapter()
recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.addItemDecoration(VerticalDivider(requireContext())) recyclerView.addItemDecoration(VerticalDivider(requireContext()))
recyclerView.adapter = adapter recyclerView.adapter = adapter
} }
private fun initMenu() = with(binding) { private fun initMenu() = binding.run {
toolBar.setOnMenuItemClickListener(this@ThemeListDialog) toolBar.setOnMenuItemClickListener(this@ThemeListDialog)
toolBar.inflateMenu(R.menu.theme_list) toolBar.inflateMenu(R.menu.theme_list)
toolBar.menu.applyTint(requireContext()) toolBar.menu.applyTint(requireContext())

@ -2,6 +2,7 @@ package io.legado.app.ui.dict
import android.os.Bundle import android.os.Bundle
import android.text.Html import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -10,6 +11,7 @@ import androidx.fragment.app.viewModels
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.databinding.DialogDictBinding import io.legado.app.databinding.DialogDictBinding
import io.legado.app.utils.invisible
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -48,6 +50,7 @@ class DictDialog : BaseDialogFragment() {
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.tvDict.movementMethod = LinkMovementMethod()
val word = arguments?.getString("word") val word = arguments?.getString("word")
if (word.isNullOrEmpty()) { if (word.isNullOrEmpty()) {
toastOnUi(R.string.cannot_empty) toastOnUi(R.string.cannot_empty)
@ -55,6 +58,7 @@ class DictDialog : BaseDialogFragment() {
return return
} }
viewModel.dictHtmlData.observe(viewLifecycleOwner) { viewModel.dictHtmlData.observe(viewLifecycleOwner) {
binding.rotateLoading.invisible()
binding.tvDict.text = Html.fromHtml(it) binding.tvDict.text = Html.fromHtml(it)
} }
viewModel.dict(word) viewModel.dict(word)

@ -82,7 +82,7 @@ class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
}, 3000) }, 3000)
} }
override fun onNavigationItemSelected(item: MenuItem): Boolean = with(binding) { override fun onNavigationItemSelected(item: MenuItem): Boolean = binding.run {
when (item.itemId) { when (item.itemId) {
R.id.menu_bookshelf -> viewPagerMain.setCurrentItem(0, false) R.id.menu_bookshelf -> viewPagerMain.setCurrentItem(0, false)
R.id.menu_discovery -> viewPagerMain.setCurrentItem(1, false) R.id.menu_discovery -> viewPagerMain.setCurrentItem(1, false)

@ -23,7 +23,7 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) :
binding: ItemBookshelfGridBinding, binding: ItemBookshelfGridBinding,
item: Book, item: Book,
payloads: MutableList<Any> payloads: MutableList<Any>
) = with(binding) { ) = binding.run {
val bundle = payloads.getOrNull(0) as? Bundle val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) { if (bundle == null) {
tvName.text = item.name tvName.text = item.name

@ -23,7 +23,7 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) :
binding: ItemBookshelfListBinding, binding: ItemBookshelfListBinding,
item: Book, item: Book,
payloads: MutableList<Any> payloads: MutableList<Any>
) = with(binding) { ) = binding.run {
val bundle = payloads.getOrNull(0) as? Bundle val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) { if (bundle == null) {
tvName.text = item.name tvName.text = item.name

@ -1,15 +1,18 @@
package io.legado.app.ui.main.explore package io.legado.app.ui.main.explore
import android.content.Context import android.content.Context
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.TextView
import androidx.core.view.children
import com.google.android.flexbox.FlexboxLayout
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.ExploreKind
import io.legado.app.databinding.ItemFilletTextBinding import io.legado.app.databinding.ItemFilletTextBinding
import io.legado.app.databinding.ItemFindBookBinding import io.legado.app.databinding.ItemFindBookBinding
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
@ -23,6 +26,8 @@ import splitties.views.onLongClick
class ExploreAdapter(context: Context, private val scope: CoroutineScope, val callBack: CallBack) : class ExploreAdapter(context: Context, private val scope: CoroutineScope, val callBack: CallBack) :
RecyclerAdapter<BookSource, ItemFindBookBinding>(context) { RecyclerAdapter<BookSource, ItemFindBookBinding>(context) {
private val recycler = arrayListOf<View>()
private var exIndex = -1 private var exIndex = -1
private var scrollTo = -1 private var scrollTo = -1
@ -36,7 +41,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
item: BookSource, item: BookSource,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
if (holder.layoutPosition == itemCount - 1) { if (holder.layoutPosition == itemCount - 1) {
root.setPadding(16.dp, 12.dp, 16.dp, 12.dp) root.setPadding(16.dp, 12.dp, 16.dp, 12.dp)
} else { } else {
@ -55,28 +60,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
Coroutine.async(scope) { Coroutine.async(scope) {
item.getExploreKinds() item.getExploreKinds()
}.onSuccess { kindList -> }.onSuccess { kindList ->
if (!kindList.isNullOrEmpty()) { upKindList(flexbox, item.bookSourceUrl, kindList)
glChild.visible()
glChild.removeAllViews()
kindList.map { kind ->
val tv = ItemFilletTextBinding.inflate(
LayoutInflater.from(context),
glChild,
false
)
glChild.addView(tv.root)
tv.textView.text = kind.title
if (!kind.url.isNullOrEmpty()) {
tv.textView.setOnClickListener {
callBack.openExplore(
item.bookSourceUrl,
kind.title,
kind.url.toString()
)
}
}
}
}
}.onFinally { }.onFinally {
rotateLoading.hide() rotateLoading.hide()
if (scrollTo >= 0) { if (scrollTo >= 0) {
@ -85,13 +69,54 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
} }
} }
} else { } else {
binding.ivStatus.setImageResource(R.drawable.ic_arrow_right) ivStatus.setImageResource(R.drawable.ic_arrow_right)
binding.rotateLoading.hide() rotateLoading.hide()
binding.glChild.gone() recyclerFlexbox(flexbox)
flexbox.gone()
}
}
}
private fun upKindList(flexbox: FlexboxLayout, sourceUrl: String, kinds: List<ExploreKind>) {
if (!kinds.isNullOrEmpty()) {
recyclerFlexbox(flexbox)
flexbox.visible()
kinds.forEach { kind ->
val tv = getFlexboxChild(flexbox)
flexbox.addView(tv)
tv.text = kind.title
val lp = tv.layoutParams as FlexboxLayout.LayoutParams
kind.style().let { style ->
lp.flexGrow = style.layout_flexGrow
lp.flexShrink = style.layout_flexShrink
lp.alignSelf = style.alignSelf()
lp.flexBasisPercent = style.layout_flexBasisPercent
lp.isWrapBefore = style.layout_wrapBefore
}
tv.setOnClickListener {
callBack.openExplore(sourceUrl, kind.title, kind.url)
}
} }
} }
} }
@Synchronized
private fun getFlexboxChild(flexbox: FlexboxLayout): TextView {
return if (recycler.isEmpty()) {
ItemFilletTextBinding.inflate(inflater, flexbox, false).root
} else {
recycler.last().also {
recycler.removeLast()
} as TextView
}
}
@Synchronized
private fun recyclerFlexbox(flexbox: FlexboxLayout) {
recycler.addAll(flexbox.children)
flexbox.removeAllViews()
}
override fun registerListener(holder: ItemViewHolder, binding: ItemFindBookBinding) { override fun registerListener(holder: ItemViewHolder, binding: ItemFindBookBinding) {
binding.apply { binding.apply {
llTitle.setOnClickListener { llTitle.setOnClickListener {
@ -146,7 +171,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
interface CallBack { interface CallBack {
fun scrollTo(pos: Int) fun scrollTo(pos: Int)
fun openExplore(sourceUrl: String, title: String, exploreUrl: String) fun openExplore(sourceUrl: String, title: String, exploreUrl: String?)
fun editSource(sourceUrl: String) fun editSource(sourceUrl: String)
fun toTop(source: BookSource) fun toTop(source: BookSource)
} }

@ -154,7 +154,8 @@ class ExploreFragment : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explo
(binding.rvFind.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, 0) (binding.rvFind.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, 0)
} }
override fun openExplore(sourceUrl: String, title: String, exploreUrl: String) { override fun openExplore(sourceUrl: String, title: String, exploreUrl: String?) {
if (exploreUrl.isNullOrBlank()) return
startActivity<ExploreShowActivity> { startActivity<ExploreShowActivity> {
putExtra("exploreName", title) putExtra("exploreName", title)
putExtra("sourceUrl", sourceUrl) putExtra("sourceUrl", sourceUrl)

@ -53,7 +53,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
initData() initData()
} }
private fun initData() = with(binding) { private fun initData() = binding.run {
toolBar.title = getString(R.string.group_manage) toolBar.title = getString(R.string.group_manage)
toolBar.inflateMenu(R.menu.group_manage) toolBar.inflateMenu(R.menu.group_manage)
toolBar.menu.applyTint(requireContext()) toolBar.menu.applyTint(requireContext())
@ -126,7 +126,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
item: String, item: String,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
root.setBackgroundColor(context.backgroundColor) root.setBackgroundColor(context.backgroundColor)
tvGroup.text = item tvGroup.text = item
} }

@ -108,7 +108,7 @@ class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
item: ReplaceRule, item: ReplaceRule,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
val bundle = payloads.getOrNull(0) as? Bundle val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) { if (bundle == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f)) root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))

@ -90,7 +90,7 @@ class ReplaceEditActivity :
return true return true
} }
private fun upReplaceView(replaceRule: ReplaceRule) = with(binding) { private fun upReplaceView(replaceRule: ReplaceRule) = binding.run {
etName.setText(replaceRule.name) etName.setText(replaceRule.name)
etGroup.setText(replaceRule.group) etGroup.setText(replaceRule.group)
etReplaceRule.setText(replaceRule.pattern) etReplaceRule.setText(replaceRule.pattern)
@ -99,7 +99,7 @@ class ReplaceEditActivity :
etScope.setText(replaceRule.scope) etScope.setText(replaceRule.scope)
} }
private fun getReplaceRule(): ReplaceRule = with(binding) { private fun getReplaceRule(): ReplaceRule = binding.run {
val replaceRule: ReplaceRule = viewModel.replaceRule ?: ReplaceRule() val replaceRule: ReplaceRule = viewModel.replaceRule ?: ReplaceRule()
replaceRule.name = etName.text.toString() replaceRule.name = etName.text.toString()
replaceRule.group = etGroup.text.toString() replaceRule.group = etGroup.text.toString()

@ -30,7 +30,7 @@ class RssArticlesAdapter(context: Context, callBack: CallBack) :
item: RssArticle, item: RssArticle,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
tvTitle.text = item.title tvTitle.text = item.title
tvPubDate.text = item.pubDate tvPubDate.text = item.pubDate
if (item.image.isNullOrBlank() && !callBack.isGridLayout) { if (item.image.isNullOrBlank() && !callBack.isGridLayout) {

@ -30,7 +30,7 @@ class RssArticlesAdapter1(context: Context, callBack: CallBack) :
item: RssArticle, item: RssArticle,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
tvTitle.text = item.title tvTitle.text = item.title
tvPubDate.text = item.pubDate tvPubDate.text = item.pubDate
if (item.image.isNullOrBlank() && !callBack.isGridLayout) { if (item.image.isNullOrBlank() && !callBack.isGridLayout) {

@ -30,7 +30,7 @@ class RssArticlesAdapter2(context: Context, callBack: CallBack) :
item: RssArticle, item: RssArticle,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
tvTitle.text = item.title tvTitle.text = item.title
tvPubDate.text = item.pubDate tvPubDate.text = item.pubDate
if (item.image.isNullOrBlank() && !callBack.isGridLayout) { if (item.image.isNullOrBlank() && !callBack.isGridLayout) {

@ -53,7 +53,7 @@ class RssArticlesFragment : VMBaseFragment<RssArticlesViewModel>(R.layout.fragme
initData() initData()
} }
private fun initView() = with(binding) { private fun initView() = binding.run {
refreshLayout.setColorSchemeColors(accentColor) refreshLayout.setColorSchemeColors(accentColor)
ATH.applyEdgeEffectColor(recyclerView) ATH.applyEdgeEffectColor(recyclerView)
recyclerView.layoutManager = if (activityViewModel.isGridLayout) { recyclerView.layoutManager = if (activityViewModel.isGridLayout) {

@ -29,7 +29,7 @@ class RssFavoritesAdapter(context: Context, val callBack: CallBack) :
item: RssStar, item: RssStar,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
tvTitle.text = item.title tvTitle.text = item.title
tvPubDate.text = item.pubDate tvPubDate.text = item.pubDate
if (item.image.isNullOrBlank()) { if (item.image.isNullOrBlank()) {

@ -33,7 +33,7 @@ class RssSourceEditAdapter : RecyclerView.Adapter<RssSourceEditAdapter.MyViewHol
class MyViewHolder(val binding: ItemSourceEditBinding) : RecyclerView.ViewHolder(binding.root) { class MyViewHolder(val binding: ItemSourceEditBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(editEntity: EditEntity) = with(binding) { fun bind(editEntity: EditEntity) = binding.run {
if (editText.getTag(R.id.tag1) == null) { if (editText.getTag(R.id.tag1) == null) {
val listener = object : View.OnAttachStateChangeListener { val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) { override fun onViewAttachedToWindow(v: View) {

@ -47,7 +47,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
return inflater.inflate(R.layout.dialog_recycler_view, container) return inflater.inflate(R.layout.dialog_recycler_view, container)
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = with(binding) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
toolBar.setBackgroundColor(primaryColor) toolBar.setBackgroundColor(primaryColor)
toolBar.title = getString(R.string.group_manage) toolBar.title = getString(R.string.group_manage)
toolBar.inflateMenu(R.menu.group_manage) toolBar.inflateMenu(R.menu.group_manage)
@ -128,7 +128,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
item: String, item: String,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
root.setBackgroundColor(context.backgroundColor) root.setBackgroundColor(context.backgroundColor)
tvGroup.text = item tvGroup.text = item
} }

@ -66,7 +66,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) :
item: RssSource, item: RssSource,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
val bundle = payloads.getOrNull(0) as? Bundle val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) { if (bundle == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f)) root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))

@ -52,7 +52,7 @@ class KeyboardToolPop(
item: String, item: String,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
textView.text = item textView.text = item
} }
} }

@ -46,12 +46,12 @@ class SelectActionBar @JvmOverloads constructor(
binding.ivMenuMore.setOnClickListener { selMenu?.show() } binding.ivMenuMore.setOnClickListener { selMenu?.show() }
} }
fun setMainActionText(text: String) = with(binding) { fun setMainActionText(text: String) = binding.run {
btnSelectActionMain.text = text btnSelectActionMain.text = text
btnSelectActionMain.visible() btnSelectActionMain.visible()
} }
fun setMainActionText(@StringRes id: Int) = with(binding) { fun setMainActionText(@StringRes id: Int) = binding.run {
btnSelectActionMain.setText(id) btnSelectActionMain.setText(id)
btnSelectActionMain.visible() btnSelectActionMain.visible()
} }
@ -71,7 +71,7 @@ class SelectActionBar @JvmOverloads constructor(
selMenu?.setOnMenuItemClickListener(listener) selMenu?.setOnMenuItemClickListener(listener)
} }
fun upCountView(selectCount: Int, allCount: Int) = with(binding) { fun upCountView(selectCount: Int, allCount: Int) = binding.run {
if (selectCount == 0) { if (selectCount == 0) {
cbSelectedAll.isChecked = false cbSelectedAll.isChecked = false
} else { } else {
@ -95,7 +95,7 @@ class SelectActionBar @JvmOverloads constructor(
setMenuClickable(selectCount > 0) setMenuClickable(selectCount > 0)
} }
private fun setMenuClickable(isClickable: Boolean) = with(binding) { private fun setMenuClickable(isClickable: Boolean) = binding.run {
btnRevertSelection.isEnabled = isClickable btnRevertSelection.isEnabled = isClickable
btnRevertSelection.isClickable = isClickable btnRevertSelection.isClickable = isClickable
btnSelectActionMain.isEnabled = isClickable btnSelectActionMain.isEnabled = isClickable

@ -47,7 +47,7 @@ class TextListDialog : BaseDialogFragment() {
return inflater.inflate(R.layout.dialog_recycler_view, container) return inflater.inflate(R.layout.dialog_recycler_view, container)
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = with(binding) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
arguments?.let { arguments?.let {
toolBar.title = it.getString("title") toolBar.title = it.getString("title")
values = it.getStringArrayList("values") values = it.getStringArrayList("values")

@ -24,7 +24,7 @@ class FontAdapter(context: Context, val callBack: CallBack) :
item: DocItem, item: DocItem,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
kotlin.runCatching { kotlin.runCatching {
val typeface: Typeface? = if (item.isContentPath) { val typeface: Typeface? = if (item.isContentPath) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

@ -168,7 +168,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference
item: CharSequence, item: CharSequence,
payloads: MutableList<Any> payloads: MutableList<Any>
) { ) {
with(binding) { binding.run {
val index = findIndexOfValue(item.toString()) val index = findIndexOfValue(item.toString())
dialogEntries?.let { dialogEntries?.let {
label.text = it[index] label.text = it[index]

@ -1,11 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:padding="16dp">
<TextView <TextView
android:id="@+id/tv_dict" android:id="@+id/tv_dict"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> <io.legado.app.ui.widget.anima.RotateLoading
android:id="@+id/rotate_loading"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center" />
</FrameLayout>

@ -56,7 +56,7 @@
android:layout_gravity="center"> android:layout_gravity="center">
<com.google.android.flexbox.FlexboxLayout <com.google.android.flexbox.FlexboxLayout
android:id="@+id/gl_child" android:id="@+id/flexbox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clipToPadding="false" android:clipToPadding="false"

@ -1,46 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/shape_card_view" android:background="@drawable/shape_card_view"
android:padding="5dp" android:padding="5dp"
android:orientation="vertical"> app:flexDirection="row"
app:flexWrap="wrap" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_menu_more"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_more_vert"
android:tint="@color/primaryText"
android:visibility="gone"
android:contentDescription="@string/more_menu" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</LinearLayout>
Loading…
Cancel
Save