mirror of
https://github.com/gedoor/legado.git
synced 2024-07-19 01:17:25 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
87265c7818
@ -27,4 +27,5 @@ object EventBus {
|
||||
const val CHECK_SOURCE_DONE = "checkSourceDone"
|
||||
const val TIP_COLOR = "tipColor"
|
||||
const val SOURCE_CHANGED = "sourceChanged"
|
||||
const val SEARCH_RESULT = "searchResult"
|
||||
}
|
@ -47,6 +47,7 @@ import io.legado.app.ui.book.read.page.ReadView
|
||||
import io.legado.app.ui.book.read.page.entities.PageDirection
|
||||
import io.legado.app.ui.book.read.page.provider.TextPageFactory
|
||||
import io.legado.app.ui.book.searchContent.SearchContentActivity
|
||||
import io.legado.app.ui.book.searchContent.SearchResult
|
||||
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
||||
import io.legado.app.ui.book.toc.BookmarkDialog
|
||||
import io.legado.app.ui.book.toc.TocActivityResult
|
||||
@ -66,6 +67,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
TextActionMenu.CallBack,
|
||||
ContentTextView.CallBack,
|
||||
ReadMenu.CallBack,
|
||||
SearchMenu.CallBack,
|
||||
ReadAloudDialog.CallBack,
|
||||
ChangeSourceDialog.CallBack,
|
||||
ReadBook.CallBack,
|
||||
@ -94,17 +96,21 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
viewModel.replaceRuleChanged()
|
||||
}
|
||||
}
|
||||
private val searchContentActivity =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
it ?: return@registerForActivityResult
|
||||
it.data?.let { data ->
|
||||
data.getIntExtra("index", ReadBook.durChapterIndex).let { index ->
|
||||
viewModel.searchContentQuery = data.getStringExtra("query") ?: ""
|
||||
val indexWithinChapter = data.getIntExtra("indexWithinChapter", 0)
|
||||
skipToSearch(index, indexWithinChapter)
|
||||
private val searchContentActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
it ?: return@registerForActivityResult
|
||||
it.data?.let { data ->
|
||||
data.getIntExtra("chapterIndex", ReadBook.durChapterIndex).let { _ ->
|
||||
viewModel.searchContentQuery = data.getStringExtra("query") ?: ""
|
||||
val searchResultIndex = data.getIntExtra("searchResultIndex", 0)
|
||||
isShowingSearchResult = true
|
||||
binding.searchMenu.updateSearchResultIndex(searchResultIndex)
|
||||
binding.searchMenu.selectedSearchResult?.let { currentResult ->
|
||||
skipToSearch(currentResult)
|
||||
showActionMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private var menu: Menu? = null
|
||||
val textActionMenu: TextActionMenu by lazy {
|
||||
TextActionMenu(this, this)
|
||||
@ -117,6 +123,11 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
private var backupJob: Job? = null
|
||||
override var autoPageProgress = 0
|
||||
override var isAutoPage = false
|
||||
override var isShowingSearchResult = false
|
||||
override var isSelectingSearchResult = false
|
||||
set(value) {
|
||||
field = value && isShowingSearchResult
|
||||
}
|
||||
private var screenTimeOut: Long = 0
|
||||
private var timeBatteryReceiver: TimeBatteryReceiver? = null
|
||||
private var loadStates: Boolean = false
|
||||
@ -644,6 +655,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
when {
|
||||
BaseReadAloudService.isRun -> showReadAloudDialog()
|
||||
isAutoPage -> showDialogFragment<AutoReadDialog>()
|
||||
isShowingSearchResult -> binding.searchMenu.runMenuIn()
|
||||
else -> binding.readMenu.runMenuIn()
|
||||
}
|
||||
}
|
||||
@ -775,6 +787,10 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
showDialogFragment<MoreConfigDialog>()
|
||||
}
|
||||
|
||||
override fun showSearchSetting() {
|
||||
showDialogFragment<MoreConfigDialog>()
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新状态栏,导航栏
|
||||
*/
|
||||
@ -783,6 +799,13 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
upNavigationBarColor()
|
||||
}
|
||||
|
||||
override fun exitSearchMenu() {
|
||||
if (isShowingSearchResult) {
|
||||
isShowingSearchResult = false
|
||||
binding.searchMenu.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun showLogin() {
|
||||
ReadBook.bookSource?.let {
|
||||
startActivity<SourceLoginActivity> {
|
||||
@ -879,34 +902,45 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
private fun skipToSearch(index: Int, indexWithinChapter: Int) {
|
||||
viewModel.openChapter(index) {
|
||||
val pages = ReadBook.curTextChapter?.pages ?: return@openChapter
|
||||
val positions = viewModel.searchResultPositions(pages, indexWithinChapter)
|
||||
ReadBook.skipToPage(positions[0]) {
|
||||
launch {
|
||||
binding.readView.curPage.selectStartMoveIndex(0, positions[1], positions[2])
|
||||
delay(20L)
|
||||
when (positions[3]) {
|
||||
0 -> binding.readView.curPage.selectEndMoveIndex(
|
||||
0,
|
||||
positions[1],
|
||||
positions[2] + viewModel.searchContentQuery.length - 1
|
||||
)
|
||||
1 -> binding.readView.curPage.selectEndMoveIndex(
|
||||
0,
|
||||
positions[1] + 1,
|
||||
positions[4]
|
||||
)
|
||||
//consider change page, jump to scroll position
|
||||
-1 -> binding.readView.curPage
|
||||
.selectEndMoveIndex(1, 0, positions[4])
|
||||
override fun navigateToSearch(searchResult: SearchResult) {
|
||||
skipToSearch(searchResult)
|
||||
}
|
||||
|
||||
private fun skipToSearch(searchResult: SearchResult) {
|
||||
val previousResult = binding.searchMenu.previousSearchResult
|
||||
|
||||
fun jumpToPosition(){
|
||||
ReadBook.curTextChapter?.let {
|
||||
binding.searchMenu.updateSearchInfo()
|
||||
val positions = viewModel.searchResultPositions(it, searchResult)
|
||||
ReadBook.skipToPage(positions[0]) {
|
||||
launch {
|
||||
isSelectingSearchResult = true
|
||||
binding.readView.curPage.selectStartMoveIndex(0, positions[1], positions[2])
|
||||
when (positions[3]) {
|
||||
0 -> binding.readView.curPage.selectEndMoveIndex(
|
||||
0, positions[1], positions[2] + viewModel.searchContentQuery.length - 1
|
||||
)
|
||||
1 -> binding.readView.curPage.selectEndMoveIndex(
|
||||
0, positions[1] + 1, positions[4]
|
||||
)
|
||||
//consider change page, jump to scroll position
|
||||
-1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, positions[4])
|
||||
}
|
||||
binding.readView.isTextSelected = true
|
||||
isSelectingSearchResult = false
|
||||
}
|
||||
binding.readView.isTextSelected = true
|
||||
delay(100L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (previousResult.chapterIndex != searchResult.chapterIndex) {
|
||||
viewModel.openChapter(searchResult.chapterIndex) {
|
||||
jumpToPosition()
|
||||
}
|
||||
} else {
|
||||
jumpToPosition()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startBackupJob() {
|
||||
|
@ -21,7 +21,8 @@ import io.legado.app.model.ReadBook
|
||||
import io.legado.app.model.localBook.LocalBook
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
import io.legado.app.service.BaseReadAloudService
|
||||
import io.legado.app.ui.book.read.page.entities.TextPage
|
||||
import io.legado.app.ui.book.read.page.entities.TextChapter
|
||||
import io.legado.app.ui.book.searchContent.SearchResult
|
||||
import io.legado.app.utils.msg
|
||||
import io.legado.app.utils.postEvent
|
||||
import io.legado.app.utils.toastOnUi
|
||||
@ -262,17 +263,16 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
||||
* 内容搜索跳转
|
||||
*/
|
||||
fun searchResultPositions(
|
||||
pages: List<TextPage>,
|
||||
indexWithinChapter: Int
|
||||
textChapter: TextChapter,
|
||||
searchResult: SearchResult
|
||||
): Array<Int> {
|
||||
// calculate search result's pageIndex
|
||||
var content = ""
|
||||
pages.map {
|
||||
content += it.text
|
||||
}
|
||||
var count = 1
|
||||
val pages = textChapter.pages
|
||||
val content = textChapter.getContent()
|
||||
|
||||
var count = 0
|
||||
var index = content.indexOf(searchContentQuery)
|
||||
while (count != indexWithinChapter) {
|
||||
while (count != searchResult.resultCountWithinChapter) {
|
||||
index = content.indexOf(searchContentQuery, index + 1)
|
||||
count += 1
|
||||
}
|
||||
|
235
app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt
Normal file
235
app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt
Normal file
@ -0,0 +1,235 @@
|
||||
package io.legado.app.ui.book.read
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.animation.Animation
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.EventBus
|
||||
import io.legado.app.databinding.ViewSearchMenuBinding
|
||||
import io.legado.app.help.*
|
||||
import io.legado.app.lib.theme.*
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.ui.book.searchContent.SearchResult
|
||||
import io.legado.app.utils.*
|
||||
import splitties.views.*
|
||||
|
||||
/**
|
||||
* 搜索界面菜单
|
||||
*/
|
||||
class SearchMenu @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : FrameLayout(context, attrs) {
|
||||
|
||||
private val callBack: CallBack get() = activity as CallBack
|
||||
private val binding = ViewSearchMenuBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
private val menuBottomIn: Animation = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_bottom_in)
|
||||
private val menuBottomOut: Animation = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_bottom_out)
|
||||
private val bgColor: Int = context.bottomBackground
|
||||
private val textColor: Int = context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor))
|
||||
private val bottomBackgroundList: ColorStateList =
|
||||
Selector.colorBuild().setDefaultColor(bgColor).setPressedColor(ColorUtils.darkenColor(bgColor)).create()
|
||||
private var onMenuOutEnd: (() -> Unit)? = null
|
||||
|
||||
private val searchResultList: MutableList<SearchResult> = mutableListOf()
|
||||
private var currentSearchResultIndex: Int = 0
|
||||
private var lastSearchResultIndex: Int = 0
|
||||
private val hasSearchResult: Boolean
|
||||
get() = searchResultList.isNotEmpty()
|
||||
val selectedSearchResult: SearchResult?
|
||||
get() = if (searchResultList.isNotEmpty()) searchResultList[currentSearchResultIndex] else null
|
||||
val previousSearchResult: SearchResult
|
||||
get() = searchResultList[lastSearchResultIndex]
|
||||
|
||||
init {
|
||||
initAnimation()
|
||||
initView()
|
||||
bindEvent()
|
||||
updateSearchInfo()
|
||||
observeSearchResultList()
|
||||
}
|
||||
|
||||
private fun observeSearchResultList() {
|
||||
activity?.let { owner ->
|
||||
eventObservable<List<SearchResult>>(EventBus.SEARCH_RESULT).observe(owner, {
|
||||
searchResultList.clear()
|
||||
searchResultList.addAll(it)
|
||||
updateSearchInfo()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() = binding.run {
|
||||
llSearchBaseInfo.setBackgroundColor(bgColor)
|
||||
tvCurrentSearchInfo.setTextColor(bottomBackgroundList)
|
||||
llBottomBg.setBackgroundColor(bgColor)
|
||||
fabLeft.backgroundTintList = bottomBackgroundList
|
||||
fabLeft.setColorFilter(textColor)
|
||||
fabRight.backgroundTintList = bottomBackgroundList
|
||||
fabRight.setColorFilter(textColor)
|
||||
tvMainMenu.setTextColor(textColor)
|
||||
tvSearchResults.setTextColor(textColor)
|
||||
tvSearchExit.setTextColor(textColor)
|
||||
//tvSetting.setTextColor(textColor)
|
||||
ivMainMenu.setColorFilter(textColor)
|
||||
ivSearchResults.setColorFilter(textColor)
|
||||
ivSearchExit.setColorFilter(textColor)
|
||||
//ivSetting.setColorFilter(textColor)
|
||||
ivSearchContentUp.setColorFilter(textColor)
|
||||
ivSearchContentDown.setColorFilter(textColor)
|
||||
tvCurrentSearchInfo.setTextColor(textColor)
|
||||
}
|
||||
|
||||
|
||||
fun runMenuIn() {
|
||||
this.visible()
|
||||
binding.llSearchBaseInfo.visible()
|
||||
binding.llBottomBg.visible()
|
||||
binding.vwMenuBg.visible()
|
||||
binding.llSearchBaseInfo.startAnimation(menuBottomIn)
|
||||
binding.llBottomBg.startAnimation(menuBottomIn)
|
||||
}
|
||||
|
||||
fun runMenuOut(onMenuOutEnd: (() -> Unit)? = null) {
|
||||
this.onMenuOutEnd = onMenuOutEnd
|
||||
if (this.isVisible) {
|
||||
binding.llSearchBaseInfo.startAnimation(menuBottomOut)
|
||||
binding.llBottomBg.startAnimation(menuBottomOut)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSearchInfo() {
|
||||
ReadBook.curTextChapter?.let {
|
||||
binding.tvCurrentSearchInfo.text = context.getString(R.string.search_content_size) + ": ${searchResultList.size} / 当前章节: ${it.title}"
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSearchResultIndex(updateIndex: Int) {
|
||||
lastSearchResultIndex = currentSearchResultIndex
|
||||
currentSearchResultIndex = when {
|
||||
updateIndex < 0 -> 0
|
||||
updateIndex >= searchResultList.size -> searchResultList.size - 1
|
||||
else -> updateIndex
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindEvent() = binding.run {
|
||||
|
||||
llSearchResults.setOnClickListener {
|
||||
runMenuOut {
|
||||
callBack.openSearchActivity(selectedSearchResult?.query)
|
||||
}
|
||||
}
|
||||
|
||||
//主菜单
|
||||
llMainMenu.setOnClickListener {
|
||||
runMenuOut {
|
||||
callBack.showMenuBar()
|
||||
this@SearchMenu.invisible()
|
||||
}
|
||||
}
|
||||
|
||||
//目录
|
||||
llSearchExit.setOnClickListener {
|
||||
runMenuOut {
|
||||
callBack.exitSearchMenu()
|
||||
this@SearchMenu.invisible()
|
||||
}
|
||||
}
|
||||
|
||||
//设置
|
||||
// llSetting.setOnClickListener {
|
||||
// runMenuOut {
|
||||
// callBack.showSearchSetting()
|
||||
// }
|
||||
// }
|
||||
|
||||
fabLeft.setOnClickListener {
|
||||
updateSearchResultIndex(currentSearchResultIndex - 1)
|
||||
callBack.navigateToSearch(searchResultList[currentSearchResultIndex])
|
||||
}
|
||||
|
||||
ivSearchContentUp.setOnClickListener {
|
||||
updateSearchResultIndex(currentSearchResultIndex - 1)
|
||||
callBack.navigateToSearch(searchResultList[currentSearchResultIndex])
|
||||
}
|
||||
|
||||
ivSearchContentDown.setOnClickListener {
|
||||
updateSearchResultIndex(currentSearchResultIndex + 1)
|
||||
callBack.navigateToSearch(searchResultList[currentSearchResultIndex])
|
||||
}
|
||||
|
||||
fabRight.setOnClickListener {
|
||||
updateSearchResultIndex(currentSearchResultIndex + 1)
|
||||
callBack.navigateToSearch(searchResultList[currentSearchResultIndex])
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAnimation() {
|
||||
//显示菜单
|
||||
menuBottomIn.setAnimationListener(object : Animation.AnimationListener {
|
||||
override fun onAnimationStart(animation: Animation) {
|
||||
callBack.upSystemUiVisibility()
|
||||
binding.fabLeft.visible(hasSearchResult)
|
||||
binding.fabRight.visible(hasSearchResult)
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
val navigationBarHeight = if (ReadBookConfig.hideNavigationBar) {
|
||||
activity?.navigationBarHeight ?: 0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
binding.run {
|
||||
vwMenuBg.setOnClickListener { runMenuOut() }
|
||||
root.padding = 0
|
||||
when (activity?.navigationBarGravity) {
|
||||
Gravity.BOTTOM -> root.bottomPadding = navigationBarHeight
|
||||
Gravity.LEFT -> root.leftPadding = navigationBarHeight
|
||||
Gravity.RIGHT -> root.rightPadding = navigationBarHeight
|
||||
}
|
||||
}
|
||||
callBack.upSystemUiVisibility()
|
||||
}
|
||||
|
||||
override fun onAnimationRepeat(animation: Animation) = Unit
|
||||
})
|
||||
|
||||
//隐藏菜单
|
||||
menuBottomOut.setAnimationListener(object : Animation.AnimationListener {
|
||||
override fun onAnimationStart(animation: Animation) {
|
||||
binding.vwMenuBg.setOnClickListener(null)
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
binding.llSearchBaseInfo.invisible()
|
||||
binding.llBottomBg.invisible()
|
||||
binding.vwMenuBg.invisible()
|
||||
binding.vwMenuBg.setOnClickListener { runMenuOut() }
|
||||
|
||||
onMenuOutEnd?.invoke()
|
||||
callBack.upSystemUiVisibility()
|
||||
}
|
||||
|
||||
override fun onAnimationRepeat(animation: Animation) = Unit
|
||||
})
|
||||
}
|
||||
|
||||
interface CallBack {
|
||||
var isShowingSearchResult: Boolean
|
||||
fun openSearchActivity(searchWord: String?)
|
||||
fun showSearchSetting()
|
||||
fun upSystemUiVisibility()
|
||||
fun exitSearchMenu()
|
||||
fun showMenuBar()
|
||||
fun navigateToSearch(searchResult: SearchResult)
|
||||
}
|
||||
|
||||
}
|
@ -145,7 +145,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
if (it.isImage) {
|
||||
drawImage(canvas, it, lineTop, lineBottom, isImageLine)
|
||||
} else {
|
||||
if(it.isSearchResult) {
|
||||
textPaint.color = context.accentColor
|
||||
}
|
||||
canvas.drawText(it.charData, it.start, lineBase, textPaint)
|
||||
textPaint.color = ReadBookConfig.textColor
|
||||
}
|
||||
if (it.selected) {
|
||||
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint)
|
||||
@ -392,6 +396,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
relativePos in selectStart[0] + 1 until selectEnd[0]
|
||||
}
|
||||
}
|
||||
textChar.isSearchResult = textChar.selected && callBack.isSelectingSearchResult
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -539,5 +544,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
||||
val headerHeight: Int
|
||||
val pageFactory: TextPageFactory
|
||||
val isScroll: Boolean
|
||||
var isSelectingSearchResult: Boolean
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ data class TextChar(
|
||||
var start: Float,
|
||||
var end: Float,
|
||||
var selected: Boolean = false,
|
||||
var isImage: Boolean = false
|
||||
var isImage: Boolean = false,
|
||||
var isSearchResult: Boolean = false
|
||||
) {
|
||||
|
||||
fun isTouch(x: Float): Boolean {
|
||||
|
@ -5,7 +5,6 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import com.github.liuyueyi.quick.transfer.ChineseUtils
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.VMBaseActivity
|
||||
import io.legado.app.constant.EventBus
|
||||
@ -13,7 +12,6 @@ import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.Book
|
||||
import io.legado.app.data.entities.BookChapter
|
||||
import io.legado.app.databinding.ActivitySearchContentBinding
|
||||
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
|
||||
@ -23,6 +21,7 @@ import io.legado.app.ui.widget.recycler.VerticalDivider
|
||||
import io.legado.app.utils.ColorUtils
|
||||
import io.legado.app.utils.applyTint
|
||||
import io.legado.app.utils.observeEvent
|
||||
import io.legado.app.utils.postEvent
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -40,9 +39,7 @@ class SearchContentActivity :
|
||||
private val searchView: SearchView by lazy {
|
||||
binding.titleBar.findViewById(R.id.search_view)
|
||||
}
|
||||
private var searchResultCounts = 0
|
||||
private var durChapterIndex = 0
|
||||
private var searchResultList: MutableList<SearchResult> = mutableListOf()
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
val bbg = bottomBackground
|
||||
@ -103,7 +100,7 @@ class SearchContentActivity :
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun initBook() {
|
||||
binding.tvCurrentSearchInfo.text = "搜索结果:$searchResultCounts"
|
||||
binding.tvCurrentSearchInfo.text = this.getString(R.string.search_content_size) +": ${viewModel.searchResultCounts}"
|
||||
viewModel.book?.let {
|
||||
initCacheFileNames(it)
|
||||
durChapterIndex = it.durChapterIndex
|
||||
@ -115,7 +112,7 @@ class SearchContentActivity :
|
||||
|
||||
private fun initCacheFileNames(book: Book) {
|
||||
launch(Dispatchers.IO) {
|
||||
adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book))
|
||||
viewModel.cacheChapterNames.addAll(BookHelp.getChapterFiles(book))
|
||||
withContext(Dispatchers.Main) {
|
||||
adapter.notifyItemRangeChanged(0, adapter.itemCount, true)
|
||||
}
|
||||
@ -126,7 +123,7 @@ class SearchContentActivity :
|
||||
observeEvent<BookChapter>(EventBus.SAVE_CONTENT) { chapter ->
|
||||
viewModel.book?.bookUrl?.let { bookUrl ->
|
||||
if (chapter.bookUrl == bookUrl) {
|
||||
adapter.cacheFileNames.add(chapter.getFileName())
|
||||
viewModel.cacheChapterNames.add(chapter.getFileName())
|
||||
adapter.notifyItemChanged(chapter.index, true)
|
||||
}
|
||||
}
|
||||
@ -134,28 +131,26 @@ class SearchContentActivity :
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun startContentSearch(newText: String) {
|
||||
fun startContentSearch(query: String) {
|
||||
// 按章节搜索内容
|
||||
if (newText.isNotBlank()) {
|
||||
if (query.isNotBlank()) {
|
||||
adapter.clearItems()
|
||||
searchResultList.clear()
|
||||
binding.refreshProgressBar.isAutoLoading = true
|
||||
searchResultCounts = 0
|
||||
viewModel.lastQuery = newText
|
||||
viewModel.searchResultList.clear()
|
||||
viewModel.searchResultCounts = 0
|
||||
viewModel.lastQuery = query
|
||||
var searchResults = listOf<SearchResult>()
|
||||
launch(Dispatchers.Main) {
|
||||
appDb.bookChapterDao.getChapterList(viewModel.bookUrl).map { chapter ->
|
||||
appDb.bookChapterDao.getChapterList(viewModel.bookUrl).map { bookChapter ->
|
||||
binding.refreshProgressBar.isAutoLoading = true
|
||||
withContext(Dispatchers.IO) {
|
||||
if (isLocalBook
|
||||
|| adapter.cacheFileNames.contains(chapter.getFileName())
|
||||
) {
|
||||
searchResults = searchChapter(newText, chapter)
|
||||
if (isLocalBook || viewModel.cacheChapterNames.contains(bookChapter.getFileName())) {
|
||||
searchResults = viewModel.searchChapter(query, bookChapter)
|
||||
}
|
||||
}
|
||||
if (searchResults.isNotEmpty()) {
|
||||
searchResultList.addAll(searchResults)
|
||||
viewModel.searchResultList.addAll(searchResults)
|
||||
binding.refreshProgressBar.isAutoLoading = false
|
||||
binding.tvCurrentSearchInfo.text = "搜索结果:$searchResultCounts"
|
||||
binding.tvCurrentSearchInfo.text = this@SearchContentActivity.getString(R.string.search_content_size) +": ${viewModel.searchResultCounts}"
|
||||
adapter.addItems(searchResults)
|
||||
searchResults = listOf()
|
||||
}
|
||||
@ -164,94 +159,17 @@ class SearchContentActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> {
|
||||
val searchResults: MutableList<SearchResult> = mutableListOf()
|
||||
var positions: List<Int>
|
||||
var replaceContents: List<String>?
|
||||
var totalContents: String
|
||||
if (chapter != null) {
|
||||
viewModel.book?.let { book ->
|
||||
val bookContent = BookHelp.getContent(book, chapter)
|
||||
if (bookContent != null) {
|
||||
//搜索替换后的正文
|
||||
withContext(Dispatchers.IO) {
|
||||
chapter.title = when (AppConfig.chineseConverterType) {
|
||||
1 -> ChineseUtils.t2s(chapter.title)
|
||||
2 -> ChineseUtils.s2t(chapter.title)
|
||||
else -> chapter.title
|
||||
}
|
||||
replaceContents =
|
||||
viewModel.contentProcessor!!.getContent(
|
||||
book,
|
||||
chapter,
|
||||
bookContent,
|
||||
chineseConvert = false,
|
||||
reSegment = false
|
||||
)
|
||||
}
|
||||
totalContents = replaceContents?.joinToString("") ?: bookContent
|
||||
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) {
|
||||
postEvent(EventBus.SEARCH_RESULT, viewModel.searchResultList as List<SearchResult>)
|
||||
val searchData = Intent()
|
||||
searchData.putExtra("index", searchResult.chapterIndex)
|
||||
searchData.putExtra("contentPosition", searchResult.contentPosition)
|
||||
searchData.putExtra("searchResultIndex", viewModel.searchResultList.indexOf(searchResult))
|
||||
searchData.putExtra("chapterIndex", searchResult.chapterIndex)
|
||||
searchData.putExtra("contentPosition", searchResult.queryIndexInChapter)
|
||||
searchData.putExtra("query", searchResult.query)
|
||||
searchData.putExtra("indexWithinChapter", searchResult.indexWithinChapter)
|
||||
searchData.putExtra("resultCountWithinChapter", searchResult.resultCountWithinChapter)
|
||||
setResult(RESULT_OK, searchData)
|
||||
finish()
|
||||
}
|
||||
|
@ -2,16 +2,26 @@ package io.legado.app.ui.book.searchContent
|
||||
|
||||
|
||||
import android.app.Application
|
||||
import com.github.liuyueyi.quick.transfer.ChineseUtils
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.data.appDb
|
||||
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.help.ContentProcessor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class SearchContentViewModel(application: Application) : BaseViewModel(application) {
|
||||
var bookUrl: String = ""
|
||||
var book: Book? = null
|
||||
var contentProcessor: ContentProcessor? = null
|
||||
private var contentProcessor: ContentProcessor? = null
|
||||
var lastQuery: String = ""
|
||||
var searchResultCounts = 0
|
||||
val cacheChapterNames = hashSetOf<String>()
|
||||
val searchResultList: MutableList<SearchResult> = mutableListOf()
|
||||
var selectedIndex = 0
|
||||
|
||||
fun initBook(bookUrl: String, success: () -> Unit) {
|
||||
this.bookUrl = bookUrl
|
||||
@ -25,4 +35,72 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchChapter(query: String, chapter: BookChapter?): List<SearchResult> {
|
||||
val searchResultsWithinChapter: MutableList<SearchResult> = mutableListOf()
|
||||
if (chapter != null) {
|
||||
book?.let { book ->
|
||||
val chapterContent = BookHelp.getContent(book, chapter)
|
||||
if (chapterContent != null) {
|
||||
//搜索替换后的正文
|
||||
val replaceContent: String
|
||||
withContext(Dispatchers.IO) {
|
||||
chapter.title = when (AppConfig.chineseConverterType) {
|
||||
1 -> ChineseUtils.t2s(chapter.title)
|
||||
2 -> ChineseUtils.s2t(chapter.title)
|
||||
else -> chapter.title
|
||||
}
|
||||
replaceContent = contentProcessor!!.getContent(
|
||||
book, chapter, chapterContent, chineseConvert = false, reSegment = false
|
||||
).joinToString("")
|
||||
}
|
||||
val positions = searchPosition(replaceContent, query)
|
||||
positions.forEachIndexed { index, position ->
|
||||
val construct = getResultAndQueryIndex(replaceContent, position, query)
|
||||
val result = SearchResult(
|
||||
resultCountWithinChapter = index,
|
||||
resultText = construct.second,
|
||||
chapterTitle = chapter.title,
|
||||
query = query,
|
||||
chapterIndex = chapter.index,
|
||||
queryIndexInResult = construct.first,
|
||||
queryIndexInChapter = position
|
||||
)
|
||||
searchResultsWithinChapter.add(result)
|
||||
}
|
||||
searchResultCounts += searchResultsWithinChapter.size
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchResultsWithinChapter
|
||||
}
|
||||
|
||||
private fun searchPosition(chapterContent: String, pattern: String): List<Int> {
|
||||
val position: MutableList<Int> = mutableListOf()
|
||||
var index = chapterContent.indexOf(pattern)
|
||||
while (index >= 0) {
|
||||
position.add(index)
|
||||
index = chapterContent.indexOf(pattern, index + 1)
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
private fun getResultAndQueryIndex(content: String, queryIndexInContent: Int, query: String): Pair<Int, String> {
|
||||
// 左右移动20个字符,构建关键词周边文字,在搜索结果里显示
|
||||
// todo: 判断段落,只在关键词所在段落内分割
|
||||
// todo: 利用标点符号分割完整的句
|
||||
// todo: length和设置结合,自由调整周边文字长度
|
||||
val length = 20
|
||||
var po1 = queryIndexInContent - length
|
||||
var po2 = queryIndexInContent + query.length + length
|
||||
if (po1 < 0) {
|
||||
po1 = 0
|
||||
}
|
||||
if (po2 > content.length) {
|
||||
po2 = content.length
|
||||
}
|
||||
val queryIndexInResult = queryIndexInContent - po1
|
||||
val newText = content.substring(po1, po2)
|
||||
return queryIndexInResult to newText
|
||||
}
|
||||
|
||||
}
|
@ -4,36 +4,29 @@ import android.text.Spanned
|
||||
import androidx.core.text.HtmlCompat
|
||||
|
||||
data class SearchResult(
|
||||
var index: Int = 0,
|
||||
var indexWithinChapter: Int = 0,
|
||||
var text: String = "",
|
||||
var chapterTitle: String = "",
|
||||
val resultCount: Int = 0,
|
||||
val resultCountWithinChapter: Int = 0,
|
||||
val resultText: String = "",
|
||||
val chapterTitle: String = "",
|
||||
val query: String,
|
||||
var pageSize: Int = 0,
|
||||
var chapterIndex: Int = 0,
|
||||
var pageIndex: Int = 0,
|
||||
var newPosition: Int = 0,
|
||||
var contentPosition: Int = 0
|
||||
val pageSize: Int = 0,
|
||||
val chapterIndex: Int = 0,
|
||||
val pageIndex: Int = 0,
|
||||
val queryIndexInResult: Int = 0,
|
||||
val queryIndexInChapter: Int = 0
|
||||
) {
|
||||
|
||||
fun getHtmlCompat(textColor: String, accentColor: String): Spanned {
|
||||
val html = colorPresentText(newPosition, query, text, textColor, accentColor) +
|
||||
"<font color=#${accentColor}>($chapterTitle)</font>"
|
||||
val queryIndexInSurrounding = resultText.indexOf(query)
|
||||
val leftString = resultText.substring(0, queryIndexInSurrounding)
|
||||
val rightString = resultText.substring(queryIndexInSurrounding + query.length, resultText.length)
|
||||
val html = leftString.colorTextForHtml(textColor) +
|
||||
query.colorTextForHtml(accentColor) +
|
||||
rightString.colorTextForHtml(textColor) +
|
||||
chapterTitle.colorTextForHtml(accentColor)
|
||||
return HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
}
|
||||
|
||||
private fun colorPresentText(
|
||||
position: Int,
|
||||
center: String,
|
||||
targetText: String,
|
||||
textColor: String,
|
||||
accentColor: String
|
||||
): String {
|
||||
val sub1 = text.substring(0, position)
|
||||
val sub2 = text.substring(position + center.length, targetText.length)
|
||||
return "<font color=#${textColor}>$sub1</font>" +
|
||||
"<font color=#${accentColor}>$center</font>" +
|
||||
"<font color=#${textColor}>$sub2</font>"
|
||||
}
|
||||
private fun String.colorTextForHtml(textColor: String) = "<font color=#${textColor}>$this</font>"
|
||||
|
||||
}
|
@ -37,6 +37,12 @@
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<io.legado.app.ui.book.read.SearchMenu
|
||||
android:id="@+id/search_menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/navigation_bar"
|
||||
android:layout_width="match_parent"
|
||||
|
276
app/src/main/res/layout/view_search_menu.xml
Normal file
276
app/src/main/res/layout/view_search_menu.xml
Normal file
@ -0,0 +1,276 @@
|
||||
<?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:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/vw_menu_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/content"
|
||||
tools:layout_editor_absoluteX="0dp"
|
||||
tools:layout_editor_absoluteY="0dp"
|
||||
tools:visibility="invisible"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabLeft"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="上个结果"
|
||||
android:rotation="180"
|
||||
android:src="@drawable/ic_arrow_right"
|
||||
android:tint="@color/primaryText"
|
||||
android:tooltipText="@string/search_content"
|
||||
app:backgroundTint="@color/background_menu"
|
||||
app:elevation="2dp"
|
||||
app:fabSize="mini"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:pressedTranslationZ="2dp"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabRight"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription="下个结果"
|
||||
android:src="@drawable/ic_arrow_right"
|
||||
android:tint="@color/primaryText"
|
||||
android:tooltipText="@string/search_content"
|
||||
app:backgroundTint="@color/background_menu"
|
||||
app:elevation="2dp"
|
||||
app:fabSize="mini"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:pressedTranslationZ="2dp"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_search_base_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="36dp"
|
||||
android:background="@color/background_menu"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="10dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/ll_bottom_bg">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/iv_search_content_up"
|
||||
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_down"
|
||||
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" />
|
||||
|
||||
<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" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_bottom_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<!--结果按钮-->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_search_results"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="结果"
|
||||
android:focusable="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="7dp">
|
||||
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/iv_search_results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="结果"
|
||||
android:src="@drawable/ic_toc"
|
||||
app:tint="@color/primaryText"
|
||||
tools:ignore="NestedWeights" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_search_results"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="3dp"
|
||||
android:maxLines="1"
|
||||
android:text="结果"
|
||||
android:textColor="@color/primaryText"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="2" />
|
||||
<!--调节按钮-->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_main_menu"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/read_aloud"
|
||||
android:focusable="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="7dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/iv_main_menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/main_menu"
|
||||
android:src="@drawable/ic_menu"
|
||||
app:tint="@color/primaryText"
|
||||
tools:ignore="NestedWeights" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_main_menu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="3dp"
|
||||
android:maxLines="1"
|
||||
android:text="@string/main_menu"
|
||||
android:textColor="@color/primaryText"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="2" />
|
||||
<!--界面按钮-->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_search_exit"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="退出"
|
||||
android:focusable="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="7dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/iv_search_exit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="退出"
|
||||
android:src="@drawable/ic_auto_page_stop"
|
||||
app:tint="@color/primaryText"
|
||||
tools:ignore="NestedWeights" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_search_exit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="3dp"
|
||||
android:maxLines="1"
|
||||
android:text="退出"
|
||||
android:textColor="@color/primaryText"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- <View-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="match_parent"-->
|
||||
<!-- android:layout_weight="2" />-->
|
||||
<!-- <!–设置按钮–>-->
|
||||
<!-- <LinearLayout-->
|
||||
<!-- android:id="@+id/ll_setting"-->
|
||||
<!-- android:layout_width="50dp"-->
|
||||
<!-- android:layout_height="50dp"-->
|
||||
<!-- android:background="?android:attr/selectableItemBackgroundBorderless"-->
|
||||
<!-- android:clickable="true"-->
|
||||
<!-- android:contentDescription="@string/setting"-->
|
||||
<!-- android:focusable="true"-->
|
||||
<!-- android:orientation="vertical"-->
|
||||
<!-- android:paddingBottom="7dp">-->
|
||||
|
||||
<!-- <androidx.appcompat.widget.AppCompatImageView-->
|
||||
<!-- android:id="@+id/iv_setting"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- android:layout_weight="1"-->
|
||||
<!-- android:contentDescription="@string/aloud_config"-->
|
||||
<!-- android:src="@drawable/ic_settings"-->
|
||||
<!-- app:tint="@color/primaryText"-->
|
||||
<!-- tools:ignore="NestedWeights" />-->
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:id="@+id/tv_setting"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:layout_gravity="center_horizontal"-->
|
||||
<!-- android:layout_marginTop="3dp"-->
|
||||
<!-- android:maxLines="1"-->
|
||||
<!-- android:text="@string/setting"-->
|
||||
<!-- android:textColor="@color/primaryText"-->
|
||||
<!-- android:textSize="12sp" />-->
|
||||
<!-- </LinearLayout>-->
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -678,6 +678,7 @@
|
||||
<string name="alouding_disable">The selected text cannot be spoken in full text speech</string>
|
||||
<string name="read_body_to_lh">Extend to cutout</string>
|
||||
<string name="toc_updateing">Updating Chapters</string>
|
||||
|
||||
<string name="media_button_on_exit_title">Headset buttons are always available</string>
|
||||
<string name="media_button_on_exit_summary">Headset buttons are available even exit the app.</string>
|
||||
<string name="contributors">Contributors</string>
|
||||
@ -903,6 +904,7 @@
|
||||
<string name="login_header">登录头</string>
|
||||
<string name="font_scale">字体大小</string>
|
||||
<string name="font_scale_summary">当前字体大小:%.1f</string>
|
||||
<string name="search_content_size">search result</string>
|
||||
<string name="tts_speech_reduce">语速减</string>
|
||||
<string name="tts_speech_add">语速加</string>
|
||||
<string name="open_sys_dir_picker_error">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>
|
||||
|
@ -907,6 +907,7 @@
|
||||
<string name="login_header">登录头</string>
|
||||
<string name="font_scale">字体大小</string>
|
||||
<string name="font_scale_summary">当前字体大小:%.1f</string>
|
||||
<string name="search_content_size">search result</string>
|
||||
<string name="tts_speech_reduce">语速减</string>
|
||||
<string name="tts_speech_add">语速加</string>
|
||||
<string name="open_sys_dir_picker_error">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>
|
||||
|
@ -907,6 +907,7 @@
|
||||
<string name="login_header">登录头</string>
|
||||
<string name="font_scale">字体大小</string>
|
||||
<string name="font_scale_summary">当前字体大小:%.1f</string>
|
||||
<string name="search_content_size">search result</string>
|
||||
<string name="tts_speech_reduce">语速减</string>
|
||||
<string name="tts_speech_add">语速加</string>
|
||||
<string name="open_sys_dir_picker_error">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>
|
||||
|
@ -898,6 +898,7 @@
|
||||
<string name="open_fun">打開方式</string>
|
||||
<string name="use_browser_open">是否使用外部瀏覽器打開?</string>
|
||||
<string name="see">查看</string>
|
||||
<string name="search_content_size">搜索結果</string>
|
||||
<string name="open">打開</string>
|
||||
<string name="del_login_header">刪除登錄頭</string>
|
||||
<string name="show_login_header">查看登錄頭</string>
|
||||
|
@ -910,5 +910,6 @@
|
||||
<string name="tts_speech_add">語速加</string>
|
||||
<string name="open_sys_dir_picker_error">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>
|
||||
<string name="expand_text_menu">展开文本选择菜单</string>
|
||||
<string name="search_content_size">搜索結果</string>
|
||||
|
||||
</resources>
|
||||
|
@ -777,6 +777,7 @@
|
||||
<string name="select_theme">切换默认主题</string>
|
||||
<string name="sort_by_lastUpdateTime">更新时间排序</string>
|
||||
<string name="search_content">全文搜索</string>
|
||||
<string name="search_content_size">搜索结果</string>
|
||||
<string name="rss_source_empty">关注公众号[开源阅读]获取订阅源!</string>
|
||||
<string name="explore_empty">当前没有发现源,关注公众号[开源阅读]添加带发现的书源!</string>
|
||||
<string name="page_key_set_help">将焦点放到输入框按下物理按键会自动录入键值,多个按键会自动用英文逗号隔开.</string>
|
||||
|
@ -907,6 +907,7 @@
|
||||
<string name="login_header">login header</string>
|
||||
<string name="font_scale">font scale</string>
|
||||
<string name="font_scale_summary">font scale:%.1f</string>
|
||||
<string name="search_content_size">search result</string>
|
||||
<string name="tts_speech_reduce">语速减</string>
|
||||
<string name="tts_speech_add">语速加</string>
|
||||
<string name="open_sys_dir_picker_error">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>
|
||||
|
Loading…
Reference in New Issue
Block a user