添加搜索历史功能

This commit is contained in:
ocean 2025-09-25 17:38:30 +08:00
parent c03e2b2cb1
commit 4f0f6bc666
12 changed files with 224 additions and 24 deletions

View File

@ -62,4 +62,5 @@ dependencies {
implementation(libs.androidpdfviewer)
implementation(libs.pdfbox.android)
implementation(libs.jp2forandroid)
implementation(libs.flexbox)
}

View File

@ -2,77 +2,93 @@ package com.all.pdfreader.pro.app.ui.act
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import androidx.lifecycle.Lifecycle
import android.widget.TextView
import androidx.core.content.edit
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivitySearchPddBinding
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.databinding.ActivitySearchPdfBinding
import com.all.pdfreader.pro.app.ui.adapter.SearchPdfAdapter
import com.all.pdfreader.pro.app.ui.dialog.ListMoreDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.PermissionDialogFragment
import com.all.pdfreader.pro.app.ui.dialog.PromptDialogFragment
import com.all.pdfreader.pro.app.util.AppUtils.showKeyboard
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.json.JSONArray
class SearchActivity : BaseActivity() {
override val TAG: String = "SearchActivity"
companion object {
const val FRAG_TAG = "SearchActivity"
private const val PREF_SEARCH_HISTORY = "pref_search_history"
private const val KEY_HISTORY_LIST = "key_history_list_json"
fun createIntent(context: Context): Intent {
return Intent(context, SearchActivity::class.java)
}
}
private lateinit var binding: ActivitySearchPddBinding
private lateinit var binding: ActivitySearchPdfBinding
private lateinit var adapter: SearchPdfAdapter
private val pdfRepository = getRepository()
private var searchJob: Job? = null
private val searchHistory: MutableList<String> = mutableListOf()
private lateinit var sp: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySearchPddBinding.inflate(layoutInflater)
binding = ActivitySearchPdfBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.bg_color).init()
sp = getSharedPreferences(PREF_SEARCH_HISTORY, MODE_PRIVATE)
loadHistory()
initView()
setupClick()
showHistory()
}
private fun setupClick() {
binding.backBtn.setOnClickListener {
finish()
}
binding.backBtn.setOnClickListener { finish() }
binding.searchEdit.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
val query = s?.toString().orEmpty()
binding.deleteIv.visibility =
if (query.isEmpty()) View.GONE else View.VISIBLE
binding.searchIv.visibility =
if (query.isEmpty()) View.VISIBLE else View.GONE
// 取消之前的任务,防止重复 collect
binding.deleteIv.visibility = if (query.isEmpty()) View.GONE else View.VISIBLE
binding.searchIv.visibility = if (query.isEmpty()) View.VISIBLE else View.GONE
if (query.isEmpty()) {
showHistory()
adapter.updateData(emptyList())
binding.noFilesLayout.visibility = View.GONE
return
} else {
binding.historyLayout.visibility = View.GONE
}
searchJob?.cancel()
searchJob = lifecycleScope.launch {
delay(150)//防止用户飞快打字
if (query.isEmpty()) {
adapter.updateData(emptyList())
binding.noFilesLayout.visibility = View.VISIBLE
return@launch
}
delay(150)
pdfRepository.searchDocuments(query).collectLatest { list ->
if (list.isNotEmpty()) {
adapter.updateData(list)
adapter.highlightItems(query.trim()) // payload 高亮
adapter.highlightItems(query.trim())
binding.noFilesLayout.visibility = View.GONE
} else {
adapter.updateData(emptyList())
@ -82,6 +98,7 @@ class SearchActivity : BaseActivity() {
}
}
})
binding.deleteIv.setOnClickListener {
binding.searchEdit.apply {
setText("")
@ -89,6 +106,18 @@ class SearchActivity : BaseActivity() {
setSelection(0)
}
}
binding.clearHistory.setOnClickListener {
PromptDialogFragment(
getString(R.string.tip),
getString(R.string.clear_history_desc),
getString(R.string.clear),
onOkClick = {
searchHistory.clear()
saveHistoryList() // 保存空列表
showHistory()
}).show(supportFragmentManager, "clearHistory")
}
}
private fun initView() {
@ -96,6 +125,8 @@ class SearchActivity : BaseActivity() {
adapter = SearchPdfAdapter(onItemClick = { pdf ->
val intent = PdfViewActivity.createIntent(this, pdf.filePath)
startActivity(intent)
val query = binding.searchEdit.text.toString()
if (query.isNotEmpty()) saveHistory(query)
}, onMoreClick = { pdf ->
ListMoreDialogFragment(pdf.filePath).show(supportFragmentManager, FRAG_TAG)
})
@ -103,4 +134,51 @@ class SearchActivity : BaseActivity() {
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
}
/** 搜索历史逻辑 **/
private fun loadHistory() {
val json = sp.getString(KEY_HISTORY_LIST, "[]")
val array = JSONArray(json)
searchHistory.clear()
for (i in 0 until array.length()) {
searchHistory.add(array.getString(i))
}
}
private fun saveHistory(query: String) {
if (searchHistory.contains(query)) searchHistory.remove(query)
searchHistory.add(0, query)
if (searchHistory.size > 20) searchHistory.removeAt(searchHistory.size - 1)
saveHistoryList()
}
private fun saveHistoryList() {
val json = JSONArray(searchHistory).toString()
sp.edit { putString(KEY_HISTORY_LIST, json) }
}
private fun showHistory() {
binding.historyFlexLayout.removeAllViews()
if (searchHistory.isEmpty()) {
binding.historyLayout.visibility = View.GONE
return
}
binding.historyLayout.visibility = View.VISIBLE
val inflater = LayoutInflater.from(this)
searchHistory.forEach { keyword ->
val tagView = inflater.inflate(
R.layout.item_history_tag,
binding.historyFlexLayout,
false
) as TextView
tagView.text = keyword
tagView.setOnClickListener {
binding.searchEdit.setText(keyword)
binding.searchEdit.setSelection(keyword.length)
}
binding.historyFlexLayout.addView(tagView)
}
}
}

View File

@ -45,6 +45,11 @@ class PdfAdapter(
.transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context)))
.into(holder.binding.tvFileImg)
}
if (item.isFavorite) {
holder.binding.collectState.visibility = View.VISIBLE
} else {
holder.binding.collectState.visibility = View.GONE
}
holder.binding.root.setOnClickListener {
onItemClick(item)

View File

@ -71,7 +71,11 @@ class SearchPdfAdapter(
.transform(CenterCrop(), RoundedCorners(8.dpToPx(context)))
.into(holder.binding.tvFileImg)
}
if (item.isFavorite) {
holder.binding.collectState.visibility = View.VISIBLE
} else {
holder.binding.collectState.visibility = View.GONE
}
holder.binding.root.setOnClickListener { onItemClick(item) }
holder.binding.moreBtn.setOnClickListener { onMoreClick(item) }
}

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#F0F0F0"/>
<corners android:radius="12dp"/>
</shape>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,20.88L10.55,19.55C5.4,14.9 3,12.36 3,9.5C3,7.01 5.01,5 7.5,5C8.74,5 9.91,5.5 10.7,6.35L12,7.63L13.3,6.35C14.09,5.5 15.26,5 16.5,5C18.99,5 21,7.01 21,9.5C21,12.36 18.6,14.9 13.45,19.55L12,20.88Z"
android:strokeWidth="1.5"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeLineCap="round"
android:strokeLineJoin="round"/>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topRightRadius="8dp"
android:bottomLeftRadius="8dp" />
<solid android:color="@color/icon_sel_on_color" />
</shape>

View File

@ -1,7 +1,6 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_color"
@ -98,6 +97,61 @@
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/historyLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="32dp"
android:gravity="center_vertical"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<TextView
android:id="@+id/historyTitle"
style="@style/TextViewFont_PopMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/history"
android:textColor="#333333"
android:textSize="16sp" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<LinearLayout
android:id="@+id/clearHistory"
android:layout_width="32dp"
android:layout_height="32dp"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/delete" />
</LinearLayout>
</LinearLayout>
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/historyFlexLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:flexWrap="wrap"
app:justifyContent="flex_start" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"

View File

@ -37,6 +37,16 @@
android:layout_width="56dp"
android:layout_height="56dp" />
<ImageView
android:id="@+id/collectState"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:background="@drawable/dr_collect_state_bg"
android:padding="2dp"
android:src="@drawable/collected_white" />
<LinearLayout
android:visibility="gone"
android:id="@+id/lock_layout"

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tagText"
style="@style/TextViewFont_PopRegular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="@drawable/bg_history_item"
android:gravity="center"
android:paddingStart="12dp"
android:paddingTop="6dp"
android:paddingEnd="12dp"
android:paddingBottom="6dp"
android:text="@string/app_name"
android:textColor="@color/black"
android:textSize="14sp" />

View File

@ -140,4 +140,8 @@
<string name="go_to_page_desc">Quickly navigate to any page in the file.</string>
<string name="name_must_be_number">Please enter a number(1-%1$d)</string>
<string name="name_out_of_range">Please enter a number between 1 and %1$d</string>
<string name="history">History</string>
<string name="tip">Tip</string>
<string name="clear_history_desc">Are you sure you want to clear history?</string>
<string name="clear">Clear</string>
</resources>

View File

@ -2,6 +2,7 @@
androidpdfviewer = "3.2.8"
appcompat = "1.7.1"
agp = "8.10.1"
flexbox = "3.0.0"
fragmentKtx = "1.8.9"
glide = "5.0.4"
jp2forandroid = "1.0.4"
@ -30,6 +31,7 @@ androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room_version" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room_version" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room_version" }
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
jp2forandroid = { module = "com.github.Tgo1014:JP2ForAndroid", version.ref = "jp2forandroid" }
junit = { group = "junit", name = "junit", version.ref = "junit" }