From f0fb62777497d4e350af967b0bff40d1d9dbfdb8 Mon Sep 17 00:00:00 2001 From: ocean <503259349@qq.com> Date: Wed, 24 Sep 2025 17:19:35 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0=E6=90=9C=E7=B4=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=202.=E4=BC=98=E5=8C=96=E4=B8=BB=E8=A6=81adapter?= =?UTF-8?q?=E7=9A=84=E5=B1=95=E7=A4=BA=EF=BC=8C=E4=BD=BF=E7=94=A8PdfDiffCa?= =?UTF-8?q?llback=E6=9D=A5=E5=8F=AA=E5=88=B7=E6=96=B0=E6=9C=89=E6=94=B9?= =?UTF-8?q?=E5=8F=98=E7=9A=84item?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 6 + .../pro/app/room/dao/PdfDocumentDao.kt | 2 +- .../pro/app/room/repository/PdfRepository.kt | 6 + .../pdfreader/pro/app/ui/act/MainActivity.kt | 2 +- .../pro/app/ui/act/SearchActivity.kt | 105 +++++++++++++++++ .../pro/app/ui/adapter/PdfAdapter.kt | 66 +++++++---- .../pro/app/ui/fragment/FavoriteFrag.kt | 2 +- .../pdfreader/pro/app/ui/fragment/HomeFrag.kt | 2 +- .../pro/app/ui/fragment/RecentlyFrag.kt | 2 +- .../all/pdfreader/pro/app/util/AppUtils.kt | 24 ++++ .../pdfreader/pro/app/util/PdfDiffCallback.kt | 20 ++++ .../main/res/layout/activity_search_pdd.xml | 107 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 13 files changed, 320 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/all/pdfreader/pro/app/ui/act/SearchActivity.kt create mode 100644 app/src/main/java/com/all/pdfreader/pro/app/util/PdfDiffCallback.kt create mode 100644 app/src/main/res/layout/activity_search_pdd.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index afd2cb2..15231ad 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,6 +79,12 @@ android:label="@string/app_name" android:screenOrientation="portrait" /> + + > - @Query("SELECT * FROM pdf_documents WHERE fileName LIKE '%' || :query || '%' OR metadataTitle LIKE '%' || :query || '%'") + @Query("""SELECT * FROM pdf_documents WHERE :query != '' AND TRIM(:query) != '' AND LOWER(fileName) LIKE '%' || LOWER(:query) || '%' """) fun searchDocuments(query: String): Flow> @Query("SELECT * FROM pdf_documents ORDER BY lastOpenedTime DESC") diff --git a/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt b/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt index 8589921..58c90ba 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/room/repository/PdfRepository.kt @@ -39,6 +39,12 @@ class PdfRepository private constructor(context: Context) { pdfDao.getRecentlyOpenedDocuments() fun getFavoriteDocuments(): Flow> = pdfDao.getFavoriteDocuments() + + /** + * 1.用户输入 " " → 不会返回任何数据 + * 2.用户输入 " my file " → 会去掉两边空格,正确匹配包含 my file 的文件 + * 3.用户输入 "my" → 正常模糊匹配 + */ fun searchDocuments(query: String): Flow> = pdfDao.searchDocuments(query) diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt index e8b5b63..69aad5c 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/MainActivity.kt @@ -161,7 +161,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback } binding.searchBtn.setClickWithAnimation { - + startActivity(SearchActivity.createIntent(this)) } binding.sortingBtn.setClickWithAnimation { diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SearchActivity.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SearchActivity.kt new file mode 100644 index 0000000..7fc7607 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/act/SearchActivity.kt @@ -0,0 +1,105 @@ +package com.all.pdfreader.pro.app.ui.act + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import androidx.lifecycle.Lifecycle +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.ui.dialog.ListMoreDialogFragment +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 + +class SearchActivity : BaseActivity() { + override val TAG: String = "SearchActivity" + + companion object { + const val FRAG_TAG = "SearchActivity" + fun createIntent(context: Context): Intent { + return Intent(context, SearchActivity::class.java) + } + } + + private lateinit var binding: ActivitySearchPddBinding + private lateinit var adapter: PdfAdapter + private val pdfRepository = getRepository() + private var searchJob: Job? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivitySearchPddBinding.inflate(layoutInflater) + setContentView(binding.root) + ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true) + .navigationBarColor(R.color.bg_color).init() + initView() + setupClick() + } + + private fun setupClick() { + 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 + searchJob?.cancel() + searchJob = lifecycleScope.launch { + delay(150)//防止用户飞快打字 + if (query.isEmpty()) { + adapter.updateData(emptyList()) + binding.noFilesLayout.visibility = View.VISIBLE + return@launch + } + pdfRepository.searchDocuments(query).collectLatest { list -> + if (list.isNotEmpty()) { + adapter.updateData(list) + adapter.highlightItems(query.trim()) // payload 高亮 + binding.noFilesLayout.visibility = View.GONE + } else { + adapter.updateData(emptyList()) + binding.noFilesLayout.visibility = View.VISIBLE + } + } + } + } + }) + binding.deleteIv.setOnClickListener { + binding.searchEdit.apply { + setText("") + clearComposingText() + setSelection(0) + } + } + } + + private fun initView() { + binding.searchEdit.showKeyboard() + adapter = PdfAdapter(onItemClick = { pdf -> + val intent = PdfViewActivity.createIntent(this, pdf.filePath) + startActivity(intent) + }, onMoreClick = { pdf -> + ListMoreDialogFragment(pdf.filePath).show(supportFragmentManager, FRAG_TAG) + }) + + binding.recyclerView.layoutManager = LinearLayoutManager(this) + binding.recyclerView.adapter = adapter + } +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/PdfAdapter.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/PdfAdapter.kt index c62ad0f..7137378 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/PdfAdapter.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/adapter/PdfAdapter.kt @@ -4,62 +4,88 @@ import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.databinding.AdapterPdfItemBinding import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity import com.all.pdfreader.pro.app.util.AppUtils.dpToPx +import com.all.pdfreader.pro.app.util.AppUtils.toHighlightedSpannable import com.all.pdfreader.pro.app.util.FileUtils.toFormatFileSize import com.all.pdfreader.pro.app.util.FileUtils.toSlashDate +import com.all.pdfreader.pro.app.util.PdfDiffCallback import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.RoundedCorners class PdfAdapter( - private var pdfList: MutableList, private val onItemClick: (PdfDocumentEntity) -> Unit, private val onMoreClick: (PdfDocumentEntity) -> Unit -) : RecyclerView.Adapter() { +) : ListAdapter(PdfDiffCallback()) { inner class PdfViewHolder(val binding: AdapterPdfItemBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfViewHolder { - val binding = - AdapterPdfItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val binding = AdapterPdfItemBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) return PdfViewHolder(binding) } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: PdfViewHolder, position: Int) { - val item = pdfList[position] - holder.binding.tvFileName.text = item.fileName + bindItem(holder, getItem(position), null) + } + + override fun onBindViewHolder(holder: PdfViewHolder, position: Int, payloads: MutableList) { + if (payloads.isNotEmpty()) { + val payload = payloads[0] as? String + bindItem(holder, getItem(position), payload) + } else { + super.onBindViewHolder(holder, position, payloads) + } + } + + private fun bindItem(holder: PdfViewHolder, item: PdfDocumentEntity, highlightKeyword: String?) { + val context = holder.binding.root.context + + // 文件名高亮,如果 highlightKeyword 为 null 或空字符串,就显示普通文本 + holder.binding.tvFileName.text = if (highlightKeyword.isNullOrBlank()) { + item.fileName + } else { + item.fileName.toHighlightedSpannable(highlightKeyword, holder.binding.root.context.getColor(R.color.icon_sel_on_color)) + } + holder.binding.tvFileSize.text = item.fileSize.toFormatFileSize() holder.binding.tvFileDate.text = item.lastModified.toSlashDate() + if (item.isPassword) { holder.binding.lockLayout.visibility = View.VISIBLE holder.binding.tvFileImg.visibility = View.GONE } else { holder.binding.lockLayout.visibility = View.GONE holder.binding.tvFileImg.visibility = View.VISIBLE - Glide.with(holder.binding.root).load(item.thumbnailPath) - .transform(CenterCrop(), RoundedCorners(8.dpToPx(holder.binding.root.context))) + Glide.with(context) + .load(item.thumbnailPath) + .transform(CenterCrop(), RoundedCorners(8.dpToPx(context))) .into(holder.binding.tvFileImg) } - holder.binding.root.setOnClickListener { - onItemClick(item) - } - holder.binding.moreBtn.setOnClickListener { - onMoreClick(item) - } + holder.binding.root.setOnClickListener { onItemClick(item) } + holder.binding.moreBtn.setOnClickListener { onMoreClick(item) } } - override fun getItemCount(): Int = pdfList.size - - @SuppressLint("NotifyDataSetChanged") fun updateData(newList: List) { - pdfList.clear() - pdfList.addAll(newList) - notifyDataSetChanged() + submitList(newList.toList()) + } + + /** + * 搜索场景使用:只刷新高亮,不刷新整个列表 + */ + fun highlightItems(keyword: String) { + for (i in 0 until itemCount) { + notifyItemChanged(i, keyword) + } } } diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/FavoriteFrag.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/FavoriteFrag.kt index d1cb4d9..c07e28a 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/FavoriteFrag.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/FavoriteFrag.kt @@ -40,7 +40,7 @@ class FavoriteFrag : BaseFrag() { } private fun initView() { - adapter = PdfAdapter(pdfList = mutableListOf(), onItemClick = { pdf -> + adapter = PdfAdapter(onItemClick = { pdf -> val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath) startActivity(intent) }, onMoreClick = { pdf -> diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt index 7f1145c..5a6fa34 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/HomeFrag.kt @@ -42,7 +42,7 @@ class HomeFrag : BaseFrag(), MainActivity.SortableFragment { } private fun initView() { - adapter = PdfAdapter(pdfList = mutableListOf(), onItemClick = { pdf -> + adapter = PdfAdapter(onItemClick = { pdf -> val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath) startActivity(intent) }, onMoreClick = { pdf -> diff --git a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/RecentlyFrag.kt b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/RecentlyFrag.kt index 07a03b1..52dc1da 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/RecentlyFrag.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/ui/fragment/RecentlyFrag.kt @@ -39,7 +39,7 @@ class RecentlyFrag : BaseFrag() { } private fun initView() { - adapter = PdfAdapter(pdfList = mutableListOf(), onItemClick = { pdf -> + adapter = PdfAdapter(onItemClick = { pdf -> val intent = PdfViewActivity.createIntent(requireContext(), pdf.filePath) startActivity(intent) }, onMoreClick = { pdf -> diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt index 28abf24..29605a2 100644 --- a/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/AppUtils.kt @@ -9,6 +9,9 @@ import android.net.Uri import android.os.ParcelFileDescriptor import android.print.PrintAttributes import android.print.PrintManager +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager @@ -28,6 +31,7 @@ import com.tom_roush.pdfbox.pdmodel.common.PDPageLabelRange import java.io.File import java.io.FileOutputStream import java.io.IOException +import java.util.regex.Pattern object AppUtils { @@ -237,4 +241,24 @@ object AppUtils { null } } + + /** + * 高亮文字,主要是搜索方面 + */ + fun String.toHighlightedSpannable(keyword: String, highlightColor: Int): SpannableString { + val spannable = SpannableString(this) + if (keyword.isBlank()) return spannable + + val regex = Regex(Pattern.quote(keyword), RegexOption.IGNORE_CASE) + regex.findAll(this).forEach { matchResult -> + spannable.setSpan( + ForegroundColorSpan(highlightColor), + matchResult.range.first, + matchResult.range.last + 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + return spannable + } + } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pro/app/util/PdfDiffCallback.kt b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfDiffCallback.kt new file mode 100644 index 0000000..d289d96 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pro/app/util/PdfDiffCallback.kt @@ -0,0 +1,20 @@ +package com.all.pdfreader.pro.app.util + +import androidx.recyclerview.widget.DiffUtil +import com.all.pdfreader.pro.app.room.entity.PdfDocumentEntity + +/** + * 用于PdfDocumentEntity数据类的PdfDiff + */ +class PdfDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: PdfDocumentEntity, newItem: PdfDocumentEntity): Boolean { + return oldItem.filePath == newItem.filePath + } + + override fun areContentsTheSame( + oldItem: PdfDocumentEntity, + newItem: PdfDocumentEntity + ): Boolean { + return oldItem == newItem + } +} diff --git a/app/src/main/res/layout/activity_search_pdd.xml b/app/src/main/res/layout/activity_search_pdd.xml new file mode 100644 index 0000000..0e8db6d --- /dev/null +++ b/app/src/main/res/layout/activity_search_pdd.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4ffd7fa..fad71ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -135,4 +135,5 @@ Your file has been successfully created Please select at least one page Splitting… + Search…… \ No newline at end of file