|
@@ -0,0 +1,365 @@
|
|
|
|
+package com.kdanmobile.reader.screen.view
|
|
|
|
+
|
|
|
|
+import android.content.Context
|
|
|
|
+import android.graphics.RectF
|
|
|
|
+import android.os.Handler
|
|
|
|
+import android.os.Message
|
|
|
|
+import android.text.InputType
|
|
|
|
+import android.util.AttributeSet
|
|
|
|
+import android.view.Gravity
|
|
|
|
+import android.view.LayoutInflater
|
|
|
|
+import android.view.View
|
|
|
|
+import android.view.inputmethod.EditorInfo
|
|
|
|
+import android.view.inputmethod.InputMethodManager
|
|
|
|
+import android.widget.*
|
|
|
|
+import com.kdanmobile.kmpdfkit.pdfcommon.TextWord
|
|
|
|
+import com.kdanmobile.reader.R
|
|
|
|
+import com.kdanmobile.reader.ReaderViewModel
|
|
|
|
+import com.kdanmobile.reader.screen.adapter.SearchAdapter
|
|
|
|
+import com.kdanmobile.reader.screen.model.SearchResultInfo
|
|
|
|
+import com.kdanmobile.reader.screen.handler.PdfInfoHandler
|
|
|
|
+import com.kdanmobile.reader.screen.handler.SearchHandler
|
|
|
|
+import com.kdanmobile.reader.screen.model.SearchTaskResult
|
|
|
|
+import com.kdanmobile.reader.screen.model.SimpleTextWatcher
|
|
|
|
+import com.kdanmobile.reader.utils.DensityUtil
|
|
|
|
+import io.reactivex.Completable
|
|
|
|
+import io.reactivex.android.schedulers.AndroidSchedulers
|
|
|
|
+import io.reactivex.disposables.Disposable
|
|
|
|
+import io.reactivex.schedulers.Schedulers
|
|
|
|
+import java.util.ArrayList
|
|
|
|
+import kotlinx.android.synthetic.main.view_search.view.*
|
|
|
|
+import java.lang.ref.WeakReference
|
|
|
|
+
|
|
|
|
+class SearchView: RelativeLayout, View.OnClickListener {
|
|
|
|
+ private lateinit var pdfInfoHandler: PdfInfoHandler
|
|
|
|
+ private lateinit var searchHandler: SearchHandler
|
|
|
|
+ private lateinit var linearLayout: LinearLayout
|
|
|
|
+ private lateinit var tv_page: TextView
|
|
|
|
+ private lateinit var tv_text:TextView
|
|
|
|
+ private lateinit var et_search: EditText
|
|
|
|
+ private lateinit var lv: ListView
|
|
|
|
+ private lateinit var tv_tishi: TextView
|
|
|
|
+ private lateinit var adapter: SearchAdapter
|
|
|
|
+ private val list = ArrayList<SearchResultInfo>()
|
|
|
|
+
|
|
|
|
+ // 判断搜索的是否是页码 true 是页码
|
|
|
|
+ private var isPage = false
|
|
|
|
+
|
|
|
|
+ private var msgHandler: Handler? = null
|
|
|
|
+
|
|
|
|
+ private val MAX_SEARCH_RESULT = 200
|
|
|
|
+
|
|
|
|
+ private var isDone = false
|
|
|
|
+ private var deviceWidth: Int = 0
|
|
|
|
+
|
|
|
|
+ private var disposable: Disposable? = null
|
|
|
|
+
|
|
|
|
+ constructor(context: Context, viewModel: ReaderViewModel) : super(context) {
|
|
|
|
+ initView(viewModel, viewModel)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ constructor(context: Context, attributeSet: AttributeSet, viewModel: ReaderViewModel) : super(context, attributeSet) {
|
|
|
|
+ initView(viewModel, viewModel)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int, viewModel: ReaderViewModel) : super(context, attributeSet, defStyleAttr) {
|
|
|
|
+ initView(viewModel, viewModel)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun initView(pdfInfoHandler: PdfInfoHandler, searchHandler: SearchHandler) {
|
|
|
|
+ LayoutInflater.from(context).inflate(R.layout.view_search, this)
|
|
|
|
+
|
|
|
|
+ this.pdfInfoHandler = pdfInfoHandler
|
|
|
|
+ this.searchHandler = searchHandler
|
|
|
|
+
|
|
|
|
+ initHandler()
|
|
|
|
+
|
|
|
|
+ initView()
|
|
|
|
+
|
|
|
|
+ setListener()
|
|
|
|
+
|
|
|
|
+ postDelayed( { showSoftKeyboard() }, 200)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun initHandler() {
|
|
|
|
+
|
|
|
|
+ class MyHandler(view: SearchView) : Handler() {
|
|
|
|
+ private val mView: WeakReference<SearchView> = WeakReference(view)
|
|
|
|
+
|
|
|
|
+ override fun handleMessage(msg: Message) {
|
|
|
|
+ val view = mView.get()
|
|
|
|
+ if (view != null) {
|
|
|
|
+ when (msg.what) {
|
|
|
|
+ 10 -> {
|
|
|
|
+ if (msg.obj is List<*>) {
|
|
|
|
+ view.lv.visibility = View.VISIBLE
|
|
|
|
+ view.tv_tishi.visibility = View.GONE
|
|
|
|
+ val item = (msg.obj as List<*>).filterIsInstance<SearchResultInfo>()
|
|
|
|
+ view.list.addAll(item)
|
|
|
|
+ view.adapter.add(item)
|
|
|
|
+ view.adapter.notifyDataSetChanged()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ 20 -> search()
|
|
|
|
+ 30 -> {
|
|
|
|
+ val position = msg.arg1
|
|
|
|
+ val info = view.list[position]
|
|
|
|
+ SearchTaskResult.set(SearchTaskResult(info.search, info.page, arrayOf(info.rf)))
|
|
|
|
+ view.pdfInfoHandler.setCurrentPage(view.list[position].page)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ msgHandler = MyHandler(this)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun initView() {
|
|
|
|
+
|
|
|
|
+ linearLayout = ll_fragmentPdfReaderSearch
|
|
|
|
+ tv_page = tv_fragmentPdfReaderSearch_page
|
|
|
|
+ tv_text = tv_fragmentPdfReaderSearch_text
|
|
|
|
+ et_search = et_fragmentPdfReaderSearch_
|
|
|
|
+
|
|
|
|
+ et_search.setHint(R.string.fragment_search_enter_text)
|
|
|
|
+ et_search.inputType = InputType.TYPE_CLASS_TEXT
|
|
|
|
+ et_search.isLongClickable = false
|
|
|
|
+ lv = lv_fragmentPdfReaderSearch_
|
|
|
|
+ tv_tishi = tv_fragmentPdfReaderSearch_tishi
|
|
|
|
+
|
|
|
|
+ var navigationHeight = 0
|
|
|
|
+ val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
|
|
|
+ if (resourceId > 0) {
|
|
|
|
+ val rid = resources.getIdentifier("config_showNavigationBar", "bool", "android")
|
|
|
|
+ if (resources.getBoolean(rid)) {
|
|
|
|
+ navigationHeight = resources.getDimensionPixelSize(resourceId)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ val screenWidth = DensityUtil.getScreenWidthPx(context)
|
|
|
|
+ val screenHeight = DensityUtil.getScreenHeightPx(context)
|
|
|
|
+ val density = DensityUtil.getDensity(context)
|
|
|
|
+ val xDp = (screenWidth / density).toInt()
|
|
|
|
+ val yDp = ((screenHeight + navigationHeight) / density).toInt()
|
|
|
|
+ val isPhone = when (xDp >= 550 && yDp >= 550) {
|
|
|
|
+ true -> false
|
|
|
|
+ false -> true
|
|
|
|
+ }
|
|
|
|
+ deviceWidth = Math.min(screenWidth, screenHeight)
|
|
|
|
+
|
|
|
|
+ linearLayout.layoutParams = LinearLayout.LayoutParams(318 * deviceWidth / 800, LinearLayout.LayoutParams.MATCH_PARENT)
|
|
|
|
+
|
|
|
|
+ val params = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
|
|
|
|
+ params.leftMargin = 21 * deviceWidth / 800
|
|
|
|
+ params.rightMargin = 21 * deviceWidth / 800
|
|
|
|
+ params.topMargin = 8 * deviceWidth / 800
|
|
|
|
+ params.bottomMargin = 8 * deviceWidth / 800
|
|
|
|
+ et_search.layoutParams = params
|
|
|
|
+ val pad = (density * 4).toInt()
|
|
|
|
+ et_search.setPadding(pad, pad, pad, pad)
|
|
|
|
+
|
|
|
|
+ if (isPhone) {
|
|
|
|
+ tv_tishi.textSize = 18f
|
|
|
|
+ } else {
|
|
|
|
+ val textSize = deviceWidth.toFloat() / 800f / density
|
|
|
|
+ tv_tishi.textSize = 24 * textSize
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ adapter = SearchAdapter(deviceWidth)
|
|
|
|
+ lv.adapter = adapter
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun setListener() {
|
|
|
|
+
|
|
|
|
+ tv_page.setOnClickListener(this)
|
|
|
|
+ tv_text.setOnClickListener(this)
|
|
|
|
+
|
|
|
|
+ lv.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
|
|
|
|
+ adapter.setSelected(position)
|
|
|
|
+
|
|
|
|
+ msgHandler?.sendMessageDelayed(msgHandler?.obtainMessage(30, position, 0), 50)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ et_search.setOnEditorActionListener { v, actionId, event ->
|
|
|
|
+ // if (actionId == EditorInfo.IME_ACTION_DONE) {
|
|
|
|
+ // search();
|
|
|
|
+ // }
|
|
|
|
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
|
|
|
+ if (!isPage && et_search.text.toString().length < 3) {
|
|
|
|
+ val la = IntArray(2)
|
|
|
|
+ et_search.getLocationOnScreen(la)
|
|
|
|
+ val y = la[1]
|
|
|
|
+ val toast = Toast.makeText(context, R.string.fileManager_thishi_search, Toast.LENGTH_SHORT)
|
|
|
|
+ toast.setGravity(Gravity.TOP or Gravity.LEFT, 11 * deviceWidth / 800, y + 30 * deviceWidth / 800)
|
|
|
|
+ toast.show()
|
|
|
|
+ } else {
|
|
|
|
+ isDone = true
|
|
|
|
+ msgHandler?.sendEmptyMessageDelayed(20, 100)
|
|
|
|
+ hideSoftKeyboard()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return@setOnEditorActionListener false
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ et_search.addTextChangedListener(object : SimpleTextWatcher() {
|
|
|
|
+ override fun onTextChanged(sequence: CharSequence, start: Int, before: Int, count: Int) {
|
|
|
|
+ super.onTextChanged(sequence, start, before, count)
|
|
|
|
+ try {
|
|
|
|
+ if (isPage && sequence.isNotEmpty()) {
|
|
|
|
+ val c = sequence[sequence.length - 1]
|
|
|
|
+ val index = sequence[0]
|
|
|
|
+ if (c < '0' || c > '9' || index == '0') {
|
|
|
|
+ val text = sequence.subSequence(0, sequence.length - 1).toString()
|
|
|
|
+ et_search.setText(text)
|
|
|
|
+ et_search.setSelection(text.length)
|
|
|
|
+ }
|
|
|
|
+ val searchSequence = et_search.text.toString()
|
|
|
|
+ if (searchSequence.isNotEmpty()) {
|
|
|
|
+ val page = Integer.parseInt(searchSequence)
|
|
|
|
+ val pageCount = pdfInfoHandler.getPdfPageCount() ?: 0
|
|
|
|
+ if (page > pageCount) {
|
|
|
|
+ val text = searchSequence.subSequence(0, searchSequence.length - 1).toString()
|
|
|
|
+ et_search.setText(text)
|
|
|
|
+ et_search.setSelection(text.length)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ } catch (e: Exception) {
|
|
|
|
+ e.printStackTrace()
|
|
|
|
+ et_search.setText("")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ override fun onClick(v: View) {
|
|
|
|
+
|
|
|
|
+ when (v.id) {
|
|
|
|
+ R.id.tv_fragmentPdfReaderSearch_page -> {
|
|
|
|
+ if (!isPage) {
|
|
|
|
+ et_search.isLongClickable = false
|
|
|
|
+ tv_page.setTextColor(-0xcc4a1b)
|
|
|
|
+ tv_text.setTextColor(-0x505051)
|
|
|
|
+ et_search.setText("")
|
|
|
|
+ et_search.setHint(R.string.fragment_search_enter_page)
|
|
|
|
+ et_search.inputType = InputType.TYPE_CLASS_NUMBER
|
|
|
|
+ et_search.setSingleLine(true)
|
|
|
|
+
|
|
|
|
+ lv.visibility = View.GONE
|
|
|
|
+ tv_tishi.visibility = View.GONE
|
|
|
|
+
|
|
|
|
+ showSoftKeyboard()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ isPage = true
|
|
|
|
+ }
|
|
|
|
+ R.id.tv_fragmentPdfReaderSearch_text -> {
|
|
|
|
+ if (isPage) {
|
|
|
|
+ et_search.isLongClickable = true
|
|
|
|
+ tv_page.setTextColor(-0x505051)
|
|
|
|
+ tv_text.setTextColor(-0xcc4a1b)
|
|
|
|
+ et_search.setText("")
|
|
|
|
+ et_search.setHint(R.string.fragment_search_enter_text)
|
|
|
|
+ et_search.inputType = InputType.TYPE_CLASS_TEXT
|
|
|
|
+ et_search.setSingleLine(true)
|
|
|
|
+
|
|
|
|
+ lv.visibility = View.VISIBLE
|
|
|
|
+ tv_tishi.visibility = View.GONE
|
|
|
|
+
|
|
|
|
+ showSoftKeyboard()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ isPage = false
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun search() {
|
|
|
|
+ val search = et_search.text.toString()
|
|
|
|
+ if (search.isNotEmpty()) {
|
|
|
|
+ if (isPage) {
|
|
|
|
+ val page = Integer.parseInt(search)
|
|
|
|
+ if (page > 0) {
|
|
|
|
+ pdfInfoHandler.setCurrentPage(page - 1)
|
|
|
|
+ }
|
|
|
|
+ } else if (search.length >= 3) {
|
|
|
|
+ if (list.size > 0) {
|
|
|
|
+ adapter.setSelected(-1)
|
|
|
|
+ adapter.clear()
|
|
|
|
+ list.clear()
|
|
|
|
+ adapter.notifyDataSetChanged()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ isDone = false
|
|
|
|
+ disposable = Completable.create {
|
|
|
|
+ val pageCount = pdfInfoHandler.getPdfPageCount()!!
|
|
|
|
+ for (i in 0 until pageCount) {
|
|
|
|
+ if (!isDone) {
|
|
|
|
+ val results = searchHandler.searchPage(i, search)
|
|
|
|
+ if (results?.isNotEmpty() != null) {
|
|
|
|
+ val texts = pdfInfoHandler.textLines(i)
|
|
|
|
+ val l = dealData(texts, results, search, i)
|
|
|
|
+ msgHandler?.sendMessage(msgHandler?.obtainMessage(10, l))
|
|
|
|
+ if (list.size + l.size >= MAX_SEARCH_RESULT) {
|
|
|
|
+ isDone = true
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ it.onComplete()
|
|
|
|
+ }
|
|
|
|
+ .subscribeOn(Schedulers.io())
|
|
|
|
+ .observeOn(AndroidSchedulers.mainThread())
|
|
|
|
+ .subscribe({
|
|
|
|
+ if (list.size < 1) {
|
|
|
|
+ lv.visibility = View.GONE
|
|
|
|
+ tv_tishi.visibility = View.VISIBLE
|
|
|
|
+ }
|
|
|
|
+ }, {
|
|
|
|
+ it.printStackTrace()
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun dealData(texts: Array<Array<TextWord>>?, results: Array<RectF>?, search: String, page: Int): List<SearchResultInfo> {
|
|
|
|
+ val list = ArrayList<SearchResultInfo>()
|
|
|
|
+ if (texts == null || results == null) {
|
|
|
|
+ return list
|
|
|
|
+ }
|
|
|
|
+ for (i in texts.indices) {
|
|
|
|
+ if (!isDone) {
|
|
|
|
+ val tw = texts[i]
|
|
|
|
+ for (j in tw.indices) {
|
|
|
|
+ if (!isDone) {
|
|
|
|
+ for (k in results.indices) {
|
|
|
|
+ if (!isDone) {
|
|
|
|
+ if (tw[j].contains(results[k])) {
|
|
|
|
+ list.add(SearchResultInfo(page, search, results[k], tw))
|
|
|
|
+ if (this.list.size + list.size >= MAX_SEARCH_RESULT) {
|
|
|
|
+ return list
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ return list
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private fun showSoftKeyboard() {
|
|
|
|
+ val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
|
|
+ et_search.requestFocus()
|
|
|
|
+ inputMethodManager.showSoftInput(et_search, InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fun hideSoftKeyboard() {
|
|
|
|
+ val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
|
|
+ inputMethodManager.hideSoftInputFromWindow(et_search.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
|
|
|
|
+ }
|
|
|
|
+}
|