| @ -1,4 +1,4 @@ | |||||||
| <resources> | <resources> | ||||||
|     <string name="app_name">阅读.debug</string> |     <string name="app_name">阅读·D</string> | ||||||
|     <string name="receiving_shared_label">阅读.debug·搜索</string> |     <string name="receiving_shared_label">阅读·D·搜索</string> | ||||||
| </resources> | </resources> | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <resources> | ||||||
|  | 
 | ||||||
|  |     <string name="app_name">閱讀Pro</string> | ||||||
|  | 
 | ||||||
|  | </resources> | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <resources> | ||||||
|  | 
 | ||||||
|  |     <string name="app_name">阅读Pro</string> | ||||||
|  | 
 | ||||||
|  | </resources> | ||||||
| @ -0,0 +1,129 @@ | |||||||
|  | ## 常见问题 | ||||||
|  | 
 | ||||||
|  | 1.为什么第一次安装好之后什么东西都没有? | ||||||
|  | * 因为阅读只是一个转码工具,不提供内容,第一次安装app,需要自己手动导入书源,可以从QQ群、公众号“开源阅读软件”、酷安评论里获取由书友制作分享的书源。 | ||||||
|  | 
 | ||||||
|  | 2.如何导入本地书源文件? | ||||||
|  | * 下载群文件里的书源文件(书源格式后缀有txt、json,其中json文件某些情况下无法导入,需要修改后缀为txt格式才可导入); | ||||||
|  | * 打开阅读软件; | ||||||
|  | * 我的 - 点击“书源管理”; | ||||||
|  | * 点击右上角选择“本地导入”; | ||||||
|  | * 左下角选择书源文件所在的路径; | ||||||
|  | * 点击书源文件导入; | ||||||
|  | * 导入后返回书源管理界面; | ||||||
|  | * 新版qq下载路径:Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/ | ||||||
|  | 
 | ||||||
|  | 3.如何新建大佬发的单独书源? | ||||||
|  | * 复制书源代码; | ||||||
|  | * 打开阅读软件; | ||||||
|  | * 我的 - 点击“书源管理”; | ||||||
|  | * 右上角选择“新建书源”; | ||||||
|  | * 进入新建书源后点击右上角“粘贴源”; | ||||||
|  | * 粘贴书源完成后点击上方保存; | ||||||
|  | * 本次新建单独书源操作完成。 | ||||||
|  | * 注:如果书源有错误或者复制不全会显示格式错误,请重新复制。 | ||||||
|  | 
 | ||||||
|  | 4.为什么导入2.0书源后看不了书? | ||||||
|  | * 2.0部分书源并不适用3.0,建议导入后进行筛选。 | ||||||
|  | 
 | ||||||
|  | 5.阅读2.0数据如何导入阅读3.0? | ||||||
|  | * 先对阅读2.0的数据进行备份,然后进入阅读3.0,点击“我的”,选择“备份与恢复”,再点击“导入旧版本数据”。 | ||||||
|  | 
 | ||||||
|  | 6.如何给朋友分享我的书源? | ||||||
|  | * 打开阅读软件; | ||||||
|  | * 点击备份; | ||||||
|  | * 打开手机自带的文件管理; | ||||||
|  | * 手机自带内存根目录找到YueDu3.0文件夹; | ||||||
|  | * 找到myBookSource.json长按选择分享; | ||||||
|  | * 选择微信分享或者QQ分享; | ||||||
|  | * 选择你要分享的好友点击发送; | ||||||
|  | * 好友接收后在手机自带内存根目录找到myBookSource.json文件(QQ在tencent--QQfile_recv微信在Tencent--MicroMsg--Download); | ||||||
|  | * 复制该文件到手机自带内存根目录找到YueDu3.0文件夹(如已有该文件请先删除该文件或者备份到其他地方再复制到文件夹); | ||||||
|  | * 打开阅读软件点击恢复。 | ||||||
|  | * 注:备份路径如已修改过请在修改后的路径下查找书源文件。 | ||||||
|  | 
 | ||||||
|  | 7.目前阅读支持哪些格式的本地书籍? | ||||||
|  | * 目前支持TXT、EPUB格式(只支持显示EPUB里的文本内容,还不支持显示图片)。 | ||||||
|  | 
 | ||||||
|  | 8.如何刷新书架? | ||||||
|  | * 在书架界面下拉即可刷新。 | ||||||
|  | 
 | ||||||
|  | 9.书架界面书籍右上角的红色或者灰色背景小数字代表什么? | ||||||
|  | * 红色代表书籍有更新,灰色代表无更新,数字代表未读章节。 | ||||||
|  | 
 | ||||||
|  | 10.如何查看书籍详情? | ||||||
|  | * 长按书籍。 | ||||||
|  | 
 | ||||||
|  | 11.如何对书架上的书进行删除、切换书架的操作? | ||||||
|  | * 书籍详情页操作即可。 | ||||||
|  | 
 | ||||||
|  | 12.如何禁止或允许某本书更新? | ||||||
|  | * 书籍详情页,点击右上角 - “允许更新”。 | ||||||
|  | 
 | ||||||
|  | 13.如何更换小说封面、名字、作者或简介? | ||||||
|  | * 书籍详情页,点击右上角修改按钮。 | ||||||
|  | 
 | ||||||
|  | 14.怎么使用自定义字体? | ||||||
|  | * 阅读界面 - 字体-点击右上角选择字体文件路径。 | ||||||
|  | 
 | ||||||
|  | 15.目前支持哪些格式的字体文件? | ||||||
|  | * 目前支持ttf、otf格式。 | ||||||
|  | 
 | ||||||
|  | 16.书籍经常“正在加载中”怎么办? | ||||||
|  | * 在线书籍出现这个问题通常是由于源质量不好或不兼容引起的,可以换其它源多试试;本地书籍出现这个问题大概率是目录规则问题,手动切换规则可以解决。 | ||||||
|  | 
 | ||||||
|  | 17.书籍内容只有标题,正文内容是路径怎么办? | ||||||
|  | * 通常是缓存路径引起的,更换缓存路径即可。 | ||||||
|  | 
 | ||||||
|  | 18.效验书源显示失效就说明书源不能用了吗? | ||||||
|  | * 效验书源只是测试书源,可以做个参考,失效了不代表书源不能用了。 | ||||||
|  | 
 | ||||||
|  | 19.发现和正版书源能不能使用? | ||||||
|  | * 发现和正版书源只能用来找书,看排行榜,不能用来看书,如需看书请切换书源。 | ||||||
|  | 
 | ||||||
|  | 20.替换净化是什么? | ||||||
|  | * 替换净化可以去除书籍内容里的广告、错别字、屏蔽词等。 | ||||||
|  | 
 | ||||||
|  | 21.如何自己填写净化替换规则? | ||||||
|  | * 第一行:替换规则名称 - 根据自己需求对替换净化规则进行命名; | ||||||
|  | * 第二行:分组 - 净化规则的分组组别; | ||||||
|  | * 第三行:替换规则 - 填写需要被替换的内容; | ||||||
|  | * 第四行:替换为 - 填写想替换成的内容(如不填则默认表示删除第二行里填写的内容); | ||||||
|  | * 第五行:替换范围,选填书名或者源名 - 填写此替换净化规则需要对哪本书籍或者哪个书源生效(如不填则对所有书籍和书源生效)。 | ||||||
|  | * 注:如常规去除方法去除不掉,则需要勾选“使用正则表达式”,同时第二行里的替换规则也需要按照正则表达式来填写(正则表达式填写方法可自行百度学习)。 | ||||||
|  | 
 | ||||||
|  | 22.如何听书? | ||||||
|  | * 可以使用手机自带的朗读引擎,也可使用第三方如谷歌、小米等朗读引擎。 | ||||||
|  | * 【具体操作:安装-系统设置-其他高级设置-辅助功能-TTS输出-选择安装的朗读引擎(不同品牌手机的操作方法及步骤也不同,视情况而定)。】 | ||||||
|  | 
 | ||||||
|  | 23.如何设置屏幕方向、屏幕显示时长、显示/隐藏状态栏、显示/隐藏导航栏、音量键翻页、长按选择文本、点击总是翻下一页、自定义翻页案件? | ||||||
|  | * 阅读界面,设置(可上划,下面还有其他设置)。 | ||||||
|  | 
 | ||||||
|  | 24.搜索的时候感觉手机卡顿,如何解决? | ||||||
|  | * 我的 - 其他设置 - “更新和搜索线程数”调低。 | ||||||
|  | 
 | ||||||
|  | 25.更新前有什么注意事项? | ||||||
|  | * 要做好备份。 | ||||||
|  | 
 | ||||||
|  | 26.看书时如遇到“目录为空”、“加载失败”和长串英文等情况怎么办? | ||||||
|  | * 一般是书源问题,切换书源即可。 | ||||||
|  | 
 | ||||||
|  | 27.为什么书源这么多,发现里却只有一点点? | ||||||
|  | * 书源想要在发现界面里显示需要在书源里添加发现规则,并不是所有书源都有发现规则。 | ||||||
|  | 
 | ||||||
|  | 28.云备份在哪? | ||||||
|  | * 我的 - 备份与恢复 - WebDav设置。 | ||||||
|  | 
 | ||||||
|  | 29.如何操作进行云备份? | ||||||
|  | * 侧栏设置,WebDav设置; | ||||||
|  | * 正确填写WebDAV 服务器地址、WebDAV 账号、WebDAV 密码;(要获得这三项的信息,需要注册一个坚果云账号,如果直接在手机上注册,坚果云会让你下载app,过程比较麻烦,为了一步到位,最好是在电脑上打开这个注册链接:https://www.jianguoyun.com/d/signup;注册后,进入坚果云;点击右上角账户名处选择 “账户信息”,然后选择“安全选项”;在“安全选项” 中找到“第三方应用管理”,并选择“添加应用”,输入名称如“阅读”后,会生成密码,选择完成;其中https://dav.jianguoyun.com/dav/就是填入“WebDAV 服务器地址”的内容,“使用情况”后面的邮箱地址就是你的“WebDAV 账号”,点击显示密码后得到的密码就是你的“WebDAV 密码”。) | ||||||
|  | * 无需操作,APP默认每天自动云备份一次。 | ||||||
|  | 
 | ||||||
|  | 30.关于云备份的相关说明 | ||||||
|  | * 在正确设置好云备份的情况下,APP默认每天自动云备份一次,当日多次手动云备份会对当日的旧云备份文件进行覆盖,并不会覆盖之前及之后不同日期的备份文件,每天所自动云备份的文件会按照日期进行命名。 | ||||||
|  | 
 | ||||||
|  | 31.本地备份和云备份都能备份哪些东西? | ||||||
|  | * 书架、看书进度、搜索记录、书源、替换、APP设置等都会备份,基本涵盖所有内容。 | ||||||
|  | 
 | ||||||
|  | 32.出现某些未知bug怎么办? | ||||||
|  | * 清除软件数据试试看,不行再进行反馈。 | ||||||
| @ -0,0 +1 @@ | |||||||
|  | #app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50;margin:0;height:100%} | ||||||
| After Width: | Height: | Size: 9.2 KiB | 
| After Width: | Height: | Size: 29 KiB | 
| After Width: | Height: | Size: 3.3 KiB | 
| After Width: | Height: | Size: 4.0 KiB | 
| After Width: | Height: | Size: 4.6 KiB | 
| After Width: | Height: | Size: 1.5 KiB | 
| After Width: | Height: | Size: 1.8 KiB | 
| After Width: | Height: | Size: 4.6 KiB | 
| After Width: | Height: | Size: 799 B | 
| After Width: | Height: | Size: 1.2 KiB | 
| After Width: | Height: | Size: 1.1 KiB | 
| After Width: | Height: | Size: 4.2 KiB | 
| After Width: | Height: | Size: 10 KiB | 
| After Width: | Height: | Size: 5.8 KiB | 
| @ -0,0 +1 @@ | |||||||
|  | {"name":"yd-web-tool","short_name":"yd-web-tool","theme_color":"#4DBA87","icons":[{"src":"./img/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"./img/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"},{"src":"./img/icons/android-chrome-maskable-192x192.png","sizes":"192x192","type":"image/png","purpose":"maskable"},{"src":"./img/icons/android-chrome-maskable-512x512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":".","display":"standalone","background_color":"#000000"} | ||||||
| @ -0,0 +1,74 @@ | |||||||
|  | package io.legado.app.help | ||||||
|  | 
 | ||||||
|  | import io.legado.app.App | ||||||
|  | import io.legado.app.R | ||||||
|  | import io.legado.app.utils.getPrefBoolean | ||||||
|  | import io.legado.app.utils.getPrefInt | ||||||
|  | import io.legado.app.utils.putPrefBoolean | ||||||
|  | import io.legado.app.utils.putPrefInt | ||||||
|  | 
 | ||||||
|  | object ReadTipConfig { | ||||||
|  |     val tipArray: Array<String> = App.INSTANCE.resources.getStringArray(R.array.read_tip) | ||||||
|  |     const val none = 0 | ||||||
|  |     const val chapterTitle = 1 | ||||||
|  |     const val time = 2 | ||||||
|  |     const val battery = 3 | ||||||
|  |     const val page = 4 | ||||||
|  |     const val totalProgress = 5 | ||||||
|  |     const val pageAndTotal = 6 | ||||||
|  | 
 | ||||||
|  |     val tipHeaderLeftStr: String get() = tipArray.getOrElse(tipHeaderLeft) { tipArray[none] } | ||||||
|  |     val tipHeaderMiddleStr: String get() = tipArray.getOrElse(tipHeaderMiddle) { tipArray[none] } | ||||||
|  |     val tipHeaderRightStr: String get() = tipArray.getOrElse(tipHeaderRight) { tipArray[none] } | ||||||
|  |     val tipFooterLeftStr: String get() = tipArray.getOrElse(tipFooterLeft) { tipArray[none] } | ||||||
|  |     val tipFooterMiddleStr: String get() = tipArray.getOrElse(tipFooterMiddle) { tipArray[none] } | ||||||
|  |     val tipFooterRightStr: String get() = tipArray.getOrElse(tipFooterRight) { tipArray[none] } | ||||||
|  | 
 | ||||||
|  |     var tipHeaderLeft: Int | ||||||
|  |         get() = App.INSTANCE.getPrefInt("tipHeaderLeft", time) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefInt("tipHeaderLeft", value) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     var tipHeaderMiddle: Int | ||||||
|  |         get() = App.INSTANCE.getPrefInt("tipHeaderMiddle", none) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefInt("tipHeaderMiddle", value) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     var tipHeaderRight: Int | ||||||
|  |         get() = App.INSTANCE.getPrefInt("tipHeaderRight", battery) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefInt("tipHeaderRight", value) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     var tipFooterLeft: Int | ||||||
|  |         get() = App.INSTANCE.getPrefInt("tipFooterLeft", chapterTitle) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefInt("tipFooterLeft", value) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     var tipFooterMiddle: Int | ||||||
|  |         get() = App.INSTANCE.getPrefInt("tipFooterMiddle", none) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefInt("tipFooterMiddle", value) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     var tipFooterRight: Int | ||||||
|  |         get() = App.INSTANCE.getPrefInt("tipFooterRight", pageAndTotal) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefInt("tipFooterRight", value) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     var hideHeader: Boolean | ||||||
|  |         get() = App.INSTANCE.getPrefBoolean("hideHeader", true) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefBoolean("hideHeader", value) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     var hideFooter: Boolean | ||||||
|  |         get() = App.INSTANCE.getPrefBoolean("hideFooter", false) | ||||||
|  |         set(value) { | ||||||
|  |             App.INSTANCE.putPrefBoolean("hideFooter", value) | ||||||
|  |         } | ||||||
|  | } | ||||||
| @ -0,0 +1,93 @@ | |||||||
|  | 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() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||