添加搜索历史功能

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.androidpdfviewer)
implementation(libs.pdfbox.android) implementation(libs.pdfbox.android)
implementation(libs.jp2forandroid) 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.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.lifecycle.Lifecycle import android.widget.TextView
import androidx.core.content.edit
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivitySearchPddBinding import com.all.pdfreader.pro.app.databinding.ActivitySearchPdfBinding
import com.all.pdfreader.pro.app.ui.adapter.PdfAdapter
import com.all.pdfreader.pro.app.ui.adapter.SearchPdfAdapter 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.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.all.pdfreader.pro.app.util.AppUtils.showKeyboard
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.json.JSONArray
class SearchActivity : BaseActivity() { class SearchActivity : BaseActivity() {
override val TAG: String = "SearchActivity" override val TAG: String = "SearchActivity"
companion object { companion object {
const val FRAG_TAG = "SearchActivity" 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 { fun createIntent(context: Context): Intent {
return Intent(context, SearchActivity::class.java) return Intent(context, SearchActivity::class.java)
} }
} }
private lateinit var binding: ActivitySearchPddBinding private lateinit var binding: ActivitySearchPdfBinding
private lateinit var adapter: SearchPdfAdapter private lateinit var adapter: SearchPdfAdapter
private val pdfRepository = getRepository() private val pdfRepository = getRepository()
private var searchJob: Job? = null private var searchJob: Job? = null
private val searchHistory: MutableList<String> = mutableListOf()
private lateinit var sp: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivitySearchPddBinding.inflate(layoutInflater) binding = ActivitySearchPdfBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true) ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.bg_color).init() .navigationBarColor(R.color.bg_color).init()
sp = getSharedPreferences(PREF_SEARCH_HISTORY, MODE_PRIVATE)
loadHistory()
initView() initView()
setupClick() setupClick()
showHistory()
} }
private fun setupClick() { private fun setupClick() {
binding.backBtn.setOnClickListener { binding.backBtn.setOnClickListener { finish() }
finish()
}
binding.searchEdit.addTextChangedListener(object : TextWatcher { binding.searchEdit.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) { override fun afterTextChanged(s: Editable?) {
val query = s?.toString().orEmpty() val query = s?.toString().orEmpty()
binding.deleteIv.visibility = binding.deleteIv.visibility = if (query.isEmpty()) View.GONE else View.VISIBLE
if (query.isEmpty()) View.GONE else View.VISIBLE binding.searchIv.visibility = if (query.isEmpty()) View.VISIBLE else View.GONE
binding.searchIv.visibility =
if (query.isEmpty()) View.VISIBLE else View.GONE if (query.isEmpty()) {
// 取消之前的任务,防止重复 collect showHistory()
adapter.updateData(emptyList())
binding.noFilesLayout.visibility = View.GONE
return
} else {
binding.historyLayout.visibility = View.GONE
}
searchJob?.cancel() searchJob?.cancel()
searchJob = lifecycleScope.launch { searchJob = lifecycleScope.launch {
delay(150)//防止用户飞快打字 delay(150)
if (query.isEmpty()) {
adapter.updateData(emptyList())
binding.noFilesLayout.visibility = View.VISIBLE
return@launch
}
pdfRepository.searchDocuments(query).collectLatest { list -> pdfRepository.searchDocuments(query).collectLatest { list ->
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
adapter.updateData(list) adapter.updateData(list)
adapter.highlightItems(query.trim()) // payload 高亮 adapter.highlightItems(query.trim())
binding.noFilesLayout.visibility = View.GONE binding.noFilesLayout.visibility = View.GONE
} else { } else {
adapter.updateData(emptyList()) adapter.updateData(emptyList())
@ -82,6 +98,7 @@ class SearchActivity : BaseActivity() {
} }
} }
}) })
binding.deleteIv.setOnClickListener { binding.deleteIv.setOnClickListener {
binding.searchEdit.apply { binding.searchEdit.apply {
setText("") setText("")
@ -89,6 +106,18 @@ class SearchActivity : BaseActivity() {
setSelection(0) 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() { private fun initView() {
@ -96,6 +125,8 @@ class SearchActivity : BaseActivity() {
adapter = SearchPdfAdapter(onItemClick = { pdf -> adapter = SearchPdfAdapter(onItemClick = { pdf ->
val intent = PdfViewActivity.createIntent(this, pdf.filePath) val intent = PdfViewActivity.createIntent(this, pdf.filePath)
startActivity(intent) startActivity(intent)
val query = binding.searchEdit.text.toString()
if (query.isNotEmpty()) saveHistory(query)
}, onMoreClick = { pdf -> }, onMoreClick = { pdf ->
ListMoreDialogFragment(pdf.filePath).show(supportFragmentManager, FRAG_TAG) ListMoreDialogFragment(pdf.filePath).show(supportFragmentManager, FRAG_TAG)
}) })
@ -103,4 +134,51 @@ class SearchActivity : BaseActivity() {
binding.recyclerView.layoutManager = LinearLayoutManager(this) binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter 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))) .transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context)))
.into(holder.binding.tvFileImg) .into(holder.binding.tvFileImg)
} }
if (item.isFavorite) {
holder.binding.collectState.visibility = View.VISIBLE
} else {
holder.binding.collectState.visibility = View.GONE
}
holder.binding.root.setOnClickListener { holder.binding.root.setOnClickListener {
onItemClick(item) onItemClick(item)

View File

@ -71,7 +71,11 @@ class SearchPdfAdapter(
.transform(CenterCrop(), RoundedCorners(8.dpToPx(context))) .transform(CenterCrop(), RoundedCorners(8.dpToPx(context)))
.into(holder.binding.tvFileImg) .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.root.setOnClickListener { onItemClick(item) }
holder.binding.moreBtn.setOnClickListener { onMoreClick(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"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/bg_color" android:background="@color/bg_color"
@ -98,6 +97,61 @@
android:textSize="20sp" /> android:textSize="20sp" />
</LinearLayout> </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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -37,6 +37,16 @@
android:layout_width="56dp" android:layout_width="56dp"
android:layout_height="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 <LinearLayout
android:visibility="gone" android:visibility="gone"
android:id="@+id/lock_layout" 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="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_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="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> </resources>

View File

@ -2,6 +2,7 @@
androidpdfviewer = "3.2.8" androidpdfviewer = "3.2.8"
appcompat = "1.7.1" appcompat = "1.7.1"
agp = "8.10.1" agp = "8.10.1"
flexbox = "3.0.0"
fragmentKtx = "1.8.9" fragmentKtx = "1.8.9"
glide = "5.0.4" glide = "5.0.4"
jp2forandroid = "1.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-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-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room_version" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", 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" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
jp2forandroid = { module = "com.github.Tgo1014:JP2ForAndroid", version.ref = "jp2forandroid" } jp2forandroid = { module = "com.github.Tgo1014:JP2ForAndroid", version.ref = "jp2forandroid" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }