| @ -1,4 +1,4 @@ | ||||
| <resources> | ||||
|     <string name="app_name">阅读.debug</string> | ||||
|     <string name="receiving_shared_label">阅读.debug·搜索</string> | ||||
|     <string name="app_name">阅读·D</string> | ||||
|     <string name="receiving_shared_label">阅读·D·搜索</string> | ||||
| </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() | ||||
|     } | ||||
| 
 | ||||
| } | ||||