Skip to content

Commit f5d9184

Browse files
Refactor: Centralize and simplify list filtering logic
This commit introduces a generic `filterItems` function in `FuzzyFinder` to centralize and reuse list filtering logic, abstracting away the implementation details of fuzzy scoring and simple matching. The filtering logic within `AppDrawerAdapter` and `ContactDrawerAdapter` has been refactored to use this new helper function. This change eliminates redundant code, simplifies the `performFiltering` implementation in both adapters, and improves maintainability. The `filterItems` function handles both score-based filtering (when `enableFilterStrength` is on) and simple boolean matching, making the filtering process more consistent across different parts of the app.
1 parent 409e0ba commit f5d9184

File tree

3 files changed

+64
-116
lines changed

3 files changed

+64
-116
lines changed

app/src/main/java/com/github/codeworkscreativehub/fuzzywuzzy/FuzzyFinder.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.github.codeworkscreativehub.fuzzywuzzy
22

3+
import com.github.codeworkscreativehub.common.AppLogger
34
import com.github.codeworkscreativehub.mlauncher.data.AppListItem
45
import com.github.codeworkscreativehub.mlauncher.data.ContactListItem
6+
import com.github.codeworkscreativehub.mlauncher.data.Prefs
57
import com.github.codeworkscreativehub.mlauncher.helper.emptyString
68
import java.text.Normalizer
79
import java.util.Locale
@@ -136,4 +138,38 @@ object FuzzyFinder {
136138
return Normalizer.normalize(input, Normalizer.Form.NFD)
137139
.replace(Regex("\\p{InCombiningDiacriticalMarks}+"), emptyString())
138140
}
141+
142+
/**
143+
* A generic helper to filter lists based on fuzzy scoring or simple matching.
144+
* T is the type of item (AppListItem or ContactListItem)
145+
*/
146+
fun <T> filterItems(
147+
itemsList: List<T>,
148+
query: String,
149+
prefs: Prefs,
150+
scoreProvider: (T, String) -> Int,
151+
labelProvider: (T) -> String,
152+
loggerTag: String
153+
): MutableList<T> {
154+
if (query.isEmpty()) return itemsList.toMutableList()
155+
156+
return if (prefs.enableFilterStrength) {
157+
// 1. Calculate scores and filter by threshold
158+
itemsList.mapNotNull { item ->
159+
val score = scoreProvider(item, query)
160+
AppLogger.d(loggerTag, "item: ${labelProvider(item)} | score: $score")
161+
if (score > prefs.filterStrength) item else null
162+
}.toMutableList()
163+
} else {
164+
// 2. Simple Boolean matching
165+
itemsList.filter { item ->
166+
val target = labelProvider(item).lowercase()
167+
if (prefs.searchFromStart) {
168+
target.startsWith(query)
169+
} else {
170+
isMatch(target, query)
171+
}
172+
}.toMutableList()
173+
}
174+
}
139175
}

app/src/main/java/com/github/codeworkscreativehub/mlauncher/ui/adapter/AppDrawerAdapter.kt

Lines changed: 13 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.github.codeworkscreativehub.common.getLocalizedString
3333
import com.github.codeworkscreativehub.common.isSystemApp
3434
import com.github.codeworkscreativehub.common.showKeyboard
3535
import com.github.codeworkscreativehub.fuzzywuzzy.FuzzyFinder
36+
import com.github.codeworkscreativehub.fuzzywuzzy.FuzzyFinder.filterItems
3637
import com.github.codeworkscreativehub.mlauncher.R
3738
import com.github.codeworkscreativehub.mlauncher.data.AppListItem
3839
import com.github.codeworkscreativehub.mlauncher.data.Constants
@@ -50,7 +51,6 @@ import kotlinx.coroutines.Dispatchers
5051
import kotlinx.coroutines.SupervisorJob
5152
import kotlinx.coroutines.launch
5253
import kotlinx.coroutines.withContext
53-
import java.text.Normalizer
5454
import java.util.concurrent.ConcurrentHashMap
5555

5656
class AppDrawerAdapter(
@@ -205,72 +205,23 @@ class AppDrawerAdapter(
205205
private fun createAppFilter(): Filter {
206206
return object : Filter() {
207207
override fun performFiltering(charSearch: CharSequence?): FilterResults {
208-
isBangSearch = listOf("#").any { prefix -> charSearch?.startsWith(prefix) == true }
209-
prefs = Prefs(context)
210-
211208
val searchChars = charSearch.toString().trim().lowercase()
212-
val filteredApps: MutableList<AppListItem>
213-
214209
val isTagSearch = searchChars.startsWith("#")
215210
val query = if (isTagSearch) searchChars.substringAfter("#") else searchChars
216-
val normalizeField: (AppListItem) -> String = { app -> if (isTagSearch) normalize(app.tag) else normalize(app.activityLabel) }
217-
218-
// Scoring logic
219-
// 1. Calculate the scores for all apps in the list
220-
val scoredApps: Map<AppListItem, Int> = if (prefs.enableFilterStrength) {
221-
appsList.associateWith { app ->
222-
if (isTagSearch) {
223-
// Normalizing the app tag and scoring it against the query
224-
FuzzyFinder.scoreString(app.tag, query, Constants.MAX_FILTER_STRENGTH)
225-
} else {
226-
// Using the specialized scoreApp helper for AppListItems
227-
FuzzyFinder.scoreApp(app, query, Constants.MAX_FILTER_STRENGTH)
228-
}
229-
}
230-
} else {
231-
// If filter strength is disabled, we don't calculate fuzzy scores
232-
emptyMap()
233-
}
234211

235-
// 2. Filter the list based on the fuzzy score and the user's preferences
236-
filteredApps = if (searchChars.isEmpty()) {
237-
appsList.toMutableList()
238-
} else {
239-
val filtered = if (prefs.enableFilterStrength) {
240-
// Logic: Use the pre-calculated scores.
241-
// If the score is above the threshold, it's a valid match.
242-
scoredApps.filter { (app, score) ->
243-
AppLogger.d("appScore", "app: ${app.activityLabel} | score: $score")
244-
score > prefs.filterStrength
245-
}.map { it.key }
246-
} else {
247-
// Logic: Simple Boolean matching without scoring.
248-
appsList.filter { app ->
249-
val target = normalizeField(app)
250-
if (prefs.searchFromStart) {
251-
target.startsWith(query)
252-
} else {
253-
// Uses your new isMatch helper for a simple true/false fuzzy check
254-
FuzzyFinder.isMatch(target, query)
255-
}
256-
}
257-
}
258-
filtered.toMutableList()
259-
}
260-
261-
if (query.isNotEmpty()) AppLogger.d("searchQuery", query)
262-
263-
val filterResults = FilterResults()
264-
filterResults.values = filteredApps
265-
return filterResults
266-
}
212+
val filtered = filterItems(
213+
itemsList = appsList,
214+
query = query,
215+
prefs = Prefs(context),
216+
scoreProvider = { app, q ->
217+
if (isTagSearch) FuzzyFinder.scoreString(app.tag, q, Constants.MAX_FILTER_STRENGTH)
218+
else FuzzyFinder.scoreApp(app, q, Constants.MAX_FILTER_STRENGTH)
219+
},
220+
labelProvider = { app -> if (isTagSearch) app.tag else app.activityLabel },
221+
loggerTag = "appScore"
222+
)
267223

268-
fun normalize(input: String): String {
269-
// Normalize to NFC to keep composed characters (é stays é, not e + ´)
270-
val temp = Normalizer.normalize(input, Normalizer.Form.NFC)
271-
return temp
272-
.lowercase() // lowercase Latin letters; other scripts unaffected
273-
.filter { it.isLetterOrDigit() } // keep letters/digits from any language, including accented letters
224+
return FilterResults().apply { values = filtered }
274225
}
275226

276227

app/src/main/java/com/github/codeworkscreativehub/mlauncher/ui/adapter/ContactDrawerAdapter.kt

Lines changed: 15 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@ import android.widget.FrameLayout
1010
import android.widget.TextView
1111
import androidx.core.view.updatePadding
1212
import androidx.recyclerview.widget.RecyclerView
13-
import com.github.codeworkscreativehub.common.AppLogger
1413
import com.github.codeworkscreativehub.fuzzywuzzy.FuzzyFinder
14+
import com.github.codeworkscreativehub.fuzzywuzzy.FuzzyFinder.filterItems
1515
import com.github.codeworkscreativehub.mlauncher.data.Constants
1616
import com.github.codeworkscreativehub.mlauncher.data.ContactListItem
1717
import com.github.codeworkscreativehub.mlauncher.data.Prefs
1818
import com.github.codeworkscreativehub.mlauncher.databinding.AdapterAppDrawerBinding
19-
import java.text.Normalizer
2019

2120
class ContactDrawerAdapter(
2221
private val context: Context,
@@ -62,57 +61,20 @@ class ContactDrawerAdapter(
6261
private fun createContactFilter(): Filter {
6362
return object : Filter() {
6463
override fun performFiltering(charSearch: CharSequence?): FilterResults {
65-
prefs = Prefs(context)
66-
6764
val searchChars = charSearch.toString().trim().lowercase()
68-
val filteredContacts: MutableList<ContactListItem>
69-
70-
// Normalization function for contacts
71-
val normalizeField: (ContactListItem) -> String = { contact -> normalize(contact.displayName) }
72-
73-
// Scoring logic
74-
val scoredContacts: Map<ContactListItem, Int> = if (prefs.enableFilterStrength) {
75-
contactsList.associateWith { contact ->
76-
FuzzyFinder.scoreContact(contact, searchChars, Constants.MAX_FILTER_STRENGTH)
77-
}
78-
} else {
79-
emptyMap()
80-
}
81-
82-
filteredContacts = if (searchChars.isEmpty()) {
83-
contactsList.toMutableList()
84-
} else {
85-
if (prefs.enableFilterStrength) {
86-
// Filter using scores
87-
scoredContacts.filter { (contact, score) ->
88-
(prefs.searchFromStart && normalizeField(contact).startsWith(searchChars)
89-
|| !prefs.searchFromStart && normalizeField(contact).contains(searchChars))
90-
&& score > prefs.filterStrength
91-
}.map { it.key }.toMutableList()
92-
} else {
93-
// Filter without scores
94-
contactsList.filter { contact ->
95-
if (prefs.searchFromStart) {
96-
normalizeField(contact).startsWith(searchChars)
97-
} else {
98-
FuzzyFinder.isMatch(normalizeField(contact), searchChars)
99-
}
100-
}.toMutableList()
101-
}
102-
}
10365

104-
if (searchChars.isNotEmpty()) AppLogger.d("searchQuery", searchChars)
105-
106-
val filterResults = FilterResults()
107-
filterResults.values = filteredContacts
108-
return filterResults
109-
}
110-
111-
fun normalize(input: String): String {
112-
val temp = Normalizer.normalize(input, Normalizer.Form.NFC)
113-
return temp
114-
.lowercase()
115-
.filter { it.isLetterOrDigit() }
66+
val filtered = filterItems(
67+
itemsList = contactsList,
68+
query = searchChars,
69+
prefs = Prefs(context),
70+
scoreProvider = { contact, q ->
71+
FuzzyFinder.scoreContact(contact, q, Constants.MAX_FILTER_STRENGTH)
72+
},
73+
labelProvider = { contact -> contact.displayName },
74+
loggerTag = "contactScore"
75+
)
76+
77+
return FilterResults().apply { values = filtered }
11678
}
11779

11880
@SuppressLint("NotifyDataSetChanged")
@@ -158,8 +120,7 @@ class ContactDrawerAdapter(
158120
contactLabelGravity: Int,
159121
contactItem: ContactListItem,
160122
contactClickListener: (ContactListItem) -> Unit,
161-
) = with(itemView) {
162-
123+
) {
163124
appTitle.text = contactItem.displayName
164125

165126
// set text gravity
@@ -172,7 +133,7 @@ class ContactDrawerAdapter(
172133
}
173134

174135
val padding = 24
175-
appTitle.updatePadding(left = padding, right = padding)
136+
return appTitle.updatePadding(left = padding, right = padding)
176137
}
177138
}
178139
}

0 commit comments

Comments
 (0)