Merge pull request #1 from gedoor/master

update fork
pull/349/head
Gladtbam 5 years ago committed by GitHub
commit 0aa6ee9803
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 62
      app/src/main/assets/httpTTS.json
  2. 8
      app/src/main/assets/readConfig.json
  3. 47
      app/src/main/assets/updateLog.md
  4. 5
      app/src/main/java/io/legado/app/App.kt
  5. 11
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  6. 5
      app/src/main/java/io/legado/app/base/VMBaseActivity.kt
  7. 10
      app/src/main/java/io/legado/app/constant/AppPattern.kt
  8. 2
      app/src/main/java/io/legado/app/constant/EventBus.kt
  9. 3
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  10. 9
      app/src/main/java/io/legado/app/constant/Theme.kt
  11. 25
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  12. 31
      app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt
  13. 14
      app/src/main/java/io/legado/app/data/dao/ReadRecordDao.kt
  14. 5
      app/src/main/java/io/legado/app/data/entities/Book.kt
  15. 12
      app/src/main/java/io/legado/app/data/entities/HttpTTS.kt
  16. 5
      app/src/main/java/io/legado/app/data/entities/ReadRecord.kt
  17. 6
      app/src/main/java/io/legado/app/data/entities/ReadRecordShow.kt
  18. 3
      app/src/main/java/io/legado/app/help/AppConfig.kt
  19. 54
      app/src/main/java/io/legado/app/help/BookHelp.kt
  20. 19
      app/src/main/java/io/legado/app/help/DefaultValueHelp.kt
  21. 3
      app/src/main/java/io/legado/app/help/ImageLoader.kt
  22. 10
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  23. 13
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  24. 8
      app/src/main/java/io/legado/app/help/http/CookieStore.kt
  25. 69
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  26. 12
      app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt
  27. 8
      app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt
  28. 2
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  29. 25
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  30. 12
      app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt
  31. 11
      app/src/main/java/io/legado/app/lib/theme/ATH.kt
  32. 16
      app/src/main/java/io/legado/app/lib/theme/view/ATERadioNoButton.kt
  33. 2
      app/src/main/java/io/legado/app/model/Debug.kt
  34. 93
      app/src/main/java/io/legado/app/model/SearchBookModel.kt
  35. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  36. 165
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  37. 2
      app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt
  38. 2
      app/src/main/java/io/legado/app/model/rss/Rss.kt
  39. 14
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  40. 5
      app/src/main/java/io/legado/app/model/webBook/BookList.kt
  41. 117
      app/src/main/java/io/legado/app/model/webBook/SearchBookModel.kt
  42. 12
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  43. 1
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  44. 2
      app/src/main/java/io/legado/app/service/DownloadService.kt
  45. 48
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  46. 2
      app/src/main/java/io/legado/app/service/help/AudioPlay.kt
  47. 2
      app/src/main/java/io/legado/app/service/help/CheckSource.kt
  48. 17
      app/src/main/java/io/legado/app/service/help/ReadAloud.kt
  49. 14
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  50. 5
      app/src/main/java/io/legado/app/ui/about/AboutFragment.kt
  51. 35
      app/src/main/java/io/legado/app/ui/about/ReadRecordActivity.kt
  52. 5
      app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt
  53. 5
      app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt
  54. 5
      app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleActivity.kt
  55. 5
      app/src/main/java/io/legado/app/ui/association/ImportRssSourceActivity.kt
  56. 7
      app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt
  57. 2
      app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
  58. 5
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  59. 11
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt
  60. 78
      app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt
  61. 14
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
  62. 85
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  63. 5
      app/src/main/java/io/legado/app/ui/book/chapterlist/BookmarkFragment.kt
  64. 28
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListAdapter.kt
  65. 8
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt
  66. 5
      app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt
  67. 3
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt
  68. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
  69. 10
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  70. 10
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  71. 17
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  72. 10
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  73. 3
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  74. 3
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  75. 39
      app/src/main/java/io/legado/app/ui/book/read/Help.kt
  76. 17
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  77. 15
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  78. 8
      app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt
  79. 9
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  80. 7
      app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt
  81. 4
      app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt
  82. 38
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt
  83. 8
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  84. 12
      app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt
  85. 164
      app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt
  86. 34
      app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt
  87. 12
      app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt
  88. 10
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt
  89. 6
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  90. 14
      app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
  91. 10
      app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt
  92. 2
      app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugModel.kt
  93. 16
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  94. 26
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
  95. 13
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  96. 19
      app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt
  97. 7
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  98. 21
      app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt
  99. 5
      app/src/main/java/io/legado/app/ui/filechooser/FileChooserDialog.kt
  100. 8
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,62 @@
[
{
"id": 1598233029304,
"name": "度小美",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=0&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029305,
"name": "度小宇",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=1&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029306,
"name": "度逍遥",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=3&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029307,
"name": "度丫丫",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=4&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029308,
"name": "度小娇",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=5&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029309,
"name": "度米朵",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=103&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029310,
"name": "度博文",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=106&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029311,
"name": "度小童",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=110&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029312,
"name": "度小萌",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=111&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029313,
"name": "百度骚男",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=11&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029314,
"name": "百度评书",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=6&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
},
{
"id": 1598233029315,
"name": "百度主持",
"url": "http://tts.baidu.com/text2audio,{\n \"method\": \"POST\",\n \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=9&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}"
}
]

@ -1,9 +1,9 @@
[ [
{ {
"bgStr": "#EBD9BB", "bgStr": "#FFFFFF",
"bgStrNight": "#1E2021", "bgStrNight": "#000000",
"textColor": "#63543C", "textColor": "#000000",
"textColorNight": "#DCDFE1", "textColorNight": "#FFFFFF",
"bgType": 0, "bgType": 0,
"bgTypeNight": 0, "bgTypeNight": 0,
"darkStatusIcon": true, "darkStatusIcon": true,

@ -3,11 +3,56 @@
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
- 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 - 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/08/23** **2020/08/29**
* 修复一个null引起的崩溃bug
* 修复我的界面滚动时图标消失的bug
* 修复从详情页目录打开章节内容不对的bug
* 书源选择增加滑动选择, 选择框区域滑动时进行选择 by [Mupceet](https://github.com/Mupceet)
* 请求头,支持http代理,socks4 socks5代理设置 by [10bits](https://github.com/10bits)
```
socks5代理
{
"proxy":"socks5://127.0.0.1:1080"
}
http代理
{
"proxy":"http://127.0.0.1:1080"
}
支持代理服务器验证
{
"proxy":"socks5://127.0.0.1:1080@用户名@密码"
}
注意:这些请求头是无意义的,会被忽略掉
```
**2020/08/28**
* 修复一些bug
* 换源不再改变书名和作者,防止换到一些错误的书源后不能再换源
**2020/08/27**
* 修复主题bug
* 修复封面bug
* 优化书籍更新,搜索,换源
* e-ink模式不再固定背景
**2020/08/26**
* js添加java.encodeURI(speakText),用于解决js编码时有~的语句朗读不出来
* 修复书名太长删除阅读记录按钮不显示的bug
* 完成本地书籍编码选择
**2020/08/25**
* 阅读记录可以删除了
* 修复翻页模式选择颜色问题
* 修复toolbar在一些情况下文字颜色不对的bug
* 多设备阅读记录叠加
* 封面链接支持修改headers
**2020/08/24**
* 应用被杀死时停止朗读 * 应用被杀死时停止朗读
* 默认封面添加删除操作 * 默认封面添加删除操作
* 备份阅读记录 * 备份阅读记录
* 书源添加移除分组支持多选,多个分组以逗号(中英均可)隔开 * 书源添加移除分组支持多选,多个分组以逗号(中英均可)隔开
* 可以自定义在线朗读了
**2020/08/22** **2020/08/22**
* 添加阅读时间记录 * 添加阅读时间记录

@ -6,6 +6,7 @@ import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.provider.Settings
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
@ -34,14 +35,16 @@ class App : MultiDexApplication() {
@JvmStatic @JvmStatic
lateinit var db: AppDatabase lateinit var db: AppDatabase
private set private set
}
lateinit var androidId: String
var versionCode = 0 var versionCode = 0
var versionName = "" var versionName = ""
}
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
INSTANCE = this INSTANCE = this
androidId = Settings.System.getString(contentResolver, Settings.Secure.ANDROID_ID)
CrashHandler().init(this) CrashHandler().init(this)
LanguageUtils.setConfigurationOld(this) LanguageUtils.setConfigurationOld(this)
db = AppDatabase.createDatabase(INSTANCE) db = AppDatabase.createDatabase(INSTANCE)

@ -25,6 +25,7 @@ abstract class BaseActivity(
private val layoutID: Int, private val layoutID: Int,
private val fullScreen: Boolean = true, private val fullScreen: Boolean = true,
private val theme: Theme = Theme.Auto, private val theme: Theme = Theme.Auto,
private val toolBarTheme: Theme = Theme.Auto,
private val transparent: Boolean = false private val transparent: Boolean = false
) : AppCompatActivity(), ) : AppCompatActivity(),
CoroutineScope by MainScope() { CoroutineScope by MainScope() {
@ -65,7 +66,7 @@ abstract class BaseActivity(
final override fun onCreateOptionsMenu(menu: Menu?): Boolean { final override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return menu?.let { return menu?.let {
val bool = onCompatCreateOptionsMenu(it) val bool = onCompatCreateOptionsMenu(it)
it.applyTint(this, theme) it.applyTint(this, toolBarTheme)
bool bool
} ?: super.onCreateOptionsMenu(menu) } ?: super.onCreateOptionsMenu(menu)
} }
@ -129,10 +130,10 @@ abstract class BaseActivity(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
} }
ATH.setStatusBarColorAuto(this, fullScreen) ATH.setStatusBarColorAuto(this, fullScreen)
if (theme == Theme.Dark) { if (toolBarTheme == Theme.Dark) {
ATH.setLightStatusBar(this, false) ATH.setLightStatusBar(window, false)
} else if (theme == Theme.Light) { } else if (toolBarTheme == Theme.Light) {
ATH.setLightStatusBar(this, true) ATH.setLightStatusBar(window, true)
} }
upNavigationBarColor() upNavigationBarColor()
} }

@ -6,8 +6,9 @@ import io.legado.app.constant.Theme
abstract class VMBaseActivity<VM : ViewModel>( abstract class VMBaseActivity<VM : ViewModel>(
layoutID: Int, layoutID: Int,
fullScreen: Boolean = true, fullScreen: Boolean = true,
theme: Theme = Theme.Auto theme: Theme = Theme.Auto,
) : BaseActivity(layoutID, fullScreen, theme) { toolBarTheme: Theme = Theme.Auto
) : BaseActivity(layoutID, fullScreen, theme, toolBarTheme) {
protected abstract val viewModel: VM protected abstract val viewModel: VM

@ -3,8 +3,14 @@ package io.legado.app.constant
import java.util.regex.Pattern import java.util.regex.Pattern
object AppPattern { object AppPattern {
val JS_PATTERN: Pattern = Pattern.compile("(<js>[\\w\\W]*?</js>|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE) val JS_PATTERN: Pattern =
Pattern.compile("(<js>[\\w\\W]*?</js>|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE)
val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}") val EXP_PATTERN: Pattern = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
val imgPattern: Pattern =
Pattern.compile("<img .*?src.*?=.*?\"(.*?(?:,\\{.*\\})?)\".*?>", Pattern.CASE_INSENSITIVE)
val authorRegex = "\\s*者\\s*[::]".toRegex() val nameRegex = Regex("\\s+作\\s*者.*")
val authorRegex = Regex(".*?作\\s*?者[::]")
val fileNameRegex = Regex("[\\\\/:*?\"<>|.]")
val splitGroupRegex = Regex("[,;]")
} }

@ -3,7 +3,7 @@ package io.legado.app.constant
object EventBus { object EventBus {
const val MEDIA_BUTTON = "mediaButton" const val MEDIA_BUTTON = "mediaButton"
const val RECREATE = "RECREATE" const val RECREATE = "RECREATE"
const val UP_BOOK = "sourceDebugLog" const val UP_BOOK = "upBookToc"
const val ALOUD_STATE = "aloud_state" const val ALOUD_STATE = "aloud_state"
const val TTS_PROGRESS = "ttsStart" const val TTS_PROGRESS = "ttsStart"
const val TTS_DS = "ttsDs" const val TTS_DS = "ttsDs"

@ -9,10 +9,9 @@ object PreferKey {
const val clickAllNext = "clickAllNext" const val clickAllNext = "clickAllNext"
const val hideNavigationBar = "hideNavigationBar" const val hideNavigationBar = "hideNavigationBar"
const val precisionSearch = "precisionSearch" const val precisionSearch = "precisionSearch"
const val readAloudOnLine = "readAloudOnLine" const val speakEngine = "speakEngine"
const val readAloudByPage = "readAloudByPage" const val readAloudByPage = "readAloudByPage"
const val ttsSpeechRate = "ttsSpeechRate" const val ttsSpeechRate = "ttsSpeechRate"
const val ttsSpeechPer = "ttsSpeechPer"
const val prevKey = "prevKeyCode" const val prevKey = "prevKeyCode"
const val nextKey = "nextKeyCode" const val nextKey = "nextKeyCode"
const val showRss = "showRss" const val showRss = "showRss"

@ -1,6 +1,7 @@
package io.legado.app.constant package io.legado.app.constant
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.utils.ColorUtils
enum class Theme { enum class Theme {
Dark, Light, Auto, Transparent; Dark, Light, Auto, Transparent;
@ -11,5 +12,13 @@ enum class Theme {
Dark Dark
} else Light } else Light
} }
fun getTheme(backgroundColor: Int): Theme {
return if (ColorUtils.isColorLight(backgroundColor)) {
Light
} else {
Dark
}
}
} }
} }

@ -6,6 +6,7 @@ import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.App
import io.legado.app.data.dao.* import io.legado.app.data.dao.*
import io.legado.app.data.entities.* import io.legado.app.data.entities.*
@ -14,8 +15,8 @@ import io.legado.app.data.entities.*
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class, RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class, ReadRecord::class], RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class],
version = 17, version = 19,
exportSchema = true exportSchema = true
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
@ -33,7 +34,9 @@ abstract class AppDatabase : RoomDatabase() {
migration_12_13, migration_12_13,
migration_13_14, migration_13_14,
migration_14_15, migration_14_15,
migration_15_17 migration_15_17,
migration_17_18,
migration_18_19
) )
.allowMainThreadQueries() .allowMainThreadQueries()
.build() .build()
@ -94,6 +97,21 @@ abstract class AppDatabase : RoomDatabase() {
database.execSQL("CREATE TABLE IF NOT EXISTS `readRecord` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))") database.execSQL("CREATE TABLE IF NOT EXISTS `readRecord` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))")
} }
} }
private val migration_17_18 = object : Migration(17, 18) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `httpTTS` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))")
}
}
private val migration_18_19 = object : Migration(18, 19) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `readRecordNew` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))")
database.execSQL("INSERT INTO readRecordNew(androidId, bookName, readTime) select '${App.androidId}' as androidId, bookName, readTime from readRecord")
database.execSQL("DROP TABLE readRecord")
database.execSQL("ALTER TABLE readRecordNew RENAME TO readRecord")
}
}
} }
abstract fun bookDao(): BookDao abstract fun bookDao(): BookDao
@ -110,4 +128,5 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun cookieDao(): CookieDao abstract fun cookieDao(): CookieDao
abstract fun txtTocRule(): TxtTocRuleDao abstract fun txtTocRule(): TxtTocRuleDao
abstract fun readRecordDao(): ReadRecordDao abstract fun readRecordDao(): ReadRecordDao
abstract fun httpTTSDao(): HttpTTSDao
} }

@ -0,0 +1,31 @@
package io.legado.app.data.dao
import androidx.lifecycle.LiveData
import androidx.room.*
import io.legado.app.data.entities.HttpTTS
@Dao
interface HttpTTSDao {
@get:Query("select * from httpTTS order by name")
val all: List<HttpTTS>
@Query("select * from httpTTS order by name")
fun observeAll(): LiveData<List<HttpTTS>>
@get:Query("select count(*) from httpTTS")
val count: Int
@Query("select * from httpTTS where id = :id")
fun get(id: Long): HttpTTS?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg httpTTS: HttpTTS)
@Delete
fun delete(vararg httpTTS: HttpTTS)
@Update
fun update(vararg httpTTS: HttpTTS)
}

@ -2,6 +2,7 @@ package io.legado.app.data.dao
import androidx.room.* import androidx.room.*
import io.legado.app.data.entities.ReadRecord import io.legado.app.data.entities.ReadRecord
import io.legado.app.data.entities.ReadRecordShow
@Dao @Dao
interface ReadRecordDao { interface ReadRecordDao {
@ -9,16 +10,27 @@ interface ReadRecordDao {
@get:Query("select * from readRecord") @get:Query("select * from readRecord")
val all: List<ReadRecord> val all: List<ReadRecord>
@get:Query("select bookName, sum(readTime) as readTime from readRecord group by bookName order by bookName")
val allShow: List<ReadRecordShow>
@get:Query("select sum(readTime) from readRecord") @get:Query("select sum(readTime) from readRecord")
val allTime: Long val allTime: Long
@Query("select readTime from readRecord where bookName = :bookName") @Query("select sum(readTime) from readRecord where bookName = :bookName")
fun getReadTime(bookName: String): Long? fun getReadTime(bookName: String): Long?
@Query("select readTime from readRecord where androidId = :androidId and bookName = :bookName")
fun getReadTime(androidId: String, bookName: String): Long?
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg readRecord: ReadRecord) fun insert(vararg readRecord: ReadRecord)
@Update @Update
fun update(vararg record: ReadRecord) fun update(vararg record: ReadRecord)
@Delete
fun delete(vararg record: ReadRecord)
@Query("delete from readRecord where bookName = :bookName")
fun deleteByName(bookName: String)
} }

@ -11,6 +11,7 @@ import io.legado.app.constant.BookType
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import kotlinx.android.parcel.IgnoredOnParcel import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@ -115,6 +116,10 @@ data class Book(
return charset(charset ?: "UTF-8") return charset(charset ?: "UTF-8")
} }
fun getFolderName(): String {
return name.replace(AppPattern.fileNameRegex, "") + MD5Utils.md5Encode16(bookUrl)
}
fun toSearchBook(): SearchBook { fun toSearchBook(): SearchBook {
return SearchBook( return SearchBook(
name = name, name = name,

@ -0,0 +1,12 @@
package io.legado.app.data.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "httpTTS")
data class HttpTTS(
@PrimaryKey
val id: Long = System.currentTimeMillis(),
var name: String = "",
var url: String = ""
)

@ -1,11 +1,10 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "readRecord") @Entity(tableName = "readRecord", primaryKeys = ["androidId", "bookName"])
data class ReadRecord( data class ReadRecord(
@PrimaryKey var androidId: String = "",
var bookName: String = "", var bookName: String = "",
var readTime: Long = 0L var readTime: Long = 0L
) )

@ -0,0 +1,6 @@
package io.legado.app.data.entities
data class ReadRecordShow(
var bookName: String = "",
var readTime: Long = 0L
)

@ -81,9 +81,6 @@ object AppConfig {
App.INSTANCE.putPrefInt(PreferKey.ttsSpeechRate, value) App.INSTANCE.putPrefInt(PreferKey.ttsSpeechRate, value)
} }
val ttsSpeechPer: String
get() = App.INSTANCE.getPrefString(PreferKey.ttsSpeechPer) ?: "0"
val clickAllNext: Boolean get() = App.INSTANCE.getPrefBoolean(PreferKey.clickAllNext, false) val clickAllNext: Boolean get() = App.INSTANCE.getPrefBoolean(PreferKey.clickAllNext, false)
var chineseConverterType: Int var chineseConverterType: Int

@ -2,6 +2,7 @@ package io.legado.app.help
import com.hankcs.hanlp.HanLP import com.hankcs.hanlp.HanLP
import io.legado.app.App import io.legado.app.App
import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
@ -16,19 +17,12 @@ import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.JaccardSimilarity import org.apache.commons.text.similarity.JaccardSimilarity
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File import java.io.File
import java.util.regex.Pattern
import kotlin.math.min import kotlin.math.min
object BookHelp { object BookHelp {
private const val cacheFolderName = "book_cache" private const val cacheFolderName = "book_cache"
private const val cacheImageFolderName = "images" private const val cacheImageFolderName = "images"
private val downloadDir: File = App.INSTANCE.externalFilesDir private val downloadDir: File = App.INSTANCE.externalFilesDir
private val srcPattern =
Pattern.compile("<img .*?src.*?=.*?\"(.*?(?:,\\{.*\\})?)\".*?>", Pattern.CASE_INSENSITIVE)
fun bookFolderName(book: Book): String {
return formatFolderName(book.name) + MD5Utils.md5Encode16(book.bookUrl)
}
fun formatChapterName(bookChapter: BookChapter): String { fun formatChapterName(bookChapter: BookChapter): String {
return String.format( return String.format(
@ -51,7 +45,7 @@ object BookHelp {
Coroutine.async { Coroutine.async {
val bookFolderNames = arrayListOf<String>() val bookFolderNames = arrayListOf<String>()
App.db.bookDao().all.forEach { App.db.bookDao().all.forEach {
bookFolderNames.add(bookFolderName(it)) bookFolderNames.add(it.getFolderName())
} }
val file = FileUtils.getDirFile(downloadDir, cacheFolderName) val file = FileUtils.getDirFile(downloadDir, cacheFolderName)
file.listFiles()?.forEach { bookFile -> file.listFiles()?.forEach { bookFile ->
@ -62,18 +56,17 @@ object BookHelp {
} }
} }
@Synchronized
fun saveContent(book: Book, bookChapter: BookChapter, content: String) { fun saveContent(book: Book, bookChapter: BookChapter, content: String) {
if (content.isEmpty()) return if (content.isEmpty()) return
//保存文本 //保存文本
FileUtils.createFileIfNotExist( FileUtils.createFileIfNotExist(
downloadDir, downloadDir,
formatChapterName(bookChapter), formatChapterName(bookChapter),
subDirs = arrayOf(cacheFolderName, bookFolderName(book)) subDirs = arrayOf(cacheFolderName, book.getFolderName())
).writeText(content) ).writeText(content)
//保存图片 //保存图片
content.split("\n").forEach { content.split("\n").forEach {
val matcher = srcPattern.matcher(it) val matcher = AppPattern.imgPattern.matcher(it)
if (matcher.find()) { if (matcher.find()) {
var src = matcher.group(1) var src = matcher.group(1)
src = NetworkUtils.getAbsoluteURL(bookChapter.url, src) src = NetworkUtils.getAbsoluteURL(bookChapter.url, src)
@ -86,29 +79,40 @@ object BookHelp {
} }
fun saveImage(book: Book, src:String) { fun saveImage(book: Book, src:String) {
val analyzeUrl = AnalyzeUrl(src, null, null, null, null) val analyzeUrl = AnalyzeUrl(src)
analyzeUrl.getImageBytes(book.origin)?.let { analyzeUrl.getImageBytes(book.origin)?.let {
FileUtils.createFileIfNotExist( FileUtils.createFileIfNotExist(
downloadDir, downloadDir,
"${MD5Utils.md5Encode16(src)}${src.substringAfterLast(".").substringBefore(",")}", "${MD5Utils.md5Encode16(src)}${getImageSuffix(src)}",
subDirs = arrayOf(cacheFolderName, bookFolderName(book), cacheImageFolderName) subDirs = arrayOf(cacheFolderName, book.getFolderName(), cacheImageFolderName)
).writeBytes(it) ).writeBytes(it)
} }
} }
fun getImage(book:Book, src:String): File { fun getImage(book: Book, src: String): File {
return FileUtils.getFile( return FileUtils.getFile(
downloadDir, downloadDir,
"${MD5Utils.md5Encode16(src)}${src.substringAfterLast(".").substringBefore(",")}", "${MD5Utils.md5Encode16(src)}${getImageSuffix(src)}",
subDirs = arrayOf(cacheFolderName, bookFolderName(book), cacheImageFolderName) subDirs = arrayOf(cacheFolderName, book.getFolderName(), cacheImageFolderName)
) )
} }
private fun getImageSuffix(src: String): String {
var suffix = src.substringAfterLast(".").substringBefore(",")
if (suffix.length > 5) {
suffix = ".jpg"
}
return suffix
}
fun getChapterFiles(book: Book): List<String> { fun getChapterFiles(book: Book): List<String> {
val fileNameList = arrayListOf<String>() val fileNameList = arrayListOf<String>()
if (book.isLocalBook()) {
return fileNameList
}
FileUtils.createFolderIfNotExist( FileUtils.createFolderIfNotExist(
downloadDir, downloadDir,
subDirs = arrayOf(cacheFolderName, bookFolderName(book)) subDirs = arrayOf(cacheFolderName, book.getFolderName())
).list()?.let { ).list()?.let {
fileNameList.addAll(it) fileNameList.addAll(it)
} }
@ -122,7 +126,7 @@ object BookHelp {
FileUtils.exists( FileUtils.exists(
downloadDir, downloadDir,
formatChapterName(bookChapter), formatChapterName(bookChapter),
subDirs = arrayOf(cacheFolderName, bookFolderName(book)) subDirs = arrayOf(cacheFolderName, book.getFolderName())
) )
} }
} }
@ -134,7 +138,7 @@ object BookHelp {
val file = FileUtils.getFile( val file = FileUtils.getFile(
downloadDir, downloadDir,
formatChapterName(bookChapter), formatChapterName(bookChapter),
subDirs = arrayOf(cacheFolderName, bookFolderName(book)) subDirs = arrayOf(cacheFolderName, book.getFolderName())
) )
if (file.exists()) { if (file.exists()) {
return file.readText() return file.readText()
@ -150,24 +154,20 @@ object BookHelp {
FileUtils.createFileIfNotExist( FileUtils.createFileIfNotExist(
downloadDir, downloadDir,
formatChapterName(bookChapter), formatChapterName(bookChapter),
subDirs = arrayOf(cacheFolderName, bookFolderName(book)) subDirs = arrayOf(cacheFolderName, book.getFolderName())
).delete() ).delete()
} }
} }
private fun formatFolderName(folderName: String): String {
return folderName.replace("[\\\\/:*?\"<>|.]".toRegex(), "")
}
fun formatBookName(name: String): String { fun formatBookName(name: String): String {
return name return name
.replace("\\s+作\\s*者.*".toRegex(), "") .replace(AppPattern.nameRegex, "")
.trim { it <= ' ' } .trim { it <= ' ' }
} }
fun formatBookAuthor(author: String): String { fun formatBookAuthor(author: String): String {
return author return author
.replace(".*?作\\s*?者[::]".toRegex(), "") .replace(AppPattern.authorRegex, "")
.trim { it <= ' ' } .trim { it <= ' ' }
} }

@ -0,0 +1,19 @@
package io.legado.app.help
import io.legado.app.App
import io.legado.app.data.entities.HttpTTS
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
object DefaultValueHelp {
fun initHttpTTS() {
val json = String(App.INSTANCE.assets.open("httpTTS.json").readBytes())
GSON.fromJsonArray<HttpTTS>(json)?.let {
App.db.httpTTSDao().insert(*it.toTypedArray())
}
}
}

@ -9,6 +9,7 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.isContentPath import io.legado.app.utils.isContentPath
import io.legado.app.model.analyzeRule.AnalyzeUrl
import java.io.File import java.io.File
object ImageLoader { object ImageLoader {
@ -19,7 +20,7 @@ object ImageLoader {
fun load(context: Context, path: String?): RequestBuilder<Drawable> { fun load(context: Context, path: String?): RequestBuilder<Drawable> {
return when { return when {
path.isNullOrEmpty() -> Glide.with(context).load(path) path.isNullOrEmpty() -> Glide.with(context).load(path)
path.isAbsUrl() -> Glide.with(context).load(path) path.isAbsUrl() -> Glide.with(context).load(AnalyzeUrl(path).getGlideUrl())
path.isContentPath() -> Glide.with(context).load(Uri.parse(path)) path.isContentPath() -> Glide.with(context).load(Uri.parse(path))
else -> try { else -> try {
Glide.with(context).load(File(path)) Glide.with(context).load(File(path))

@ -6,6 +6,7 @@ import io.legado.app.constant.AppConst.dateFormat
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.EncoderUtils import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import java.net.URLEncoder
import java.util.* import java.util.*
@Keep @Keep
@ -52,10 +53,19 @@ interface JsExtensions {
fun timeFormat(time: Long): String { fun timeFormat(time: Long): String {
return dateFormat.format(Date(time)) return dateFormat.format(Date(time))
} }
//utf8编码转gbk编码 //utf8编码转gbk编码
fun utf8ToGbk(str: String): String { fun utf8ToGbk(str: String): String {
val utf8 = String(str.toByteArray(charset("UTF-8"))) val utf8 = String(str.toByteArray(charset("UTF-8")))
val unicode = String(utf8.toByteArray(), charset("UTF-8")) val unicode = String(utf8.toByteArray(), charset("UTF-8"))
return String(unicode.toByteArray(charset("GBK"))) return String(unicode.toByteArray(charset("GBK")))
} }
fun encodeURI(str: String): String {
return try {
URLEncoder.encode(str, "UTF-8")
} catch (e: Exception) {
""
}
}
} }

@ -29,13 +29,7 @@ object ReadBookConfig {
var bg: Drawable? = null var bg: Drawable? = null
var bgMeanColor: Int = 0 var bgMeanColor: Int = 0
val textColor: Int val textColor: Int get() = durConfig.textColor()
get() =
if (AppConfig.isEInkMode && !AppConfig.isNightTheme) {
Color.BLACK
} else {
durConfig.textColor()
}
init { init {
upConfig() upConfig()
@ -77,10 +71,6 @@ object ReadBookConfig {
val dm = resources.displayMetrics val dm = resources.displayMetrics
val width = dm.widthPixels val width = dm.widthPixels
val height = dm.heightPixels val height = dm.heightPixels
if (AppConfig.isEInkMode && !AppConfig.isNightTheme) {
bg = ColorDrawable(Color.WHITE)
bgMeanColor = Color.WHITE
} else {
bg = durConfig.bgDrawable(width, height).apply { bg = durConfig.bgDrawable(width, height).apply {
if (this is BitmapDrawable) { if (this is BitmapDrawable) {
bgMeanColor = BitmapUtils.getMeanColor(bitmap) bgMeanColor = BitmapUtils.getMeanColor(bitmap)
@ -88,7 +78,6 @@ object ReadBookConfig {
bgMeanColor = color bgMeanColor = color
} }
} }
}
isScroll = pageAnim == 3 isScroll = pageAnim == 3
} }

@ -6,13 +6,13 @@ import com.franmontiel.persistentcookiejar.persistence.SerializableCookie
import io.legado.app.App import io.legado.app.App
import io.legado.app.data.entities.Cookie import io.legado.app.data.entities.Cookie
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.utils.NetworkUtils
object CookieStore : CookiePersistor { object CookieStore : CookiePersistor {
fun setCookie(url: String, cookie: String?) { fun setCookie(url: String, cookie: String?) {
Coroutine.async { Coroutine.async {
val cookieBean = Cookie(url, cookie ?: "") val cookieBean = Cookie(NetworkUtils.getSubDomain(url), cookie ?: "")
App.db.cookieDao().insert(cookieBean) App.db.cookieDao().insert(cookieBean)
} }
} }
@ -33,12 +33,12 @@ object CookieStore : CookiePersistor {
} }
fun getCookie(url: String): String { fun getCookie(url: String): String {
val cookieBean = App.db.cookieDao().get(url) val cookieBean = App.db.cookieDao().get(NetworkUtils.getSubDomain(url))
return cookieBean?.cookie ?: "" return cookieBean?.cookie ?: ""
} }
fun removeCookie(url: String) { fun removeCookie(url: String) {
App.db.cookieDao().delete(url) App.db.cookieDao().delete(NetworkUtils.getSubDomain(url))
} }
private fun cookieToMap(cookie: String): MutableMap<String, String> { private fun cookieToMap(cookie: String): MutableMap<String, String> {

@ -4,11 +4,10 @@ import io.legado.app.constant.AppConst
import io.legado.app.help.http.api.HttpGetApi import io.legado.app.help.http.api.HttpGetApi
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.ConnectionSpec import okhttp3.*
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Protocol
import retrofit2.Retrofit import retrofit2.Retrofit
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -83,6 +82,18 @@ object HttpHelper {
return getRetrofit(baseUrl, encode).create(T::class.java) return getRetrofit(baseUrl, encode).create(T::class.java)
} }
inline fun <reified T> getApiServiceWithProxy(
baseUrl: String,
encode: String? = null,
proxy: String? = null
): T {
return getRetrofitWithProxy(baseUrl, encode, proxy).create(T::class.java)
}
inline fun <reified T> getBytesApiService(baseUrl: String): T {
return getByteRetrofit(baseUrl).create(T::class.java)
}
fun getRetrofit(baseUrl: String, encode: String? = null): Retrofit { fun getRetrofit(baseUrl: String, encode: String? = null): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl) return Retrofit.Builder().baseUrl(baseUrl)
//增加返回值为字符串的支持(以实体类返回) //增加返回值为字符串的支持(以实体类返回)
@ -91,6 +102,56 @@ object HttpHelper {
.build() .build()
} }
fun getRetrofitWithProxy(
baseUrl: String,
encode: String? = null,
proxy: String? = null
): Retrofit {
val r = Regex("(http|socks4|socks5)://(.*):(\\d{2,5})(@.*@.*)?")
val ms = proxy?.let { r.findAll(it) };
val group = ms?.first()
var type = "direct" //直接连接
var host = "127.0.0.1" //代理服务器hostname
var port = 1080 //代理服务器port
var username = "" //代理服务器验证用户名
var password = "" //代理服务器验证密码
if (group != null) {
type = if (group.groupValues[1] == "http") {
"http"
} else {
"socks"
}
host = group.groupValues[2]
port = group.groupValues[3].toInt()
if (group.groupValues[4] != "") {
username = group.groupValues[4].split("@")[1]
password = group.groupValues[4].split("@")[2]
}
}
val builder = client.newBuilder()
if (type != "direct" && host != "") {
if (type == "http") {
builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port)));
} else {
builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port)));
}
if (username != "" && password != "") {
builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码
val credential: String = Credentials.basic(username, password)
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
}
}
return Retrofit.Builder().baseUrl(baseUrl)
//增加返回值为字符串的支持(以实体类返回)
.addConverterFactory(EncodeConverter(encode))
.client(builder.build())
.build()
}
fun getByteRetrofit(baseUrl: String): Retrofit { fun getByteRetrofit(baseUrl: String): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl) return Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(ByteConverter()) .addConverterFactory(ByteConverter())

@ -32,6 +32,12 @@ interface HttpGetApi {
@HeaderMap headers: Map<String, String> @HeaderMap headers: Map<String, String>
): Call<String> ): Call<String>
@GET
fun getByte(
@Url url: String,
@HeaderMap headers: Map<String, String>
): Call<ByteArray>
@GET @GET
fun getMap( fun getMap(
@Url url: String, @Url url: String,
@ -46,6 +52,12 @@ interface HttpGetApi {
@HeaderMap headers: Map<String, String> @HeaderMap headers: Map<String, String>
): Call<ByteArray> ): Call<ByteArray>
@GET
suspend fun getByteAsync(
@Url url: String,
@HeaderMap headers: Map<String, String>
): Response<ByteArray>
@GET @GET
suspend fun getMapByteAsync( suspend fun getMapByteAsync(
@Url url: String, @Url url: String,

@ -49,4 +49,12 @@ interface HttpPostApi {
@FieldMap(encoded = true) fieldMap: Map<String, String>, @FieldMap(encoded = true) fieldMap: Map<String, String>,
@HeaderMap headers: Map<String, String> @HeaderMap headers: Map<String, String>
): Response<ByteArray> ): Response<ByteArray>
@POST
suspend fun postBodyByteAsync(
@Url url: String,
@Body body: RequestBody,
@HeaderMap headers: Map<String, String>
): Response<ByteArray>
} }

@ -32,6 +32,7 @@ object Backup {
"replaceRule.json", "replaceRule.json",
"txtTocRule.json", "txtTocRule.json",
"readRecord.json", "readRecord.json",
"httpTTS.json",
ReadBookConfig.readConfigFileName, ReadBookConfig.readConfigFileName,
"config.xml" "config.xml"
) )
@ -59,6 +60,7 @@ object Backup {
writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath) writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath)
writeListToJson(App.db.txtTocRule().all, "txtTocRule.json", backupPath) writeListToJson(App.db.txtTocRule().all, "txtTocRule.json", backupPath)
writeListToJson(App.db.readRecordDao().all, "readRecord.json", backupPath) writeListToJson(App.db.readRecordDao().all, "readRecord.json", backupPath)
writeListToJson(App.db.httpTTSDao().all, "httpTTS.json", backupPath)
GSON.toJson(ReadBookConfig.configList)?.let { GSON.toJson(ReadBookConfig.configList)?.let {
FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName) FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName)
.writeText(it) .writeText(it)

@ -39,7 +39,8 @@ object Restore {
"readConfig", "readConfig",
PreferKey.themeMode, PreferKey.themeMode,
PreferKey.bookshelfLayout, PreferKey.bookshelfLayout,
PreferKey.showRss PreferKey.showRss,
PreferKey.threadCount
) )
//忽略标题 //忽略标题
@ -47,7 +48,8 @@ object Restore {
App.INSTANCE.getString(R.string.read_config), App.INSTANCE.getString(R.string.read_config),
App.INSTANCE.getString(R.string.theme_mode), App.INSTANCE.getString(R.string.theme_mode),
App.INSTANCE.getString(R.string.bookshelf_layout), App.INSTANCE.getString(R.string.bookshelf_layout),
App.INSTANCE.getString(R.string.show_rss) App.INSTANCE.getString(R.string.show_rss),
App.INSTANCE.getString(R.string.thread_count)
) )
//默认忽略keys //默认忽略keys
@ -135,7 +137,21 @@ object Restore {
App.db.txtTocRule().insert(*it.toTypedArray()) App.db.txtTocRule().insert(*it.toTypedArray())
} }
fileToListT<ReadRecord>(path, "readRecord.json")?.let { fileToListT<ReadRecord>(path, "readRecord.json")?.let {
App.db.readRecordDao().insert(*it.toTypedArray()) it.forEach { readRecord ->
//判断是不是本机记录
if (readRecord.androidId != App.androidId) {
App.db.readRecordDao().insert(readRecord)
} else {
val time = App.db.readRecordDao()
.getReadTime(readRecord.androidId, readRecord.bookName)
if (time == null || time < readRecord.readTime) {
App.db.readRecordDao().insert(readRecord)
}
}
}
}
fileToListT<HttpTTS>(path, "httpTTS.json")?.let {
App.db.httpTTSDao().insert(*it.toTypedArray())
} }
} }
} }
@ -205,6 +221,7 @@ object Restore {
PreferKey.themeMode == key && ignoreThemeMode -> false PreferKey.themeMode == key && ignoreThemeMode -> false
PreferKey.bookshelfLayout == key && ignoreBookshelfLayout -> false PreferKey.bookshelfLayout == key && ignoreBookshelfLayout -> false
PreferKey.showRss == key && ignoreShowRss -> false PreferKey.showRss == key && ignoreShowRss -> false
PreferKey.threadCount == key && ignoreThreadCount -> false
else -> true else -> true
} }
} }
@ -217,6 +234,8 @@ object Restore {
get() = ignoreConfig[PreferKey.bookshelfLayout] == true get() = ignoreConfig[PreferKey.bookshelfLayout] == true
private val ignoreShowRss: Boolean private val ignoreShowRss: Boolean
get() = ignoreConfig[PreferKey.showRss] == true get() = ignoreConfig[PreferKey.showRss] == true
private val ignoreThreadCount: Boolean
get() = ignoreConfig[PreferKey.threadCount] == true
fun saveIgnoreConfig() { fun saveIgnoreConfig() {
val json = GSON.toJson(ignoreConfig) val json = GSON.toJson(ignoreConfig)

@ -51,11 +51,11 @@ object WebDavHelp {
return false return false
} }
@Throws(Exception::class)
private fun getWebDavFileNames(): ArrayList<String> { private fun getWebDavFileNames(): ArrayList<String> {
val url = rootWebDavUrl val url = rootWebDavUrl
val names = arrayListOf<String>() val names = arrayListOf<String>()
if (initWebDav()) { if (initWebDav()) {
try {
var files = WebDav(url).listFiles() var files = WebDav(url).listFiles()
files = files.reversed() files = files.reversed()
for (index: Int in 0 until min(10, files.size)) { for (index: Int in 0 until min(10, files.size)) {
@ -63,16 +63,13 @@ object WebDavHelp {
names.add(it) names.add(it)
} }
} }
} catch (e: Exception) {
return names
}
} }
return names return names
} }
suspend fun showRestoreDialog(context: Context): Boolean { suspend fun showRestoreDialog(context: Context) {
val names = withContext(IO) { getWebDavFileNames() } val names = withContext(IO) { getWebDavFileNames() }
return if (names.isNotEmpty()) { if (names.isNotEmpty()) {
withContext(Main) { withContext(Main) {
context.selector( context.selector(
title = context.getString(R.string.select_restore_file), title = context.getString(R.string.select_restore_file),
@ -83,9 +80,8 @@ object WebDavHelp {
} }
} }
} }
true
} else { } else {
false throw Exception("Web dav no back up file")
} }
} }

@ -9,6 +9,7 @@ import android.graphics.drawable.GradientDrawable
import android.os.Build import android.os.Build
import android.view.View import android.view.View
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
import android.view.Window
import android.widget.EdgeEffect import android.widget.EdgeEffect
import android.widget.ScrollView import android.widget.ScrollView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
@ -63,16 +64,16 @@ object ATH {
} else { } else {
activity.window.statusBarColor = color activity.window.statusBarColor = color
} }
setLightStatusBarAuto(activity, color) setLightStatusBarAuto(activity.window, color)
} }
fun setLightStatusBarAuto(activity: Activity, bgColor: Int) { fun setLightStatusBarAuto(window: Window, bgColor: Int) {
setLightStatusBar(activity, ColorUtils.isColorLight(bgColor)) setLightStatusBar(window, ColorUtils.isColorLight(bgColor))
} }
fun setLightStatusBar(activity: Activity, enabled: Boolean) { fun setLightStatusBar(window: Window, enabled: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val decorView = activity.window.decorView val decorView = window.decorView
val systemUiVisibility = decorView.systemUiVisibility val systemUiVisibility = decorView.systemUiVisibility
if (enabled) { if (enabled) {
decorView.systemUiVisibility = decorView.systemUiVisibility =

@ -34,7 +34,6 @@ class ATERadioNoButton(context: Context, attrs: AttributeSet) :
isBottomBackground -> { isBottomBackground -> {
val isLight = ColorUtils.isColorLight(context.bottomBackground) val isLight = ColorUtils.isColorLight(context.bottomBackground)
val textColor = context.getPrimaryTextColor(isLight) val textColor = context.getPrimaryTextColor(isLight)
setTextColor(textColor)
background = Selector.shapeBuild() background = Selector.shapeBuild()
.setCornerRadius(2.dp) .setCornerRadius(2.dp)
.setStrokeWidth(2.dp) .setStrokeWidth(2.dp)
@ -42,15 +41,28 @@ class ATERadioNoButton(context: Context, attrs: AttributeSet) :
.setCheckedStrokeColor(context.accentColor) .setCheckedStrokeColor(context.accentColor)
.setDefaultStrokeColor(textColor) .setDefaultStrokeColor(textColor)
.create() .create()
setTextColor(
Selector.colorBuild()
.setDefaultColor(textColor)
.setCheckedColor(context.getPrimaryTextColor(ColorUtils.isColorLight(context.accentColor)))
.create()
)
} }
else -> { else -> {
val textColor = context.getCompatColor(R.color.primaryText)
background = Selector.shapeBuild() background = Selector.shapeBuild()
.setCornerRadius(2.dp) .setCornerRadius(2.dp)
.setStrokeWidth(2.dp) .setStrokeWidth(2.dp)
.setCheckedBgColor(context.accentColor) .setCheckedBgColor(context.accentColor)
.setCheckedStrokeColor(context.accentColor) .setCheckedStrokeColor(context.accentColor)
.setDefaultStrokeColor(context.getCompatColor(R.color.primaryText)) .setDefaultStrokeColor(textColor)
.create()
setTextColor(
Selector.colorBuild()
.setDefaultColor(textColor)
.setCheckedColor(context.getPrimaryTextColor(ColorUtils.isColorLight(context.accentColor)))
.create() .create()
)
} }
} }

@ -6,6 +6,8 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.model.rss.Rss
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.htmlFormat import io.legado.app.utils.htmlFormat
import io.legado.app.utils.isAbsUrl import io.legado.app.utils.isAbsUrl
import java.text.SimpleDateFormat import java.text.SimpleDateFormat

@ -1,93 +0,0 @@
package io.legado.app.model
import io.legado.app.App
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) {
private var searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
private var mSearchId = System.currentTimeMillis()
private var searchPage = 1
private var searchKey: String = ""
private var task: Coroutine<*>? = null
private fun initSearchPool() {
searchPool =
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
}
fun search(searchId: Long, key: String) {
if (searchId != mSearchId) {
task?.cancel()
searchPool.close()
initSearchPool()
mSearchId = searchId
searchPage = 1
if (key.isEmpty()) {
return
} else {
this.searchKey = key
}
} else {
searchPage++
}
task = Coroutine.async(scope, searchPool) {
val searchGroup = App.INSTANCE.getPrefString("searchGroup") ?: ""
val bookSourceList = if (searchGroup.isBlank()) {
App.db.bookSourceDao().allEnabled
} else {
App.db.bookSourceDao().getEnabledByGroup(searchGroup)
}
for (item in bookSourceList) {
//task取消时自动取消 by (scope = this@execute)
WebBook(item).searchBook(
searchKey,
searchPage,
scope = this,
context = searchPool
)
.timeout(30000L)
.onSuccess(IO) {
if (searchId == mSearchId) {
callBack.onSearchSuccess(it)
}
}
}
}.onStart {
callBack.onSearchStart()
}
task?.invokeOnCompletion {
if (searchId == mSearchId) {
callBack.onSearchFinish()
}
}
}
fun cancelSearch() {
task?.cancel()
mSearchId = 0
callBack.onSearchCancel()
}
fun close() {
task?.cancel()
mSearchId = 0
searchPool.close()
}
interface CallBack {
fun onSearchStart()
fun onSearchSuccess(searchBooks: ArrayList<SearchBook>)
fun onSearchFinish()
fun onSearchCancel()
}
}

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

@ -3,6 +3,8 @@ package io.legado.app.model.analyzeRule
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.text.TextUtils import android.text.TextUtils
import androidx.annotation.Keep import androidx.annotation.Keep
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import io.legado.app.constant.AppConst.SCRIPT_ENGINE import io.legado.app.constant.AppConst.SCRIPT_ENGINE
import io.legado.app.constant.AppConst.UA_NAME import io.legado.app.constant.AppConst.UA_NAME
import io.legado.app.constant.AppConst.userAgent import io.legado.app.constant.AppConst.userAgent
@ -23,7 +25,6 @@ import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
import javax.script.SimpleBindings import javax.script.SimpleBindings
/** /**
* Created by GKF on 2018/1/24. * Created by GKF on 2018/1/24.
* 搜索URL规则解析 * 搜索URL规则解析
@ -34,10 +35,12 @@ class AnalyzeUrl(
var ruleUrl: String, var ruleUrl: String,
key: String? = null, key: String? = null,
page: Int? = null, page: Int? = null,
speakText: String? = null,
speakSpeed: Int? = null,
headerMapF: Map<String, String>? = null, headerMapF: Map<String, String>? = null,
baseUrl: String? = null, baseUrl: String? = null,
book: BaseBook? = null, book: BaseBook? = null,
var useWebView: Boolean = false var useWebView: Boolean = false,
) : JsExtensions { ) : JsExtensions {
companion object { companion object {
private val pagePattern = Pattern.compile("<(.*?)>") private val pagePattern = Pattern.compile("<(.*?)>")
@ -47,8 +50,7 @@ class AnalyzeUrl(
private var baseUrl: String = "" private var baseUrl: String = ""
lateinit var url: String lateinit var url: String
private set private set
var path: String? = null private lateinit var urlHasQuery: String
private set
val headerMap = HashMap<String, String>() val headerMap = HashMap<String, String>()
private var queryStr: String? = null private var queryStr: String? = null
private val fieldMap = LinkedHashMap<String, String>() private val fieldMap = LinkedHashMap<String, String>()
@ -56,20 +58,34 @@ class AnalyzeUrl(
private var body: String? = null private var body: String? = null
private var requestBody: RequestBody? = null private var requestBody: RequestBody? = null
private var method = RequestMethod.GET private var method = RequestMethod.GET
private val splitUrlRegex = Regex(",\\s*(?=\\{)")
private var proxy: String? = null
init { init {
baseUrl?.let { baseUrl?.let {
this.baseUrl = it.split(",[^\\{]*".toRegex(), 1)[0] this.baseUrl = it.split(splitUrlRegex, 1)[0]
}
headerMapF?.let {
headerMap.putAll(it)
if (it.containsKey("proxy")) {
proxy = it["proxy"]
headerMap.remove("proxy")
}
} }
headerMapF?.let { headerMap.putAll(it) }
//替换参数 //替换参数
analyzeJs(key, page, book) analyzeJs(key, page, speakText, speakSpeed, book)
replaceKeyPageJs(key, page, book) replaceKeyPageJs(key, page, speakText, speakSpeed, book)
//处理URL //处理URL
initUrl() initUrl()
} }
private fun analyzeJs(key: String?, page: Int?, book: BaseBook?) { private fun analyzeJs(
key: String?,
page: Int?,
speakText: String?,
speakSpeed: Int?,
book: BaseBook?,
) {
val ruleList = arrayListOf<String>() val ruleList = arrayListOf<String>()
var start = 0 var start = 0
var tmp: String var tmp: String
@ -96,11 +112,13 @@ class AnalyzeUrl(
when { when {
ruleStr.startsWith("<js>") -> { ruleStr.startsWith("<js>") -> {
ruleStr = ruleStr.substring(4, ruleStr.lastIndexOf("<")) ruleStr = ruleStr.substring(4, ruleStr.lastIndexOf("<"))
ruleUrl = evalJS(ruleStr, ruleUrl, page, key, book) as String ruleUrl =
evalJS(ruleStr, ruleUrl, page, key, speakText, speakSpeed, book) as String
} }
ruleStr.startsWith("@js", true) -> { ruleStr.startsWith("@js", true) -> {
ruleStr = ruleStr.substring(4) ruleStr = ruleStr.substring(4)
ruleUrl = evalJS(ruleStr, ruleUrl, page, key, book) as String ruleUrl =
evalJS(ruleStr, ruleUrl, page, key, speakText, speakSpeed, book) as String
} }
else -> ruleUrl = ruleStr.replace("@result", ruleUrl) else -> ruleUrl = ruleStr.replace("@result", ruleUrl)
} }
@ -110,7 +128,13 @@ class AnalyzeUrl(
/** /**
* 替换关键字,页数,JS * 替换关键字,页数,JS
*/ */
private fun replaceKeyPageJs(key: String?, page: Int?, book: BaseBook?) { private fun replaceKeyPageJs(
key: String?,
page: Int?,
speakText: String?,
speakSpeed: Int?,
book: BaseBook?,
) {
//page //page
page?.let { page?.let {
val matcher = pagePattern.matcher(ruleUrl) val matcher = pagePattern.matcher(ruleUrl)
@ -132,10 +156,14 @@ class AnalyzeUrl(
simpleBindings["baseUrl"] = baseUrl simpleBindings["baseUrl"] = baseUrl
simpleBindings["page"] = page simpleBindings["page"] = page
simpleBindings["key"] = key simpleBindings["key"] = key
simpleBindings["speakText"] = speakText
simpleBindings["speakSpeed"] = speakSpeed
simpleBindings["book"] = book simpleBindings["book"] = book
val expMatcher = EXP_PATTERN.matcher(ruleUrl) val expMatcher = EXP_PATTERN.matcher(ruleUrl)
while (expMatcher.find()) { while (expMatcher.find()) {
jsEval = SCRIPT_ENGINE.eval(expMatcher.group(1), simpleBindings) jsEval = expMatcher.group(1)?.let {
SCRIPT_ENGINE.eval(it, simpleBindings)
} ?: ""
if (jsEval is String) { if (jsEval is String) {
expMatcher.appendReplacement(sb, jsEval) expMatcher.appendReplacement(sb, jsEval)
} else if (jsEval is Double && jsEval % 1.0 == 0.0) { } else if (jsEval is Double && jsEval % 1.0 == 0.0) {
@ -153,8 +181,9 @@ class AnalyzeUrl(
* 处理URL * 处理URL
*/ */
private fun initUrl() { private fun initUrl() {
var urlArray = ruleUrl.split(",[^\\{]*".toRegex(), 2) var urlArray = ruleUrl.split(splitUrlRegex, 2)
url = urlArray[0] url = urlArray[0]
urlHasQuery = urlArray[0]
NetworkUtils.getBaseUrl(url)?.let { NetworkUtils.getBaseUrl(url)?.let {
baseUrl = it baseUrl = it
} }
@ -172,6 +201,7 @@ class AnalyzeUrl(
?.let { headerMap.putAll(it) } ?.let { headerMap.putAll(it) }
} }
} }
headerMap[UA_NAME] = headerMap[UA_NAME] ?: userAgent
charset = option.charset charset = option.charset
body = if (option.body is String) { body = if (option.body is String) {
option.body option.body
@ -242,12 +272,16 @@ class AnalyzeUrl(
result: Any?, result: Any?,
page: Int?, page: Int?,
key: String?, key: String?,
book: BaseBook? speakText: String?,
speakSpeed: Int?,
book: BaseBook?,
): Any { ): Any {
val bindings = SimpleBindings() val bindings = SimpleBindings()
bindings["java"] = this bindings["java"] = this
bindings["page"] = page bindings["page"] = page
bindings["key"] = key bindings["key"] = key
bindings["speakText"] = speakText
bindings["speakSpeed"] = speakSpeed
bindings["book"] = book bindings["book"] = book
bindings["result"] = result bindings["result"] = result
bindings["baseUrl"] = baseUrl bindings["baseUrl"] = baseUrl
@ -285,7 +319,7 @@ class AnalyzeUrl(
suspend fun getResponseAwait( suspend fun getResponseAwait(
tag: String, tag: String,
jsStr: String? = null, jsStr: String? = null,
sourceRegex: String? = null sourceRegex: String? = null,
): Res { ): Res {
if (useWebView) { if (useWebView) {
val params = AjaxWebView.AjaxParams(url) val params = AjaxWebView.AjaxParams(url)
@ -304,51 +338,110 @@ class AnalyzeUrl(
val res = when { val res = when {
method == RequestMethod.POST -> { method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) { if (fieldMap.isNotEmpty()) {
if (proxy == null) {
HttpHelper HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset) .getApiService<HttpPostApi>(baseUrl, charset)
.postMapAsync(url, fieldMap, headerMap) .postMapAsync(url, fieldMap, headerMap)
} else { } else {
HttpHelper
.getApiServiceWithProxy<HttpPostApi>(baseUrl, charset, proxy)
.postMapAsync(url, fieldMap, headerMap)
}
} else {
if (proxy == null) {
HttpHelper HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset) .getApiService<HttpPostApi>(baseUrl, charset)
.postBodyAsync(url, requestBody!!, headerMap) .postBodyAsync(url, requestBody!!, headerMap)
} else {
HttpHelper
.getApiServiceWithProxy<HttpPostApi>(baseUrl, charset, proxy)
.postBodyAsync(url, requestBody!!, headerMap)
} }
} }
fieldMap.isEmpty() -> HttpHelper }
fieldMap.isEmpty() -> {
if (proxy == null) {
HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset) .getApiService<HttpGetApi>(baseUrl, charset)
.getAsync(url, headerMap) .getAsync(url, headerMap)
else -> HttpHelper
} else {
HttpHelper
.getApiServiceWithProxy<HttpGetApi>(baseUrl, charset, proxy)
.getAsync(url, headerMap)
}
}
else -> {
if (proxy == null) {
HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset) .getApiService<HttpGetApi>(baseUrl, charset)
.getMapAsync(url, fieldMap, headerMap) .getMapAsync(url, fieldMap, headerMap)
} else {
HttpHelper
.getApiServiceWithProxy<HttpGetApi>(baseUrl, charset, proxy)
.getMapAsync(url, fieldMap, headerMap)
}
}
} }
return Res(NetworkUtils.getUrl(res), res.body()) return Res(NetworkUtils.getUrl(res), res.body())
} }
@Throws(Exception::class) @Throws(Exception::class)
fun getImageBytes( fun getImageBytes(tag: String): ByteArray? {
tag: String
): ByteArray? {
//资源为本站的资源,保留cookie
//图片盗链的不保留当前的cookie,可由js生成图片源站的cookie
val cookie = CookieStore.getCookie(tag) val cookie = CookieStore.getCookie(tag)
NetworkUtils.getBaseUrl(url)?.let {
val regex: Any
if(it.lastIndexOf(".") != it.indexOf(".")) {
regex = it.substring(it.indexOf(".")+1).toRegex()
} else {
regex = it.substring(it.lastIndexOf("/")+1).toRegex()
}
if(regex.containsMatchIn(tag)) {
if (cookie.isNotEmpty()) { if (cookie.isNotEmpty()) {
headerMap["Cookie"] += cookie headerMap["Cookie"] += cookie
} }
return if (fieldMap.isEmpty()) {
HttpHelper.getBytes(url, mapOf(), headerMap)
} else {
HttpHelper.getBytes(url, fieldMap, headerMap)
}
}
@Throws(Exception::class)
suspend fun getResponseBytes(tag: String? = null): ByteArray? {
if (tag != null) {
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
headerMap["Cookie"] = cookie
} }
} }
headerMap[UA_NAME] = headerMap[UA_NAME] ?:userAgent val response = when {
if(fieldMap.isEmpty()) { method == RequestMethod.POST -> {
return HttpHelper.getBytes(url, mapOf(), headerMap) if (fieldMap.isNotEmpty()) {
HttpHelper
.getBytesApiService<HttpPostApi>(baseUrl)
.postMapByteAsync(url, fieldMap, headerMap)
} else { } else {
return HttpHelper.getBytes(url, fieldMap, headerMap) HttpHelper
.getBytesApiService<HttpPostApi>(baseUrl)
.postBodyByteAsync(url, requestBody!!, headerMap)
}
}
fieldMap.isEmpty() -> HttpHelper
.getBytesApiService<HttpGetApi>(baseUrl)
.getByteAsync(url, headerMap)
else -> HttpHelper
.getBytesApiService<HttpGetApi>(baseUrl)
.getMapByteAsync(url, fieldMap, headerMap)
}
return response.body()
}
@Throws(Exception::class)
fun getGlideUrl(): Any? {
var glideUrl: Any = urlHasQuery
if (headerMap.isNotEmpty()) {
val headers = LazyHeaders.Builder()
headerMap.forEach { (key, value) ->
headers.addHeader(key, value)
}
glideUrl = GlideUrl(urlHasQuery, headers.build())
} }
return glideUrl
} }
data class UrlOption( data class UrlOption(
@ -356,7 +449,7 @@ class AnalyzeUrl(
val charset: String?, val charset: String?,
val webView: Any?, val webView: Any?,
val headers: Any?, val headers: Any?,
val body: Any? val body: Any?,
) )
} }

@ -20,7 +20,9 @@ class AnalyzeTxtFile {
@Throws(Exception::class) @Throws(Exception::class)
fun analyze(book: Book): ArrayList<BookChapter> { fun analyze(book: Book): ArrayList<BookChapter> {
val bookFile = getBookFile(book) val bookFile = getBookFile(book)
if (book.charset == null) {
book.charset = EncodingDetect.getEncode(bookFile) book.charset = EncodingDetect.getEncode(bookFile)
}
charset = book.fileCharset() charset = book.fileCharset()
val rulePattern = if (book.tocUrl.isNotEmpty()) { val rulePattern = if (book.tocUrl.isNotEmpty()) {
Pattern.compile(book.tocUrl, Pattern.MULTILINE) Pattern.compile(book.tocUrl, Pattern.MULTILINE)

@ -1,4 +1,4 @@
package io.legado.app.model package io.legado.app.model.rss
import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource

@ -2,7 +2,6 @@ package io.legado.app.model.webBook
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
@ -19,7 +18,8 @@ object BookInfo {
book: Book, book: Book,
body: String?, body: String?,
bookSource: BookSource, bookSource: BookSource,
baseUrl: String baseUrl: String,
canReName: Boolean,
) { ) {
body ?: throw Exception( body ?: throw Exception(
App.INSTANCE.getString(R.string.error_get_web_content, baseUrl) App.INSTANCE.getString(R.string.error_get_web_content, baseUrl)
@ -35,13 +35,17 @@ object BookInfo {
} }
} }
Debug.log(bookSource.bookSourceUrl, "┌获取书名") Debug.log(bookSource.bookSourceUrl, "┌获取书名")
BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).trim { it <= ' ' }.let { BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let {
if (it.isNotEmpty()) book.name = it if (it.isNotEmpty() && (canReName || book.name.isEmpty())) {
book.name = it
}
} }
Debug.log(bookSource.bookSourceUrl, "${book.name}") Debug.log(bookSource.bookSourceUrl, "${book.name}")
Debug.log(bookSource.bookSourceUrl, "┌获取作者") Debug.log(bookSource.bookSourceUrl, "┌获取作者")
BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let { BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let {
if (it.isNotEmpty()) book.author = it.replace(AppPattern.authorRegex, "") if (it.isNotEmpty() && (canReName || book.name.isEmpty())) {
book.author = it
}
} }
Debug.log(bookSource.bookSourceUrl, "${book.author}") Debug.log(bookSource.bookSourceUrl, "${book.author}")
Debug.log(bookSource.bookSourceUrl, "┌获取分类") Debug.log(bookSource.bookSourceUrl, "┌获取分类")

@ -11,6 +11,7 @@ import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.NetworkUtils import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.StringUtils.wordCountFormat import io.legado.app.utils.StringUtils.wordCountFormat
import io.legado.app.utils.htmlFormat
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@ -161,7 +162,7 @@ object BookList {
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}") Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}")
if (!scope.isActive) throw CancellationException() if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取简介") Debug.log(bookSource.bookSourceUrl, "┌获取简介")
searchBook.intro = analyzeRule.getString(intro) searchBook.intro = analyzeRule.getString(intro).htmlFormat()
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", true) Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", true)
if (!scope.isActive) throw CancellationException() if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
@ -221,7 +222,7 @@ object BookList {
Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log) Debug.log(bookSource.bookSourceUrl, "${searchBook.latestChapterTitle}", log)
if (!scope.isActive) throw CancellationException() if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) Debug.log(bookSource.bookSourceUrl, "┌获取简介", log)
searchBook.intro = analyzeRule.getString(ruleIntro) searchBook.intro = analyzeRule.getString(ruleIntro).htmlFormat()
Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log, true) Debug.log(bookSource.bookSourceUrl, "${searchBook.intro}", log, true)
if (!scope.isActive) throw CancellationException() if (!scope.isActive) throw CancellationException()
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log)

@ -0,0 +1,117 @@
package io.legado.app.model.webBook
import io.legado.app.App
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
import kotlin.math.min
class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) {
val threadCount = AppConfig.threadCount
private var searchPool: ExecutorCoroutineDispatcher? = null
private var mSearchId = 0L
private var searchPage = 1
private var searchKey: String = ""
private var tasks = CompositeCoroutine()
private var bookSourceList = arrayListOf<BookSource>()
@Volatile
private var searchIndex = -1
private fun initSearchPool() {
searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
}
fun search(searchId: Long, key: String) {
callBack.onSearchStart()
if (searchId != mSearchId) {
if (key.isEmpty()) {
callBack.onSearchCancel()
return
} else {
this.searchKey = key
}
if (mSearchId != 0L) {
close()
}
initSearchPool()
mSearchId = searchId
searchPage = 1
val searchGroup = App.INSTANCE.getPrefString("searchGroup") ?: ""
bookSourceList.clear()
if (searchGroup.isBlank()) {
bookSourceList.addAll(App.db.bookSourceDao().allEnabled)
} else {
bookSourceList.addAll(App.db.bookSourceDao().getEnabledByGroup(searchGroup))
}
} else {
searchPage++
}
searchIndex = -1
for (i in 0 until threadCount) {
search(searchId)
}
}
private fun search(searchId: Long) {
synchronized(this) {
if (searchIndex >= bookSourceList.lastIndex) {
return
}
searchIndex++
val source = bookSourceList[searchIndex]
val task = WebBook(source).searchBook(
searchKey,
searchPage,
scope = scope,
context = searchPool!!
).timeout(30000L)
.onSuccess(IO) {
if (searchId == mSearchId) {
callBack.onSearchSuccess(it)
}
}
.onFinally {
synchronized(this) {
if (searchIndex < bookSourceList.lastIndex) {
search(searchId)
} else {
searchIndex++
}
if (searchIndex >= bookSourceList.lastIndex + min(bookSourceList.size,
threadCount)
) {
callBack.onSearchFinish()
}
}
}
tasks.add(task)
}
}
fun cancelSearch() {
close()
callBack.onSearchCancel()
}
fun close() {
tasks.clear()
searchPool?.close()
mSearchId = 0L
}
interface CallBack {
fun onSearchStart()
fun onSearchSuccess(searchBooks: ArrayList<SearchBook>)
fun onSearchFinish()
fun onSearchCancel()
}
}

@ -1,15 +1,12 @@
package io.legado.app.model package io.legado.app.model.webBook
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.Debug
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.webBook.BookChapterList
import io.legado.app.model.webBook.BookContent
import io.legado.app.model.webBook.BookInfo
import io.legado.app.model.webBook.BookList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@ -93,7 +90,8 @@ class WebBook(val bookSource: BookSource) {
fun getBookInfo( fun getBookInfo(
book: Book, book: Book,
scope: CoroutineScope = Coroutine.DEFAULT, scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO,
canReName: Boolean = true,
): Coroutine<Book> { ): Coroutine<Book> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
book.type = bookSource.bookSourceType book.type = bookSource.bookSourceType
@ -109,7 +107,7 @@ class WebBook(val bookSource: BookSource) {
) )
analyzeUrl.getResponseAwait(bookSource.bookSourceUrl).body analyzeUrl.getResponseAwait(bookSource.bookSourceUrl).body
} }
BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl) BookInfo.analyzeBookInfo(book, body, bookSource, book.bookUrl, canReName)
book book
} }
} }

@ -336,6 +336,7 @@ abstract class BaseReadAloudService : BaseService(),
abstract fun aloudServicePendingIntent(actionStr: String): PendingIntent? abstract fun aloudServicePendingIntent(actionStr: String): PendingIntent?
open fun nextChapter() { open fun nextChapter() {
ReadBook.upReadStartTime()
if (!ReadBook.moveToNextChapter(true)) { if (!ReadBook.moveToNextChapter(true)) {
stopSelf() stopSelf()
} }

@ -16,7 +16,7 @@ import io.legado.app.help.BookHelp
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.Download import io.legado.app.service.help.Download
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO

@ -7,8 +7,8 @@ import io.legado.app.constant.EventBus
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.IntentHelp import io.legado.app.help.IntentHelp
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.http.HttpHelper import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.help.http.api.HttpPostApi import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.LogUtils import io.legado.app.utils.LogUtils
@ -17,7 +17,6 @@ import kotlinx.coroutines.isActive
import java.io.File import java.io.File
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.FileInputStream import java.io.FileInputStream
import java.net.URLEncoder
class HttpReadAloudService : BaseReadAloudService(), class HttpReadAloudService : BaseReadAloudService(),
MediaPlayer.OnPreparedListener, MediaPlayer.OnPreparedListener,
@ -31,7 +30,7 @@ class HttpReadAloudService : BaseReadAloudService(),
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ttsFolder = cacheDir.absolutePath + File.separator + "bdTts" ttsFolder = externalCacheDir!!.absolutePath + File.separator + "httpTTS"
mediaPlayer.setOnErrorListener(this) mediaPlayer.setOnErrorListener(this)
mediaPlayer.setOnPreparedListener(this) mediaPlayer.setOnPreparedListener(this)
mediaPlayer.setOnCompletionListener(this) mediaPlayer.setOnCompletionListener(this)
@ -72,13 +71,13 @@ class HttpReadAloudService : BaseReadAloudService(),
FileUtils.deleteFile(ttsFolder) FileUtils.deleteFile(ttsFolder)
for (index in 0 until contentList.size) { for (index in 0 until contentList.size) {
if (isActive) { if (isActive) {
val bytes = HttpHelper.getByteRetrofit("http://tts.baidu.com") ReadAloud.httpTTS?.let {
.create(HttpPostApi::class.java) AnalyzeUrl(
.postMapByteAsync( it.url,
"http://tts.baidu.com/text2audio", speakText = contentList[index],
getAudioBody(contentList[index]), mapOf() speakSpeed = AppConfig.ttsSpeechRate
).body() ).getResponseBytes()?.let { bytes ->
if (bytes != null && isActive) { if (isActive) {
val file = getSpeakFile(index) val file = getSpeakFile(index)
file.writeBytes(bytes) file.writeBytes(bytes)
if (index == nowSpeak) { if (index == nowSpeak) {
@ -87,6 +86,8 @@ class HttpReadAloudService : BaseReadAloudService(),
playAudio(fis.fd) playAudio(fis.fd)
} }
} }
}
}
} else { } else {
break break
} }
@ -113,31 +114,6 @@ class HttpReadAloudService : BaseReadAloudService(),
return FileUtils.createFileIfNotExist("${ttsFolder}${File.separator}${index}.mp3") return FileUtils.createFileIfNotExist("${ttsFolder}${File.separator}${index}.mp3")
} }
private fun getAudioBody(content: String): Map<String, String> {
return mapOf(
Pair("tex", encodeTwo(content)),
Pair("spd", ((AppConfig.ttsSpeechRate + 5) / 10 + 4).toString()),
Pair("per", AppConfig.ttsSpeechPer),
Pair("cuid", "baidu_speech_demo"),
Pair("idx", "1"),
Pair("cod", "2"),
Pair("lan", "zh"),
Pair("ctp", "1"),
Pair("pdt", "1"),
Pair("vol", "5"),
Pair("pit", "5"),
Pair("_res_tag_", "audio")
)
}
private fun encodeTwo(content: String): String {
return try {
URLEncoder.encode(URLEncoder.encode(content, "UTF-8"), "UTF-8")
} catch (e: Exception) {
" "
}
}
override fun pauseReadAloud(pause: Boolean) { override fun pauseReadAloud(pause: Boolean) {
super.pauseReadAloud(pause) super.pauseReadAloud(pause)
mediaPlayer.pause() mediaPlayer.pause()

@ -6,7 +6,7 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.constant.Status import io.legado.app.constant.Status
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.AudioPlayService import io.legado.app.service.AudioPlayService
object AudioPlay { object AudioPlay {

@ -7,7 +7,7 @@ import io.legado.app.R
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.CheckSourceService import io.legado.app.service.CheckSourceService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

@ -5,22 +5,31 @@ import android.content.Intent
import io.legado.app.App import io.legado.app.App
import io.legado.app.constant.IntentAction import io.legado.app.constant.IntentAction
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.HttpTTS
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.HttpReadAloudService import io.legado.app.service.HttpReadAloudService
import io.legado.app.service.TTSReadAloudService import io.legado.app.service.TTSReadAloudService
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefLong
object ReadAloud { object ReadAloud {
var aloudClass: Class<*> = getReadAloudClass() private var aloudClass: Class<*> = getReadAloudClass()
var httpTTS: HttpTTS? = null
fun getReadAloudClass(): Class<*> { private fun getReadAloudClass(): Class<*> {
return if (App.INSTANCE.getPrefBoolean(PreferKey.readAloudOnLine)) { val spId = App.INSTANCE.getPrefLong(PreferKey.speakEngine)
httpTTS = App.db.httpTTSDao().get(spId)
stop(App.INSTANCE)
return if (httpTTS != null) {
HttpReadAloudService::class.java HttpReadAloudService::class.java
} else { } else {
TTSReadAloudService::class.java TTSReadAloudService::class.java
} }
} }
fun upReadAloudClass() {
aloudClass = getReadAloudClass()
}
fun play( fun play(
context: Context, context: Context,
title: String, title: String,

@ -13,7 +13,7 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentDataHelp
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
@ -22,6 +22,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.anko.getStackTraceString
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
@ -370,12 +371,20 @@ object ReadBook {
} }
}.onError { }.onError {
it.printStackTrace() it.printStackTrace()
App.INSTANCE.toast(it.localizedMessage ?: "ChapterProvider ERROR") App.INSTANCE.toast("ChapterProvider ERROR:\n${it.getStackTraceString()}")
} }
} }
private val imageStyle get() = webBook?.bookSource?.ruleContent?.imageStyle private val imageStyle get() = webBook?.bookSource?.ruleContent?.imageStyle
fun setCharset(charset: String) {
book?.let {
it.charset = charset
callBack?.loadChapterList(it)
}
saveRead()
}
fun saveRead() { fun saveRead() {
Coroutine.async { Coroutine.async {
book?.let { book -> book?.let { book ->
@ -392,6 +401,7 @@ object ReadBook {
} }
interface CallBack { interface CallBack {
fun loadChapterList(book: Book)
fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true)
fun upView() fun upView()
fun pageChanged() fun pageChanged()

@ -11,6 +11,7 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.dialog.TextDialog
import io.legado.app.utils.applyTint
import io.legado.app.utils.openUrl import io.legado.app.utils.openUrl
import io.legado.app.utils.sendToClip import io.legado.app.utils.sendToClip
import io.legado.app.utils.toast import io.legado.app.utils.toast
@ -34,7 +35,7 @@ class AboutFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.about) addPreferencesFromResource(R.xml.about)
findPreference<Preference>("check_update")?.summary = findPreference<Preference>("check_update")?.summary =
"${getString(R.string.version)} ${App.INSTANCE.versionName}" "${getString(R.string.version)} ${App.versionName}"
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -92,7 +93,7 @@ class AboutFragment : PreferenceFragmentCompat() {
} }
} }
} }
}.show() }.show().applyTint()
} }
private fun joinQQGroup(key: String): Boolean { private fun joinQQGroup(key: String): Boolean {

@ -8,7 +8,12 @@ import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.data.entities.ReadRecord import io.legado.app.data.entities.ReadRecordShow
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.utils.applyTint
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.activity_read_record.* import kotlinx.android.synthetic.main.activity_read_record.*
import kotlinx.android.synthetic.main.item_read_record.* import kotlinx.android.synthetic.main.item_read_record.*
import kotlinx.android.synthetic.main.item_read_record.view.* import kotlinx.android.synthetic.main.item_read_record.view.*
@ -16,6 +21,7 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jetbrains.anko.sdk27.listeners.onClick
class ReadRecordActivity : BaseActivity(R.layout.activity_read_record) { class ReadRecordActivity : BaseActivity(R.layout.activity_read_record) {
@ -27,7 +33,7 @@ class ReadRecordActivity : BaseActivity(R.layout.activity_read_record) {
} }
private fun initView() { private fun initView() {
tv_book_name.text = "总阅读时间" tv_book_name.setText(R.string.all_read_time)
recycler_view.layoutManager = LinearLayoutManager(this) recycler_view.layoutManager = LinearLayoutManager(this)
adapter = RecordAdapter(this) adapter = RecordAdapter(this)
recycler_view.adapter = adapter recycler_view.adapter = adapter
@ -39,7 +45,7 @@ class ReadRecordActivity : BaseActivity(R.layout.activity_read_record) {
withContext(Main) { withContext(Main) {
tv_read_time.text = formatDuring(allTime) tv_read_time.text = formatDuring(allTime)
} }
val readRecords = App.db.readRecordDao().all val readRecords = App.db.readRecordDao().allShow
withContext(Main) { withContext(Main) {
adapter.setItems(readRecords) adapter.setItems(readRecords)
} }
@ -47,17 +53,34 @@ class ReadRecordActivity : BaseActivity(R.layout.activity_read_record) {
} }
inner class RecordAdapter(context: Context) : inner class RecordAdapter(context: Context) :
SimpleRecyclerAdapter<ReadRecord>(context, R.layout.item_read_record) { SimpleRecyclerAdapter<ReadRecordShow>(context, R.layout.item_read_record) {
override fun convert(holder: ItemViewHolder, item: ReadRecord, payloads: MutableList<Any>) { override fun convert(
holder: ItemViewHolder,
item: ReadRecordShow,
payloads: MutableList<Any>
) {
holder.itemView.apply { holder.itemView.apply {
tv_book_name.text = item.bookName tv_book_name.text = item.bookName
tv_read_time.text = formatDuring(item.readTime) tv_read_time.text = formatDuring(item.readTime)
iv_remove.visible()
} }
} }
override fun registerListener(holder: ItemViewHolder) { override fun registerListener(holder: ItemViewHolder) {
holder.itemView.apply {
iv_remove.onClick {
alert(R.string.delete, R.string.sure_del) {
okButton {
getItem(holder.layoutPosition)?.let {
App.db.readRecordDao().deleteByName(it.bookName)
initData()
}
}
noButton()
}.show().applyTint()
}
}
} }
} }

@ -2,7 +2,6 @@ package io.legado.app.ui.association
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.Observer
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.Theme import io.legado.app.constant.Theme
@ -23,12 +22,12 @@ class FileAssociationActivity : VMBaseActivity<FileAssociationViewModel>(
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
rotate_loading.show() rotate_loading.show()
viewModel.errorLiveData.observe(this, Observer { viewModel.errorLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
toast(it) toast(it)
finish() finish()
}) })
viewModel.successLiveData.observe(this, Observer { viewModel.successLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
startActivity(it) startActivity(it)
finish() finish()

@ -9,7 +9,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
@ -41,11 +40,11 @@ class ImportBookSourceActivity : VMBaseActivity<ImportBookSourceViewModel>(
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
rotate_loading.show() rotate_loading.show()
viewModel.errorLiveData.observe(this, Observer { viewModel.errorLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
errorDialog(it) errorDialog(it)
}) })
viewModel.successLiveData.observe(this, Observer { viewModel.successLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
if (it > 0) { if (it > 0) {
successDialog() successDialog()

@ -1,7 +1,6 @@
package io.legado.app.ui.association package io.legado.app.ui.association
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.Observer
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
@ -26,11 +25,11 @@ class ImportReplaceRuleActivity : VMBaseActivity<ImportReplaceRuleViewModel>(
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
rotate_loading.show() rotate_loading.show()
viewModel.errorLiveData.observe(this, Observer { viewModel.errorLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
errorDialog(it) errorDialog(it)
}) })
viewModel.successLiveData.observe(this, Observer { viewModel.successLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
if (it.size > 0) { if (it.size > 0) {
successDialog(it) successDialog(it)

@ -9,7 +9,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
@ -41,11 +40,11 @@ class ImportRssSourceActivity : VMBaseActivity<ImportRssSourceViewModel>(
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
rotate_loading.show() rotate_loading.show()
viewModel.errorLiveData.observe(this, Observer { viewModel.errorLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
errorDialog(it) errorDialog(it)
}) })
viewModel.successLiveData.observe(this, Observer { viewModel.successLiveData.observe(this, {
rotate_loading.hide() rotate_loading.hide()
if (it > 0) { if (it > 0) {
successDialog() successDialog()

@ -8,7 +8,6 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.SeekBar import android.widget.SeekBar
import androidx.lifecycle.Observer
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions.bitmapTransform import com.bumptech.glide.request.RequestOptions.bitmapTransform
@ -37,7 +36,7 @@ import org.jetbrains.anko.startActivityForResult
class AudioPlayActivity : class AudioPlayActivity :
VMBaseActivity<AudioPlayViewModel>(R.layout.activity_audio_play, theme = Theme.Dark), VMBaseActivity<AudioPlayViewModel>(R.layout.activity_audio_play, toolBarTheme = Theme.Dark),
ChangeSourceDialog.CallBack { ChangeSourceDialog.CallBack {
override val viewModel: AudioPlayViewModel override val viewModel: AudioPlayViewModel
@ -48,8 +47,8 @@ class AudioPlayActivity :
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.transparent() title_bar.transparent()
AudioPlay.titleData.observe(this, Observer { title_bar.title = it }) AudioPlay.titleData.observe(this, { title_bar.title = it })
AudioPlay.coverData.observe(this, Observer { upCover(it) }) AudioPlay.coverData.observe(this, { upCover(it) })
viewModel.initData(intent) viewModel.initData(intent)
initView() initView()
} }

@ -8,7 +8,7 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.AudioPlay import io.legado.app.service.help.AudioPlay
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

@ -5,7 +5,6 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
@ -94,7 +93,7 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
private fun initGroupData() { private fun initGroupData() {
groupLiveData?.removeObservers(this) groupLiveData?.removeObservers(this)
groupLiveData = App.db.bookGroupDao().liveDataAll() groupLiveData = App.db.bookGroupDao().liveDataAll()
groupLiveData?.observe(this, Observer { groupLiveData?.observe(this, {
groupList.clear() groupList.clear()
groupList.addAll(it) groupList.addAll(it)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
@ -112,7 +111,7 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup() AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }
booksLiveData?.observe(this, Observer { list -> booksLiveData?.observe(this, { list ->
val books = when (getPrefInt(PreferKey.bookshelfSort)) { val books = when (getPrefInt(PreferKey.bookshelfSort)) {
1 -> list.sortedByDescending { it.latestChapterTime } 1 -> list.sortedByDescending { it.latestChapterTime }
2 -> list.sortedBy { it.name } 2 -> list.sortedBy { it.name }

@ -8,13 +8,12 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
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.constant.Theme import io.legado.app.constant.Theme
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.dialog_change_source.* import kotlinx.android.synthetic.main.dialog_change_source.*
@ -61,7 +60,7 @@ class ChangeCoverDialog : BaseDialogFragment(),
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.setBackgroundColor(bottomBackground) tool_bar.setBackgroundColor(primaryColor)
tool_bar.setTitle(R.string.change_cover_source) tool_bar.setTitle(R.string.change_cover_source)
viewModel.initData(arguments) viewModel.initData(arguments)
initMenu() initMenu()
@ -70,7 +69,7 @@ class ChangeCoverDialog : BaseDialogFragment(),
private fun initMenu() { private fun initMenu() {
tool_bar.inflateMenu(R.menu.change_cover) tool_bar.inflateMenu(R.menu.change_cover)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
} }
@ -83,7 +82,7 @@ class ChangeCoverDialog : BaseDialogFragment(),
override fun observeLiveBus() { override fun observeLiveBus() {
super.observeLiveBus() super.observeLiveBus()
viewModel.searchStateData.observe(viewLifecycleOwner, Observer { viewModel.searchStateData.observe(viewLifecycleOwner, {
refresh_progress_bar.isAutoLoading = it refresh_progress_bar.isAutoLoading = it
if (it) { if (it) {
stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp) stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp)
@ -92,7 +91,7 @@ class ChangeCoverDialog : BaseDialogFragment(),
} }
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext(), Theme.getTheme())
}) })
viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer { viewModel.searchBooksLiveData.observe(viewLifecycleOwner, {
val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it)) val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it))
adapter.setItems(it) adapter.setItems(it)
diffResult.dispatchUpdatesTo(adapter) diffResult.dispatchUpdatesTo(adapter)

@ -6,24 +6,31 @@ import androidx.lifecycle.MutableLiveData
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min
class ChangeCoverViewModel(application: Application) : BaseViewModel(application) { class ChangeCoverViewModel(application: Application) : BaseViewModel(application) {
private var searchPool = private val threadCount = AppConfig.threadCount
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() private var searchPool: ExecutorCoroutineDispatcher? = null
var name: String = "" var name: String = ""
var author: String = "" var author: String = ""
private var task: Coroutine<*>? = null private var tasks = CompositeCoroutine()
private var bookSourceList = arrayListOf<BookSource>()
val searchStateData = MutableLiveData<Boolean>() val searchStateData = MutableLiveData<Boolean>()
val searchBooksLiveData = MutableLiveData<List<SearchBook>>() val searchBooksLiveData = MutableLiveData<List<SearchBook>>()
private val searchBooks = ArrayList<SearchBook>() private val searchBooks = ArrayList<SearchBook>()
@Volatile
private var searchIndex = -1
fun initData(arguments: Bundle?) { fun initData(arguments: Bundle?) {
arguments?.let { bundle -> arguments?.let { bundle ->
bundle.getString("name")?.let { bundle.getString("name")?.let {
@ -35,13 +42,18 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
} }
} }
private fun initSearchPool() {
searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
searchIndex = -1
}
fun loadDbSearchBook() { fun loadDbSearchBook() {
execute { execute {
App.db.searchBookDao().getEnableHasCover(name, author).let { App.db.searchBookDao().getEnableHasCover(name, author).let {
searchBooks.addAll(it) searchBooks.addAll(it)
if (it.size <= 1) { if (it.size <= 1) {
searchBooksLiveData.postValue(searchBooks) searchBooksLiveData.postValue(searchBooks)
search() startSearch()
} else { } else {
searchBooksLiveData.postValue(searchBooks) searchBooksLiveData.postValue(searchBooks)
} }
@ -49,13 +61,27 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
} }
} }
fun search() { private fun startSearch() {
task = execute { execute {
val bookSourceList = App.db.bookSourceDao().allEnabled bookSourceList.clear()
for (item in bookSourceList) { bookSourceList.addAll(App.db.bookSourceDao().allEnabled)
//task取消时自动取消 by (scope = this@execute) searchStateData.postValue(true)
WebBook(item).searchBook(name, scope = this@execute, context = searchPool) initSearchPool()
.timeout(30000L) for (i in 0 until threadCount) {
search()
}
}
}
private fun search() {
synchronized(this) {
if (searchIndex >= bookSourceList.lastIndex) {
return
}
searchIndex++
val source = bookSourceList[searchIndex]
val task = WebBook(source).searchBook(name, scope = this, context = searchPool!!)
.timeout(60000L)
.onSuccess(Dispatchers.IO) { .onSuccess(Dispatchers.IO) {
if (it.isNotEmpty()) { if (it.isNotEmpty()) {
val searchBook = it[0] val searchBook = it[0]
@ -70,29 +96,35 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
} }
} }
} }
.onFinally {
synchronized(this) {
if (searchIndex < bookSourceList.lastIndex) {
search()
} else {
searchIndex++
} }
}.onStart { if (searchIndex >= bookSourceList.lastIndex + min(bookSourceList.size,
searchStateData.postValue(true) threadCount)
}.onCancel { ) {
searchStateData.postValue(false) searchStateData.postValue(false)
} }
}
task?.invokeOnCompletion { }
searchStateData.postValue(false) tasks.add(task)
} }
} }
fun stopSearch() { fun stopSearch() {
if (task?.isActive == true) { if (tasks.isEmpty) {
task?.cancel() startSearch()
} else { } else {
search() tasks.clear()
} }
} }
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
searchPool.close() searchPool?.close()
} }
} }

@ -9,17 +9,15 @@ import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.constant.Theme
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
@ -69,7 +67,7 @@ class ChangeSourceDialog : BaseDialogFragment(),
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.setBackgroundColor(bottomBackground) tool_bar.setBackgroundColor(primaryColor)
viewModel.initData(arguments) viewModel.initData(arguments)
showTitle() showTitle()
initMenu() initMenu()
@ -86,7 +84,7 @@ class ChangeSourceDialog : BaseDialogFragment(),
private fun initMenu() { private fun initMenu() {
tool_bar.inflateMenu(R.menu.change_source) tool_bar.inflateMenu(R.menu.change_source)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked =
getPrefBoolean(PreferKey.changeSourceLoadToc) getPrefBoolean(PreferKey.changeSourceLoadToc)
@ -136,16 +134,16 @@ class ChangeSourceDialog : BaseDialogFragment(),
} }
private fun initLiveData() { private fun initLiveData() {
viewModel.searchStateData.observe(viewLifecycleOwner, Observer { viewModel.searchStateData.observe(viewLifecycleOwner, {
refresh_progress_bar.isAutoLoading = it refresh_progress_bar.isAutoLoading = it
if (it) { if (it) {
stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp) stopMenuItem?.setIcon(R.drawable.ic_stop_black_24dp)
} else { } else {
stopMenuItem?.setIcon(R.drawable.ic_refresh_black_24dp) stopMenuItem?.setIcon(R.drawable.ic_refresh_black_24dp)
} }
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext())
}) })
viewModel.searchBooksLiveData.observe(viewLifecycleOwner, Observer { viewModel.searchBooksLiveData.observe(viewLifecycleOwner, {
val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it)) val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), it))
adapter.setItems(it) adapter.setItems(it)
diffResult.dispatchUpdatesTo(adapter) diffResult.dispatchUpdatesTo(adapter)

@ -10,30 +10,36 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import org.jetbrains.anko.debug import org.jetbrains.anko.debug
import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min
class ChangeSourceViewModel(application: Application) : BaseViewModel(application) { class ChangeSourceViewModel(application: Application) : BaseViewModel(application) {
private var searchPool = private val threadCount = AppConfig.threadCount
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() private var searchPool: ExecutorCoroutineDispatcher? = null
val handler = Handler() val handler = Handler()
val searchStateData = MutableLiveData<Boolean>() val searchStateData = MutableLiveData<Boolean>()
val searchBooksLiveData = MutableLiveData<List<SearchBook>>() val searchBooksLiveData = MutableLiveData<List<SearchBook>>()
var name: String = "" var name: String = ""
var author: String = "" var author: String = ""
private var task: Coroutine<*>? = null private var tasks = CompositeCoroutine()
private var screenKey: String = "" private var screenKey: String = ""
private val searchBooks = hashSetOf<SearchBook>() private var bookSourceList = arrayListOf<BookSource>()
private val searchBooks = CopyOnWriteArraySet<SearchBook>()
private var postTime = 0L private var postTime = 0L
private val sendRunnable = Runnable { upAdapter() } private val sendRunnable = Runnable { upAdapter() }
@Volatile
private var searchIndex = -1
fun initData(arguments: Bundle?) { fun initData(arguments: Bundle?) {
arguments?.let { bundle -> arguments?.let { bundle ->
@ -46,13 +52,18 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} }
} }
private fun initSearchPool() {
searchPool = Executors.newFixedThreadPool(threadCount).asCoroutineDispatcher()
searchIndex = -1
}
fun loadDbSearchBook() { fun loadDbSearchBook() {
execute { execute {
App.db.searchBookDao().getByNameAuthorEnable(name, author).let { App.db.searchBookDao().getByNameAuthorEnable(name, author).let {
searchBooks.addAll(it) searchBooks.addAll(it)
if (it.size <= 1) { if (it.size <= 1) {
upAdapter() upAdapter()
search() startSearch()
} else { } else {
upAdapter() upAdapter()
} }
@ -83,13 +94,27 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
upAdapter() upAdapter()
} }
fun search() { private fun startSearch() {
task = execute { execute {
val bookSourceList = App.db.bookSourceDao().allEnabled bookSourceList.clear()
for (item in bookSourceList) { bookSourceList.addAll(App.db.bookSourceDao().allEnabled)
//task取消时自动取消 by (scope = this@execute) searchStateData.postValue(true)
WebBook(item).searchBook(name, scope = this@execute, context = searchPool) initSearchPool()
.timeout(30000L) for (i in 0 until threadCount) {
search()
}
}
}
private fun search() {
synchronized(this) {
if (searchIndex >= bookSourceList.lastIndex) {
return
}
searchIndex++
val source = bookSourceList[searchIndex]
val task = WebBook(source).searchBook(name, scope = this, context = searchPool!!)
.timeout(60000L)
.onSuccess(IO) { .onSuccess(IO) {
it.forEach { searchBook -> it.forEach { searchBook ->
if (searchBook.name == name && searchBook.author == author) { if (searchBook.name == name && searchBook.author == author) {
@ -102,19 +127,25 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} else { } else {
searchFinish(searchBook) searchFinish(searchBook)
} }
return@onSuccess return@forEach
} }
} }
} }
.onFinally {
synchronized(this) {
if (searchIndex < bookSourceList.lastIndex) {
search()
} else {
searchIndex++
} }
}.onStart { if (searchIndex >= bookSourceList.lastIndex + min(bookSourceList.size,
searchStateData.postValue(true) threadCount)
}.onCancel { ) {
searchStateData.postValue(false) searchStateData.postValue(false)
} }
}
task?.invokeOnCompletion { }
searchStateData.postValue(false) tasks.add(task)
} }
} }
@ -166,16 +197,18 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
} }
fun stopSearch() { fun stopSearch() {
if (task?.isActive == true) { if (tasks.isEmpty) {
task?.cancel() startSearch()
} else { } else {
search() tasks.clear()
searchPool?.close()
searchStateData.postValue(false)
} }
} }
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
searchPool.close() searchPool?.close()
} }
fun disableSource(searchBook: SearchBook) { fun disableSource(searchBook: SearchBook) {

@ -8,7 +8,6 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.paging.LivePagedListBuilder import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -59,7 +58,7 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
LivePagedListBuilder( LivePagedListBuilder(
App.db.bookmarkDao().observeByBook(book.bookUrl, book.name, book.author), 20 App.db.bookmarkDao().observeByBook(book.bookUrl, book.name, book.author), 20
).build() ).build()
bookmarkLiveData?.observe(viewLifecycleOwner, Observer { adapter.submitList(it) }) bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) })
} }
} }
@ -74,7 +73,7 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
newText newText
), 20 ), 20
).build() ).build()
bookmarkLiveData?.observe(viewLifecycleOwner, Observer { adapter.submitList(it) }) bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) })
} }
} }

@ -1,7 +1,7 @@
package io.legado.app.ui.book.chapterlist package io.legado.app.ui.book.chapterlist
import android.content.Context import android.content.Context
import android.widget.TextView import android.view.View
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
@ -21,8 +21,11 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
override fun convert(holder: ItemViewHolder, item: BookChapter, payloads: MutableList<Any>) { override fun convert(holder: ItemViewHolder, item: BookChapter, payloads: MutableList<Any>) {
with(holder.itemView) { with(holder.itemView) {
val isDur = callback.durChapterIndex() == item.index
val cached = callback.isLocalBook
|| cacheFileNames.contains(BookHelp.formatChapterName(item))
if (payloads.isEmpty()) { if (payloads.isEmpty()) {
if (callback.durChapterIndex() == item.index) { if (isDur) {
tv_chapter_name.setTextColor(context.accentColor) tv_chapter_name.setTextColor(context.accentColor)
} else { } else {
tv_chapter_name.setTextColor(context.getCompatColor(R.color.primaryText)) tv_chapter_name.setTextColor(context.getCompatColor(R.color.primaryText))
@ -32,15 +35,9 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
tv_tag.text = item.tag tv_tag.text = item.tag
tv_tag.visible() tv_tag.visible()
} }
upHasCache( upHasCache(this, isDur, cached)
tv_chapter_name,
cacheFileNames.contains(BookHelp.formatChapterName(item))
)
} else { } else {
upHasCache( upHasCache(this, isDur, cached)
tv_chapter_name,
cacheFileNames.contains(BookHelp.formatChapterName(item))
)
} }
} }
} }
@ -53,11 +50,18 @@ class ChapterListAdapter(context: Context, val callback: Callback) :
} }
} }
private fun upHasCache(textView: TextView, contains: Boolean) { private fun upHasCache(itemView: View, isDur: Boolean, cached: Boolean) = itemView.apply {
textView.paint.isFakeBoldText = contains tv_chapter_name.paint.isFakeBoldText = cached
iv_checked.setImageResource(R.drawable.ic_outline_cloud_24)
iv_checked.visible(!cached)
if (isDur) {
iv_checked.setImageResource(R.drawable.ic_check)
iv_checked.visible()
}
} }
interface Callback { interface Callback {
val isLocalBook: Boolean
fun openChapter(bookChapter: BookChapter) fun openChapter(bookChapter: BookChapter)
fun durChapterIndex(): Int fun durChapterIndex(): Int
} }

@ -6,7 +6,6 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
@ -89,7 +88,7 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
private fun initDoc() { private fun initDoc() {
tocLiveData?.removeObservers(this@ChapterListFragment) tocLiveData?.removeObservers(this@ChapterListFragment)
tocLiveData = App.db.bookChapterDao().observeByBook(viewModel.bookUrl) tocLiveData = App.db.bookChapterDao().observeByBook(viewModel.bookUrl)
tocLiveData?.observe(viewLifecycleOwner, Observer { tocLiveData?.observe(viewLifecycleOwner, {
adapter.setItems(it) adapter.setItems(it)
if (!scrollToDurChapter) { if (!scrollToDurChapter) {
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0) mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
@ -124,12 +123,15 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
} else { } else {
tocLiveData?.removeObservers(this) tocLiveData?.removeObservers(this)
tocLiveData = App.db.bookChapterDao().liveDataSearch(viewModel.bookUrl, newText) tocLiveData = App.db.bookChapterDao().liveDataSearch(viewModel.bookUrl, newText)
tocLiveData?.observe(viewLifecycleOwner, Observer { tocLiveData?.observe(viewLifecycleOwner, {
adapter.setItems(it) adapter.setItems(it)
}) })
} }
} }
override val isLocalBook: Boolean
get() = viewModel.book?.isLocalBook() == true
override fun durChapterIndex(): Int { override fun durChapterIndex(): Int {
return durChapterIndex return durChapterIndex
} }

@ -6,7 +6,6 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.legado.app.App import io.legado.app.App
@ -129,7 +128,7 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup() AppConst.bookGroupNone.groupId -> App.db.bookDao().observeNoGroup()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }
booksLiveData?.observe(this, Observer { list -> booksLiveData?.observe(this, { list ->
val booksDownload = list.filter { val booksDownload = list.filter {
it.isOnLineTxt() it.isOnLineTxt()
} }
@ -147,7 +146,7 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
private fun initGroupData() { private fun initGroupData() {
groupLiveData?.removeObservers(this) groupLiveData?.removeObservers(this)
groupLiveData = App.db.bookGroupDao().liveDataAll() groupLiveData = App.db.bookGroupDao().liveDataAll()
groupLiveData?.observe(this, Observer { groupLiveData?.observe(this, {
groupList.clear() groupList.clear()
groupList.addAll(it) groupList.addAll(it)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()

@ -1,7 +1,6 @@
package io.legado.app.ui.book.explore package io.legado.app.ui.book.explore
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
@ -27,7 +26,7 @@ class ExploreShowActivity : VMBaseActivity<ExploreShowViewModel>(R.layout.activi
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.title = intent.getStringExtra("exploreName") title_bar.title = intent.getStringExtra("exploreName")
initRecyclerView() initRecyclerView()
viewModel.booksData.observe(this, Observer { upData(it) }) viewModel.booksData.observe(this, { upData(it) })
viewModel.initData(intent) viewModel.initData(intent)
} }

@ -7,7 +7,7 @@ import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
class ExploreShowViewModel(application: Application) : BaseViewModel(application) { class ExploreShowViewModel(application: Application) : BaseViewModel(application) {

@ -10,7 +10,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -20,7 +19,6 @@ import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Theme
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
@ -30,7 +28,7 @@ import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
@ -65,7 +63,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.setBackgroundColor(bottomBackground) tool_bar.setBackgroundColor(primaryColor)
tool_bar.title = getString(R.string.group_manage) tool_bar.title = getString(R.string.group_manage)
initData() initData()
initMenu() initMenu()
@ -79,7 +77,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
tv_ok.setTextColor(requireContext().accentColor) tv_ok.setTextColor(requireContext().accentColor)
tv_ok.visible() tv_ok.visible()
tv_ok.onClick { dismiss() } tv_ok.onClick { dismiss() }
App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, {
val diffResult = val diffResult =
DiffUtil.calculateDiff(GroupDiffCallBack(ArrayList(adapter.getItems()), it)) DiffUtil.calculateDiff(GroupDiffCallBack(ArrayList(adapter.getItems()), it))
adapter.setItems(it, diffResult) adapter.setItems(it, diffResult)
@ -94,7 +92,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
tool_bar.inflateMenu(R.menu.book_group_manage) tool_bar.inflateMenu(R.menu.book_group_manage)
tool_bar.menu.let { tool_bar.menu.let {
it.applyTint(requireContext(), Theme.getTheme()) it.applyTint(requireContext())
it.findItem(R.id.menu_group_all) it.findItem(R.id.menu_group_all)
.isChecked = AppConfig.bookGroupAllShow .isChecked = AppConfig.bookGroupAllShow
it.findItem(R.id.menu_group_local) it.findItem(R.id.menu_group_local)

@ -11,7 +11,6 @@ import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -20,7 +19,6 @@ import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Theme
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
@ -29,7 +27,7 @@ import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.accentColor import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
@ -81,7 +79,7 @@ class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.setBackgroundColor(bottomBackground) tool_bar.setBackgroundColor(primaryColor)
callBack = activity as? CallBack callBack = activity as? CallBack
arguments?.let { arguments?.let {
groupId = it.getInt("groupId") groupId = it.getInt("groupId")
@ -94,7 +92,7 @@ class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
private fun initView() { private fun initView() {
tool_bar.title = getString(R.string.group_select) tool_bar.title = getString(R.string.group_select)
tool_bar.inflateMenu(R.menu.book_group_manage) tool_bar.inflateMenu(R.menu.book_group_manage)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
tool_bar.menu.setGroupVisible(R.id.menu_groups, false) tool_bar.menu.setGroupVisible(R.id.menu_groups, false)
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
@ -114,7 +112,7 @@ class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
} }
private fun initData() { private fun initData() {
App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, {
adapter.setItems(it) adapter.setItems(it)
}) })
} }

@ -9,7 +9,6 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.lifecycle.Observer
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions.bitmapTransform import com.bumptech.glide.request.RequestOptions.bitmapTransform
@ -25,6 +24,7 @@ import io.legado.app.help.IntentDataHelp
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.ui.audio.AudioPlayActivity import io.legado.app.ui.audio.AudioPlayActivity
import io.legado.app.ui.book.changecover.ChangeCoverDialog import io.legado.app.ui.book.changecover.ChangeCoverDialog
import io.legado.app.ui.book.changesource.ChangeSourceDialog import io.legado.app.ui.book.changesource.ChangeSourceDialog
@ -35,10 +35,7 @@ import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.widget.image.CoverImageView import io.legado.app.ui.widget.image.CoverImageView
import io.legado.app.utils.dp import io.legado.app.utils.*
import io.legado.app.utils.getViewModel
import io.legado.app.utils.gone
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.activity_book_info.* import kotlinx.android.synthetic.main.activity_book_info.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
@ -47,7 +44,7 @@ import org.jetbrains.anko.toast
class BookInfoActivity : class BookInfoActivity :
VMBaseActivity<BookInfoViewModel>(R.layout.activity_book_info, theme = Theme.Dark), VMBaseActivity<BookInfoViewModel>(R.layout.activity_book_info, toolBarTheme = Theme.Dark),
GroupSelectDialog.CallBack, GroupSelectDialog.CallBack,
ChapterListAdapter.CallBack, ChapterListAdapter.CallBack,
ChangeSourceDialog.CallBack, ChangeSourceDialog.CallBack,
@ -60,14 +57,16 @@ class BookInfoActivity :
override val viewModel: BookInfoViewModel override val viewModel: BookInfoViewModel
get() = getViewModel(BookInfoViewModel::class.java) get() = getViewModel(BookInfoViewModel::class.java)
@SuppressLint("PrivateResource")
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
title_bar.transparent() title_bar.transparent()
arc_view.setBgColor(backgroundColor) arc_view.setBgColor(backgroundColor)
ll_info.setBackgroundColor(backgroundColor) ll_info.setBackgroundColor(backgroundColor)
scroll_view.setBackgroundColor(backgroundColor) scroll_view.setBackgroundColor(backgroundColor)
fl_action.setBackgroundColor(bottomBackground) fl_action.setBackgroundColor(bottomBackground)
viewModel.bookData.observe(this, Observer { showBook(it) }) tv_shelf.setTextColor(getPrimaryTextColor(ColorUtils.isColorLight(bottomBackground)))
viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) viewModel.bookData.observe(this, { showBook(it) })
viewModel.chapterListData.observe(this, { upLoading(false, it) })
viewModel.initData(intent) viewModel.initData(intent)
initOnClick() initOnClick()
} }
@ -97,7 +96,7 @@ class BookInfoActivity :
if (it.isLocalBook()) { if (it.isLocalBook()) {
it.tocUrl = "" it.tocUrl = ""
} }
viewModel.loadBookInfo(it) viewModel.loadBookInfo(it, false)
} }
} }
R.id.menu_can_update -> { R.id.menu_can_update -> {

@ -9,8 +9,8 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.model.WebBook
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -49,15 +49,15 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
} }
fun loadBookInfo( fun loadBookInfo(
book: Book, book: Book, canReName: Boolean = true,
changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null changeDruChapterIndex: ((chapters: List<BookChapter>) -> Unit)? = null,
) { ) {
execute { execute {
if (book.isLocalBook()) { if (book.isLocalBook()) {
loadChapter(book, changeDruChapterIndex) loadChapter(book, changeDruChapterIndex)
} else { } else {
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource ->
WebBook(bookSource).getBookInfo(book, this) WebBook(bookSource).getBookInfo(book, this, canReName = canReName)
.onSuccess(IO) { .onSuccess(IO) {
bookData.postValue(book) bookData.postValue(book)
if (inBookshelf) { if (inBookshelf) {
@ -138,7 +138,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
} }
bookData.postValue(newBook) bookData.postValue(newBook)
if (newBook.tocUrl.isEmpty()) { if (newBook.tocUrl.isEmpty()) {
loadBookInfo(newBook) { upChangeDurChapterIndex(newBook, it) } loadBookInfo(newBook, false) { upChangeDurChapterIndex(newBook, it) }
} else { } else {
loadChapter(newBook) { upChangeDurChapterIndex(newBook, it) } loadChapter(newBook) { upChangeDurChapterIndex(newBook, it) }
} }

@ -7,7 +7,6 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.Observer
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
@ -30,7 +29,7 @@ class BookInfoEditActivity :
get() = getViewModel(BookInfoEditViewModel::class.java) get() = getViewModel(BookInfoEditViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
viewModel.bookData.observe(this, Observer { upView(it) }) viewModel.bookData.observe(this, { upView(it) })
if (viewModel.bookData.value == null) { if (viewModel.bookData.value == null) {
intent.getStringExtra("bookUrl")?.let { intent.getStringExtra("bookUrl")?.let {
viewModel.loadBook(it) viewModel.loadBook(it)

@ -12,7 +12,6 @@ import android.view.View
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
@ -114,7 +113,7 @@ class ImportBookActivity : VMBaseActivity<ImportBookViewModel>(R.layout.activity
private fun initData() { private fun initData() {
localUriLiveData?.removeObservers(this) localUriLiveData?.removeObservers(this)
localUriLiveData = App.db.bookDao().observeLocalUri() localUriLiveData = App.db.bookDao().observeLocalUri()
localUriLiveData?.observe(this, Observer { localUriLiveData?.observe(this, {
adapter.upBookHas(it) adapter.upBookHas(it)
}) })
} }

@ -14,15 +14,13 @@ import io.legado.app.R
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.*
import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.service.help.Download import io.legado.app.service.help.Download
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.requestInputMethod import io.legado.app.utils.requestInputMethod
import kotlinx.android.synthetic.main.dialog_download_choice.view.* import kotlinx.android.synthetic.main.dialog_download_choice.view.*
@ -37,7 +35,7 @@ object Help {
/** /**
* 更新状态栏,导航栏 * 更新状态栏,导航栏
*/ */
fun upSystemUiVisibility(activity: Activity, toolBarHide: Boolean = true) { fun upSystemUiVisibility(window: Window, toolBarHide: Boolean = true) {
var flag = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN var flag = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_IMMERSIVE or View.SYSTEM_UI_FLAG_IMMERSIVE
@ -53,13 +51,13 @@ object Help {
flag = flag or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION flag = flag or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
} }
} }
activity.window.decorView.systemUiVisibility = flag window.decorView.systemUiVisibility = flag
if (toolBarHide) { if (toolBarHide) {
ATH.setLightStatusBar(activity, ReadBookConfig.durConfig.statusIconDark()) ATH.setLightStatusBar(window, ReadBookConfig.durConfig.statusIconDark())
} else { } else {
ATH.setLightStatusBarAuto( ATH.setLightStatusBarAuto(
activity, window,
ThemeStore.statusBarColor(activity, AppConfig.isTransparentStatusBar) ThemeStore.statusBarColor(App.INSTANCE, AppConfig.isTransparentStatusBar)
) )
} }
} }
@ -181,4 +179,27 @@ object Help {
noButton() noButton()
}.show().applyTint().requestInputMethod() }.show().applyTint().requestInputMethod()
} }
@SuppressLint("InflateParams")
fun showCharsetConfig(context: Context) = with(context) {
val charsets =
arrayListOf("UTF-8", "GB2312", "GBK", "Unicode", "UTF-16", "UTF-16LE", "ASCII")
alert(R.string.set_charset) {
var editText: AutoCompleteTextView? = null
customView {
layoutInflater.inflate(R.layout.dialog_edit_text, null).apply {
editText = edit_view
edit_view.setFilterValues(charsets)
edit_view.setText(ReadBook.book?.charset)
}
}
okButton {
val text = editText?.text?.toString()
text?.let {
ReadBook.setCharset(it)
}
}
cancelButton()
}.show().applyTint()
}
} }

@ -14,7 +14,6 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.core.view.get import androidx.core.view.get
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.size import androidx.core.view.size
import androidx.lifecycle.Observer
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
import io.legado.app.R import io.legado.app.R
@ -108,11 +107,11 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
initView() initView()
upScreenTimeOut() upScreenTimeOut()
ReadBook.callBack = this ReadBook.callBack = this
ReadBook.titleDate.observe(this, Observer { ReadBook.titleDate.observe(this) {
title_bar.title = it title_bar.title = it
upMenu() upMenu()
upView() upView()
}) }
viewModel.initData(intent) viewModel.initData(intent)
} }
@ -128,6 +127,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
ReadBook.readStartTime = System.currentTimeMillis()
upSystemUiVisibility() upSystemUiVisibility()
timeBatteryReceiver = TimeBatteryReceiver.register(this) timeBatteryReceiver = TimeBatteryReceiver.register(this)
page_view.upTime() page_view.upTime()
@ -251,8 +251,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
R.id.menu_copy_text -> R.id.menu_copy_text ->
TextDialog.show(supportFragmentManager, ReadBook.curTextChapter?.getContent()) TextDialog.show(supportFragmentManager, ReadBook.curTextChapter?.getContent())
R.id.menu_update_toc -> ReadBook.book?.let { R.id.menu_update_toc -> ReadBook.book?.let {
ReadBook.upMsg(getString(R.string.toc_updateing)) loadChapterList(it)
viewModel.loadChapterList(it)
} }
R.id.menu_enable_replace -> ReadBook.book?.let { R.id.menu_enable_replace -> ReadBook.book?.let {
it.useReplaceRule = !it.useReplaceRule it.useReplaceRule = !it.useReplaceRule
@ -275,6 +274,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
Pair("loginUrl", it.loginUrl) Pair("loginUrl", it.loginUrl)
) )
} }
R.id.menu_set_charset -> Help.showCharsetConfig(this)
} }
return super.onCompatOptionsItemSelected(item) return super.onCompatOptionsItemSelected(item)
} }
@ -515,6 +515,11 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
return false return false
} }
override fun loadChapterList(book: Book) {
ReadBook.upMsg(getString(R.string.toc_updateing))
viewModel.loadChapterList(book)
}
/** /**
* 内容加载完成 * 内容加载完成
*/ */
@ -686,7 +691,7 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
* 更新状态栏,导航栏 * 更新状态栏,导航栏
*/ */
override fun upSystemUiVisibility() { override fun upSystemUiVisibility() {
Help.upSystemUiVisibility(this, !read_menu.isVisible) Help.upSystemUiVisibility(window, !read_menu.isVisible)
upNavigationBarColor() upNavigationBarColor()
} }

@ -10,8 +10,8 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.help.AppConfig import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentDataHelp
import io.legado.app.model.WebBook
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
@ -64,10 +64,17 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
ReadBook.loadContent(resetPageOffset = true) ReadBook.loadContent(resetPageOffset = true)
} }
} else { } else {
isInitFinish = true if (ReadBook.durChapterIndex != book.durChapterIndex) {
ReadBook.durChapterIndex = book.durChapterIndex
ReadBook.durPageIndex = book.durChapterPos
ReadBook.prevTextChapter = null
ReadBook.curTextChapter = null
ReadBook.nextTextChapter = null
}
ReadBook.book!!.group = book.group ReadBook.book!!.group = book.group
ReadBook.titleDate.postValue(book.name) ReadBook.titleDate.postValue(book.name)
ReadBook.upWebBook(book) ReadBook.upWebBook(book)
isInitFinish = true
if (!book.isLocalBook() && ReadBook.webBook == null) { if (!book.isLocalBook() && ReadBook.webBook == null) {
autoChangeSource(book.name, book.author) autoChangeSource(book.name, book.author)
return return
@ -82,6 +89,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} else { } else {
if (ReadBook.curTextChapter != null) { if (ReadBook.curTextChapter != null) {
ReadBook.callBack?.upContent(resetPageOffset = false) ReadBook.callBack?.upContent(resetPageOffset = false)
} else {
ReadBook.loadContent(resetPageOffset = true)
} }
} }
} }
@ -95,7 +104,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
if (book.isLocalBook()) { if (book.isLocalBook()) {
loadChapterList(book, changeDruChapterIndex) loadChapterList(book, changeDruChapterIndex)
} else { } else {
ReadBook.webBook?.getBookInfo(book, this) ReadBook.webBook?.getBookInfo(book, this, canReName = false)
?.onSuccess { ?.onSuccess {
loadChapterList(book, changeDruChapterIndex) loadChapterList(book, changeDruChapterIndex)
} }

@ -2,10 +2,7 @@ package io.legado.app.ui.book.read.config
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar import android.widget.SeekBar
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
@ -14,7 +11,6 @@ import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.ui.book.read.Help
import io.legado.app.utils.ColorUtils import io.legado.app.utils.ColorUtils
import kotlinx.android.synthetic.main.dialog_auto_read.* import kotlinx.android.synthetic.main.dialog_auto_read.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
@ -26,10 +22,10 @@ class AutoReadDialog : BaseDialogFragment() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.let { dialog?.window?.let {
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
it.setBackgroundDrawableResource(R.color.background) it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 0, 0, 0) it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes val attr = it.attributes

@ -9,10 +9,7 @@ import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -47,10 +44,10 @@ class BgTextConfigDialog : BaseDialogFragment() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.let { dialog?.window?.let {
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
it.setBackgroundDrawableResource(R.color.background) it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 5, 0, 0) it.decorView.setPadding(0, 5, 0, 0)
val attr = it.attributes val attr = it.attributes
@ -102,7 +99,7 @@ class BgTextConfigDialog : BaseDialogFragment() {
sw_dark_status_icon.onCheckedChange { buttonView, isChecked -> sw_dark_status_icon.onCheckedChange { buttonView, isChecked ->
if (buttonView?.isPressed == true) { if (buttonView?.isPressed == true) {
setStatusIconDark(isChecked) setStatusIconDark(isChecked)
activity?.let { activity?.window?.let {
Help.upSystemUiVisibility(it) Help.upSystemUiVisibility(it)
} }
} }

@ -4,10 +4,7 @@ import android.annotation.SuppressLint
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.preference.Preference import androidx.preference.Preference
@ -30,10 +27,10 @@ class MoreConfigDialog : DialogFragment() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.let { dialog?.window?.let {
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
it.setBackgroundDrawableResource(R.color.background) it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 0, 0, 0) it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes val attr = it.attributes

@ -6,11 +6,11 @@ import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager
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.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.ui.book.read.Help
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import kotlinx.android.synthetic.main.dialog_read_padding.* import kotlinx.android.synthetic.main.dialog_read_padding.*
@ -20,10 +20,10 @@ class PaddingConfigDialog : BaseDialogFragment() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.let { dialog?.window?.let {
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
val attr = it.attributes val attr = it.attributes
attr.dimAmount = 0.0f attr.dimAmount = 0.0f
it.attributes = attr it.attributes = attr

@ -10,16 +10,16 @@ import android.widget.LinearLayout
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BasePreferenceFragment import io.legado.app.base.BasePreferenceFragment
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.ui.book.read.Help import io.legado.app.utils.getPrefLong
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
class ReadAloudConfigDialog : DialogFragment() { class ReadAloudConfigDialog : DialogFragment() {
@ -29,10 +29,10 @@ class ReadAloudConfigDialog : DialogFragment() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.let { dialog?.window?.let {
it.setBackgroundDrawableResource(R.color.transparent) it.setBackgroundDrawableResource(R.color.transparent)
it.setLayout((dm.widthPixels * 0.9).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT) it.setLayout((dm.widthPixels * 0.9).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT)
} }
@ -62,11 +62,18 @@ class ReadAloudConfigDialog : DialogFragment() {
class ReadAloudPreferenceFragment : BasePreferenceFragment(), class ReadAloudPreferenceFragment : BasePreferenceFragment(),
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
private val speakEngineSummary: String
get() {
val eid = App.INSTANCE.getPrefLong(PreferKey.speakEngine)
val ht = App.db.httpTTSDao().get(eid)
return ht?.name ?: getString(R.string.local_tts)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_aloud) addPreferencesFromResource(R.xml.pref_config_aloud)
upPreferenceSummary( upPreferenceSummary(
findPreference<ListPreference>(PreferKey.ttsSpeechPer), findPreference(PreferKey.speakEngine),
AppConfig.ttsSpeechPer speakEngineSummary
) )
} }
@ -85,6 +92,14 @@ class ReadAloudConfigDialog : DialogFragment() {
super.onPause() super.onPause()
} }
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) {
PreferKey.speakEngine ->
SpeakEngineDialog().show(childFragmentManager, "speakEngine")
}
return super.onPreferenceTreeClick(preference)
}
override fun onSharedPreferenceChanged( override fun onSharedPreferenceChanged(
sharedPreferences: SharedPreferences?, sharedPreferences: SharedPreferences?,
key: String? key: String?
@ -95,16 +110,9 @@ class ReadAloudConfigDialog : DialogFragment() {
postEvent(EventBus.MEDIA_BUTTON, false) postEvent(EventBus.MEDIA_BUTTON, false)
} }
} }
PreferKey.readAloudOnLine -> { PreferKey.speakEngine -> {
ReadAloud.stop(requireContext()) upPreferenceSummary(findPreference(key), speakEngineSummary)
ReadAloud.aloudClass = ReadAloud.getReadAloudClass() ReadAloud.upReadAloudClass()
}
PreferKey.ttsSpeechPer -> {
upPreferenceSummary(
findPreference<ListPreference>(PreferKey.ttsSpeechPer),
AppConfig.ttsSpeechPer
)
ReadAloud.upTtsSpeechRate(requireContext())
} }
} }
} }

@ -2,10 +2,7 @@ package io.legado.app.ui.book.read.config
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SeekBar import android.widget.SeekBar
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
@ -16,7 +13,6 @@ import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook import io.legado.app.service.help.ReadBook
import io.legado.app.ui.book.read.Help
import io.legado.app.utils.ColorUtils import io.legado.app.utils.ColorUtils
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.observeEvent import io.legado.app.utils.observeEvent
@ -31,10 +27,10 @@ class ReadAloudDialog : BaseDialogFragment() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.let { dialog?.window?.let {
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
it.setBackgroundDrawableResource(R.color.background) it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 0, 0, 0) it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes val attr = it.attributes

@ -4,16 +4,12 @@ import android.annotation.SuppressLint
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.Gravity import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.get import androidx.core.view.get
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.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
@ -21,7 +17,6 @@ import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.getPrimaryTextColor
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.Help
import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.widget.font.FontSelectDialog import io.legado.app.ui.widget.font.FontSelectDialog
import io.legado.app.utils.* import io.legado.app.utils.*
@ -40,10 +35,10 @@ class ReadStyleDialog : BaseDialogFragment(), FontSelectDialog.CallBack {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.let { dialog?.window?.let {
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
it.setBackgroundDrawableResource(R.color.background) it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 0, 0, 0) it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes val attr = it.attributes
@ -209,9 +204,6 @@ class ReadStyleDialog : BaseDialogFragment(), FontSelectDialog.CallBack {
upBg() upBg()
postEvent(EventBus.UP_CONFIG, true) postEvent(EventBus.UP_CONFIG, true)
} }
if (AppConfig.isEInkMode) {
toast(R.string.e_ink_change_bg)
}
} }
private fun showBgTextConfig(index: Int): Boolean { private fun showBgTextConfig(index: Int): Boolean {

@ -0,0 +1,164 @@
package io.legado.app.ui.book.read.config
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.LiveData
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.HttpTTS
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.cancelButton
import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.theme.primaryColor
import io.legado.app.service.help.ReadAloud
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_http_tts_edit.view.*
import kotlinx.android.synthetic.main.dialog_recycler_view.*
import kotlinx.android.synthetic.main.item_http_tts.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
class SpeakEngineDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
override fun onStart() {
super.onStart()
val dm = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.9).toInt())
}
lateinit var adapter: Adapter
lateinit var viewModel: SpeakEngineViewModel
private var httpTTSData: LiveData<List<HttpTTS>>? = null
var engineId = App.INSTANCE.getPrefLong(PreferKey.speakEngine)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = getViewModel(SpeakEngineViewModel::class.java)
return inflater.inflate(R.layout.dialog_recycler_view, container)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
initView()
initMenu()
initData()
}
private fun initView() {
tool_bar.setBackgroundColor(primaryColor)
tool_bar.setTitle(R.string.speak_engine)
recycler_view.layoutManager = LinearLayoutManager(requireContext())
adapter = Adapter(requireContext())
recycler_view.adapter = adapter
tv_footer_left.setText(R.string.local_tts)
tv_footer_left.visible()
tv_footer_left.onClick {
removePref(PreferKey.speakEngine)
dismiss()
}
tv_ok.visible()
tv_ok.onClick {
putPrefLong(PreferKey.speakEngine, engineId)
dismiss()
}
tv_cancel.visible()
tv_cancel.onClick {
dismiss()
}
}
private fun initMenu() {
tool_bar.inflateMenu(R.menu.speak_engine)
tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this)
}
private fun initData() {
httpTTSData?.removeObservers(this)
httpTTSData = App.db.httpTTSDao().observeAll()
httpTTSData?.observe(this, {
adapter.setItems(it)
})
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_add -> editHttpTTS()
R.id.menu_default -> viewModel.importDefault()
}
return true
}
@SuppressLint("InflateParams")
private fun editHttpTTS(v: HttpTTS? = null) {
val httpTTS = v?.copy() ?: HttpTTS()
requireContext().alert(titleResource = R.string.speak_engine) {
var rootView: View? = null
customView {
LayoutInflater.from(requireContext())
.inflate(R.layout.dialog_http_tts_edit, null).apply {
rootView = this
tv_name.setText(httpTTS.name)
tv_url.setText(httpTTS.url)
}
}
cancelButton()
okButton {
rootView?.apply {
httpTTS.name = tv_name.text.toString()
httpTTS.url = tv_url.text.toString()
App.db.httpTTSDao().insert(httpTTS)
ReadAloud.upReadAloudClass()
}
}
}.show().applyTint()
}
inner class Adapter(context: Context) :
SimpleRecyclerAdapter<HttpTTS>(context, R.layout.item_http_tts) {
override fun convert(holder: ItemViewHolder, item: HttpTTS, payloads: MutableList<Any>) {
holder.itemView.apply {
cb_name.text = item.name
cb_name.isChecked = item.id == engineId
}
}
override fun registerListener(holder: ItemViewHolder) {
holder.itemView.apply {
cb_name.onClick {
getItem(holder.layoutPosition)?.let { httpTTS ->
engineId = httpTTS.id
notifyItemRangeChanged(0, getActualItemCount())
}
}
iv_edit.onClick {
editHttpTTS(getItem(holder.layoutPosition))
}
iv_menu_delete.onClick {
getItem(holder.layoutPosition)?.let { httpTTS ->
App.db.httpTTSDao().delete(httpTTS)
}
}
}
}
}
}

@ -0,0 +1,34 @@
package io.legado.app.ui.book.read.config
import android.app.Application
import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.help.DefaultValueHelp
import io.legado.app.help.http.HttpHelper
import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonArray
class SpeakEngineViewModel(application: Application) : BaseViewModel(application) {
fun importDefault() {
execute {
DefaultValueHelp.initHttpTTS()
}
}
fun importOnLine(url: String, finally: (msg: String) -> Unit) {
execute {
HttpHelper.simpleGetAsync(url)?.let { json ->
GSON.fromJsonArray<TxtTocRule>(json)?.let {
App.db.txtTocRule().insert(*it.toTypedArray())
}
}
}.onSuccess {
finally("导入成功")
}.onError {
finally("导入失败")
}
}
}

@ -22,19 +22,19 @@ class TipConfigDialog : BaseDialogFragment() {
super.onStart() super.onStart()
val dm = DisplayMetrics() val dm = DisplayMetrics()
activity?.let { activity?.let {
Help.upSystemUiVisibility(it)
it.windowManager?.defaultDisplay?.getMetrics(dm) it.windowManager?.defaultDisplay?.getMetrics(dm)
} }
dialog?.window?.setLayout( dialog?.window?.let {
ViewGroup.LayoutParams.MATCH_PARENT, Help.upSystemUiVisibility(it)
ViewGroup.LayoutParams.WRAP_CONTENT it.setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
) ViewGroup.LayoutParams.WRAP_CONTENT)
}
} }
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?,
): View? { ): View? {
return inflater.inflate(R.layout.dialog_tip_config, container) return inflater.inflate(R.layout.dialog_tip_config, container)
} }

@ -11,7 +11,6 @@ import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -21,7 +20,6 @@ import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Theme
import io.legado.app.data.entities.TxtTocRule import io.legado.app.data.entities.TxtTocRule
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
@ -29,7 +27,7 @@ import io.legado.app.lib.dialogs.cancelButton
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
@ -68,11 +66,11 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
} }
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
tool_bar.setBackgroundColor(bottomBackground) tool_bar.setBackgroundColor(primaryColor)
durRegex = arguments?.getString("tocRegex") durRegex = arguments?.getString("tocRegex")
tool_bar.setTitle(R.string.txt_toc_regex) tool_bar.setTitle(R.string.txt_toc_regex)
tool_bar.inflateMenu(R.menu.txt_toc_regex) tool_bar.inflateMenu(R.menu.txt_toc_regex)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
initView() initView()
initData() initData()
@ -105,7 +103,7 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
private fun initData() { private fun initData() {
tocRegexLiveData?.removeObservers(viewLifecycleOwner) tocRegexLiveData?.removeObservers(viewLifecycleOwner)
tocRegexLiveData = App.db.txtTocRule().observeAll() tocRegexLiveData = App.db.txtTocRule().observeAll()
tocRegexLiveData?.observe(viewLifecycleOwner, Observer { tocRules -> tocRegexLiveData?.observe(viewLifecycleOwner, { tocRules ->
initSelectedName(tocRules) initSelectedName(tocRules)
adapter.setItems(tocRules) adapter.setItems(tocRules)
}) })

@ -7,6 +7,7 @@ import android.text.StaticLayout
import android.text.TextPaint import android.text.TextPaint
import android.text.TextUtils import android.text.TextUtils
import io.legado.app.App import io.legado.app.App
import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
@ -18,7 +19,6 @@ import io.legado.app.ui.book.read.page.entities.TextLine
import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.utils.* import io.legado.app.utils.*
import java.util.* import java.util.*
import java.util.regex.Pattern
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -38,8 +38,6 @@ object ChapterProvider {
var typeface: Typeface = Typeface.SANS_SERIF var typeface: Typeface = Typeface.SANS_SERIF
lateinit var titlePaint: TextPaint lateinit var titlePaint: TextPaint
lateinit var contentPaint: TextPaint lateinit var contentPaint: TextPaint
private val srcPattern =
Pattern.compile("<img .*?src.*?=.*?\"(.*?(?:,\\{.*\\})?)\".*?>", Pattern.CASE_INSENSITIVE)
init { init {
upStyle() upStyle()
@ -62,7 +60,7 @@ object ChapterProvider {
var durY = 0f var durY = 0f
textPages.add(TextPage()) textPages.add(TextPage())
contents.forEachIndexed { index, text -> contents.forEachIndexed { index, text ->
val matcher = srcPattern.matcher(text) val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) { if (matcher.find()) {
var src = matcher.group(1) var src = matcher.group(1)
if (!book.isEpub()) { if (!book.isEpub()) {

@ -7,13 +7,13 @@ import android.view.View.GONE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.FlexboxLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
@ -176,17 +176,17 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
} }
private fun initLiveData() { private fun initLiveData() {
App.db.bookSourceDao().liveGroupEnabled().observe(this, Observer { App.db.bookSourceDao().liveGroupEnabled().observe(this, {
groups.clear() groups.clear()
it.map { group -> it.map { group ->
groups.addAll(group.splitNotBlank(",", ";")) groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
} }
upGroupMenu() upGroupMenu()
}) })
viewModel.searchBookLiveData.observe(this, Observer { viewModel.searchBookLiveData.observe(this, {
upSearchItems(it) upSearchItems(it)
}) })
viewModel.isSearchLiveData.observe(this, Observer { viewModel.isSearchLiveData.observe(this, {
if (it) { if (it) {
startSearch() startSearch()
} else { } else {
@ -254,7 +254,7 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
rv_bookshelf_search.gone() rv_bookshelf_search.gone()
} else { } else {
bookData = App.db.bookDao().liveDataSearch(key) bookData = App.db.bookDao().liveDataSearch(key)
bookData?.observe(this, Observer { bookData?.observe(this, {
if (it.isEmpty()) { if (it.isEmpty()) {
tv_book_show.gone() tv_book_show.gone()
rv_bookshelf_search.gone() rv_bookshelf_search.gone()
@ -272,7 +272,7 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
} else { } else {
App.db.searchKeywordDao().liveDataSearch(key) App.db.searchKeywordDao().liveDataSearch(key)
} }
historyData?.observe(this, Observer { historyData?.observe(this, {
historyKeyAdapter.setItems(it) historyKeyAdapter.setItems(it)
if (it.isEmpty()) { if (it.isEmpty()) {
tv_clear_history.invisible() tv_clear_history.invisible()

@ -8,7 +8,7 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.SearchKeyword import io.legado.app.data.entities.SearchKeyword
import io.legado.app.model.SearchBookModel import io.legado.app.model.webBook.SearchBookModel
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@ -30,13 +30,16 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
* 开始搜索 * 开始搜索
*/ */
fun search(key: String) { fun search(key: String) {
if (searchKey != key && key.isNotEmpty()) { if ((searchKey == key) || key.isNotEmpty()) {
searchBookModel.cancelSearch() searchBookModel.cancelSearch()
searchBooks.clear() searchBooks.clear()
searchBookLiveData.postValue(searchBooks) searchBookLiveData.postValue(searchBooks)
searchID = System.currentTimeMillis() searchID = System.currentTimeMillis()
searchKey = key searchKey = key
} }
if (searchKey.isEmpty()) {
return
}
searchBookModel.search(searchID, searchKey) searchBookModel.search(searchID, searchKey)
} }
@ -54,6 +57,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
override fun onSearchStart() { override fun onSearchStart() {
isSearchLiveData.postValue(true) isSearchLiveData.postValue(true)
isLoading = true
} }
override fun onSearchSuccess(searchBooks: ArrayList<SearchBook>) { override fun onSearchSuccess(searchBooks: ArrayList<SearchBook>) {
@ -110,7 +114,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application),
&& item.author == searchBook.author && item.author == searchBook.author
) { ) {
hasSame = true hasSame = true
searchBook.addOrigin(item.bookUrl) searchBook.addOrigin(item.origin)
break break
} }
} }

@ -4,7 +4,7 @@ import android.app.Application
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.model.Debug import io.legado.app.model.Debug
import io.legado.app.model.WebBook import io.legado.app.model.webBook.WebBook
class BookSourceDebugModel(application: Application) : BaseViewModel(application), class BookSourceDebugModel(application: Application) : BaseViewModel(application),
Debug.Callback { Debug.Callback {

@ -11,13 +11,13 @@ import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentDataHelp
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
@ -31,6 +31,7 @@ import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.qrcode.QrCodeActivity import io.legado.app.ui.qrcode.QrCodeActivity
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.* import io.legado.app.utils.*
@ -137,6 +138,13 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
val itemTouchCallback = ItemTouchCallback() val itemTouchCallback = ItemTouchCallback()
itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.onItemTouchCallbackListener = adapter
itemTouchCallback.isCanDrag = true itemTouchCallback.isCanDrag = true
val dragSelectTouchHelper: DragSelectTouchHelper =
DragSelectTouchHelper(adapter.initDragSelectTouchHelperCallback()).setSlideArea(16, 50)
dragSelectTouchHelper.attachToRecyclerView(recycler_view)
// When this page is opened, it is in selection mode
dragSelectTouchHelper.activeSlideSelect()
// Note: need judge selection first, so add ItemTouchHelper after it.
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view)
} }
@ -164,7 +172,7 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
App.db.bookSourceDao().liveDataSearch("%$searchKey%") App.db.bookSourceDao().liveDataSearch("%$searchKey%")
} }
} }
bookSourceLiveDate?.observe(this, Observer { data -> bookSourceLiveDate?.observe(this, { data ->
val sourceList = when (sort) { val sourceList = when (sort) {
1 -> data.sortedBy { it.weight } 1 -> data.sortedBy { it.weight }
2 -> data.sortedBy { it.bookSourceName } 2 -> data.sortedBy { it.bookSourceName }
@ -179,10 +187,10 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
} }
private fun initLiveDataGroup() { private fun initLiveDataGroup() {
App.db.bookSourceDao().liveGroup().observe(this, Observer { App.db.bookSourceDao().liveGroup().observe(this, {
groups.clear() groups.clear()
it.map { group -> it.map { group ->
groups.addAll(group.splitNotBlank(",", ";")) groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
} }
upGroupMenu() upGroupMenu()
}) })

@ -14,6 +14,7 @@ import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.ItemTouchCallback.OnItemTouchCallbackListener import io.legado.app.help.ItemTouchCallback.OnItemTouchCallbackListener
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.ui.widget.recycler.DragSelectTouchHelper
import io.legado.app.utils.invisible import io.legado.app.utils.invisible
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.item_book_source.view.* import kotlinx.android.synthetic.main.item_book_source.view.*
@ -193,6 +194,31 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
} }
} }
fun initDragSelectTouchHelperCallback(): DragSelectTouchHelper.Callback {
return object : DragSelectTouchHelper.AdvanceCallback<BookSource>(Mode.ToggleAndReverse) {
override fun currentSelectedId(): MutableSet<BookSource> {
return selected
}
override fun getItemId(position: Int): BookSource {
return getItem(position)!!
}
override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {
getItem(position)?.let {
if (isSelected) {
selected.add(it)
} else {
selected.remove(it)
}
notifyItemChanged(position, bundleOf(Pair("selected", null)))
return true
}
return false
}
}
}
interface CallBack { interface CallBack {
fun del(bookSource: BookSource) fun del(bookSource: BookSource)
fun edit(bookSource: BookSource) fun edit(bookSource: BookSource)

@ -5,6 +5,7 @@ import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import io.legado.app.App import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
@ -96,9 +97,9 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
fun selectionAddToGroups(sources: List<BookSource>, groups: String) { fun selectionAddToGroups(sources: List<BookSource>, groups: String) {
execute { execute {
val list = arrayListOf<BookSource>() val list = arrayListOf<BookSource>()
sources.forEach { sources.forEach { source ->
val newGroupList = arrayListOf<String>() val newGroupList = arrayListOf<String>()
it.bookSourceGroup?.splitNotBlank(",", ";")?.forEach { source.bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.forEach {
newGroupList.add(it) newGroupList.add(it)
} }
groups.splitNotBlank(",", ";", "").forEach { groups.splitNotBlank(",", ";", "").forEach {
@ -106,7 +107,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
} }
val lh = LinkedHashSet(newGroupList) val lh = LinkedHashSet(newGroupList)
val newGroup = ArrayList(lh).joinToString(separator = ",") val newGroup = ArrayList(lh).joinToString(separator = ",")
list.add(it.copy(bookSourceGroup = newGroup)) list.add(source.copy(bookSourceGroup = newGroup))
} }
App.db.bookSourceDao().update(*list.toTypedArray()) App.db.bookSourceDao().update(*list.toTypedArray())
} }
@ -115,9 +116,9 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
fun selectionRemoveFromGroups(sources: List<BookSource>, groups: String) { fun selectionRemoveFromGroups(sources: List<BookSource>, groups: String) {
execute { execute {
val list = arrayListOf<BookSource>() val list = arrayListOf<BookSource>()
sources.forEach { sources.forEach { source ->
val newGroupList = arrayListOf<String>() val newGroupList = arrayListOf<String>()
it.bookSourceGroup?.splitNotBlank(",", ";")?.forEach { source.bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.forEach {
newGroupList.add(it) newGroupList.add(it)
} }
groups.splitNotBlank(",", ";", "").forEach { groups.splitNotBlank(",", ";", "").forEach {
@ -125,7 +126,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
} }
val lh = LinkedHashSet(newGroupList) val lh = LinkedHashSet(newGroupList)
val newGroup = ArrayList(lh).joinToString(separator = ",") val newGroup = ArrayList(lh).joinToString(separator = ",")
list.add(it.copy(bookSourceGroup = newGroup)) list.add(source.copy(bookSourceGroup = newGroup))
} }
App.db.bookSourceDao().update(*list.toTypedArray()) App.db.bookSourceDao().update(*list.toTypedArray())
} }

@ -11,18 +11,18 @@ import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Theme import io.legado.app.constant.AppPattern
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.dialogs.yesButton
import io.legado.app.lib.theme.backgroundColor import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.getViewModelOfActivity
@ -56,22 +56,23 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
view.setBackgroundColor(backgroundColor) view.setBackgroundColor(backgroundColor)
initData() tool_bar.setBackgroundColor(primaryColor)
}
private fun initData() {
tool_bar.title = getString(R.string.group_manage) tool_bar.title = getString(R.string.group_manage)
tool_bar.inflateMenu(R.menu.group_manage) tool_bar.inflateMenu(R.menu.group_manage)
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
adapter = GroupAdapter(requireContext()) adapter = GroupAdapter(requireContext())
recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.addItemDecoration(VerticalDivider(requireContext()))
recycler_view.adapter = adapter recycler_view.adapter = adapter
App.db.bookSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { initData()
}
private fun initData() {
App.db.bookSourceDao().liveGroup().observe(viewLifecycleOwner, {
val groups = linkedSetOf<String>() val groups = linkedSetOf<String>()
it.map { group -> it.map { group ->
groups.addAll(group.splitNotBlank(",", ";")) groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
} }
adapter.setItems(groups.toList()) adapter.setItems(groups.toList())
}) })

@ -19,6 +19,7 @@ import io.legado.app.help.storage.WebDavHelp
import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.utils.getPrefString import io.legado.app.utils.getPrefString
import io.legado.app.utils.isContentPath import io.legado.app.utils.isContentPath
import io.legado.app.utils.longToast
import io.legado.app.utils.toast import io.legado.app.utils.toast
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
@ -77,8 +78,9 @@ object BackupRestoreUi {
fun restore(fragment: Fragment) { fun restore(fragment: Fragment) {
Coroutine.async(context = Main) { Coroutine.async(context = Main) {
val restoreFromWebDav = WebDavHelp.showRestoreDialog(fragment.requireContext()) WebDavHelp.showRestoreDialog(fragment.requireContext())
if (!restoreFromWebDav) { }.onError {
fragment.longToast("WebDavError:${it.localizedMessage},将从本地备份恢复。")
val backupPath = fragment.getPrefString(PreferKey.backupPath) val backupPath = fragment.getPrefString(PreferKey.backupPath)
if (backupPath?.isNotEmpty() == true) { if (backupPath?.isNotEmpty() == true) {
if (backupPath.isContentPath()) { if (backupPath.isContentPath()) {
@ -97,7 +99,6 @@ object BackupRestoreUi {
} }
} }
} }
}
fun restoreByFolder(fragment: Fragment) { fun restoreByFolder(fragment: Fragment) {
selectBackupFolder(fragment, restoreSelectRequestCode) selectBackupFolder(fragment, restoreSelectRequestCode)

@ -20,6 +20,9 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.receiver.SharedReceiverActivity import io.legado.app.receiver.SharedReceiverActivity
@ -79,11 +82,7 @@ class OtherConfigFragment : BasePreferenceFragment(),
.show { .show {
putPrefInt(PreferKey.webPort, it) putPrefInt(PreferKey.webPort, it)
} }
PreferKey.cleanCache -> { PreferKey.cleanCache -> clearCache()
BookHelp.clearCache()
FileUtils.deleteFile(requireActivity().cacheDir.absolutePath)
toast(R.string.clear_cache_success)
}
PreferKey.defaultCover -> if (getPrefString(PreferKey.defaultCover).isNullOrEmpty()) { PreferKey.defaultCover -> if (getPrefString(PreferKey.defaultCover).isNullOrEmpty()) {
selectDefaultCover() selectDefaultCover()
} else { } else {
@ -145,6 +144,18 @@ class OtherConfigFragment : BasePreferenceFragment(),
} }
} }
private fun clearCache() {
requireContext().alert(titleResource = R.string.clear_cache,
messageResource = R.string.sure_del) {
okButton {
BookHelp.clearCache()
FileUtils.deleteFile(requireActivity().cacheDir.absolutePath)
toast(R.string.clear_cache_success)
}
noButton()
}.show().applyTint()
}
private fun selectDefaultCover() { private fun selectDefaultCover() {
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)

@ -12,7 +12,7 @@ import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.Theme import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.filechooser.adapter.FileAdapter import io.legado.app.ui.filechooser.adapter.FileAdapter
import io.legado.app.ui.filechooser.adapter.PathAdapter import io.legado.app.ui.filechooser.adapter.PathAdapter
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
@ -93,6 +93,7 @@ class FileChooserDialog : DialogFragment(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
tool_bar.setBackgroundColor(primaryColor)
view.setBackgroundResource(R.color.background_card) view.setBackgroundResource(R.color.background_card)
arguments?.let { arguments?.let {
requestCode = it.getInt("requestCode") requestCode = it.getInt("requestCode")
@ -129,7 +130,7 @@ class FileChooserDialog : DialogFragment(),
tool_bar.menu.add(menuTitle) tool_bar.menu.add(menuTitle)
} }
} }
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
} }

@ -62,7 +62,7 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
//自动更新书籍 //自动更新书籍
if (AppConfig.autoRefreshBook) { if (AppConfig.autoRefreshBook) {
view_pager_main.postDelayed({ view_pager_main.postDelayed({
viewModel.upChapterList() viewModel.upAllBookToc()
}, 1000) }, 1000)
} }
view_pager_main.postDelayed({ view_pager_main.postDelayed({
@ -93,8 +93,8 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
} }
private fun upVersion() { private fun upVersion() {
if (getPrefInt(PreferKey.versionCode) != App.INSTANCE.versionCode) { if (getPrefInt(PreferKey.versionCode) != App.versionCode) {
putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) putPrefInt(PreferKey.versionCode, App.versionCode)
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
val log = String(assets.open("updateLog.md").readBytes()) val log = String(assets.open("updateLog.md").readBytes())
TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000, true) TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000, true)
@ -168,7 +168,7 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
} }
} }
private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : private inner class TabFragmentPageAdapter(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItemPosition(`object`: Any): Int { override fun getItemPosition(`object`: Any): Int {

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

Loading…
Cancel
Save