Merge remote-tracking branch 'upstream/master' into h11128dev

pull/393/head
Jason Yao 4 years ago
commit 9559fcf131
  1. 19
      app/src/main/assets/updateLog.md
  2. 35
      app/src/main/java/io/legado/app/api/ReaderProvider.kt
  3. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  4. 2
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  5. 1
      app/src/main/java/io/legado/app/help/ReadTipConfig.kt
  6. 1
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  7. 25
      app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt
  8. 65
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  9. 1
      app/src/main/java/io/legado/app/ui/README.md
  10. 26
      app/src/main/java/io/legado/app/ui/association/ImportBookSourceActivity.kt
  11. 25
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  12. 12
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  13. 4
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  14. 19
      app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt
  15. 271
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchListActivity.kt
  16. 243
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchListFragment.kt
  17. 10
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchListViewModel.kt
  18. 22
      app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt
  19. 83
      app/src/main/res/layout/activity_search_list.xml
  20. 66
      app/src/main/res/layout/fragment_search_list.xml
  21. 4
      app/src/main/res/menu/content_select_action.xml
  22. 6
      app/src/main/res/menu/import_source.xml
  23. 11
      app/src/main/res/values-zh-rHK/arrays.xml
  24. 4
      app/src/main/res/values-zh-rHK/strings.xml
  25. 1
      app/src/main/res/values-zh-rTW/arrays.xml
  26. 5
      app/src/main/res/values-zh-rTW/strings.xml
  27. 1
      app/src/main/res/values-zh/arrays.xml
  28. 7
      app/src/main/res/values-zh/strings.xml
  29. 1
      app/src/main/res/values/arrays.xml
  30. 7
      app/src/main/res/values/strings.xml
  31. 9
      app/src/main/res/xml/pref_config_backup.xml

@ -3,6 +3,25 @@
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
- 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 - 旧版数据导入教程:先在旧版阅读(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** **2020/09/15**
* 修复导入排版字体重复报错的bug * 修复导入排版字体重复报错的bug
* 添加正文搜索 by [h11128](https://github.com/h11128) * 添加正文搜索 by [h11128](https://github.com/h11128)

@ -7,10 +7,7 @@ import android.content.ContentProvider
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.content.UriMatcher import android.content.UriMatcher
import android.database.CharArrayBuffer import android.database.*
import android.database.ContentObserver
import android.database.Cursor
import android.database.DataSetObserver
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import com.google.gson.Gson import com.google.gson.Gson
@ -109,40 +106,22 @@ class ReaderProvider : ContentProvider() {
uri: Uri, values: ContentValues?, selection: String?, uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>? selectionArgs: Array<String>?
) = throw UnsupportedOperationException("Not yet implemented") ) = throw UnsupportedOperationException("Not yet implemented")
/** /**
* Simple inner class to deliver json callback data. * Simple inner class to deliver json callback data.
* *
* Only getString() makes sense. * 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) private val mData: String = Gson().toJson(data)
override fun getCount() = 1 init {
addRow(arrayOf(mData))
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
override fun isAfterLast() = true override fun getCount() = 1
override fun getColumnIndex(s: String) = 0 override fun getColumnIndex(s: String) = 0

@ -33,6 +33,7 @@ object PreferKey {
const val webDavAccount = "web_dav_account" const val webDavAccount = "web_dav_account"
const val webDavPassword = "web_dav_password" const val webDavPassword = "web_dav_password"
const val webDavCreateDir = "webDavCreateDir" const val webDavCreateDir = "webDavCreateDir"
const val webDavExport = "webDavExport"
const val changeSourceLoadToc = "changeSourceLoadToc" const val changeSourceLoadToc = "changeSourceLoadToc"
const val chineseConverterType = "chineseConverterType" const val chineseConverterType = "chineseConverterType"
const val launcherIcon = "launcherIcon" const val launcherIcon = "launcherIcon"

@ -75,7 +75,7 @@ object ReadBookConfig {
} }
fun initShareConfig() { fun initShareConfig() {
val configFile = File(configFilePath) val configFile = File(shareConfigFilePath)
var c: Config? = null var c: Config? = null
if (configFile.exists()) { if (configFile.exists()) {
try { try {

@ -12,6 +12,7 @@ object ReadTipConfig {
const val page = 4 const val page = 4
const val totalProgress = 5 const val totalProgress = 5
const val pageAndTotal = 6 const val pageAndTotal = 6
const val bookName = 7
val tipHeaderLeftStr: String get() = tipArray.getOrElse(tipHeaderLeft) { tipArray[none] } val tipHeaderLeftStr: String get() = tipArray.getOrElse(tipHeaderLeft) { tipArray[none] }
val tipHeaderMiddleStr: String get() = tipArray.getOrElse(tipHeaderMiddle) { tipArray[none] } val tipHeaderMiddleStr: String get() = tipArray.getOrElse(tipHeaderMiddle) { tipArray[none] }

@ -54,6 +54,7 @@ object Backup {
context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis()) context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis())
withContext(IO) { withContext(IO) {
synchronized(this@Backup) { synchronized(this@Backup) {
FileUtils.deleteFile(backupPath)
writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath)
writeListToJson(App.db.bookmarkDao().all, "bookmark.json", backupPath) writeListToJson(App.db.bookmarkDao().all, "bookmark.json", backupPath)
writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath)

@ -10,10 +10,7 @@ import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.selector import io.legado.app.lib.dialogs.selector
import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.http.HttpAuth import io.legado.app.lib.webdav.http.HttpAuth
import io.legado.app.utils.FileUtils import io.legado.app.utils.*
import io.legado.app.utils.ZipUtils
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext 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}")
}
}
}
} }

@ -238,6 +238,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
if (ruleList.isNotEmpty()) result = o if (ruleList.isNotEmpty()) result = o
for (sourceRule in ruleList) { for (sourceRule in ruleList) {
putRule(sourceRule.putMap) putRule(sourceRule.putMap)
sourceRule.makeUpRule(result)
result?.let { result?.let {
result = when (sourceRule.mode) { result = when (sourceRule.mode) {
Mode.Regex -> AnalyzeByRegex.getElement( Mode.Regex -> AnalyzeByRegex.getElement(
@ -324,37 +325,17 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
*/ */
private fun replaceRegex(result: String, rule: SourceRule): String { private fun replaceRegex(result: String, rule: SourceRule): String {
var vResult = result var vResult = result
val stringBuffer = StringBuffer() if (rule.replaceRegex.isNotEmpty()) {
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()) {
vResult = if (rule.replaceFirst) { vResult = if (rule.replaceFirst) {
val pattern = Pattern.compile(replaceRegex) val pattern = Pattern.compile(rule.replaceRegex)
val matcher = pattern.matcher(vResult) val matcher = pattern.matcher(vResult)
if (matcher.find()) { if (matcher.find()) {
matcher.group(0)!!.replaceFirst(replaceRegex.toRegex(), rule.replacement) matcher.group(0)!!.replaceFirst(rule.replaceRegex.toRegex(), rule.replacement)
} else { } else {
"" ""
} }
} else { } else {
vResult.replace(replaceRegex.toRegex(), rule.replacement) vResult.replace(rule.replaceRegex.toRegex(), rule.replacement)
} }
} }
return vResult return vResult
@ -465,33 +446,11 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
} }
//分离put //分离put
rule = splitPutRule(rule, putMap) 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,{{ }},$1, 拆分
var start = 0 var start = 0
var tmp: String var tmp: String
val evalMatcher = evalPattern.matcher(rule) val evalMatcher = evalPattern.matcher(rule)
while (evalMatcher.find()) { while (evalMatcher.find()) {
if (mode != Mode.Js) {
mode = Mode.Regex
}
if (evalMatcher.start() > start) { if (evalMatcher.start() > start) {
tmp = rule.substring(start, evalMatcher.start()) tmp = rule.substring(start, evalMatcher.start())
ruleType.add(0) ruleType.add(0)
@ -499,7 +458,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
} }
tmp = evalMatcher.group() tmp = evalMatcher.group()
when { when {
tmp.startsWith("$") -> { tmp.startsWith("$") && !rule.contains("##") -> {
ruleType.add(tmp.substring(1).toInt()) ruleType.add(tmp.substring(1).toInt())
ruleParam.add(tmp) ruleParam.add(tmp)
} }
@ -574,6 +533,18 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
} }
rule = infoVal.toString() 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 { private fun isRule(ruleStr: String): Boolean {

@ -1,6 +1,7 @@
## 放置与界面有关的类 ## 放置与界面有关的类
* about 关于界面 * about 关于界面
* association 导入书源界面
* audio 音频播放界面 * audio 音频播放界面
* book\arrange 书架整理界面 * book\arrange 书架整理界面
* book\info 书籍信息查看 * book\info 书籍信息查看

@ -20,16 +20,21 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentDataHelp
import io.legado.app.help.SourceHelp import io.legado.app.help.SourceHelp
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.customView
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton 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.applyTint
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import io.legado.app.utils.visible import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.activity_translucence.* 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.dialog_recycler_view.*
import kotlinx.android.synthetic.main.item_source_import.view.* import kotlinx.android.synthetic.main.item_source_import.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
class ImportBookSourceActivity : VMBaseActivity<ImportBookSourceViewModel>( class ImportBookSourceActivity : VMBaseActivity<ImportBookSourceViewModel>(
R.layout.activity_translucence, R.layout.activity_translucence,
theme = Theme.Transparent theme = Theme.Transparent
@ -112,6 +117,7 @@ class ImportBookSourceActivity : VMBaseActivity<ImportBookSourceViewModel>(
class SourcesDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { class SourcesDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
lateinit var adapter: SourcesAdapter lateinit var adapter: SourcesAdapter
private var _groupName: String? = null
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
@ -165,6 +171,23 @@ class ImportBookSourceActivity : VMBaseActivity<ImportBookSourceViewModel>(
override fun onMenuItemClick(item: MenuItem): Boolean { override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) { 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 -> { R.id.menu_select_all -> {
adapter.selectStatus.forEachIndexed { index, b -> adapter.selectStatus.forEachIndexed { index, b ->
if (!b) { if (!b) {
@ -193,6 +216,9 @@ class ImportBookSourceActivity : VMBaseActivity<ImportBookSourceViewModel>(
private fun importSelect() { private fun importSelect() {
val selectSource = arrayListOf<BookSource>() val selectSource = arrayListOf<BookSource>()
adapter.selectStatus.forEachIndexed { index, b -> adapter.selectStatus.forEachIndexed { index, b ->
if (_groupName != null) {
adapter.getItem(index)!!.bookSourceGroup = _groupName
}
if (b) { if (b) {
selectSource.add(adapter.getItem(index)!!) selectSource.add(adapter.getItem(index)!!)
} }

@ -7,8 +7,10 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.storage.WebDavHelp
import io.legado.app.utils.* import io.legado.app.utils.*
import java.io.File import java.io.File
@ -34,8 +36,21 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
private fun export(doc: DocumentFile, book: Book) { private fun export(doc: DocumentFile, book: Book) {
DocumentUtils.createFileIfNotExist(doc, "${book.name} 作者:${book.author}.txt") val filename = "${book.name} by ${book.author}.txt"
?.writeText(context, getAllContents(book)) 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 -> App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter ->
BookHelp.getContent(book, chapter).let { content -> BookHelp.getContent(book, chapter).let { content ->
content?.split("\n")?.forEachIndexed { index, text -> content?.split("\n")?.forEachIndexed { index, text ->
@ -61,8 +76,12 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
} }
private fun export(file: File, book: Book) { 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)) .writeText(getAllContents(book))
if(App.INSTANCE.getPrefBoolean(PreferKey.webDavExport,false)) {
WebDavHelp.exportWebDav(file.absolutePath, filename)//导出到webdav
}
App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter -> App.db.bookChapterDao().getChapterList(book.bookUrl).forEach { chapter ->
BookHelp.getContent(book, chapter).let { content -> BookHelp.getContent(book, chapter).let { content ->
content?.split("\n")?.forEachIndexed { index, text -> content?.split("\n")?.forEachIndexed { index, text ->

@ -9,7 +9,6 @@ import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.* import android.view.*
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.core.view.get 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.TextPageFactory
import io.legado.app.ui.book.read.page.delegate.PageDelegate 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.SearchListActivity
import io.legado.app.ui.book.searchContent.SearchResult
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.login.SourceLogin import io.legado.app.ui.login.SourceLogin
import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.replacerule.ReplaceRuleActivity
@ -496,6 +494,10 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
) )
return true return true
} }
R.id.menu_search_content -> {
openSearchActivity(selectedText)
return true
}
} }
return false return false
} }
@ -680,12 +682,12 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
/** /**
* 打开搜索界面 * 打开搜索界面
*/ */
//todo: change request code override fun openSearchActivity(searchWord: String?) {
override fun openSearchList() {
ReadBook.book?.let { ReadBook.book?.let {
startActivityForResult<SearchListActivity>( startActivityForResult<SearchListActivity>(
requestCodeSearchResult, requestCodeSearchResult,
Pair("bookUrl", it.bookUrl) Pair("bookUrl", it.bookUrl),
Pair("searchWord", searchWord)
) )
} }
} }

@ -175,7 +175,7 @@ class ReadMenu : FrameLayout {
//搜索 //搜索
fabSearch.onClick { fabSearch.onClick {
runMenuOut { runMenuOut {
callBack?.openSearchList() callBack?.openSearchActivity(null)
} }
} }
@ -300,7 +300,7 @@ class ReadMenu : FrameLayout {
fun autoPage() fun autoPage()
fun openReplaceRule() fun openReplaceRule()
fun openChapterList() fun openChapterList()
fun openSearchList() fun openSearchActivity(searchWord: String?)
fun showReadStyle() fun showReadStyle()
fun showMoreSetting() fun showMoreSetting()
fun showReadAloudDialog() fun showReadAloudDialog()

@ -11,6 +11,7 @@ import io.legado.app.base.BaseActivity
import io.legado.app.constant.AppConst.timeFormat import io.legado.app.constant.AppConst.timeFormat
import io.legado.app.help.ReadBookConfig import io.legado.app.help.ReadBookConfig
import io.legado.app.help.ReadTipConfig 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.entities.TextPage
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.widget.BatteryView import io.legado.app.ui.widget.BatteryView
@ -29,6 +30,7 @@ class ContentView(context: Context) : FrameLayout(context) {
private var tvPage: BatteryView? = null private var tvPage: BatteryView? = null
private var tvTotalProgress: BatteryView? = null private var tvTotalProgress: BatteryView? = null
private var tvPageAndTotal: BatteryView? = null private var tvPageAndTotal: BatteryView? = null
private var tvBookName: BatteryView? = null
val headerHeight: Int val headerHeight: Int
get() = if (ReadBookConfig.hideStatusBar) { get() = if (ReadBookConfig.hideStatusBar) {
@ -185,6 +187,19 @@ class ContentView(context: Context) : FrameLayout(context) {
isBattery = false isBattery = false
textSize = 12f 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?) { fun setBg(bg: Drawable?) {
@ -213,8 +228,8 @@ class ContentView(context: Context) : FrameLayout(context) {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun setProgress(textPage: TextPage) = textPage.apply { fun setProgress(textPage: TextPage) = textPage.apply {
val title = textPage.title tvBookName?.text = ReadBook.book?.name
tvTitle?.text = title tvTitle?.text = textPage.title
tvPage?.text = "${index.plus(1)}/$pageSize" tvPage?.text = "${index.plus(1)}/$pageSize"
tvTotalProgress?.text = readProgress tvTotalProgress?.text = readProgress
tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress" tvPageAndTotal?.text = "${index.plus(1)}/$pageSize $readProgress"

@ -1,93 +1,264 @@
package io.legado.app.ui.book.searchContent package io.legado.app.ui.book.searchContent
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.isGone import com.hankcs.hanlp.HanLP
import androidx.fragment.app.Fragment import io.legado.app.App
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.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.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.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.getViewModel
import io.legado.app.utils.gone import io.legado.app.utils.observeEvent
import io.legado.app.utils.visible import kotlinx.android.synthetic.main.activity_search_list.*
import kotlinx.android.synthetic.main.activity_chapter_list.* import kotlinx.android.synthetic.main.view_search.*
import kotlinx.android.synthetic.main.view_tab_layout.* import kotlinx.coroutines.*
import org.jetbrains.anko.sdk27.listeners.onClick
class SearchListActivity : VMBaseActivity<SearchListViewModel>(R.layout.activity_search_list) { class SearchListActivity : VMBaseActivity<SearchListViewModel>(R.layout.activity_search_list),
// todo: 完善搜索界面UI SearchListAdapter.Callback {
override val viewModel: SearchListViewModel override val viewModel: SearchListViewModel
get() = getViewModel(SearchListViewModel::class.java) 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<SearchResult> = mutableListOf()
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
tab_layout.isTabIndicatorFullWidth = false val bbg = bottomBackground
tab_layout.setSelectedTabIndicatorColor(accentColor) 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 { intent.getStringExtra("bookUrl")?.let {
viewModel.initBook(it) { viewModel.initBook(it) {
view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) initBook()
tab_layout.setupWithViewPager(view_pager)
} }
} }
} }
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { private fun initSearchView() {
menuInflater.inflate(R.menu.search_view, menu) ATH.setTint(search_view, primaryTextColor)
val search = menu.findItem(R.id.menu_search) search_view.onActionViewExpanded()
searchView = search.actionView as SearchView search_view.isSubmitButtonEnabled = true
ATH.setTint(searchView!!, primaryTextColor) search_view.queryHint = getString(R.string.search)
searchView?.maxWidth = resources.displayMetrics.widthPixels search_view.clearFocus()
searchView?.onActionViewCollapsed() search_view.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
searchView?.setOnCloseListener {
tab_layout.visible()
//to do clean
false
}
searchView?.setOnSearchClickListener { tab_layout.gone() }
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
if (viewModel.lastQuery != query){ if (viewModel.lastQuery != query) {
viewModel.startContentSearch(query) startContentSearch(query)
} }
return false return false
} }
override fun onQueryTextChange(newText: String): Boolean { override fun onQueryTextChange(newText: String?): Boolean {
return false return false
} }
}) })
return super.onCompatCreateOptionsMenu(menu)
} }
private inner class TabFragmentPageAdapter(fm: FragmentManager) : private fun initRecyclerView() {
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { adapter = SearchListAdapter(this, this)
override fun getItem(position: Int): Fragment { mLayoutManager = UpLinearLayoutManager(this)
return SearchListFragment() 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<BookChapter>(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 { @SuppressLint("SetTextI18n")
return 1 fun startContentSearch(newText: String) {
// 按章节搜索内容
if (!newText.isBlank()) {
adapter.clearItems()
searchResultList.clear()
searchResultCounts = 0
viewModel.lastQuery = newText
var searchResults = listOf<SearchResult>()
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? { private suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> {
return "Search" val searchResults: MutableList<SearchResult> = mutableListOf()
var positions: List<Int>
var replaceContents: List<String>? = 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<Int> {
val position: MutableList<Int> = mutableListOf()
var index = content.indexOf(pattern)
while (index >= 0) {
position.add(index)
index = content.indexOf(pattern, index + 1)
}
return position
} }
override fun onBackPressed() { private fun constructText(content: String, position: Int, query: String): Array<Any> {
if (tab_layout.isGone) { // 构建关键词周边文字,在搜索结果里显示
searchView?.onActionViewCollapsed() // todo: 判断段落,只在关键词所在段落内分割
tab_layout.visible() // todo: 利用标点符号分割完整的句
} else { // todo: length和设置结合,自由调整周边文字长度
super.onBackPressed() 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
}
} }

@ -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<SearchListViewModel>(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<SearchResult> = 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<BookChapter>(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<SearchResult>()
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<SearchResult>()
}
}
}
}
}
private suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> {
val searchResults: MutableList<SearchResult> = mutableListOf()
var positions : List<Int> = listOf()
var replaceContents: List<String>? = 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<Int> {
val position : MutableList<Int> = 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<Any>{
// 构建关键词周边文字,在搜索结果里显示
// 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
}
}

@ -9,7 +9,6 @@ import io.legado.app.data.entities.Book
class SearchListViewModel(application: Application) : BaseViewModel(application) { class SearchListViewModel(application: Application) : BaseViewModel(application) {
var bookUrl: String = "" var bookUrl: String = ""
var book: Book? = null var book: Book? = null
var searchCallBack: SearchListCallBack? = null
var lastQuery: String = "" var lastQuery: String = ""
fun initBook(bookUrl: String, success: () -> Unit) { 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)
}
} }

@ -123,6 +123,26 @@ class FontSelectDialog : BaseDialogFragment(),
} }
} }
private fun getLocalFonts(): ArrayList<DocItem> {
val fontItems = arrayListOf<DocItem>()
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) { private fun loadFontFiles(doc: DocumentFile) {
execute { execute {
val fontItems = arrayListOf<DocItem>() val fontItems = arrayListOf<DocItem>()
@ -132,6 +152,7 @@ class FontSelectDialog : BaseDialogFragment(),
fontItems.add(item) fontItems.add(item)
} }
} }
fontItems.addAll(getLocalFonts())
fontItems.sortedBy { it.name } fontItems.sortedBy { it.name }
}.onSuccess { }.onSuccess {
adapter?.setItems(it) adapter?.setItems(it)
@ -167,6 +188,7 @@ class FontSelectDialog : BaseDialogFragment(),
) )
) )
} }
fontItems.addAll(getLocalFonts())
fontItems.sortedBy { it.name } fontItems.sortedBy { it.name }
}.onSuccess { }.onSuccess {
adapter?.setItems(it) adapter?.setItems(it)

@ -1,19 +1,74 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<io.legado.app.ui.widget.TitleBar <io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:contentLayout="@layout/view_tab_layout"/> app:contentLayout="@layout/view_search"
app:contentInsetRight="24dp"
app:layout_constraintTop_toTopOf="parent" />
<androidx.viewpager.widget.ViewPager <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView
android:id="@+id/view_pager" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintBottom_toTopOf="@+id/ll_search_base_info"
app:layout_constraintTop_toBottomOf="@id/title_bar" />
</LinearLayout> <LinearLayout
android:id="@+id/ll_search_base_info"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@color/background"
android:elevation="5dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/tv_current_search_info"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:ellipsize="middle"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:singleLine="true"
android:textColor="@color/primaryText"
android:textSize="12sp" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_search_content_top"
android:layout_width="36dp"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/go_to_top"
android:src="@drawable/ic_arrow_drop_up"
android:tooltipText="@string/go_to_top"
app:tint="@color/primaryText"
tools:ignore="UnusedAttribute" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_search_content_bottom"
android:layout_width="36dp"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/go_to_bottom"
android:src="@drawable/ic_arrow_drop_down"
android:tooltipText="@string/go_to_bottom"
app:tint="@color/primaryText"
tools:ignore="UnusedAttribute" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants">
<io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/ll_search_base_info" />
<LinearLayout
android:id="@+id/ll_search_base_info"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="@color/background"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:elevation="5dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/tv_current_search_info"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:ellipsize="middle"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:singleLine="true"
android:gravity="center_vertical"
android:textColor="@color/primaryText"
android:textSize="12sp" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_search_content_top"
android:layout_width="36dp"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/go_to_top"
android:src="@drawable/ic_arrow_drop_up"
android:tooltipText="@string/go_to_top"
app:tint="@color/primaryText"
tools:ignore="UnusedAttribute" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_search_content_bottom"
android:layout_width="36dp"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/go_to_bottom"
android:src="@drawable/ic_arrow_drop_down"
android:tooltipText="@string/go_to_bottom"
app:tint="@color/primaryText"
tools:ignore="UnusedAttribute" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -13,6 +13,10 @@
android:id="@+id/menu_aloud" android:id="@+id/menu_aloud"
android:title="@string/read_aloud" /> android:title="@string/read_aloud" />
<item
android:id="@+id/menu_search_content"
android:title="@string/search_content" />
<item <item
android:id="@+id/menu_browser" android:id="@+id/menu_browser"
android:title="@string/browser" /> android:title="@string/browser" />

@ -3,6 +3,12 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_new_group"
android:title="@string/diy_source_group"
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item <item
android:id="@+id/menu_select_all" android:id="@+id/menu_select_all"
android:title="@string/select_all" android:title="@string/select_all"

@ -35,6 +35,17 @@
<item>系統等寬字體</item> <item>系統等寬字體</item>
</string-array> </string-array>
<string-array name="read_tip">
<item></item>
<item>标题</item>
<item>时间</item>
<item>电量</item>
<item>页数</item>
<item>进度</item>
<item>页数及进度</item>
<item>書名</item>
</string-array>
<string-array name="text_font_weight"> <string-array name="text_font_weight">
<item>正常</item> <item>正常</item>
<item>粗體</item> <item>粗體</item>

@ -10,6 +10,8 @@
<string name="menu_import_old">導入閲讀數據</string> <string name="menu_import_old">導入閲讀數據</string>
<string name="mkdirs">創建子文件夾</string> <string name="mkdirs">創建子文件夾</string>
<string name="mkdirs_description">創建 legado 文件夾作爲備份路徑</string> <string name="mkdirs_description">創建 legado 文件夾作爲備份路徑</string>
<string name="export_webdav">離線導出WebDav</string>
<string name="export_webdav_s">默認導出到legado文件夾下exports目錄</string>
<string name="backup_path">備份路徑</string> <string name="backup_path">備份路徑</string>
<string name="menu_import_old_version">導入舊版數據</string> <string name="menu_import_old_version">導入舊版數據</string>
<string name="menu_import_github">導入 Github 數據</string> <string name="menu_import_github">導入 Github 數據</string>
@ -764,6 +766,6 @@
<string name="select_theme">切換默認主題</string> <string name="select_theme">切換默認主題</string>
<string name="share_selected_source">分享選中書源</string> <string name="share_selected_source">分享選中書源</string>
<string name="sort_by_lastUppdateTime">時間排序</string> <string name="sort_by_lastUppdateTime">時間排序</string>
<string name="search_content">搜索</string> <string name="search_content">全文搜索</string>
</resources> </resources>

@ -89,6 +89,7 @@
<item>頁數</item> <item>頁數</item>
<item>進度</item> <item>進度</item>
<item>頁數及進度</item> <item>頁數及進度</item>
<item>書名</item>
</string-array> </string-array>
<string-array name="text_font_weight"> <string-array name="text_font_weight">

@ -10,6 +10,8 @@
<string name="menu_import_old">匯入閱讀資料</string> <string name="menu_import_old">匯入閱讀資料</string>
<string name="mkdirs">建立子資料夾</string> <string name="mkdirs">建立子資料夾</string>
<string name="mkdirs_description">建立legado資料夾作為備份資料夾</string> <string name="mkdirs_description">建立legado資料夾作為備份資料夾</string>
<string name="export_webdav">離線導出WebDav</string>
<string name="export_webdav_s">默認導出到legado文件夾下exports目錄</string>
<string name="backup_path">備份路徑</string> <string name="backup_path">備份路徑</string>
<string name="menu_import_old_version">匯入舊版資料</string> <string name="menu_import_old_version">匯入舊版資料</string>
<string name="menu_import_github">匯入Github資料</string> <string name="menu_import_github">匯入Github資料</string>
@ -764,7 +766,6 @@
<string name="select_theme">切換默認主題</string> <string name="select_theme">切換默認主題</string>
<string name="share_selected_source">分享選中書源</string> <string name="share_selected_source">分享選中書源</string>
<string name="sort_by_lastUppdateTime">時間排序</string> <string name="sort_by_lastUppdateTime">時間排序</string>
<string name="search_content">搜索</string> <string name="search_content">全文搜索</string>
</resources> </resources>

@ -89,6 +89,7 @@
<item>页数</item> <item>页数</item>
<item>进度</item> <item>进度</item>
<item>页数及进度</item> <item>页数及进度</item>
<item>书名</item>
</string-array> </string-array>
<string-array name="text_font_weight"> <string-array name="text_font_weight">

@ -10,6 +10,8 @@
<string name="menu_import_old">导入阅读数据</string> <string name="menu_import_old">导入阅读数据</string>
<string name="mkdirs">创建子文件夹</string> <string name="mkdirs">创建子文件夹</string>
<string name="mkdirs_description">创建legado文件夹作为备份文件夹</string> <string name="mkdirs_description">创建legado文件夹作为备份文件夹</string>
<string name="export_webdav">离线导出WebDav</string>
<string name="export_webdav_s">默认导出到legado文件夹下exports目录</string>
<string name="backup_path">备份路径</string> <string name="backup_path">备份路径</string>
<string name="select_backup_path">请选择备份路径</string> <string name="select_backup_path">请选择备份路径</string>
<string name="menu_import_old_version">导入旧版数据</string> <string name="menu_import_old_version">导入旧版数据</string>
@ -387,6 +389,9 @@
<string name="source_name">源名称(sourceName)</string> <string name="source_name">源名称(sourceName)</string>
<string name="source_url">源URL(sourceUrl)</string> <string name="source_url">源URL(sourceUrl)</string>
<string name="source_group">源分组(sourceGroup)</string> <string name="source_group">源分组(sourceGroup)</string>
<string name="diy_source_group">自定义源分组</string>
<string name="diy_edit_source_group">输入自定义源分组名称</string>
<string name="diy_edit_source_group_title">【%s】</string>
<string name="sort_url">分类Url</string> <string name="sort_url">分类Url</string>
<string name="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string> <string name="comment">源注释(sourceComment)</string>
@ -764,6 +769,6 @@
<string name="theme_list_summary">使用保存主题,导入,分享主题</string> <string name="theme_list_summary">使用保存主题,导入,分享主题</string>
<string name="select_theme">切换默认主题</string> <string name="select_theme">切换默认主题</string>
<string name="sort_by_lastUppdateTime">时间排序</string> <string name="sort_by_lastUppdateTime">时间排序</string>
<string name="search_content">搜索</string> <string name="search_content">全文搜索</string>
</resources> </resources>

@ -89,6 +89,7 @@
<item>Pages</item> <item>Pages</item>
<item>Progress</item> <item>Progress</item>
<item>Pages and progress</item> <item>Pages and progress</item>
<item>Book name</item>
</string-array> </string-array>
<string-array name="text_font_weight"> <string-array name="text_font_weight">

@ -10,6 +10,8 @@
<string name="menu_import_old">Import Legado data</string> <string name="menu_import_old">Import Legado data</string>
<string name="mkdirs">Create a subfolder</string> <string name="mkdirs">Create a subfolder</string>
<string name="mkdirs_description">Create a folder named Legado as the backup folder.</string> <string name="mkdirs_description">Create a folder named Legado as the backup folder.</string>
<string name="export_webdav">Export Webdav</string>
<string name="export_webdav_s">Default export to the exports directory under the legado folder</string>
<string name="backup_path">Backup to</string> <string name="backup_path">Backup to</string>
<string name="select_backup_path">Please select a backup path.</string> <string name="select_backup_path">Please select a backup path.</string>
<string name="menu_import_old_version">Import legacy data</string> <string name="menu_import_old_version">Import legacy data</string>
@ -387,6 +389,9 @@
<string name="source_name">源名称(sourceName)</string> <string name="source_name">源名称(sourceName)</string>
<string name="source_url">源URL(sourceUrl)</string> <string name="source_url">源URL(sourceUrl)</string>
<string name="source_group">源分组(sourceGroup)</string> <string name="source_group">源分组(sourceGroup)</string>
<string name="diy_source_group">自定义源分组</string>
<string name="diy_edit_source_group">输入自定义源分组名称</string>
<string name="diy_edit_source_group_title">【%s】</string>
<string name="sort_url">分类Url</string> <string name="sort_url">分类Url</string>
<string name="login_url">登录URL(loginUrl)</string> <string name="login_url">登录URL(loginUrl)</string>
<string name="comment">源注释(sourceComment)</string> <string name="comment">源注释(sourceComment)</string>
@ -766,6 +771,6 @@
<string name="theme_list_summary">Save, Import, Share theme</string> <string name="theme_list_summary">Save, Import, Share theme</string>
<string name="share_selected_source">Share selected sources</string> <string name="share_selected_source">Share selected sources</string>
<string name="sort_by_lastUppdateTime">Sort by update time</string> <string name="sort_by_lastUppdateTime">Sort by update time</string>
<string name="search_content">Search</string> <string name="search_content">Search content</string>
</resources> </resources>

@ -36,6 +36,15 @@
app:allowDividerBelow="false" app:allowDividerBelow="false"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.SwitchPreference
android:key="webDavExport"
android:defaultValue="false"
android:title="@string/export_webdav"
android:summary="@string/export_webdav_s"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory> </io.legado.app.ui.widget.prefs.PreferenceCategory>
<io.legado.app.ui.widget.prefs.PreferenceCategory <io.legado.app.ui.widget.prefs.PreferenceCategory

Loading…
Cancel
Save