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

pull/1114/head
bushixuanqi 3 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)
## 阅读3.0
* [书源规则](https://alanskycn.gitee.io/teachme/)
* [书源规则](https://alanskycn.gitee.io/teachme)
* [更新日志](/app/src/main/assets/updateLog.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)

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

@ -9,31 +9,22 @@
"sourceUrl": "https://www.yuque.com/legado"
},
{
"articleStyle": 0,
"customOrder": 2,
"enableJs": true,
"enabled": true,
"loadWithBaseUrl": false,
"ruleArticles": "class.release-main-section||tbody@tr",
"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}",
"ruleImage": "@js:if(baseUrl.match(\/legado|704\/g)){\nresult='https:\/\/urlmin.cn\/034'\n}else{result=\"https:\/\/urlmin.cn\/15c\"}",
"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": ""
"singleUrl": true,
"sourceIcon": "http:\/\/mmbiz.qpic.cn\/mmbiz_png\/hpfMV8hEuL2eS6vnCxvTzoOiaCAibV6exBzJWq9xMic9xDg3YXAick87tsfafic0icRwkQ5ibV0bJ84JtSuxhPuEDVquA\/0?wx_fmt=png",
"sourceName": "小说拾遗",
"sourceUrl": "snssdk1128:\/\/user\/profile\/562564899806367"
},
{
"customOrder": 3,
"enableJs": true,
"enabled": true,
"loadWithBaseUrl": false,
"singleUrl": true,
"sourceIcon": "http:\/\/mmbiz.qpic.cn\/mmbiz_png\/hpfMV8hEuL2eS6vnCxvTzoOiaCAibV6exBzJWq9xMic9xDg3YXAick87tsfafic0icRwkQ5ibV0bJ84JtSuxhPuEDVquA\/0?wx_fmt=png",
"sourceName": "小说拾遗",
"sourceUrl": "snssdk1128:\/\/user\/profile\/562564899806367"
"sourceIcon": "https://Cloud.miaogongzi.net/images/icon.png",
"sourceName": "Meow云",
"sourceUrl": "https://pan.miaogongzi.net"
}
]

@ -11,6 +11,23 @@
: regex规则,不可省略,只可以用在书籍列表和目录列表
```
* 发现url格式
```json
[
{
"title": "xxx",
"url": "",
"style": {
"layout_flexGrow": 0,
"layout_flexShrink": 1,
"layout_alignSelf": "auto",
"layout_flexBasisPercent": -1,
"layout_wrapBefore": false
}
}
]
```
* 获取登录后的cookie
```
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
* 格式形如 `[index,index, ...]``[!index,index, ...]` 其中`[!`开头表示筛选方式为排除,`index`可以是单个索引,也可以是区间。
* 区间格式为 `start:end``start:end:step`,其中`start`为`0`可省略,`end`为`-1`可省略。
@ -20,6 +40,7 @@
3. 添加Umd格式支持 by ag2s20150909
4. 修复web页面按键重复监听的bug
5. 亮度条往中间移了一点,防止误触
6. 添加内置字典
**2021/06/29**
* 修复html格式化bug
@ -428,44 +449,3 @@ getTxtInFolder(unzipPath: String): String
```
* 增加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")
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>>
@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")
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>>
@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>>
@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) <> ''")
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>>
@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.JsExtensions
import io.legado.app.help.http.CookieStore
import io.legado.app.utils.ACache
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.*
import kotlinx.parcelize.Parcelize
import splitties.init.appCtx
import javax.script.SimpleBindings
@ -100,42 +97,48 @@ data class BookSource(
}
}
fun getExploreKinds() = arrayListOf<ExploreKind>().apply {
exploreUrl?.let { urlRule ->
var a = urlRule
if (a.isNotBlank()) {
kotlin.runCatching {
if (urlRule.startsWith("<js>", false)
|| urlRule.startsWith("@js", false)
) {
val aCache = ACache.get(appCtx, "explore")
a = aCache.getAsString(bookSourceUrl) ?: ""
if (a.isBlank()) {
val bindings = SimpleBindings()
bindings["baseUrl"] = bookSourceUrl
bindings["java"] = this
bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager
val jsStr = if (urlRule.startsWith("@")) {
urlRule.substring(3)
} else {
urlRule.substring(4, urlRule.lastIndexOf("<"))
}
a = AppConst.SCRIPT_ENGINE.eval(jsStr, bindings).toString()
aCache.put(bookSourceUrl, a)
fun getExploreKinds(): List<ExploreKind> {
val exploreUrl = exploreUrl ?: return emptyList()
val kinds = arrayListOf<ExploreKind>()
var ruleStr = exploreUrl
if (ruleStr.isNotBlank()) {
kotlin.runCatching {
if (exploreUrl.startsWith("<js>", false)
|| exploreUrl.startsWith("@js", false)
) {
val aCache = ACache.get(appCtx, "explore")
ruleStr = aCache.getAsString(bookSourceUrl) ?: ""
if (ruleStr.isBlank()) {
val bindings = SimpleBindings()
bindings["baseUrl"] = bookSourceUrl
bindings["java"] = this
bindings["cookie"] = CookieStore
bindings["cache"] = CacheManager
val jsStr = if (exploreUrl.startsWith("@")) {
exploreUrl.substring(3)
} else {
exploreUrl.substring(4, exploreUrl.lastIndexOf("<"))
}
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("::")
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())
data class ExploreKind(
var title: String,
var url: String? = null
)
class Converters {
@TypeConverter
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")
val hasUpRssSources: Boolean
get() = !isLastVersion(3, "rssSourceVersion")
get() = !isLastVersion(4, "rssSourceVersion")
var versionCode
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 pos = 0 //处理到的位置
private var rule = arrayOf<String>()
private var pos = 0 //当前处理到的位置
private var start = 0 //当前处理字段的开始
private var startX = 0 //当前规则的开始
private var start = 0 //每次处理字段的开始
private var startX = 0 //规则的开始
private var step:Int = 0 //分割字符的长度
private var rule = arrayOf<String>() //分割出的规则列表
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 currBalancedString( stepStart:Int = 1 , stepEnd:Int = 1): String { //stepStart平衡字符的起始分隔字串长度,stepEnd平衡字符的结束分隔字串长度
return queue.substring(startX+stepStart,pos-stepEnd) //当前平衡字段
}
fun trim(){ // 修剪当前规则之前的"@"或者空白符
fun trim() { // 修剪当前规则之前的"@"或者空白符
while (queue[pos] == '@' || queue[pos] < '!') pos++
}
@ -33,14 +27,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
//当前拉取字段
fun currString(): String {
return queue.substring(start,pos) //当前拉取到的字段
}
//剩余字串
private fun remainingString(): String {
start = pos
pos = queue.length
return queue.substring(start)
return queue.substring(start, pos) //当前拉取到的字段
}
/**
@ -48,7 +35,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @return 若剩余字串中已无字符则返回true
*/
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 分隔字符 **区分大小写**
* @return 是否找到相应字段
*/
fun consumeTo(seq: String,setStartPos:Boolean = true): Boolean {
if(setStartPos)start = pos //将处理到的位置设置为规则起点
fun consumeTo(seq: String): Boolean {
start = pos //将处理到的位置设置为规则起点
val offset = queue.indexOf(seq, pos)
return if (offset != -1) {
pos = offset
true
} else false
}
/**
@ -88,7 +73,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @param seq 匹配字符串序列
* @return 成功返回true并设置间隔失败则直接返回fasle
*/
fun consumeToAny(vararg seq:String): Boolean {
fun consumeToAny(vararg seq: String): Boolean {
var pos = pos //声明新变量记录匹配位置,不更改类本身的位置
@ -113,13 +98,13 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @param seq 匹配字符序列
* @return 返回匹配位置
*/
private fun findToAny(vararg seq:Char): Int {
private fun findToAny(vararg seq: Char): Int {
var pos = pos //声明新变量记录匹配位置,不更改类本身的位置
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++ //逐个试探
@ -129,13 +114,15 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
}
//其中js只要符合语法,就不用避开任何阅读关键字,自由发挥
fun chompJsBalanced(f: ((Char) -> Boolean?) = {
when (it) {
'{' -> true //开始嵌套一层
'}' -> false //闭合一层嵌套
else -> null
}
} ): Boolean {
fun chompJsBalanced(
f: ((Char) -> Boolean?) = {
when (it) {
'{' -> true //开始嵌套一层
'}' -> false //闭合一层嵌套
else -> null
}
}
): Boolean {
var pos = pos //声明变量记录临时处理位置
var depth = 0 //嵌套深度
var bracketsDepth = 0 //[]嵌套深度
@ -151,22 +138,23 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
if (pos == queue.length) break
var c = queue[pos++]
if (c != '\\') { //非转义字符
if (c == '\'' && !commits && !commit && !regex && !inDoubleQuote && !inOtherQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号
else if (c == '"' && !commits && !commit && !regex && !inSingleQuote && !inOtherQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号
else if (c == '`' && !commits && !commit && !regex && !inSingleQuote && !inDoubleQuote) inOtherQuote = !inOtherQuote //匹配具有语法功能的'`'
if (c == '\'' && !commits && !commit && !regex && !inDoubleQuote && !inOtherQuote) inSingleQuote =
!inSingleQuote //匹配具有语法功能的单引号
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) { //匹配注释或正则起点
c = queue[pos++]
when(c){
'/'->commit=true //匹配单行注释起点
'*'->commits=true //匹配多行注释起点
else ->regex=true //匹配正则起点
when (c) {
'/' -> commit = true //匹配单行注释起点
'*' -> commits = true //匹配多行注释起点
else -> regex = true //匹配正则起点
}
}
else if(commits && c == '*') { //匹配多行注释终点
} else if (commits && c == '*') { //匹配多行注释终点
c = queue[pos++]
if(c == '/')commits = false
}
else if(regex && c == '/') { //正则的终点或[]平衡
if (c == '/') commits = false
} else if (regex && c == '/') { //正则的终点或[]平衡
when (c) {
'/' -> regex = false//匹配正则终点
@ -177,25 +165,25 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
']' -> 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
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 //同步位置
true
}
}
/**
* 拉出一个代码平衡组存在转义文本没有实体字符通常以{}作为模块
* 拉出一个非内嵌代码平衡组存在转义文本
*/
fun chompCodeBalanced(open: Char, close: Char): Boolean {
@ -214,22 +202,21 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
if (c == '\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号
else if (c == '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号
if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环
if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环
if ( c == '[' )depth++ //开始嵌套一层
else if ( c== ']') depth-- //闭合一层嵌套
else if(depth == 0 ) {
if (c == '[') depth++ //开始嵌套一层
else if (c == ']') depth-- //闭合一层嵌套
else if (depth == 0) {
//处于默认嵌套中的非默认字符不需要平衡,仅depth为0时默认嵌套全部闭合,此字符才进行嵌套
if(c == open)otherDepth++
else if(c == close)otherDepth--
if (c == open) otherDepth++
else if (c == close) otherDepth--
}
}else pos++
} else pos++
} while (depth > 0 || otherDepth > 0) //拉出一个平衡字串
return if(depth > 0 || otherDepth > 0) false else {
startX = this.pos //内嵌规则起始
return if (depth > 0 || otherDepth > 0) false else {
this.pos = pos //同步位置
true
}
@ -251,18 +238,18 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
if (c == '\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号
else if (c == '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号
if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环
else if( c=='\\' ){ //不在引号中的转义字符才将下个字符转义
if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束,直接进入下个循环
else if (c == '\\') { //不在引号中的转义字符才将下个字符转义
pos++
continue
}
if ( c == open )depth++ //开始嵌套一层
else if ( c== close) depth-- //闭合一层嵌套
if (c == open) 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 //同步位置
true
}
@ -272,18 +259,18 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则
* 解决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] //设置分割字串
return if(!consumeTo(elementsType)) {
return if (!consumeTo(elementsType)) {
rule += queue.substring(startX)
rule
}else {
} else {
step = elementsType.length //设置分隔符长度
splitRule()
} //递归匹配
}else if (!consumeToAny(* split)) { //未找到分隔符
} else if (!consumeToAny(* split)) { //未找到分隔符
rule += queue.substring(startX)
return rule
}
@ -291,7 +278,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
val end = pos //记录分隔位置
pos = start //重回开始,启动另一种查找
do{
do {
val st = findToAny('[', '(') //查找筛选器位置
if (st == -1) {
@ -323,11 +310,10 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
pos += step //跳过分隔符
}
return if(pos > st) {
return if (pos > st) {
startX = start
splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配
}
else { //执行到此,证明后面再无分隔字符
} else { //执行到此,证明后面再无分隔字符
rule += queue.substring(pos) //将剩余字段压入数组末尾
rule
}
@ -337,13 +323,13 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符
if (!chompBalanced(queue[pos], next)) throw Error(
queue.substring(
0,
start
) + "后未平衡"
queue.substring(
0,
start
) + "后未平衡"
) //拉出一个筛选器,不平衡则报错
}while( end > pos )
} while (end > pos)
start = pos //设置开始查找筛选器位置的起始位置
@ -351,12 +337,12 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
}
@JvmName("splitRuleNext")
private tailrec fun splitRule(): Array<String>{ //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快
private tailrec fun splitRule(): Array<String> { //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快
val end = pos //记录分隔位置
pos = start //重回开始,启动另一种查找
do{
do {
val st = findToAny('[', '(') //查找筛选器位置
if (st == -1) {
@ -384,11 +370,10 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
pos += step //跳过分隔符
}
return if(pos > st) {
return if (pos > st) {
startX = start
splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配
}
else { //执行到此,证明后面再无分隔字符
} else { //执行到此,证明后面再无分隔字符
rule += queue.substring(pos) //将剩余字段压入数组末尾
rule
}
@ -398,24 +383,23 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符
if (!chompBalanced(queue[pos], next)) throw Error(
queue.substring(
0,
start
) + "后未平衡"
queue.substring(
0,
start
) + "后未平衡"
) //拉出一个筛选器,不平衡则报错
}while( end > pos )
} while (end > pos)
start = pos //设置开始查找筛选器位置的起始位置
return if(!consumeTo(elementsType)) {
return if (!consumeTo(elementsType)) {
rule += queue.substring(startX)
rule
}else splitRule() //递归匹配
} else splitRule() //递归匹配
}
/**
* 替换内嵌规则
* @param inner 起始标志,{$. {{
@ -424,67 +408,32 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* @param fr 查找到内嵌规则时用于解析的函数
*
* */
fun innerRule( inner:String,startStep:Int = 1,endStep:Int = 1,fr:(String)->String?): String {
val start0 = pos //规则匹配前起点
fun innerRule(
inner: String,
startStep: Int = 1,
endStep: Int = 1,
fr: (String) -> String?
): String {
startX = pos //设置规则起点
val st = StringBuilder()
while (!isEmpty && consumeTo(inner)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true
val posPre = pos //记录上次结束位置
if (chompBalanced('{', '}')) {
val frv= fr(currBalancedString(startStep,endStep))
if(frv != null) {
st.append(queue.substring(start,posPre)+frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串
while (consumeTo(inner)) { //拉取成功返回true,ruleAnalyzes里的字符序列索引变量pos后移相应位置,否则返回false,且isEmpty为true
if (chompCodeBalanced('{', '}')) {
val frv = fr(queue.substring(start + startStep, pos - endStep))
if (frv != null) {
st.append(queue.substring(startX, start) + frv) //压入内嵌规则前的内容,及内嵌规则解析得到的字符串
startX = pos //记录下次规则起点
continue //获取内容成功,继续选择下个内嵌规则
}
}
pos += inner.length //拉出字段不平衡,inner只是个普通字串,跳到此inner后继续匹配
}
//匹配前起点与当前规则起点相同,证明无替换成功的内嵌规则,返回空字符串。否则返回替换后的字符串
return if(start0 == start) "" else {
st.append(remainingString()) //压入剩余字符串
st.toString()
}
st.append(queue.substring(startX)) //压入剩余字符串
return 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 {
/**
* 转义字符
@ -495,7 +444,7 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
* 阅读共有分隔字串起始部分
* "##","@@","{{","{[","<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脚本结束字串
@ -542,27 +491,27 @@ class RuleAnalyzer(data: String, code:Boolean = false) {
/**
* '[', ']', '(', ')','{','}'
*/
val splitListPublic = charArrayOf('[', ']', '(', ')','{','}')
val splitListPublic = charArrayOf('[', ']', '(', ')', '{', '}')
/**
* '*',"/","//",":","::","@","|","@xpath:"
*/
val splitListXpath = arrayOf("*","/","//",":","::","@","|","@xpath:")
val splitListXpath = arrayOf("*", "/", "//", ":", "::", "@", "|", "@xpath:")
/**
* '*','$',".","..", "@json:"
*/
val splitListJson = arrayOf('*','$',".","..", "@json:")
val splitListJson = arrayOf('*', '$', ".", "..", "@json:")
/**
* '*',"+","~",".",",","|","@","@css:",":"
*/
val splitListCss = arrayOf('*',"+","~",".",",","|","@","@css:",":")
val splitListCss = arrayOf('*', "+", "~", ".", ",", "|", "@", "@css:", ":")
/**
* "-",".","!","@","@@"
*/
val splitListDefault = arrayOf("-",".","!","@","@@")
val splitListDefault = arrayOf("-", ".", "!", "@", "@@")
}
}

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

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

@ -31,7 +31,7 @@ class CacheAdapter(context: Context, private val callBack: CallBack) :
item: Book,
payloads: MutableList<Any>
) {
with(binding) {
binding.run {
if (payloads.isEmpty()) {
tvName.text = item.name
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) {
with(binding) {
binding.run {
ivDownload.setOnClickListener {
getItem(holder.layoutPosition)?.let {
if (downloadMap?.containsKey(it.bookUrl) == true) {

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

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

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

@ -172,7 +172,7 @@ class BookInfoActivity :
return super.onCompatOptionsItemSelected(item)
}
private fun showBook(book: Book) = with(binding) {
private fun showBook(book: Book) = binding.run {
showCover(book)
tvName.text = book.name
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 {
viewModel.bookData.value?.let {
ChangeCoverDialog.show(supportFragmentManager, it.name, it.author)

@ -55,7 +55,7 @@ class BookInfoEditActivity :
return super.onCompatOptionsItemSelected(item)
}
private fun initEvent() = with(binding) {
private fun initEvent() = binding.run {
tvChangeCover.setOnClickListener {
viewModel.bookData.value?.let {
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)
tieBookAuthor.setText(book.author)
tieCoverUrl.setText(book.getDisplayCover())
@ -84,7 +84,7 @@ class BookInfoEditActivity :
}
}
private fun saveData() = with(binding) {
private fun saveData() = binding.run {
viewModel.book?.let { book ->
book.name = tieBookName.text?.toString() ?: ""
book.author = tieBookAuthor.text?.toString() ?: ""

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

@ -105,7 +105,12 @@ class ReadBookActivity : ReadBookBaseActivity(),
}
private var menu: Menu? = null
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
@ -416,7 +421,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
* view触摸,文字选择
*/
@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) {
MotionEvent.ACTION_DOWN -> textActionMenu.dismiss()
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.y = y
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.y = y
cursorRight.visible(true)
@ -459,7 +464,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/**
* 取消文字选择
*/
override fun onCancelSelect() = with(binding) {
override fun onCancelSelect() = binding.run {
cursorLeft.invisible()
cursorRight.invisible()
textActionMenu.dismiss()
@ -468,11 +473,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/**
* 显示文本操作菜单
*/
override fun showTextActionMenu() = with(binding) {
textActionMenu.contentView.measure(
View.MeasureSpec.UNSPECIFIED,
View.MeasureSpec.UNSPECIFIED
)
override fun showTextActionMenu() = binding.run {
val popupHeight = textActionMenu.contentView.measuredHeight
val x = textMenuPosition.x.toInt()
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()
readView.curPage.cancelSelect()
readView.isTextSelected = false
@ -796,7 +797,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
/**
* colorSelectDialog
*/
override fun onColorSelected(dialogId: Int, color: Int) = with(ReadBookConfig.durConfig) {
override fun onColorSelected(dialogId: Int, color: Int) = ReadBookConfig.durConfig.run {
when (dialogId) {
TEXT_COLOR -> {
setCurTextColor(color)
@ -899,7 +900,7 @@ class ReadBookActivity : ReadBookBaseActivity(),
}
}
override fun observeLiveBus() = with(binding) {
override fun observeLiveBus() = binding.run {
super.observeLiveBus()
observeEvent<String>(EventBus.TIME_CHANGED) { readView.upTime() }
observeEvent<Int>(EventBus.BATTERY_CHANGED) { readView.upBattery(it) }

@ -52,7 +52,7 @@ class ReadMenu @JvmOverloads constructor(
bindEvent()
}
private fun initView() = with(binding) {
private fun initView() = binding.run {
if (AppConfig.isNightTheme) {
fabNightTheme.setImageResource(R.drawable.ic_daytime)
} else {
@ -134,7 +134,7 @@ class ReadMenu @JvmOverloads constructor(
return context.getPrefBoolean("brightnessAuto", true) || !showBrightnessView
}
private fun bindEvent() = with(binding) {
private fun bindEvent() = binding.run {
tvChapterName.setOnClickListener {
callBack.openSourceEditActivity()
}
@ -304,7 +304,7 @@ class ReadMenu @JvmOverloads constructor(
binding.seekReadPage.progress = seek
}
fun setAutoPage(autoPage: Boolean) = with(binding) {
fun setAutoPage(autoPage: Boolean) = binding.run {
if (autoPage) {
fabAutoPage.setImageResource(R.drawable.ic_auto_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.menu.MenuBuilder
import androidx.appcompat.view.menu.MenuItemImpl
import androidx.core.view.isVisible
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.PopupActionMenuBinding
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.*
@SuppressLint("RestrictedApi")
class TextActionMenu(private val context: Context, private val callBack: CallBack) :
PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT),
TextToSpeech.OnInitListener {
private val binding = PopupActionMenuBinding.inflate(LayoutInflater.from(context))
private val adapter = Adapter(context)
private val menu = MenuBuilder(context)
private val moreMenu = MenuBuilder(context)
private val ttsListener by lazy {
@ -47,68 +48,23 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac
isOutsideTouchable = 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)
adapter.setItems(menu.visibleItems)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
onInitializeMenu(moreMenu)
}
if (moreMenu.size() > 0) {
ivMenuMore.visible()
}
ivMenuMore.setOnClickListener {
if (recyclerView.isVisible) {
ivMenuMore.setImageResource(R.drawable.ic_arrow_back)
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 {
val menuItems = menu.visibleItems + moreMenu.visibleItems
menuItems.forEach {
val textView = ItemTextBinding.inflate(LayoutInflater.from(context)).root.apply {
tag = it
text = it.title
onClick {
if (!callBack.onMenuItemSelected(it.itemId)) {
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)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {
val bg = requireContext().bottomBackground
val isLight = ColorUtils.isColorLight(bg)
val textColor = requireContext().getPrimaryTextColor(isLight)
binding.root.setBackgroundColor(bg)
binding.tvReadSpeedTitle.setTextColor(textColor)
binding.tvReadSpeed.setTextColor(textColor)
binding.ivCatalog.setColorFilter(textColor)
binding.tvCatalog.setTextColor(textColor)
binding.ivMainMenu.setColorFilter(textColor)
binding.tvMainMenu.setTextColor(textColor)
binding.ivAutoPageStop.setColorFilter(textColor)
binding.tvAutoPageStop.setTextColor(textColor)
binding.ivSetting.setColorFilter(textColor)
binding.tvSetting.setTextColor(textColor)
root.setBackgroundColor(bg)
tvReadSpeedTitle.setTextColor(textColor)
tvReadSpeed.setTextColor(textColor)
ivCatalog.setColorFilter(textColor)
tvCatalog.setTextColor(textColor)
ivMainMenu.setColorFilter(textColor)
tvMainMenu.setTextColor(textColor)
ivAutoPageStop.setColorFilter(textColor)
tvAutoPageStop.setTextColor(textColor)
ivSetting.setColorFilter(textColor)
tvSetting.setTextColor(textColor)
initOnChange()
initData()
initEvent()

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

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

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

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

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

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

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

@ -72,7 +72,7 @@ class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
initData()
}
private fun initView() = with(binding) {
private fun initView() = binding.run {
toolBar.setBackgroundColor(primaryColor)
toolBar.setTitle(R.string.speak_engine)
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.menu.applyTint(requireContext())
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)
dsbTitleSize.progress = ReadBookConfig.titleSize
dsbTitleTop.progress = ReadBookConfig.titleTopSpacing
@ -74,7 +74,7 @@ class TipConfigDialog : BaseDialogFragment() {
}
}
private fun initEvent() = with(binding) {
private fun initEvent() = binding.run {
rgTitleMode.setOnCheckedChangeListener { _, checkedId ->
ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId)
postEvent(EventBus.UP_CONFIG, true)

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

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

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

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

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

@ -28,7 +28,7 @@ class SearchContentAdapter(context: Context, val callback: Callback) :
item: SearchResult,
payloads: MutableList<Any>
) {
with(binding) {
binding.run {
val isDur = callback.durChapterIndex() == item.chapterIndex
if (payloads.isEmpty()) {
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) {
fun bind(editEntity: EditEntity) = with(binding) {
fun bind(editEntity: EditEntity) = binding.run {
if (editText.getTag(R.id.tag1) == null) {
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {

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

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

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

@ -39,7 +39,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
private var tocLiveData: LiveData<List<BookChapter>>? = null
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
val bbg = bottomBackground
val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg))
@ -62,7 +62,7 @@ class ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapt
binding.recyclerView.adapter = adapter
}
private fun initView() = with(binding) {
private fun initView() = binding.run {
ivChapterTop.setOnClickListener { mLayoutManager.scrollToPositionWithOffset(0, 0) }
ivChapterBottom.setOnClickListener {
if (adapter.itemCount > 0) {

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

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

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

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

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

@ -1,15 +1,18 @@
package io.legado.app.ui.main.explore
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.appDb
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.ItemFindBookBinding
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) :
RecyclerAdapter<BookSource, ItemFindBookBinding>(context) {
private val recycler = arrayListOf<View>()
private var exIndex = -1
private var scrollTo = -1
@ -36,7 +41,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
item: BookSource,
payloads: MutableList<Any>
) {
with(binding) {
binding.run {
if (holder.layoutPosition == itemCount - 1) {
root.setPadding(16.dp, 12.dp, 16.dp, 12.dp)
} else {
@ -55,28 +60,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
Coroutine.async(scope) {
item.getExploreKinds()
}.onSuccess { kindList ->
if (!kindList.isNullOrEmpty()) {
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()
)
}
}
}
}
upKindList(flexbox, item.bookSourceUrl, kindList)
}.onFinally {
rotateLoading.hide()
if (scrollTo >= 0) {
@ -85,13 +69,54 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
}
}
} else {
binding.ivStatus.setImageResource(R.drawable.ic_arrow_right)
binding.rotateLoading.hide()
binding.glChild.gone()
ivStatus.setImageResource(R.drawable.ic_arrow_right)
rotateLoading.hide()
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) {
binding.apply {
llTitle.setOnClickListener {
@ -146,7 +171,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca
interface CallBack {
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 toTop(source: BookSource)
}

@ -154,7 +154,8 @@ class ExploreFragment : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explo
(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> {
putExtra("exploreName", title)
putExtra("sourceUrl", sourceUrl)

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

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

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

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

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

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

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

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

@ -33,7 +33,7 @@ class RssSourceEditAdapter : RecyclerView.Adapter<RssSourceEditAdapter.MyViewHol
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) {
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {

@ -47,7 +47,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
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.title = getString(R.string.group_manage)
toolBar.inflateMenu(R.menu.group_manage)
@ -128,7 +128,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
item: String,
payloads: MutableList<Any>
) {
with(binding) {
binding.run {
root.setBackgroundColor(context.backgroundColor)
tvGroup.text = item
}

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

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

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

@ -47,7 +47,7 @@ class TextListDialog : BaseDialogFragment() {
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 {
toolBar.title = it.getString("title")
values = it.getStringArrayList("values")

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

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

@ -1,11 +1,18 @@
<?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_height="wrap_content">
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/tv_dict"
android:layout_width="match_parent"
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">
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/gl_child"
android:id="@+id/flexbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"

@ -1,46 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<com.google.android.flexbox.FlexboxLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:padding="5dp"
android:orientation="vertical">
<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>
app:flexDirection="row"
app:flexWrap="wrap" />
Loading…
Cancel
Save