diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 40348af60..0e4cfb8b6 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -3,6 +3,25 @@ * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 - 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 +**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/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index c6e222849..3e89dd2e2 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -33,6 +33,7 @@ object PreferKey { const val webDavAccount = "web_dav_account" const val webDavPassword = "web_dav_password" const val webDavCreateDir = "webDavCreateDir" + const val webDavExport = "webDavExport" const val changeSourceLoadToc = "changeSourceLoadToc" const val chineseConverterType = "chineseConverterType" const val launcherIcon = "launcherIcon" 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..327ed5fda 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..81dfe2f61 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 @@ -465,33 +446,11 @@ 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, 拆分 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) @@ -499,7 +458,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions { } tmp = evalMatcher.group() when { - tmp.startsWith("$") -> { + tmp.startsWith("$") && !rule.contains("##") -> { ruleType.add(tmp.substring(1).toInt()) ruleParam.add(tmp) } @@ -574,6 +533,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 { 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..afc78bc37 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,21 @@ 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.webDavExport,false)) { + //写出文件到cache目录 + FileUtils.createFileIfNotExist( + File(FileUtils.getCachePath()), + filename + ).writeText(content) + //导出到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 +76,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.webDavExport,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/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 7abcdfc3a..b6bb4503c 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 @@ -9,7 +9,6 @@ import android.graphics.drawable.ColorDrawable import android.net.Uri import android.os.Bundle import android.os.Handler -import android.util.Log import android.view.* import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.core.view.get @@ -49,7 +48,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 @@ -496,6 +494,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo ) return true } + R.id.menu_search_content -> { + openSearchActivity(selectedText) + return true + } } return false } @@ -680,12 +682,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) ) } } 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 bef8898ee..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 @@ -175,7 +175,7 @@ class ReadMenu : FrameLayout { //搜索 fabSearch.onClick { runMenuOut { - callBack?.openSearchList() + callBack?.openSearchActivity(null) } } @@ -300,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/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/SearchListFragment.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt deleted file mode 100644 index 646153b6c..000000000 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt +++ /dev/null @@ -1,243 +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) - //Log.d("h11128", "find ${searchResults.size} results in chapter ${chapter.title}") - } - } - job.await() - if (searchResults.isNotEmpty()){ - //Log.d("h11128", "load ${searchResults.size} results in chapter ${chapter.title}") - searchResultList.addAll(searchResults) - tv_current_search_info.text = "搜索结果:$searchResultCounts" - //Log.d("h11128", "searchResultList size ${searchResultList.size}") - 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) - Log.d("h11128","current chapter index ${searchResult.chapterIndex}") - 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/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/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/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 649f57d1a..2e7d44236 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 文件夾作爲備份路徑 + 離線導出WebDav + 默認導出到legado文件夾下exports目錄 備份路徑 導入舊版數據 導入 Github 數據 @@ -764,6 +766,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 17cc16af4..d7d39b40c 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資料夾作為備份資料夾 + 離線導出WebDav + 默認導出到legado文件夾下exports目錄 備份路徑 匯入舊版資料 匯入Github資料 @@ -764,7 +766,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 2a5645ab8..8e4821f0a 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -10,6 +10,8 @@ 导入阅读数据 创建子文件夹 创建legado文件夹作为备份文件夹 + 离线导出WebDav + 默认导出到legado文件夹下exports目录 备份路径 请选择备份路径 导入旧版数据 @@ -387,6 +389,9 @@ 源名称(sourceName) 源URL(sourceUrl) 源分组(sourceGroup) + 自定义源分组 + 输入自定义源分组名称 + 【%s】 分类Url 登录URL(loginUrl) 源注释(sourceComment) @@ -764,6 +769,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/strings.xml b/app/src/main/res/values/strings.xml index 8ae687184..77e490b8c 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. + Export Webdav + Default export to the exports directory under the legado folder Backup to Please select a backup path. Import legacy data @@ -387,6 +389,9 @@ 源名称(sourceName) 源URL(sourceUrl) 源分组(sourceGroup) + 自定义源分组 + 输入自定义源分组名称 + 【%s】 分类Url 登录URL(loginUrl) 源注释(sourceComment) @@ -766,6 +771,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..1d0ee0a9d 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" /> + +