commit
						1bfd9fa755
					
				@ -0,0 +1,93 @@ | 
				
			||||
package io.legado.app.ui.book.searchContent | 
				
			||||
 | 
				
			||||
import android.os.Bundle | 
				
			||||
import android.view.Menu | 
				
			||||
import androidx.appcompat.widget.SearchView | 
				
			||||
import androidx.core.view.isGone | 
				
			||||
import androidx.fragment.app.Fragment | 
				
			||||
import androidx.fragment.app.FragmentManager | 
				
			||||
import androidx.fragment.app.FragmentPagerAdapter | 
				
			||||
import io.legado.app.R | 
				
			||||
import io.legado.app.base.VMBaseActivity | 
				
			||||
import io.legado.app.lib.theme.ATH | 
				
			||||
import io.legado.app.lib.theme.accentColor | 
				
			||||
import io.legado.app.lib.theme.primaryTextColor | 
				
			||||
import io.legado.app.utils.getViewModel | 
				
			||||
import io.legado.app.utils.gone | 
				
			||||
import io.legado.app.utils.visible | 
				
			||||
import kotlinx.android.synthetic.main.activity_chapter_list.* | 
				
			||||
import kotlinx.android.synthetic.main.view_tab_layout.* | 
				
			||||
 | 
				
			||||
 | 
				
			||||
class SearchListActivity : VMBaseActivity<SearchListViewModel>(R.layout.activity_search_list) { | 
				
			||||
    // todo: 完善搜索界面UI | 
				
			||||
    override val viewModel: SearchListViewModel | 
				
			||||
        get() = getViewModel(SearchListViewModel::class.java) | 
				
			||||
 | 
				
			||||
    private var searchView: SearchView? = null | 
				
			||||
 | 
				
			||||
    override fun onActivityCreated(savedInstanceState: Bundle?) { | 
				
			||||
        tab_layout.isTabIndicatorFullWidth = false | 
				
			||||
        tab_layout.setSelectedTabIndicatorColor(accentColor) | 
				
			||||
        intent.getStringExtra("bookUrl")?.let { | 
				
			||||
            viewModel.initBook(it) { | 
				
			||||
                view_pager.adapter = TabFragmentPageAdapter(supportFragmentManager) | 
				
			||||
                tab_layout.setupWithViewPager(view_pager) | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { | 
				
			||||
        menuInflater.inflate(R.menu.search_view, menu) | 
				
			||||
        val search = menu.findItem(R.id.menu_search) | 
				
			||||
        searchView = search.actionView as SearchView | 
				
			||||
        ATH.setTint(searchView!!, primaryTextColor) | 
				
			||||
        searchView?.maxWidth = resources.displayMetrics.widthPixels | 
				
			||||
        searchView?.onActionViewCollapsed() | 
				
			||||
        searchView?.setOnCloseListener { | 
				
			||||
            tab_layout.visible() | 
				
			||||
            //to do clean | 
				
			||||
            false | 
				
			||||
        } | 
				
			||||
        searchView?.setOnSearchClickListener { tab_layout.gone() } | 
				
			||||
        searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | 
				
			||||
            override fun onQueryTextSubmit(query: String): Boolean { | 
				
			||||
                if (viewModel.lastQuery != query){ | 
				
			||||
                    viewModel.startContentSearch(query) | 
				
			||||
                } | 
				
			||||
                return false | 
				
			||||
            } | 
				
			||||
 | 
				
			||||
            override fun onQueryTextChange(newText: String): Boolean { | 
				
			||||
 | 
				
			||||
                return false | 
				
			||||
            } | 
				
			||||
        }) | 
				
			||||
        return super.onCompatCreateOptionsMenu(menu) | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    private inner class TabFragmentPageAdapter(fm: FragmentManager) : | 
				
			||||
        FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { | 
				
			||||
        override fun getItem(position: Int): Fragment { | 
				
			||||
            return SearchListFragment() | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        override fun getCount(): Int { | 
				
			||||
            return 1 | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
        override fun getPageTitle(position: Int): CharSequence? { | 
				
			||||
            return "Search" | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    override fun onBackPressed() { | 
				
			||||
        if (tab_layout.isGone) { | 
				
			||||
            searchView?.onActionViewCollapsed() | 
				
			||||
            tab_layout.visible() | 
				
			||||
        } else { | 
				
			||||
            super.onBackPressed() | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,51 @@ | 
				
			||||
package io.legado.app.ui.book.searchContent | 
				
			||||
 | 
				
			||||
import android.content.Context | 
				
			||||
import android.os.Build | 
				
			||||
import android.text.Html | 
				
			||||
import android.util.Log | 
				
			||||
import android.view.View | 
				
			||||
import androidx.annotation.RequiresApi | 
				
			||||
import androidx.core.text.HtmlCompat | 
				
			||||
import io.legado.app.R | 
				
			||||
import io.legado.app.base.adapter.ItemViewHolder | 
				
			||||
import io.legado.app.base.adapter.SimpleRecyclerAdapter | 
				
			||||
import io.legado.app.data.entities.BookChapter | 
				
			||||
import io.legado.app.help.BookHelp | 
				
			||||
import io.legado.app.lib.theme.accentColor | 
				
			||||
import io.legado.app.utils.getCompatColor | 
				
			||||
import io.legado.app.utils.visible | 
				
			||||
import kotlinx.android.synthetic.main.item_bookmark.view.* | 
				
			||||
import kotlinx.android.synthetic.main.item_search_list.view.* | 
				
			||||
import org.jetbrains.anko.sdk27.listeners.onClick | 
				
			||||
 | 
				
			||||
class SearchListAdapter(context: Context, val callback: Callback) : | 
				
			||||
    SimpleRecyclerAdapter<SearchResult>(context, R.layout.item_search_list) { | 
				
			||||
 | 
				
			||||
    val cacheFileNames = hashSetOf<String>() | 
				
			||||
 | 
				
			||||
    override fun convert(holder: ItemViewHolder, item: SearchResult, payloads: MutableList<Any>) { | 
				
			||||
        with(holder.itemView) { | 
				
			||||
            val isDur = callback.durChapterIndex() == item.chapterIndex | 
				
			||||
            if (payloads.isEmpty()) { | 
				
			||||
                tv_search_result.text = item.parseText(item.presentText) | 
				
			||||
                if (isDur){ | 
				
			||||
                    tv_search_result.paint.isFakeBoldText = true | 
				
			||||
                } | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    override fun registerListener(holder: ItemViewHolder) { | 
				
			||||
        holder.itemView.onClick { | 
				
			||||
            getItem(holder.layoutPosition)?.let { | 
				
			||||
                callback.openSearchResult(it) | 
				
			||||
            } | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    interface Callback { | 
				
			||||
        fun openSearchResult(searchResult: SearchResult) | 
				
			||||
        fun durChapterIndex(): Int | 
				
			||||
    } | 
				
			||||
} | 
				
			||||
@ -0,0 +1,239 @@ | 
				
			||||
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) | 
				
			||||
                        } | 
				
			||||
                    } | 
				
			||||
                    job.await() | 
				
			||||
                    if (searchResults.isNotEmpty()){ | 
				
			||||
                        searchResultList.addAll(searchResults) | 
				
			||||
                        tv_current_search_info.text = "搜索结果:$searchResultCounts" | 
				
			||||
                        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) | 
				
			||||
        activity?.setResult(RESULT_OK, searchData) | 
				
			||||
        activity?.finish() | 
				
			||||
 | 
				
			||||
 | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    override fun durChapterIndex(): Int { | 
				
			||||
        return durChapterIndex | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,33 @@ | 
				
			||||
package io.legado.app.ui.book.searchContent | 
				
			||||
 | 
				
			||||
 | 
				
			||||
import android.app.Application | 
				
			||||
import io.legado.app.App | 
				
			||||
import io.legado.app.base.BaseViewModel | 
				
			||||
import io.legado.app.data.entities.Book | 
				
			||||
 | 
				
			||||
class SearchListViewModel(application: Application) : BaseViewModel(application) { | 
				
			||||
    var bookUrl: String = "" | 
				
			||||
    var book: Book? = null | 
				
			||||
    var searchCallBack: SearchListCallBack? = null | 
				
			||||
    var lastQuery: String = "" | 
				
			||||
 | 
				
			||||
    fun initBook(bookUrl: String, success: () -> Unit) { | 
				
			||||
        this.bookUrl = bookUrl | 
				
			||||
        execute { | 
				
			||||
            book = App.db.bookDao().getBook(bookUrl) | 
				
			||||
        }.onSuccess { | 
				
			||||
            success.invoke() | 
				
			||||
        } | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    fun startContentSearch(newText: String) { | 
				
			||||
        searchCallBack?.startContentSearch(newText) | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
 | 
				
			||||
    interface SearchListCallBack { | 
				
			||||
        fun startContentSearch(newText: String) | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,40 @@ | 
				
			||||
package io.legado.app.ui.book.searchContent | 
				
			||||
 | 
				
			||||
import android.text.Spanned | 
				
			||||
import android.util.Log | 
				
			||||
import androidx.core.text.HtmlCompat | 
				
			||||
import io.legado.app.ui.book.read.page.entities.TextPage | 
				
			||||
 | 
				
			||||
data class SearchResult( | 
				
			||||
    var index: Int = 0, | 
				
			||||
    var indexWithinChapter: Int = 0, | 
				
			||||
    var text: String = "", | 
				
			||||
    var 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 presentText: String | 
				
			||||
        get(){ | 
				
			||||
            return colorPresentText(newPosition, query, text) + | 
				
			||||
                    "<font color=#0000ff>($chapterTitle)</font>" | 
				
			||||
        } | 
				
			||||
 | 
				
			||||
    fun colorPresentText(position: Int, center: String, targetText: String): String{ | 
				
			||||
        val sub1 = text.substring(0, position) | 
				
			||||
        val sub2 = text.substring(position + center.length, targetText.length) | 
				
			||||
        return "<font color=#000000>$sub1</font>" + | 
				
			||||
                "<font color=#ff0000>$center</font>" + | 
				
			||||
                "<font color=#000000>$sub2</font>" | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    fun parseText(targetText: String): Spanned { | 
				
			||||
        return HtmlCompat.fromHtml(targetText, HtmlCompat.FROM_HTML_MODE_LEGACY) | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
} | 
				
			||||
@ -0,0 +1,19 @@ | 
				
			||||
<?xml version="1.0" encoding="utf-8"?> | 
				
			||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | 
				
			||||
              xmlns:app="http://schemas.android.com/apk/res-auto" | 
				
			||||
              android:orientation="vertical" | 
				
			||||
              android:layout_width="match_parent" | 
				
			||||
              android:layout_height="match_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_tab_layout"/> | 
				
			||||
 | 
				
			||||
    <androidx.viewpager.widget.ViewPager | 
				
			||||
            android:id="@+id/view_pager" | 
				
			||||
            android:layout_width="match_parent" | 
				
			||||
            android:layout_height="match_parent"/> | 
				
			||||
 | 
				
			||||
</LinearLayout> | 
				
			||||
@ -0,0 +1,66 @@ | 
				
			||||
<?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> | 
				
			||||
@ -0,0 +1,19 @@ | 
				
			||||
<?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" | 
				
			||||
    android:orientation="vertical" | 
				
			||||
    android:layout_width="match_parent" | 
				
			||||
    android:layout_height="wrap_content" | 
				
			||||
    android:background="?android:attr/selectableItemBackground" | 
				
			||||
    android:padding="12dp"> | 
				
			||||
 | 
				
			||||
    <TextView | 
				
			||||
        android:id="@+id/tv_search_result" | 
				
			||||
        android:layout_width="match_parent" | 
				
			||||
        android:layout_height="wrap_content" | 
				
			||||
        android:singleLine="false" | 
				
			||||
        app:layout_constraintTop_toTopOf="parent" | 
				
			||||
        app:layout_constraintLeft_toLeftOf="parent" | 
				
			||||
        app:layout_constraintRight_toLeftOf="parent" /> | 
				
			||||
 | 
				
			||||
</androidx.constraintlayout.widget.ConstraintLayout> | 
				
			||||
					Loading…
					
					
				
		Reference in new issue