diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 40348af60..db5123e54 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -3,6 +3,29 @@ * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 - 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 +**2020/09/23** +* 修复规则解析bug + +**2020/09/21** +* 修复规则解析bug +* 换源时无最新章节信息时可加载详情页来获取(默认关闭) + +**2020/09/20** +* 优化正文搜索 +* 阅读界面信息添加书名 + +**2020/09/18** +* 解决正文替换{{title}}问题 +* 修复共用布局配置不能读取的问题 +* 添加自定义源分组功能 by KKL369 +* 解决跨进程调用ReaderProvider出现CursorIndexOutOfBoundsException问题 + +**2020/09/17** +* 优化正文搜索文字颜色 +* 优化书源校验 by KKL369 +* 缓存导出到webDav by 10bits +* 导入的字体在字体选择界面显示 + **2020/09/15** * 修复导入排版字体重复报错的bug * 添加正文搜索 by [h11128](https://github.com/h11128) diff --git a/app/src/main/java/io/legado/app/api/ReaderProvider.kt b/app/src/main/java/io/legado/app/api/ReaderProvider.kt index fcf50aa9c..3ae8c7963 100644 --- a/app/src/main/java/io/legado/app/api/ReaderProvider.kt +++ b/app/src/main/java/io/legado/app/api/ReaderProvider.kt @@ -7,10 +7,7 @@ import android.content.ContentProvider import android.content.ContentResolver import android.content.ContentValues import android.content.UriMatcher -import android.database.CharArrayBuffer -import android.database.ContentObserver -import android.database.Cursor -import android.database.DataSetObserver +import android.database.* import android.net.Uri import android.os.Bundle import com.google.gson.Gson @@ -109,40 +106,22 @@ class ReaderProvider : ContentProvider() { uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array? ) = throw UnsupportedOperationException("Not yet implemented") - + /** * Simple inner class to deliver json callback data. * * Only getString() makes sense. */ - private class SimpleCursor(data: ReturnData?) : Cursor { + private class SimpleCursor(data: ReturnData?) : MatrixCursor(arrayOf("result"), 1) { private val mData: String = Gson().toJson(data) - override fun getCount() = 1 - - override fun getPosition() = 0 - - override fun move(i: Int) = true - - override fun moveToPosition(i: Int) = true - - override fun moveToFirst() = true - - override fun moveToLast() = true - - override fun moveToNext() = true - - override fun moveToPrevious() = true - - override fun isFirst() = true - - override fun isLast() = true - - override fun isBeforeFirst() = true + init { + addRow(arrayOf(mData)) + } - override fun isAfterLast() = true + override fun getCount() = 1 override fun getColumnIndex(s: String) = 0 diff --git a/app/src/main/java/io/legado/app/constant/EventBus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt index 6fe8b5cea..8ea4122b9 100644 --- a/app/src/main/java/io/legado/app/constant/EventBus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -20,4 +20,8 @@ object EventBus { const val WEB_SERVICE_STOP = "webServiceStop" const val UP_DOWNLOAD = "upDownload" const val SAVE_CONTENT = "saveContent" + const val CHECK_INIT = "checkInit" + const val CHECK_UP_PROGRESS = "checkProgress" + const val CHECK_UP_PROGRESS_STRING = "checkProgressTxt" + const val CHECK_DONE = "checkDone" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index c6e222849..7ef56460e 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -33,7 +33,9 @@ object PreferKey { const val webDavAccount = "web_dav_account" const val webDavPassword = "web_dav_password" const val webDavCreateDir = "webDavCreateDir" + const val webDavCacheBackup = "webDavCacheBackup" const val changeSourceLoadToc = "changeSourceLoadToc" + const val changeSourceLoadInfo = "changeSourceLoadInfo" const val chineseConverterType = "chineseConverterType" const val launcherIcon = "launcherIcon" const val textSelectAble = "selectText" diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt index e2538e902..eddc4f428 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -58,6 +58,9 @@ object AppConfig { App.INSTANCE.putPrefBoolean(PreferKey.showRss, value) } + val backgroundVerification: Boolean + get() = App.INSTANCE.getPrefBoolean(R.string.pk_background_verification) + val autoRefreshBook: Boolean get() = App.INSTANCE.getPrefBoolean(R.string.pk_auto_refresh) diff --git a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt index 4353e2fe1..5cceab22d 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -75,7 +75,7 @@ object ReadBookConfig { } fun initShareConfig() { - val configFile = File(configFilePath) + val configFile = File(shareConfigFilePath) var c: Config? = null if (configFile.exists()) { try { diff --git a/app/src/main/java/io/legado/app/help/ReadTipConfig.kt b/app/src/main/java/io/legado/app/help/ReadTipConfig.kt index eb37a10e1..204851aee 100644 --- a/app/src/main/java/io/legado/app/help/ReadTipConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadTipConfig.kt @@ -12,6 +12,7 @@ object ReadTipConfig { const val page = 4 const val totalProgress = 5 const val pageAndTotal = 6 + const val bookName = 7 val tipHeaderLeftStr: String get() = tipArray.getOrElse(tipHeaderLeft) { tipArray[none] } val tipHeaderMiddleStr: String get() = tipArray.getOrElse(tipHeaderMiddle) { tipArray[none] } diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index d1437ce7c..6e6cb6791 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -54,6 +54,7 @@ object Backup { context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis()) withContext(IO) { synchronized(this@Backup) { + FileUtils.deleteFile(backupPath) writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) writeListToJson(App.db.bookmarkDao().all, "bookmark.json", backupPath) writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) diff --git a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt index e728c28de..407bcf2c5 100644 --- a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt +++ b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt @@ -10,10 +10,7 @@ import io.legado.app.help.coroutine.Coroutine import io.legado.app.lib.dialogs.selector import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.http.HttpAuth -import io.legado.app.utils.FileUtils -import io.legado.app.utils.ZipUtils -import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.getPrefString +import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.withContext @@ -125,4 +122,24 @@ object WebDavHelp { } } } + fun exportWebDav(path: String, fileName: String) { + try { + if (initWebDav()) { + // 默认导出到legado文件夹下exports目录 + val exportsWebDavUrl = rootWebDavUrl + EncoderUtils.escape("exports") + "/" + // 在legado文件夹创建exports目录,如果不存在的话 + WebDav(exportsWebDavUrl).makeAsDir() + val file = File("${path}${File.separator}${fileName}") + // 如果导出的本地文件存在,开始上传 + if(file.exists()) { + val putUrl = exportsWebDavUrl + fileName + WebDav(putUrl).upload("${path}${File.separator}${fileName}") + } + } + } catch (e: Exception) { + Handler(Looper.getMainLooper()).post { + App.INSTANCE.toast("WebDav导出\n${e.localizedMessage}") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt index f9ed9230f..1b77cdd1a 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt @@ -238,6 +238,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { if (ruleList.isNotEmpty()) result = o for (sourceRule in ruleList) { putRule(sourceRule.putMap) + sourceRule.makeUpRule(result) result?.let { result = when (sourceRule.mode) { Mode.Regex -> AnalyzeByRegex.getElement( @@ -324,37 +325,17 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { */ private fun replaceRegex(result: String, rule: SourceRule): String { var vResult = result - val stringBuffer = StringBuffer() - val evalMatcher = replacePattern.matcher(rule.replaceRegex) - while (evalMatcher.find()) { - val jsEval = evalMatcher.group().let { - if (it.startsWith("@get:", true)) { - get(it.substring(6, it.lastIndex)) - } else { - evalJS(it.substring(2, it.length - 2), result) - } - } ?: "" - if (jsEval is String) { - evalMatcher.appendReplacement(stringBuffer, jsEval) - } else if (jsEval is Double && jsEval % 1.0 == 0.0) { - evalMatcher.appendReplacement(stringBuffer, String.format("%.0f", jsEval)) - } else { - evalMatcher.appendReplacement(stringBuffer, jsEval.toString()) - } - } - evalMatcher.appendTail(stringBuffer) - val replaceRegex = stringBuffer.toString() - if (replaceRegex.isNotEmpty()) { + if (rule.replaceRegex.isNotEmpty()) { vResult = if (rule.replaceFirst) { - val pattern = Pattern.compile(replaceRegex) + val pattern = Pattern.compile(rule.replaceRegex) val matcher = pattern.matcher(vResult) if (matcher.find()) { - matcher.group(0)!!.replaceFirst(replaceRegex.toRegex(), rule.replacement) + matcher.group(0)!!.replaceFirst(rule.replaceRegex.toRegex(), rule.replacement) } else { "" } } else { - vResult.replace(replaceRegex.toRegex(), rule.replacement) + vResult.replace(rule.replaceRegex.toRegex(), rule.replacement) } } return vResult @@ -425,6 +406,9 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { internal val putMap = HashMap() private val ruleParam = ArrayList() private val ruleType = ArrayList() + private val getRuleType = -2 + private val jsRuleType = -1 + private val defaultRuleType = 0 init { this.mode = mainMode @@ -465,68 +449,71 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { } //分离put rule = splitPutRule(rule, putMap) - //分离正则表达式 - val index = rule.indexOf("}}") - var rule1 = "" - var rule2 = rule - if (index > 0) { - rule1 = rule.substring(0, index) - rule2 = rule.substring(index) - } - val ruleStrS = rule2.trim { it <= ' ' }.split("##") - rule = rule1 + ruleStrS[0] - if (ruleStrS.size > 1) { - replaceRegex = ruleStrS[1] - } - if (ruleStrS.size > 2) { - replacement = ruleStrS[2] - } - if (ruleStrS.size > 3) { - replaceFirst = true - } - //@get,{{ }},$1, 拆分 + //@get,{{ }}, 拆分 var start = 0 var tmp: String val evalMatcher = evalPattern.matcher(rule) while (evalMatcher.find()) { - if (mode != Mode.Js) { - mode = Mode.Regex - } if (evalMatcher.start() > start) { tmp = rule.substring(start, evalMatcher.start()) - ruleType.add(0) - ruleParam.add(tmp) + if (start == 0 && !tmp.contains("##") && mode != Mode.Js) { + mode = Mode.Regex + } + splitRegex(tmp) } tmp = evalMatcher.group() when { - tmp.startsWith("$") -> { - ruleType.add(tmp.substring(1).toInt()) - ruleParam.add(tmp) - } tmp.startsWith("@get:", true) -> { - ruleType.add(-2) + ruleType.add(getRuleType) ruleParam.add(tmp.substring(6, tmp.lastIndex)) } tmp.startsWith("{{") -> { - ruleType.add(-1) + ruleType.add(jsRuleType) ruleParam.add(tmp.substring(2, tmp.length - 2)) } else -> { - ruleType.add(0) - ruleParam.add(tmp) + splitRegex(tmp) } } start = evalMatcher.end() } if (rule.length > start) { tmp = rule.substring(start) - ruleType.add(0) + splitRegex(tmp) + } + } + + /** + * 拆分\$\d{1,2} + */ + private fun splitRegex(ruleStr: String) { + var start = 0 + var tmp: String + val ruleStrArray = ruleStr.split("##") + val regexMatcher = regexPattern.matcher(ruleStrArray[0]) + while (regexMatcher.find()) { + if (mode != Mode.Js) { + mode = Mode.Regex + } + if (regexMatcher.start() > start) { + tmp = ruleStr.substring(start, regexMatcher.start()) + ruleType.add(defaultRuleType) + ruleParam.add(tmp) + } + tmp = regexMatcher.group() + ruleType.add(tmp.substring(1).toInt()) + ruleParam.add(tmp) + start = regexMatcher.end() + } + if (ruleStr.length > start) { + tmp = ruleStr.substring(start) + ruleType.add(defaultRuleType) ruleParam.add(tmp) } } /** - * 替换@get,{{ }},$1, + * 替换@get,{{ }} */ fun makeUpRule(result: Any?) { val infoVal = StringBuilder() @@ -535,7 +522,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { while (index-- > 0) { val regType = ruleType[index] when { - regType > 0 -> { + regType > defaultRuleType -> { @Suppress("UNCHECKED_CAST") val resultList = result as? List if (resultList != null) { @@ -548,7 +535,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { infoVal.insert(0, ruleParam[index]) } } - regType == -1 -> { + regType == jsRuleType -> { if (isRule(ruleParam[index])) { getString(arrayListOf(SourceRule(ruleParam[index]))).let { infoVal.insert(0, it) @@ -566,7 +553,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { } } } - regType == -2 -> { + regType == getRuleType -> { infoVal.insert(0, get(ruleParam[index])) } else -> infoVal.insert(0, ruleParam[index]) @@ -574,6 +561,18 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { } rule = infoVal.toString() } + //分离正则表达式 + val ruleStrS = rule.trim { it <= ' ' }.split("##") + rule = ruleStrS[0] + if (ruleStrS.size > 1) { + replaceRegex = ruleStrS[1] + } + if (ruleStrS.size > 2) { + replacement = ruleStrS[2] + } + if (ruleStrS.size > 3) { + replaceFirst = true + } } private fun isRule(ruleStr: String): Boolean { @@ -660,14 +659,9 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { companion object { private val putPattern = Pattern.compile("@put:(\\{[^}]+?\\})", Pattern.CASE_INSENSITIVE) private val getPattern = Pattern.compile("@get:\\{([^}]+?)\\}", Pattern.CASE_INSENSITIVE) - private val evalPattern = Pattern.compile( - "@get:\\{[^}]+?\\}|\\{\\{[\\w\\W]*?\\}\\}|\\$\\d{1,2}", - Pattern.CASE_INSENSITIVE - ) - private val replacePattern = Pattern.compile( - "@get:\\{[^}]+?\\}|\\{\\{[\\w\\W]*?\\}\\}", - Pattern.CASE_INSENSITIVE - ) + private val evalPattern = + Pattern.compile("@get:\\{[^}]+?\\}|\\{\\{[\\w\\W]*?\\}\\}", Pattern.CASE_INSENSITIVE) + private val regexPattern = Pattern.compile("\\$\\d{1,2}") } } diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index 09900d0d0..705ff19c9 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -6,12 +6,15 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseService import io.legado.app.constant.AppConst +import io.legado.app.constant.EventBus import io.legado.app.constant.IntentAction import io.legado.app.help.AppConfig +import io.legado.app.help.AppConfig.backgroundVerification import io.legado.app.help.IntentHelp import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.service.help.CheckSource import io.legado.app.ui.book.source.manage.BookSourceActivity +import io.legado.app.utils.postEvent import kotlinx.coroutines.asCoroutineDispatcher import org.jetbrains.anko.toast import java.util.concurrent.Executors @@ -42,7 +45,9 @@ class CheckSourceService : BaseService() { override fun onCreate() { super.onCreate() - updateNotification(0, getString(R.string.start)) + if (backgroundVerification) { + updateNotification(0, getString(R.string.start)) + } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -59,6 +64,7 @@ class CheckSourceService : BaseService() { super.onDestroy() tasks.clear() searchPool.close() + postEvent(EventBus.CHECK_DONE, 0) } private fun check(ids: List) { @@ -72,7 +78,11 @@ class CheckSourceService : BaseService() { allIds.addAll(ids) processIndex = 0 threadCount = min(allIds.size, threadCount) - updateNotification(0, getString(R.string.progress_show, 0, allIds.size)) + if (backgroundVerification) { + updateNotification(0, getString(R.string.progress_show, "", 0, allIds.size)) + } else { + postEvent(EventBus.CHECK_INIT, allIds.size) + } for (i in 0 until threadCount) { check() } @@ -91,25 +101,30 @@ class CheckSourceService : BaseService() { val sourceUrl = allIds[index] App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> if (source.searchUrl.isNullOrEmpty()) { - onNext(sourceUrl) + onNext(sourceUrl, source.bookSourceName) } else { CheckSource(source).check(this, searchPool) { - onNext(it) + onNext(it, source.bookSourceName) } } - } ?: onNext(sourceUrl) + } ?: onNext(sourceUrl, "") } } } - private fun onNext(sourceUrl: String) { + private fun onNext(sourceUrl: String, sourceName: String) { synchronized(this) { check() checkedIds.add(sourceUrl) - updateNotification( - checkedIds.size, - getString(R.string.progress_show, checkedIds.size, allIds.size) - ) + if (backgroundVerification) { + updateNotification( + checkedIds.size, + getString(R.string.progress_show, sourceName, checkedIds.size, allIds.size) + ) + } else { + postEvent(EventBus.CHECK_UP_PROGRESS, checkedIds.size) + postEvent(EventBus.CHECK_UP_PROGRESS_STRING, getString(R.string.progress_show, sourceName, checkedIds.size, allIds.size)) + } if (processIndex >= allIds.size + threadCount - 1) { stopSelf() } diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index 2e96221f3..479850620 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -368,7 +368,19 @@ object ReadBook { val currentLine = currentPage.textLines[lineIndex] length -= currentLine.text.length val charIndex = contentPosition - length - return arrayOf(pageIndex, lineIndex, charIndex) + var addLine = 0 + var charIndex2 = 0 + // change line + if ((charIndex + query.length) > currentLine.text.length){ + addLine = 1 + charIndex2 = charIndex + query.length - currentLine.text.length - 1 + } + // changePage + if ((lineIndex + addLine + 1) > currentPage.textLines.size){ + addLine = -1 + charIndex2 = charIndex + query.length - currentLine.text.length - 1 + } + return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2) } /** diff --git a/app/src/main/java/io/legado/app/ui/README.md b/app/src/main/java/io/legado/app/ui/README.md index ef44bed69..102071a0c 100644 --- a/app/src/main/java/io/legado/app/ui/README.md +++ b/app/src/main/java/io/legado/app/ui/README.md @@ -1,6 +1,7 @@ ## 放置与界面有关的类 * about 关于界面 +* association 导入书源界面 * audio 音频播放界面 * book\arrange 书架整理界面 * book\info 书籍信息查看 diff --git a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt index 07b6451da..c045d0e33 100644 --- a/app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt @@ -20,16 +20,21 @@ import io.legado.app.data.entities.BookSource import io.legado.app.help.IntentDataHelp import io.legado.app.help.SourceHelp import io.legado.app.lib.dialogs.alert +import io.legado.app.lib.dialogs.customView +import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.okButton +import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel import io.legado.app.utils.visible import kotlinx.android.synthetic.main.activity_translucence.* +import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_source_import.view.* import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.toast + class ImportBookSourceActivity : VMBaseActivity( R.layout.activity_translucence, theme = Theme.Transparent @@ -112,6 +117,7 @@ class ImportBookSourceActivity : VMBaseActivity( class SourcesDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { lateinit var adapter: SourcesAdapter + private var _groupName: String? = null override fun onStart() { super.onStart() @@ -165,6 +171,23 @@ class ImportBookSourceActivity : VMBaseActivity( override fun onMenuItemClick(item: MenuItem): Boolean { when (item.itemId) { + R.id.menu_new_group -> { + alert(R.string.diy_edit_source_group) { + var editText: AutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + } + } + okButton { + editText?.text?.toString()?.let { group -> + _groupName = group + item.title = getString(R.string.diy_edit_source_group_title, _groupName) + } + } + noButton { } + }.show().applyTint() + } R.id.menu_select_all -> { adapter.selectStatus.forEachIndexed { index, b -> if (!b) { @@ -193,6 +216,9 @@ class ImportBookSourceActivity : VMBaseActivity( private fun importSelect() { val selectSource = arrayListOf() adapter.selectStatus.forEachIndexed { index, b -> + if (_groupName != null) { + adapter.getItem(index)!!.bookSourceGroup = _groupName + } if (b) { selectSource.add(adapter.getItem(index)!!) } diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index 0673aaaab..52e26862c 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -7,8 +7,10 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppPattern +import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.help.BookHelp +import io.legado.app.help.storage.WebDavHelp import io.legado.app.utils.* import java.io.File @@ -34,8 +36,20 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } private fun export(doc: DocumentFile, book: Book) { - DocumentUtils.createFileIfNotExist(doc, "${book.name} 作者:${book.author}.txt") - ?.writeText(context, getAllContents(book)) + val filename = "${book.name} by ${book.author}.txt" + val content = getAllContents(book) + DocumentUtils.createFileIfNotExist(doc, filename) + ?.writeText(context, content) + if(App.INSTANCE.getPrefBoolean(PreferKey.webDavCacheBackup,false)) { + FileUtils.createFileIfNotExist( + File(FileUtils.getCachePath()), + filename + ).writeText(content) // 写出文件到cache目录 + // 导出到webdav + WebDavHelp.exportWebDav(FileUtils.getCachePath(), filename) + // 上传完删除cache文件 + FileUtils.deleteFile("${FileUtils.getCachePath()}${File.separator}${filename}") + } App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter -> BookHelp.getContent(book, chapter).let { content -> content?.split("\n")?.forEachIndexed { index, text -> @@ -61,8 +75,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) { } private fun export(file: File, book: Book) { - FileUtils.createFileIfNotExist(file, "${book.name} 作者:${book.author}.txt") + val filename = "${book.name} by ${book.author}.txt" + FileUtils.createFileIfNotExist(file, filename) .writeText(getAllContents(book)) + if(App.INSTANCE.getPrefBoolean(PreferKey.webDavCacheBackup,false)) { + WebDavHelp.exportWebDav(file.absolutePath, filename) // 导出到webdav + } App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter -> BookHelp.getContent(book, chapter).let { content -> content?.split("\n")?.forEachIndexed { index, text -> diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt index bbd627466..e73b443a6 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt @@ -88,6 +88,8 @@ class ChangeSourceDialog : BaseDialogFragment(), tool_bar.setOnMenuItemClickListener(this) tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = getPrefBoolean(PreferKey.changeSourceLoadToc) + tool_bar.menu.findItem(R.id.menu_load_info)?.isChecked = + getPrefBoolean(PreferKey.changeSourceLoadInfo) } private fun initRecyclerView() { @@ -159,6 +161,10 @@ class ChangeSourceDialog : BaseDialogFragment(), putPrefBoolean(PreferKey.changeSourceLoadToc, !item.isChecked) item.isChecked = !item.isChecked } + R.id.menu_load_info -> { + putPrefBoolean(PreferKey.changeSourceLoadInfo, !item.isChecked) + item.isChecked = !item.isChecked + } R.id.menu_stop -> viewModel.stopSearch() } return false diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index ec423c3dd..0cbc1e2ee 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -120,11 +120,11 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio .onSuccess(IO) { it.forEach { searchBook -> if (searchBook.name == name && searchBook.author == author) { - if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { - if (searchBook.tocUrl.isEmpty()) { + if (searchBook.latestChapterTitle.isNullOrEmpty()) { + if (context.getPrefBoolean(PreferKey.changeSourceLoadInfo) || context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { loadBookInfo(searchBook.toBook()) } else { - loadChapter(searchBook.toBook()) + searchFinish(searchBook) } } else { searchFinish(searchBook) @@ -156,7 +156,14 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getBookInfo(book, this) .onSuccess { - loadChapter(it) + if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { + loadChapter(it) + } else { + //从详情页里获取最新章节 + book.latestChapterTitle = it.latestChapterTitle + val searchBook = book.toSearchBook() + searchFinish(searchBook) + } }.onError { debug { context.getString(R.string.error_get_book_info) } } diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt index 322e4a7a3..1118eb8cc 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt @@ -26,7 +26,11 @@ class ExploreShowAdapter(context: Context, val callBack: CallBack) : tv_lasted.text = context.getString(R.string.lasted_show, item.latestChapterTitle) tv_lasted.visible() } - tv_introduce.text = context.getString(R.string.intro_show, item.intro) + if (item.intro.isNullOrEmpty()) { + tv_introduce.text = context.getString(R.string.intro_show_null) + } else { + tv_introduce.text = context.getString(R.string.intro_show, item.intro) + } val kinds = item.getKindList() if (kinds.isEmpty()) { ll_kind.gone() diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 7120e047a..92df11f6a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -49,7 +49,6 @@ import io.legado.app.ui.book.read.page.PageView import io.legado.app.ui.book.read.page.TextPageFactory import io.legado.app.ui.book.read.page.delegate.PageDelegate import io.legado.app.ui.book.searchContent.SearchListActivity -import io.legado.app.ui.book.searchContent.SearchResult import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.replacerule.ReplaceRuleActivity @@ -58,16 +57,14 @@ import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_read.* import kotlinx.android.synthetic.main.view_read_menu.* -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.async -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivityForResult import org.jetbrains.anko.toast +import java.lang.Runnable class ReadBookActivity : VMBaseActivity(R.layout.activity_book_read), View.OnTouchListener, @@ -496,6 +493,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo ) return true } + R.id.menu_search_content -> { + openSearchActivity(selectedText) + return true + } } return false } @@ -680,12 +681,12 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo /** * 打开搜索界面 */ - //todo: change request code - override fun openSearchList() { + override fun openSearchActivity(searchWord: String?) { ReadBook.book?.let { startActivityForResult( requestCodeSearchResult, - Pair("bookUrl", it.bookUrl) + Pair("bookUrl", it.bookUrl), + Pair("searchWord", searchWord) ) } } @@ -787,14 +788,22 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo pages = ReadBook.curTextChapter?.pages } val positions = ReadBook.searchResultPositions(pages, indexWithinChapter, query!!) - //todo: show selected text - val job1 = async(Main){ + while (ReadBook.durPageIndex != positions[0]){ + delay(100L) ReadBook.skipToPage(positions[0]) - page_view.curPage.selectStartMoveIndex(positions[0], positions[1], 0) - page_view.curPage.selectEndMoveIndex(positions[0], positions[1], 0 + query.length ) + } + withContext(Main){ + page_view.curPage.selectStartMoveIndex(0, positions[1], positions[2]) + delay(20L) + when (positions[3]){ + 0 -> page_view.curPage.selectEndMoveIndex(0, positions[1], positions[2] + query.length - 1) + 1 -> page_view.curPage.selectEndMoveIndex(0, positions[1] + 1, positions[4]) + //todo: consider change page, jump to scroll position + -1 -> page_view.curPage.selectEndMoveIndex(1, 0, positions[4]) + } page_view.isTextSelected = true + delay(100L) } - job1.await() } } requestCodeReplace -> onReplaceRuleSave() diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt index 26423e701..a532bac2a 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt @@ -65,6 +65,8 @@ class ReadMenu : FrameLayout { brightnessBackground.setColor(ColorUtils.adjustAlpha(bgColor, 0.5f)) ll_brightness.background = brightnessBackground ll_bottom_bg.setBackgroundColor(bgColor) + fabSearch.backgroundTintList = bottomBackgroundList + fabSearch.setColorFilter(textColor) fabAutoPage.backgroundTintList = bottomBackgroundList fabAutoPage.setColorFilter(textColor) fabReplaceRule.backgroundTintList = bottomBackgroundList @@ -170,6 +172,13 @@ class ReadMenu : FrameLayout { } }) + //搜索 + fabSearch.onClick { + runMenuOut { + callBack?.openSearchActivity(null) + } + } + //自动翻页 fabAutoPage.onClick { runMenuOut { @@ -200,12 +209,6 @@ class ReadMenu : FrameLayout { } } - ll_search.onClick { - runMenuOut { - callBack?.openSearchList() - } - } - //朗读 ll_read_aloud.onClick { runMenuOut { @@ -297,7 +300,7 @@ class ReadMenu : FrameLayout { fun autoPage() fun openReplaceRule() fun openChapterList() - fun openSearchList() + fun openSearchActivity(searchWord: String?) fun showReadStyle() fun showMoreSetting() fun showReadAloudDialog() diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index e89ee5ae8..166a9b731 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -11,6 +11,7 @@ import io.legado.app.base.BaseActivity import io.legado.app.constant.AppConst.timeFormat import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadTipConfig +import io.legado.app.service.help.ReadBook import io.legado.app.ui.book.read.page.entities.TextPage import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.widget.BatteryView @@ -29,6 +30,7 @@ class ContentView(context: Context) : FrameLayout(context) { private var tvPage: BatteryView? = null private var tvTotalProgress: BatteryView? = null private var tvPageAndTotal: BatteryView? = null + private var tvBookName: BatteryView? = null val headerHeight: Int get() = if (ReadBookConfig.hideStatusBar) { @@ -185,6 +187,19 @@ class ContentView(context: Context) : FrameLayout(context) { isBattery = false textSize = 12f } + tvBookName = when (ReadTipConfig.bookName) { + ReadTipConfig.tipHeaderLeft -> bv_header_left + ReadTipConfig.tipHeaderMiddle -> tv_header_middle + ReadTipConfig.tipHeaderRight -> tv_header_right + ReadTipConfig.tipFooterLeft -> bv_footer_left + ReadTipConfig.tipFooterMiddle -> tv_footer_middle + ReadTipConfig.tipFooterRight -> tv_footer_right + else -> null + } + tvBookName?.apply { + isBattery = false + textSize = 12f + } } fun setBg(bg: Drawable?) { @@ -213,8 +228,8 @@ class ContentView(context: Context) : FrameLayout(context) { @SuppressLint("SetTextI18n") fun setProgress(textPage: TextPage) = textPage.apply { - val title = textPage.title - tvTitle?.text = title + tvBookName?.text = ReadBook.book?.name + tvTitle?.text = textPage.title tvPage?.text = "${index.plus(1)}/$pageSize" tvTotalProgress?.text = readProgress tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress" diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt index a96244113..4370e9697 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt @@ -42,7 +42,13 @@ class SearchAdapter(context: Context, val callBack: CallBack) : tv_author.text = context.getString(R.string.author_show, searchBook.author) bv_originCount.setBadgeCount(searchBook.origins.size) upLasted(itemView, searchBook.latestChapterTitle) - tv_introduce.text = context.getString(R.string.intro_show, searchBook.intro) + if (searchBook.intro.isNullOrEmpty()) { + tv_introduce.text = + context.getString(R.string.intro_show_null) + } else { + tv_introduce.text = + context.getString(R.string.intro_show, searchBook.intro) + } upKind(itemView, searchBook.getKindList()) iv_cover.load(searchBook.coverUrl, searchBook.name, searchBook.author) @@ -58,8 +64,15 @@ class SearchAdapter(context: Context, val callBack: CallBack) : context.getString(R.string.author_show, searchBook.author) "origins" -> bv_originCount.setBadgeCount(searchBook.origins.size) "last" -> upLasted(itemView, searchBook.latestChapterTitle) - "intro" -> tv_introduce.text = - context.getString(R.string.intro_show, searchBook.intro) + "intro" -> { + if (searchBook.intro.isNullOrEmpty()) { + tv_introduce.text = + context.getString(R.string.intro_show_null) + } else { + tv_introduce.text = + context.getString(R.string.intro_show, searchBook.intro) + } + } "kind" -> upKind(itemView, searchBook.getKindList()) "cover" -> iv_cover.load( searchBook.coverUrl, diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt index ec1cbc0c7..6c01801c3 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt @@ -1,93 +1,264 @@ package io.legado.app.ui.book.searchContent +import android.annotation.SuppressLint +import android.content.Intent import android.os.Bundle -import android.view.Menu import androidx.appcompat.widget.SearchView -import androidx.core.view.isGone -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter +import com.hankcs.hanlp.HanLP +import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity +import io.legado.app.constant.EventBus +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter +import io.legado.app.help.AppConfig +import io.legado.app.help.BookHelp import io.legado.app.lib.theme.ATH -import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.bottomBackground +import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.primaryTextColor +import io.legado.app.ui.widget.recycler.UpLinearLayoutManager +import io.legado.app.ui.widget.recycler.VerticalDivider +import io.legado.app.utils.ColorUtils import io.legado.app.utils.getViewModel -import io.legado.app.utils.gone -import io.legado.app.utils.visible -import kotlinx.android.synthetic.main.activity_chapter_list.* -import kotlinx.android.synthetic.main.view_tab_layout.* +import io.legado.app.utils.observeEvent +import kotlinx.android.synthetic.main.activity_search_list.* +import kotlinx.android.synthetic.main.view_search.* +import kotlinx.coroutines.* +import org.jetbrains.anko.sdk27.listeners.onClick -class SearchListActivity : VMBaseActivity(R.layout.activity_search_list) { - // todo: 完善搜索界面UI +class SearchListActivity : VMBaseActivity(R.layout.activity_search_list), + SearchListAdapter.Callback { + override val viewModel: SearchListViewModel get() = getViewModel(SearchListViewModel::class.java) - private var searchView: SearchView? = null + lateinit var adapter: SearchListAdapter + private lateinit var mLayoutManager: UpLinearLayoutManager + private var searchResultCounts = 0 + private var durChapterIndex = 0 + private var searchResultList: MutableList = mutableListOf() override fun onActivityCreated(savedInstanceState: Bundle?) { - tab_layout.isTabIndicatorFullWidth = false - tab_layout.setSelectedTabIndicatorColor(accentColor) + val bbg = bottomBackground + val btc = getPrimaryTextColor(ColorUtils.isColorLight(bbg)) + ll_search_base_info.setBackgroundColor(bbg) + tv_current_search_info.setTextColor(btc) + iv_search_content_top.setColorFilter(btc) + iv_search_content_bottom.setColorFilter(btc) + initSearchView() + initRecyclerView() + initView() intent.getStringExtra("bookUrl")?.let { viewModel.initBook(it) { - view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) - tab_layout.setupWithViewPager(view_pager) + initBook() } } } - override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.search_view, menu) - val search = menu.findItem(R.id.menu_search) - searchView = search.actionView as SearchView - ATH.setTint(searchView!!, primaryTextColor) - searchView?.maxWidth = resources.displayMetrics.widthPixels - searchView?.onActionViewCollapsed() - searchView?.setOnCloseListener { - tab_layout.visible() - //to do clean - false - } - searchView?.setOnSearchClickListener { tab_layout.gone() } - searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + private fun initSearchView() { + ATH.setTint(search_view, primaryTextColor) + search_view.onActionViewExpanded() + search_view.isSubmitButtonEnabled = true + search_view.queryHint = getString(R.string.search) + search_view.clearFocus() + search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { - if (viewModel.lastQuery != query){ - viewModel.startContentSearch(query) + if (viewModel.lastQuery != query) { + startContentSearch(query) } return false } - override fun onQueryTextChange(newText: String): Boolean { - + override fun onQueryTextChange(newText: String?): Boolean { return false } }) - return super.onCompatCreateOptionsMenu(menu) } - private inner class TabFragmentPageAdapter(fm: FragmentManager) : - FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { - override fun getItem(position: Int): Fragment { - return SearchListFragment() + private fun initRecyclerView() { + adapter = SearchListAdapter(this, this) + mLayoutManager = UpLinearLayoutManager(this) + recycler_view.layoutManager = mLayoutManager + recycler_view.addItemDecoration(VerticalDivider(this)) + recycler_view.adapter = adapter + } + + private fun initView() { + iv_search_content_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) } + iv_search_content_bottom.onClick { + if (adapter.itemCount > 0) { + mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0) + } + } + } + + @SuppressLint("SetTextI18n") + private fun initBook() { + launch { + tv_current_search_info.text = "搜索结果:$searchResultCounts" + viewModel.book?.let { + initCacheFileNames(it) + durChapterIndex = it.durChapterIndex + intent.getStringExtra("searchWord")?.let { searchWord -> + search_view.setQuery(searchWord, true) + } + } + } + } + + private fun initCacheFileNames(book: Book) { + launch(Dispatchers.IO) { + adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book)) + withContext(Dispatchers.Main) { + adapter.notifyItemRangeChanged(0, adapter.getActualItemCount(), true) + } + } + } + + override fun observeLiveBus() { + observeEvent(EventBus.SAVE_CONTENT) { chapter -> + viewModel.book?.bookUrl?.let { bookUrl -> + if (chapter.bookUrl == bookUrl) { + adapter.cacheFileNames.add(BookHelp.formatChapterName(chapter)) + adapter.notifyItemChanged(chapter.index, true) + } + } } + } - override fun getCount(): Int { - return 1 + @SuppressLint("SetTextI18n") + fun startContentSearch(newText: String) { + // 按章节搜索内容 + if (!newText.isBlank()) { + adapter.clearItems() + searchResultList.clear() + searchResultCounts = 0 + viewModel.lastQuery = newText + var searchResults = listOf() + launch(Dispatchers.Main) { + App.db.bookChapterDao().getChapterList(viewModel.bookUrl).map { chapter -> + val job = async(Dispatchers.IO) { + if (isLocalBook || adapter.cacheFileNames.contains( + BookHelp.formatChapterName( + chapter + ) + ) + ) { + searchResults = searchChapter(newText, chapter) + } + } + job.await() + if (searchResults.isNotEmpty()) { + searchResultList.addAll(searchResults) + tv_current_search_info.text = "搜索结果:$searchResultCounts" + adapter.addItems(searchResults) + searchResults = listOf() + } + } + } } + } - override fun getPageTitle(position: Int): CharSequence? { - return "Search" + private suspend fun searchChapter(query: String, chapter: BookChapter?): List { + val searchResults: MutableList = mutableListOf() + var positions: List + var replaceContents: List? = null + var totalContents: String + if (chapter != null) { + viewModel.book?.let { bookSource -> + val bookContent = BookHelp.getContent(bookSource, chapter) + if (bookContent != null) { + //搜索替换后的正文 + val job = async(Dispatchers.IO) { + chapter.title = when (AppConfig.chineseConverterType) { + 1 -> HanLP.convertToSimplifiedChinese(chapter.title) + 2 -> HanLP.convertToTraditionalChinese(chapter.title) + else -> chapter.title + } + replaceContents = BookHelp.disposeContent( + chapter.title, + bookSource.name, + bookSource.bookUrl, + bookContent, + bookSource.useReplaceRule + ) + } + job.await() + while (replaceContents == null) { + delay(100L) + } + totalContents = replaceContents!!.joinToString("") + positions = searchPosition(totalContents, query) + var count = 1 + positions.map { + val construct = constructText(totalContents, it, query) + val result = SearchResult( + index = searchResultCounts, + indexWithinChapter = count, + text = construct[1] as String, + chapterTitle = chapter.title, + query = query, + chapterIndex = chapter.index, + newPosition = construct[0] as Int, + contentPosition = it + ) + count += 1 + searchResultCounts += 1 + searchResults.add(result) + } + } + } } + return searchResults + } + private fun searchPosition(content: String, pattern: String): List { + val position: MutableList = mutableListOf() + var index = content.indexOf(pattern) + while (index >= 0) { + position.add(index) + index = content.indexOf(pattern, index + 1) + } + return position } - override fun onBackPressed() { - if (tab_layout.isGone) { - searchView?.onActionViewCollapsed() - tab_layout.visible() - } else { - super.onBackPressed() + private fun constructText(content: String, position: Int, query: String): Array { + // 构建关键词周边文字,在搜索结果里显示 + // todo: 判断段落,只在关键词所在段落内分割 + // todo: 利用标点符号分割完整的句 + // todo: length和设置结合,自由调整周边文字长度 + val length = 20 + var po1 = position - length + var po2 = position + query.length + length + if (po1 < 0) { + po1 = 0 + } + if (po2 > content.length) { + po2 = content.length } + val newPosition = position - po1 + val newText = content.substring(po1, po2) + return arrayOf(newPosition, newText) } + + val isLocalBook: Boolean + get() = viewModel.book?.isLocalBook() == true + + override fun openSearchResult(searchResult: SearchResult) { + + val searchData = Intent() + searchData.putExtra("index", searchResult.chapterIndex) + searchData.putExtra("contentPosition", searchResult.contentPosition) + searchData.putExtra("query", searchResult.query) + searchData.putExtra("indexWithinChapter", searchResult.indexWithinChapter) + setResult(RESULT_OK, searchData) + finish() + } + + override fun durChapterIndex(): Int { + return durChapterIndex + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt index d2c98b955..d68eb85ca 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListAdapter.kt @@ -1,21 +1,12 @@ package io.legado.app.ui.book.searchContent import android.content.Context -import android.os.Build -import android.text.Html -import android.util.Log -import android.view.View -import androidx.annotation.RequiresApi -import androidx.core.text.HtmlCompat import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.data.entities.BookChapter -import io.legado.app.help.BookHelp import io.legado.app.lib.theme.accentColor import io.legado.app.utils.getCompatColor -import io.legado.app.utils.visible -import kotlinx.android.synthetic.main.item_bookmark.view.* +import io.legado.app.utils.hexString import kotlinx.android.synthetic.main.item_search_list.view.* import org.jetbrains.anko.sdk27.listeners.onClick @@ -23,15 +14,15 @@ class SearchListAdapter(context: Context, val callback: Callback) : SimpleRecyclerAdapter(context, R.layout.item_search_list) { val cacheFileNames = hashSetOf() + val textColor = context.getCompatColor(R.color.primaryText).hexString.substring(2) + val accentColor = context.accentColor.hexString.substring(2) override fun convert(holder: ItemViewHolder, item: SearchResult, payloads: MutableList) { with(holder.itemView) { val isDur = callback.durChapterIndex() == item.chapterIndex if (payloads.isEmpty()) { - tv_search_result.text = item.parseText(item.presentText) - if (isDur){ - tv_search_result.paint.isFakeBoldText = true - } + tv_search_result.text = item.getHtmlCompat(textColor, accentColor) + tv_search_result.paint.isFakeBoldText = isDur } } } diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt deleted file mode 100644 index 38ac013df..000000000 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt +++ /dev/null @@ -1,239 +0,0 @@ -package io.legado.app.ui.book.searchContent - -import android.annotation.SuppressLint -import android.app.Activity.RESULT_OK -import android.content.Intent -import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.lifecycle.LiveData -import com.hankcs.hanlp.HanLP -import io.legado.app.App -import io.legado.app.R -import io.legado.app.base.VMBaseFragment -import io.legado.app.constant.EventBus -import io.legado.app.data.entities.Book -import io.legado.app.data.entities.BookChapter -import io.legado.app.help.AppConfig -import io.legado.app.help.BookHelp -import io.legado.app.lib.theme.bottomBackground -import io.legado.app.lib.theme.getPrimaryTextColor -import io.legado.app.service.help.ReadBook -import io.legado.app.ui.book.read.page.entities.TextPage -import io.legado.app.ui.book.read.page.provider.ChapterProvider -import io.legado.app.ui.widget.recycler.UpLinearLayoutManager -import io.legado.app.ui.widget.recycler.VerticalDivider -import io.legado.app.utils.ColorUtils -import io.legado.app.utils.getViewModelOfActivity -import io.legado.app.utils.observeEvent -import kotlinx.android.synthetic.main.fragment_search_list.* -import kotlinx.coroutines.* -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main -import org.jetbrains.anko.sdk27.listeners.onClick -import java.util.regex.Pattern - -class SearchListFragment : VMBaseFragment(R.layout.fragment_search_list), - SearchListAdapter.Callback, - SearchListViewModel.SearchListCallBack{ - override val viewModel: SearchListViewModel - get() = getViewModelOfActivity(SearchListViewModel::class.java) - - lateinit var adapter: SearchListAdapter - private lateinit var mLayoutManager: UpLinearLayoutManager - private var searchResultCounts = 0 - private var durChapterIndex = 0 - private var searchResultList: MutableList = mutableListOf() - - override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { - viewModel.searchCallBack = this - val bbg = bottomBackground - val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg)) - ll_search_base_info.setBackgroundColor(bbg) - tv_current_search_info.setTextColor(btc) - iv_search_content_top.setColorFilter(btc) - iv_search_content_bottom.setColorFilter(btc) - initRecyclerView() - initView() - initBook() - } - - private fun initRecyclerView() { - adapter = SearchListAdapter(requireContext(), this) - mLayoutManager = UpLinearLayoutManager(requireContext()) - recycler_view.layoutManager = mLayoutManager - recycler_view.addItemDecoration(VerticalDivider(requireContext())) - recycler_view.adapter = adapter - } - - private fun initView() { - iv_search_content_top.onClick { mLayoutManager.scrollToPositionWithOffset(0, 0) } - iv_search_content_bottom.onClick { - if (adapter.itemCount > 0) { - mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0) - } - } - } - - @SuppressLint("SetTextI18n") - private fun initBook() { - launch { - - tv_current_search_info.text = "搜索结果:$searchResultCounts" - viewModel.book?.let { - initCacheFileNames(it) - durChapterIndex = it.durChapterIndex - } - } - } - - private fun initCacheFileNames(book: Book) { - launch(IO) { - adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book)) - withContext(Main) { - adapter.notifyItemRangeChanged(0, adapter.getActualItemCount(), true) - } - } - } - - override fun observeLiveBus() { - observeEvent(EventBus.SAVE_CONTENT) { chapter -> - viewModel.book?.bookUrl?.let { bookUrl -> - if (chapter.bookUrl == bookUrl) { - adapter.cacheFileNames.add(BookHelp.formatChapterName(chapter)) - adapter.notifyItemChanged(chapter.index, true) - } - } - } - } - - @SuppressLint("SetTextI18n") - override fun startContentSearch(newText: String) { - // 按章节搜索内容 - if (!newText.isBlank()) { - adapter.clearItems() - searchResultList.clear() - searchResultCounts = 0 - viewModel.lastQuery = newText - var searchResults = listOf() - launch(Main){ - App.db.bookChapterDao().getChapterList(viewModel.bookUrl).map{ chapter -> - val job = async(IO){ - if (isLocalBook || adapter.cacheFileNames.contains(BookHelp.formatChapterName(chapter))) { - searchResults = searchChapter(newText, chapter) - } - } - job.await() - if (searchResults.isNotEmpty()){ - searchResultList.addAll(searchResults) - tv_current_search_info.text = "搜索结果:$searchResultCounts" - adapter.addItems(searchResults) - searchResults = listOf() - } - } - } - } - } - - private suspend fun searchChapter(query: String, chapter: BookChapter?): List { - val searchResults: MutableList = mutableListOf() - var positions : List = listOf() - var replaceContents: List? = null - var totalContents = "" - if (chapter != null){ - viewModel.book?.let { bookSource -> - val bookContent = BookHelp.getContent(bookSource, chapter) - if (bookContent != null){ - //搜索替换后的正文 - val job = async(IO) { - chapter.title = when (AppConfig.chineseConverterType) { - 1 -> HanLP.convertToSimplifiedChinese(chapter.title) - 2 -> HanLP.convertToTraditionalChinese(chapter.title) - else -> chapter.title - } - replaceContents = BookHelp.disposeContent( - chapter.title, - bookSource.name, - bookSource.bookUrl, - bookContent, - bookSource.useReplaceRule - ) - } - job.await() - while (replaceContents == null){ - delay(100L) - } - totalContents = replaceContents!!.joinToString("") - positions = searchPosition(totalContents, query) - var count = 1 - positions.map{ - val construct = constructText(totalContents, it, query) - val result = SearchResult(index = searchResultCounts, - indexWithinChapter = count, - text = construct[1] as String, - chapterTitle = chapter.title, - query = query, - chapterIndex = chapter.index, - newPosition = construct[0] as Int, - contentPosition = it - ) - count += 1 - searchResultCounts += 1 - searchResults.add(result) - } - } - } - } - return searchResults - } - - private fun searchPosition(content: String, pattern: String): List { - val position : MutableList = mutableListOf() - var index = content.indexOf(pattern) - while(index >= 0){ - position.add(index) - index = content.indexOf(pattern, index + 1); - } - return position - } - - private fun constructText(content: String, position: Int, query: String): Array{ - // 构建关键词周边文字,在搜索结果里显示 - // todo: 判断段落,只在关键词所在段落内分割 - // todo: 利用标点符号分割完整的句 - // todo: length和设置结合,自由调整周边文字长度 - val length = 20 - var po1 = position - length - var po2 = position + query.length + length - if (po1 <0) { - po1 = 0 - } - if (po2 > content.length){ - po2 = content.length - } - val newPosition = position - po1 - val newText = content.substring(po1, po2) - return arrayOf(newPosition, newText) - } - - val isLocalBook: Boolean - get() = viewModel.book?.isLocalBook() == true - - override fun openSearchResult(searchResult: SearchResult) { - - val searchData = Intent() - searchData.putExtra("index", searchResult.chapterIndex) - searchData.putExtra("contentPosition", searchResult.contentPosition) - searchData.putExtra("query", searchResult.query) - searchData.putExtra("indexWithinChapter", searchResult.indexWithinChapter) - activity?.setResult(RESULT_OK, searchData) - activity?.finish() - - - } - - override fun durChapterIndex(): Int { - return durChapterIndex - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt index 060d74067..0b8cd1259 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt @@ -9,7 +9,6 @@ import io.legado.app.data.entities.Book class SearchListViewModel(application: Application) : BaseViewModel(application) { var bookUrl: String = "" var book: Book? = null - var searchCallBack: SearchListCallBack? = null var lastQuery: String = "" fun initBook(bookUrl: String, success: () -> Unit) { @@ -21,13 +20,4 @@ class SearchListViewModel(application: Application) : BaseViewModel(application) } } - fun startContentSearch(newText: String) { - searchCallBack?.startContentSearch(newText) - } - - - interface SearchListCallBack { - fun startContentSearch(newText: String) - } - } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt index 3a629839b..482afab30 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt +++ b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt @@ -2,10 +2,6 @@ package io.legado.app.ui.book.searchContent import android.text.Spanned import androidx.core.text.HtmlCompat -import io.legado.app.App -import io.legado.app.R -import io.legado.app.utils.getCompatColor -import io.legado.app.utils.hexString data class SearchResult( var index: Int = 0, @@ -19,25 +15,25 @@ data class SearchResult( var newPosition: Int = 0, var contentPosition: Int =0 ) { - val presentText: String - get(){ - return colorPresentText(newPosition, query, text) + - "($chapterTitle)" - } - fun colorPresentText(position: Int, center: String, targetText: String): String { + fun getHtmlCompat(textColor: String, accentColor: String): Spanned { + val html = colorPresentText(newPosition, query, text, textColor, accentColor) + + "($chapterTitle)" + return HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY) + } + + private fun colorPresentText( + position: Int, + center: String, + targetText: String, + textColor: String, + accentColor: String + ): String { val sub1 = text.substring(0, position) val sub2 = text.substring(position + center.length, targetText.length) - val textColor = App.INSTANCE.getCompatColor(R.color.primaryText).hexString return "$sub1" + - "$center" + + "$center" + "$sub2" } - fun parseText(targetText: String): Spanned { - return HtmlCompat.fromHtml(targetText, HtmlCompat.FROM_HTML_MODE_LEGACY) - } - - - } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index 07f959719..c461797dd 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -5,9 +5,8 @@ import android.app.Activity import android.content.ActivityNotFoundException import android.content.Intent import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.SubMenu +import android.util.DisplayMetrics +import android.view.* import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView import androidx.core.content.FileProvider @@ -19,8 +18,10 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.BuildConfig import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import io.legado.app.base.VMBaseActivity import io.legado.app.constant.AppPattern +import io.legado.app.constant.EventBus import io.legado.app.data.entities.BookSource import io.legado.app.help.IntentDataHelp import io.legado.app.lib.dialogs.* @@ -39,8 +40,12 @@ import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_source.* +import kotlinx.android.synthetic.main.activity_book_source.recycler_view import kotlinx.android.synthetic.main.dialog_edit_text.view.* +import kotlinx.android.synthetic.main.dialog_progressbar_view.* +import kotlinx.android.synthetic.main.dialog_progressbar_view.tv_footer_left import kotlinx.android.synthetic.main.view_search.* +import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivityForResult import org.jetbrains.anko.toast @@ -384,6 +389,24 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity }.show().applyTint() } + override fun observeLiveBus() { + observeEvent(EventBus.CHECK_INIT) { max-> + val bundle = Bundle() + bundle.putInt("maxProgress", max) + CheckSourceDialog().apply { + arguments = bundle + }.show(supportFragmentManager, "CheckDialog") + } + observeEvent(EventBus.CHECK_DONE) { + groups.map { group-> + if (group.contains("失效")) { + search_view.setQuery("失效", true) + toast("发现有失效书源,已为您自动筛选!") + } + } + } + } + override fun upCountView() { select_action_bar.upCountView(adapter.getSelection().size, adapter.getActualItemCount()) } @@ -478,4 +501,47 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity search_view.setQuery("", true) } } + + class CheckSourceDialog : BaseDialogFragment() { + override fun onStart() { + super.onStart() + val dm = DisplayMetrics() + activity?.windowManager?.defaultDisplay?.getMetrics(dm) + dialog?.window?.setLayout( + (dm.widthPixels * 0.9).toInt(), + (dm.heightPixels * 0.14).toInt() + ) + dialog?.setCancelable(false) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_progressbar_view, container) + } + + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { + arguments?.let { bundle -> + val maxProgress = bundle.getInt("maxProgress") + ck_progress_text.text = getString(R.string.progress_show, "", 0, maxProgress) + ck_progress.max = maxProgress + observeEvent(EventBus.CHECK_UP_PROGRESS) { progress-> + ck_progress.progress = progress + } + observeEvent(EventBus.CHECK_UP_PROGRESS_STRING) { + ck_progress_text.text = it + } + observeEvent(EventBus.CHECK_DONE) { + dismiss() + } + tv_footer_left.onClick { + CheckSource.stop(requireContext()).apply { + dismiss() + } + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt index ccfa5bf82..0fdfa373f 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt @@ -11,10 +11,7 @@ import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.BookSource import io.legado.app.help.coroutine.Coroutine import io.legado.app.lib.theme.accentColor -import io.legado.app.utils.ACache -import io.legado.app.utils.dp -import io.legado.app.utils.gone -import io.legado.app.utils.visible +import io.legado.app.utils.* import kotlinx.android.synthetic.main.item_fillet_text.view.* import kotlinx.android.synthetic.main.item_find_book.view.* import kotlinx.coroutines.CoroutineScope @@ -55,12 +52,12 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca .inflate(R.layout.item_fillet_text, gl_child, false) gl_child.addView(tv) tv.text_view.text = kind.title - tv.text_view.onClick { - kind.url?.let { kindUrl -> + if (!kind.url.isNullOrEmpty()) { + tv.text_view.onClick { callBack.openExplore( item.bookSourceUrl, kind.title, - kindUrl + kind.url.toString() ) } } diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt index 0fa7bea7d..ca932932a 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt @@ -123,6 +123,26 @@ class FontSelectDialog : BaseDialogFragment(), } } + private fun getLocalFonts(): ArrayList { + val fontItems = arrayListOf() + val fontDir = + FileUtils.createFolderIfNotExist(requireContext().externalFilesDir, "font") + fontDir.listFiles { pathName -> + pathName.name.toLowerCase(Locale.getDefault()).matches(fontRegex) + }?.forEach { + fontItems.add( + DocItem( + it.name, + it.extension, + it.length(), + Date(it.lastModified()), + Uri.parse(it.absolutePath) + ) + ) + } + return fontItems + } + private fun loadFontFiles(doc: DocumentFile) { execute { val fontItems = arrayListOf() @@ -132,6 +152,7 @@ class FontSelectDialog : BaseDialogFragment(), fontItems.add(item) } } + fontItems.addAll(getLocalFonts()) fontItems.sortedBy { it.name } }.onSuccess { adapter?.setItems(it) @@ -167,6 +188,7 @@ class FontSelectDialog : BaseDialogFragment(), ) ) } + fontItems.addAll(getLocalFonts()) fontItems.sortedBy { it.name } }.onSuccess { adapter?.setItems(it) diff --git a/app/src/main/res/layout/activity_search_list.xml b/app/src/main/res/layout/activity_search_list.xml index 159f77324..ce7e57b93 100644 --- a/app/src/main/res/layout/activity_search_list.xml +++ b/app/src/main/res/layout/activity_search_list.xml @@ -1,19 +1,74 @@ - + + android:id="@+id/title_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:contentLayout="@layout/view_search" + app:contentInsetRight="24dp" + app:layout_constraintTop_toTopOf="parent" /> - + - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_progressbar_view.xml b/app/src/main/res/layout/dialog_progressbar_view.xml new file mode 100644 index 000000000..f310c9ced --- /dev/null +++ b/app/src/main/res/layout/dialog_progressbar_view.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_list.xml b/app/src/main/res/layout/fragment_search_list.xml deleted file mode 100644 index 79fefe11f..000000000 --- a/app/src/main/res/layout/fragment_search_list.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_read_menu.xml b/app/src/main/res/layout/view_read_menu.xml index b63fba1ff..d48221169 100644 --- a/app/src/main/res/layout/view_read_menu.xml +++ b/app/src/main/res/layout/view_read_menu.xml @@ -92,6 +92,26 @@ android:paddingStart="32dp" android:paddingEnd="32dp"> + + + + - - - - - - - - - + + \ No newline at end of file diff --git a/app/src/main/res/menu/content_select_action.xml b/app/src/main/res/menu/content_select_action.xml index ff83efd96..692f1092f 100644 --- a/app/src/main/res/menu/content_select_action.xml +++ b/app/src/main/res/menu/content_select_action.xml @@ -13,6 +13,10 @@ android:id="@+id/menu_aloud" android:title="@string/read_aloud" /> + + diff --git a/app/src/main/res/menu/import_source.xml b/app/src/main/res/menu/import_source.xml index 37c34f4de..8a2971e3f 100644 --- a/app/src/main/res/menu/import_source.xml +++ b/app/src/main/res/menu/import_source.xml @@ -3,6 +3,12 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> + + 系統等寬字體 + + + 标题 + 时间 + 电量 + 页数 + 进度 + 页数及进度 + 書名 + + 正常 粗體 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index c585ffb97..4d38941b5 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -10,6 +10,8 @@ 導入閲讀數據 創建子文件夾 創建 legado 文件夾作爲備份路徑 + 離線緩存書籍備份 + 導出本地同時備份到legado文件夾下exports目錄 備份路徑 導入舊版數據 導入 Github 數據 @@ -94,6 +96,8 @@ 閲讀3.0下載地址:\nhttps://play.google.com/store/apps/details?id=io.legado.play.release Version %s + 後臺校驗書源 + 打開后可以在校驗書源時自由操作 自動刷新 打開程式時自動更新書輯 自動下載最新章節 @@ -223,6 +227,7 @@ 載入失敗,點擊重試 內容簡介 簡介: %s + 簡介: 暫無簡介 打開外部書籍 來源: %s 本地導入 @@ -288,7 +293,7 @@ 右邊距 校驗書源 校驗所選 - 進度 %1$d/%2$d + %1$s 進度 %2$d/%3$d 請安裝並選擇中文 TTS! TTS 初始化失敗! 簡繁轉換 @@ -563,6 +568,7 @@ 導出所選 導出 加載目錄 + 加載詳情頁 TTS WebDav 密碼 輸入你的 WebDav 授權密碼 @@ -761,6 +767,6 @@ 切換默認主題 分享選中書源 時間排序 - 搜索 + 全文搜索 diff --git a/app/src/main/res/values-zh-rTW/arrays.xml b/app/src/main/res/values-zh-rTW/arrays.xml index 23909ad85..35192b3a1 100644 --- a/app/src/main/res/values-zh-rTW/arrays.xml +++ b/app/src/main/res/values-zh-rTW/arrays.xml @@ -89,6 +89,7 @@ 頁數 進度 頁數及進度 + 書名 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 006b4ff45..6335186ca 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -10,6 +10,8 @@ 匯入閱讀資料 建立子資料夾 建立legado資料夾作為備份資料夾 + 離線緩存書籍備份 + 導出本地同時備份到legado文件夾下exports目錄 備份路徑 匯入舊版資料 匯入Github資料 @@ -94,6 +96,8 @@ 閱讀3.0下載網址:\nhttps://play.google.com/store/apps/details?id=io.legado.play.release Version %s + 後臺校驗書源 + 打開后可以在校驗書源時自由操作 自動重新整理 打開軟體時自動更新書籍 自動下載最新章節 @@ -223,6 +227,7 @@ 載入失敗,點擊重試 內容簡介 簡介:%s + 簡介: 暫無簡介 打開外部書籍 來源: %s 本機匯入 @@ -288,7 +293,7 @@ 右邊距 校驗書源 校驗所選 - 進度 %1$d/%2$d + %1$s 進度 %2$d/%3$d 請安裝並選擇中文TTS! TTS初始化失敗! 簡繁轉換 @@ -564,6 +569,7 @@ 匯出所選 匯出 載入目錄 + 載入詳情頁 TTS WebDav 密碼 輸入你的WebDav授權密碼 @@ -761,7 +767,6 @@ 切換默認主題 分享選中書源 時間排序 - 搜索 - + 全文搜索 diff --git a/app/src/main/res/values-zh/arrays.xml b/app/src/main/res/values-zh/arrays.xml index 5fdf42b1d..26baaa668 100644 --- a/app/src/main/res/values-zh/arrays.xml +++ b/app/src/main/res/values-zh/arrays.xml @@ -89,6 +89,7 @@ 页数 进度 页数及进度 + 书名 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 78487a2be..92977d3f5 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -10,6 +10,8 @@ 导入阅读数据 创建子文件夹 创建legado文件夹作为备份文件夹 + 离线缓存书籍备份 + 导出本地同时备份到legado文件夹下exports目录 备份路径 请选择备份路径 导入旧版数据 @@ -96,6 +98,8 @@ 阅读3.0下载地址:\nhttps://www.coolapk.com/apk/256030 Version %s + 后台校验书源 + 打开后可以在校验书源时自由操作 自动刷新 打开软件时自动更新书籍 自动下载最新章节 @@ -225,6 +229,7 @@ 加载失败,点击重试 内容简介 简介:%s + 简介: 暂无简介 打开外部书籍 来源: %s 本地导入 @@ -290,7 +295,7 @@ 右边距 校验书源 校验所选 - 进度 %1$d/%2$d + %1$s 进度 %2$d/%3$d 请安装并选择中文TTS! TTS初始化失败! 简繁转换 @@ -384,6 +389,9 @@ 源名称(sourceName) 源URL(sourceUrl) 源分组(sourceGroup) + 自定义源分组 + 输入自定义源分组名称 + 【%s】 分类Url 登录URL(loginUrl) 源注释(sourceComment) @@ -567,6 +575,7 @@ 导出所选 导出 加载目录 + 加载详情页 TTS WebDav 密码 输入你的WebDav授权密码 @@ -761,6 +770,6 @@ 使用保存主题,导入,分享主题 切换默认主题 时间排序 - 搜索 + 全文搜索 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 5847f7765..9a0410c81 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -89,6 +89,7 @@ Pages Progress Pages and progress + Book name diff --git a/app/src/main/res/values/pref_key_value.xml b/app/src/main/res/values/pref_key_value.xml index 42058ce0c..7ce2f7f13 100644 --- a/app/src/main/res/values/pref_key_value.xml +++ b/app/src/main/res/values/pref_key_value.xml @@ -1,5 +1,6 @@ + background_verification auto_refresh list_screen_direction full_screen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 893230b30..4779b5909 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,8 @@ Import Legado data Create a subfolder Create a folder named Legado as the backup folder. + Offline cache book backup + Export to local and back up to the exports directory under the legado folder Backup to Please select a backup path. Import legacy data @@ -96,6 +98,8 @@ Legado (YueDu 3.0) download link:\n https://play.google.com/store/apps/details?id=io.legado.play.release Version %s + Background-verification + you can operate freely when verifying the book source Auto-refresh Update books automatically when opening the software Auto-download @@ -225,6 +229,7 @@ Load failed, tap to retry Book description Description:%s + Description: no introduction Open external book Origin: %s Import local rules @@ -290,7 +295,7 @@ Padding right Check book sources Check the selected source - Progress %1$d/%2$d + %1$s Progress %2$d/%3$d Please install and select Chinese TTS! TTS initialization failed! Simplified conversion @@ -384,6 +389,9 @@ 源名称(sourceName) 源URL(sourceUrl) 源分组(sourceGroup) + 自定义源分组 + 输入自定义源分组名称 + 【%s】 分类Url 登录URL(loginUrl) 源注释(sourceComment) @@ -569,6 +577,7 @@ Export selected Export Load chapters + Load book detail TTS WebDav password Input you WebDav authorized password @@ -763,6 +772,6 @@ Save, Import, Share theme Share selected sources Sort by update time - Search + Search content \ No newline at end of file diff --git a/app/src/main/res/xml/pref_config_backup.xml b/app/src/main/res/xml/pref_config_backup.xml index b16c6f32b..dc5ec4f37 100644 --- a/app/src/main/res/xml/pref_config_backup.xml +++ b/app/src/main/res/xml/pref_config_backup.xml @@ -36,6 +36,15 @@ app:allowDividerBelow="false" app:iconSpaceReserved="false" /> + + + +