basic search menu skeleton

pull/1395/head
Jason Yao 3 years ago
parent 0b00a5dd00
commit d2c0cfd478
  1. 18
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  2. 204
      app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt
  3. 115
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt
  4. 1
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentAdapter.kt
  5. 79
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt
  6. 41
      app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt
  7. 6
      app/src/main/res/layout/activity_book_read.xml
  8. 276
      app/src/main/res/layout/view_search_menu.xml

@ -65,6 +65,7 @@ class ReadBookActivity : BaseReadBookActivity(),
TextActionMenu.CallBack, TextActionMenu.CallBack,
ContentTextView.CallBack, ContentTextView.CallBack,
ReadMenu.CallBack, ReadMenu.CallBack,
SearchMenu.CallBack,
ReadAloudDialog.CallBack, ReadAloudDialog.CallBack,
ChangeSourceDialog.CallBack, ChangeSourceDialog.CallBack,
ReadBook.CallBack, ReadBook.CallBack,
@ -117,6 +118,7 @@ class ReadBookActivity : BaseReadBookActivity(),
private var backupJob: Job? = null private var backupJob: Job? = null
override var autoPageProgress = 0 override var autoPageProgress = 0
override var isAutoPage = false override var isAutoPage = false
override var isShowingSearchResult = true
private var screenTimeOut: Long = 0 private var screenTimeOut: Long = 0
private var timeBatteryReceiver: TimeBatteryReceiver? = null private var timeBatteryReceiver: TimeBatteryReceiver? = null
private var loadStates: Boolean = false private var loadStates: Boolean = false
@ -635,6 +637,7 @@ class ReadBookActivity : BaseReadBookActivity(),
when { when {
BaseReadAloudService.isRun -> showReadAloudDialog() BaseReadAloudService.isRun -> showReadAloudDialog()
isAutoPage -> showDialogFragment<AutoReadDialog>() isAutoPage -> showDialogFragment<AutoReadDialog>()
isShowingSearchResult -> binding.searchMenu.runMenuIn()
else -> binding.readMenu.runMenuIn() else -> binding.readMenu.runMenuIn()
} }
} }
@ -766,6 +769,14 @@ class ReadBookActivity : BaseReadBookActivity(),
showDialogFragment<MoreConfigDialog>() showDialogFragment<MoreConfigDialog>()
} }
override fun returnSearchActivity() {
TODO("Not yet implemented")
}
override fun showSearchSetting() {
TODO("Not yet implemented")
}
/** /**
* 更新状态栏,导航栏 * 更新状态栏,导航栏
*/ */
@ -774,6 +785,13 @@ class ReadBookActivity : BaseReadBookActivity(),
upNavigationBarColor() upNavigationBarColor()
} }
override fun searchExit() {
if(isShowingSearchResult){
isShowingSearchResult = false
binding.searchMenu.invalidate()
}
}
override fun showLogin() { override fun showLogin() {
ReadBook.bookSource?.let { ReadBook.bookSource?.let {
startActivity<SourceLoginActivity> { startActivity<SourceLoginActivity> {

@ -0,0 +1,204 @@
package io.legado.app.ui.book.read
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import android.view.WindowManager
import android.view.animation.Animation
import android.widget.FrameLayout
import android.widget.SeekBar
import androidx.activity.viewModels
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.databinding.ViewSearchMenuBinding
import io.legado.app.help.*
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.*
import io.legado.app.model.ReadBook
import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.searchContent.SearchContentViewModel
import io.legado.app.ui.browser.WebViewActivity
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
import io.legado.app.utils.*
import splitties.views.*
/**
* 阅读界面菜单
*/
class SearchMenu @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
private val searchView: SearchView by lazy {
binding.titleBar.findViewById(R.id.search_view)
}
val viewModel by viewModels<SearchContentViewModel>()
private val callBack: CallBack get() = activity as CallBack
private val binding = ViewSearchMenuBinding.inflate(LayoutInflater.from(context), this, true)
private val menuTopIn: Animation = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_top_in)
private val menuTopOut: Animation = AnimationUtilsSupport.loadAnimation(context, R.anim.anim_readbook_top_out)
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 var hasSearchResult: Boolean = true
init {
initAnimation()
initView()
bindEvent()
}
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)
ivSearchContentBottom.setColorFilter(textColor)
ivSearchContentTop.setColorFilter(textColor)
}
fun runMenuIn() {
this.visible()
binding.titleBar.visible()
binding.llSearchBaseInfo.visible()
binding.llBottomBg.visible()
binding.titleBar.startAnimation(menuTopIn)
binding.llSearchBaseInfo.startAnimation(menuBottomIn)
binding.llBottomBg.startAnimation(menuBottomIn)
}
fun runMenuOut(onMenuOutEnd: (() -> Unit)? = null) {
this.onMenuOutEnd = onMenuOutEnd
if (this.isVisible) {
binding.titleBar.startAnimation(menuTopOut)
binding.llSearchBaseInfo.startAnimation(menuBottomOut)
binding.llBottomBg.startAnimation(menuBottomOut)
}
}
private fun bindEvent() = binding.run {
titleBar.toolbar.setOnClickListener {
ReadBook.book?.let {
context.startActivity<BookInfoActivity> {
putExtra("name", it.name)
putExtra("author", it.author)
}
}
}
llSearchResults.setOnClickListener {
runMenuOut {
callBack.returnSearchActivity()
}
}
//主菜单
llMainMenu.setOnClickListener {
runMenuOut {
callBack.showMenuBar()
}
}
//目录
llSearchExit.setOnClickListener {
runMenuOut {
callBack.searchExit()
}
}
//设置
llSetting.setOnClickListener {
runMenuOut {
callBack.showSearchSetting()
}
}
}
private fun initAnimation() {
//显示菜单
menuTopIn.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
})
//隐藏菜单
menuTopOut.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {
binding.vwMenuBg.setOnClickListener(null)
}
override fun onAnimationEnd(animation: Animation) {
this@SearchMenu.invisible()
binding.titleBar.invisible()
binding.llSearchBaseInfo.invisible()
binding.llBottomBg.invisible()
binding.fabRight.invisible()
binding.fabLeft.invisible()
onMenuOutEnd?.invoke()
callBack.upSystemUiVisibility()
}
override fun onAnimationRepeat(animation: Animation) = Unit
})
}
interface CallBack {
var isShowingSearchResult: Boolean
fun returnSearchActivity()
fun showSearchSetting()
fun upSystemUiVisibility()
fun searchExit()
fun showMenuBar()
}
}

@ -5,7 +5,6 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import com.github.liuyueyi.quick.transfer.ChineseUtils
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.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.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.databinding.ActivitySearchContentBinding import io.legado.app.databinding.ActivitySearchContentBinding
import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.lib.theme.bottomBackground import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor import io.legado.app.lib.theme.getPrimaryTextColor
@ -40,9 +38,7 @@ class SearchContentActivity :
private val searchView: SearchView by lazy { private val searchView: SearchView by lazy {
binding.titleBar.findViewById(R.id.search_view) binding.titleBar.findViewById(R.id.search_view)
} }
private var searchResultCounts = 0
private var durChapterIndex = 0 private var durChapterIndex = 0
private var searchResultList: MutableList<SearchResult> = mutableListOf()
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
val bbg = bottomBackground val bbg = bottomBackground
@ -103,7 +99,7 @@ class SearchContentActivity :
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun initBook() { private fun initBook() {
binding.tvCurrentSearchInfo.text = "搜索结果:$searchResultCounts" binding.tvCurrentSearchInfo.text = "搜索结果:${viewModel.searchResultCounts}"
viewModel.book?.let { viewModel.book?.let {
initCacheFileNames(it) initCacheFileNames(it)
durChapterIndex = it.durChapterIndex durChapterIndex = it.durChapterIndex
@ -115,7 +111,7 @@ class SearchContentActivity :
private fun initCacheFileNames(book: Book) { private fun initCacheFileNames(book: Book) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book)) viewModel.cacheChapterNames.addAll(BookHelp.getChapterFiles(book))
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
adapter.notifyItemRangeChanged(0, adapter.itemCount, true) adapter.notifyItemRangeChanged(0, adapter.itemCount, true)
} }
@ -126,7 +122,7 @@ class SearchContentActivity :
observeEvent<BookChapter>(EventBus.SAVE_CONTENT) { chapter -> observeEvent<BookChapter>(EventBus.SAVE_CONTENT) { chapter ->
viewModel.book?.bookUrl?.let { bookUrl -> viewModel.book?.bookUrl?.let { bookUrl ->
if (chapter.bookUrl == bookUrl) { if (chapter.bookUrl == bookUrl) {
adapter.cacheFileNames.add(chapter.getFileName()) viewModel.cacheChapterNames.add(chapter.getFileName())
adapter.notifyItemChanged(chapter.index, true) adapter.notifyItemChanged(chapter.index, true)
} }
} }
@ -134,28 +130,26 @@ class SearchContentActivity :
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun startContentSearch(newText: String) { fun startContentSearch(query: String) {
// 按章节搜索内容 // 按章节搜索内容
if (newText.isNotBlank()) { if (query.isNotBlank()) {
adapter.clearItems() adapter.clearItems()
searchResultList.clear() viewModel.searchResultList.clear()
binding.refreshProgressBar.isAutoLoading = true viewModel.searchResultCounts = 0
searchResultCounts = 0 viewModel.lastQuery = query
viewModel.lastQuery = newText
var searchResults = listOf<SearchResult>() var searchResults = listOf<SearchResult>()
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
appDb.bookChapterDao.getChapterList(viewModel.bookUrl).map { chapter -> appDb.bookChapterDao.getChapterList(viewModel.bookUrl).map { bookChapter ->
binding.refreshProgressBar.isAutoLoading = true
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
if (isLocalBook if (isLocalBook || viewModel.cacheChapterNames.contains(bookChapter.getFileName())) {
|| adapter.cacheFileNames.contains(chapter.getFileName()) searchResults = viewModel.searchChapter(query, bookChapter)
) {
searchResults = searchChapter(newText, chapter)
} }
} }
if (searchResults.isNotEmpty()) { if (searchResults.isNotEmpty()) {
searchResultList.addAll(searchResults) viewModel.searchResultList.addAll(searchResults)
binding.refreshProgressBar.isAutoLoading = false binding.refreshProgressBar.isAutoLoading = false
binding.tvCurrentSearchInfo.text = "搜索结果:$searchResultCounts" binding.tvCurrentSearchInfo.text = "搜索结果:${viewModel.searchResultCounts}"
adapter.addItems(searchResults) adapter.addItems(searchResults)
searchResults = listOf() searchResults = listOf()
} }
@ -164,85 +158,6 @@ 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 val isLocalBook: Boolean
get() = viewModel.book?.isLocalBook() == true get() = viewModel.book?.isLocalBook() == true
@ -251,7 +166,7 @@ class SearchContentActivity :
searchData.putExtra("index", searchResult.chapterIndex) searchData.putExtra("index", searchResult.chapterIndex)
searchData.putExtra("contentPosition", searchResult.contentPosition) searchData.putExtra("contentPosition", searchResult.contentPosition)
searchData.putExtra("query", searchResult.query) searchData.putExtra("query", searchResult.query)
searchData.putExtra("indexWithinChapter", searchResult.indexWithinChapter) searchData.putExtra("indexWithinChapter", searchResult.resultCountWithinChapter)
setResult(RESULT_OK, searchData) setResult(RESULT_OK, searchData)
finish() finish()
} }

@ -14,7 +14,6 @@ import io.legado.app.utils.hexString
class SearchContentAdapter(context: Context, val callback: Callback) : class SearchContentAdapter(context: Context, val callback: Callback) :
RecyclerAdapter<SearchResult, ItemSearchListBinding>(context) { RecyclerAdapter<SearchResult, ItemSearchListBinding>(context) {
val cacheFileNames = hashSetOf<String>()
val textColor = context.getCompatColor(R.color.primaryText).hexString.substring(2) val textColor = context.getCompatColor(R.color.primaryText).hexString.substring(2)
val accentColor = context.accentColor.hexString.substring(2) val accentColor = context.accentColor.hexString.substring(2)

@ -2,16 +2,25 @@ package io.legado.app.ui.book.searchContent
import android.app.Application import android.app.Application
import com.github.liuyueyi.quick.transfer.ChineseUtils
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.Book 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 io.legado.app.help.ContentProcessor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class SearchContentViewModel(application: Application) : BaseViewModel(application) { class SearchContentViewModel(application: Application) : BaseViewModel(application) {
var bookUrl: String = "" var bookUrl: String = ""
var book: Book? = null var book: Book? = null
var contentProcessor: ContentProcessor? = null private var contentProcessor: ContentProcessor? = null
var lastQuery: String = "" var lastQuery: String = ""
var searchResultCounts = 0
val cacheChapterNames = hashSetOf<String>()
val searchResultList: MutableList<SearchResult> = mutableListOf()
fun initBook(bookUrl: String, success: () -> Unit) { fun initBook(bookUrl: String, success: () -> Unit) {
this.bookUrl = bookUrl this.bookUrl = bookUrl
@ -25,4 +34,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,
contentPosition = position
)
searchResultsWithinChapter.add(result)
}
searchResultCounts += searchResultsWithinChapter.size
}
}
}
return searchResultsWithinChapter
}
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 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 import androidx.core.text.HtmlCompat
data class SearchResult( data class SearchResult(
var index: Int = 0, val resultCount: Int = 0,
var indexWithinChapter: Int = 0, val resultCountWithinChapter: Int = 0,
var text: String = "", val resultText: String = "",
var chapterTitle: String = "", val chapterTitle: String = "",
val query: String, val query: String,
var pageSize: Int = 0, val pageSize: Int = 0,
var chapterIndex: Int = 0, val chapterIndex: Int = 0,
var pageIndex: Int = 0, val pageIndex: Int = 0,
var newPosition: Int = 0, val queryIndexInResult: Int = 0,
var contentPosition: Int = 0 val contentPosition: Int = 0
) { ) {
fun getHtmlCompat(textColor: String, accentColor: String): Spanned { fun getHtmlCompat(textColor: String, accentColor: String): Spanned {
val html = colorPresentText(newPosition, query, text, textColor, accentColor) + val queryIndexInSurrounding = resultText.indexOf(query)
"<font color=#${accentColor}>($chapterTitle)</font>" 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) return HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
} }
private fun colorPresentText( private fun String.colorTextForHtml(textColor: String) = "<font color=#${textColor}>$this</font>"
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>"
}
} }

@ -37,6 +37,12 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone" /> 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 <View
android:id="@+id/navigation_bar" android:id="@+id/navigation_bar"
android:layout_width="match_parent" android:layout_width="match_parent"

@ -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" />
<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:src="@drawable/ic_search"
android:tint="@color/primaryText"
android:tooltipText="@string/search_content"
app:backgroundTint="@color/background_menu"
app:elevation="2dp"
app:fabSize="mini"
app:pressedTranslationZ="2dp"
tools:ignore="UnusedAttribute"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<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_search"
android:tint="@color/primaryText"
android:tooltipText="@string/search_content"
app:backgroundTint="@color/background_menu"
app:elevation="2dp"
app:fabSize="mini"
app:pressedTranslationZ="2dp"
tools:ignore="UnusedAttribute"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentLayout="@layout/view_search"
app:contentInsetRight="24dp"
app:layout_constraintTop_toTopOf="parent" />
<io.legado.app.ui.widget.anima.RefreshProgressBar
android:id="@+id/refresh_progress_bar"
android:layout_width="match_parent"
android:layout_height="2dp"
app:layout_constraintTop_toBottomOf="@id/title_bar"/>
<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">
<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>
<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">
<!--结果按钮-->
<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:text="结果"
android:maxLines="1"
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:text="@string/main_menu"
android:maxLines="1"
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:text="退出"
android:maxLines="1"
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:text="@string/setting"
android:maxLines="1"
android:textColor="@color/primaryText"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Loading…
Cancel
Save