@@ -8,157 +8,116 @@ import android.net.Uri
8
8
import android.widget.EditText
9
9
import android.widget.Toast
10
10
import androidx.recyclerview.widget.RecyclerView
11
+ import kotlinx.coroutines.*
12
+ import java.util.concurrent.ConcurrentHashMap
11
13
12
14
class AppSearchManager (
13
- private val packageManager : PackageManager ,
14
- private val appList : MutableList <ResolveInfo >,
15
- private val fullAppList : MutableList <ResolveInfo >,
15
+ private val pm : PackageManager ,
16
+ private val apps : MutableList <ResolveInfo >,
17
+ private var allApps : MutableList <ResolveInfo >,
16
18
private val adapter : AppAdapter ,
17
- private val recyclerView : RecyclerView ,
18
- private val searchBox : EditText ,
19
- private val contactsList : List <String > // List of contact names or numbers
19
+ private val rv : RecyclerView ,
20
+ private val search : EditText ,
21
+ private val contacts : List <String >
20
22
) {
23
+ private var searchJob: Job ? = null
24
+ private val searchScope = CoroutineScope (Dispatchers .Main )
25
+ private var currentQuery: String = " "
26
+ private val appLabels = ConcurrentHashMap <ResolveInfo , String >() // Cache app labels
21
27
22
28
init {
23
- searchBox.addTextChangedListener(object : android.text.TextWatcher {
24
- override fun beforeTextChanged (s : CharSequence? , start : Int , count : Int , after : Int ) {}
29
+ // Pre-cache app labels for faster lookup
30
+ CoroutineScope (Dispatchers .IO ).launch {
31
+ allApps.forEach { app ->
32
+ appLabels[app] = app.loadLabel(pm).toString().lowercase()
33
+ }
34
+ }
35
+
36
+ search.addTextChangedListener(object : android.text.TextWatcher {
37
+ override fun beforeTextChanged (s : CharSequence? , start : Int , count : Int , after : Int ) {
38
+ // Implementation (can be empty if not needed)
39
+ }
25
40
26
41
override fun onTextChanged (s : CharSequence? , start : Int , before : Int , count : Int ) {
27
- filterAppsAndContacts(s.toString())
42
+ val newQuery = s.toString()
43
+ if (newQuery != currentQuery) {
44
+ currentQuery = newQuery
45
+ searchJob?.cancel() // Cancel previous job
46
+ searchJob = searchScope.launch {
47
+ delay(300 ) // Debounce delay
48
+ filter(newQuery)
49
+ }
50
+ }
28
51
}
29
52
30
- override fun afterTextChanged (s : android.text.Editable ? ) {}
53
+ override fun afterTextChanged (s : android.text.Editable ? ) {
54
+ }
31
55
})
32
56
33
- searchBox.setOnLongClickListener {
34
- val intent = Intent (Intent .ACTION_VIEW , Uri .parse(" https://www.google.com" ))
35
- intent.addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
57
+ search.setOnLongClickListener {
36
58
try {
37
- searchBox .context.startActivity(intent )
59
+ search .context.startActivity(Intent ( Intent . ACTION_VIEW , Uri .parse( " https://www.google.com " )).addFlags( Intent . FLAG_ACTIVITY_NEW_TASK ) )
38
60
} catch (e: Exception ) {
39
- Toast .makeText(searchBox .context, " No browser found!" , Toast .LENGTH_SHORT ).show()
61
+ Toast .makeText(search .context, " Browser not found!" , Toast .LENGTH_SHORT ).show()
40
62
}
41
63
true
42
64
}
43
65
}
44
66
45
- fun filterAppsAndContacts (query : String ) {
46
- val newFilteredList = mutableListOf<ResolveInfo >()
47
-
48
- if (query.isNotEmpty()) {
49
- // Filter installed apps
50
- newFilteredList.addAll(fullAppList.filter {
51
- it.loadLabel(packageManager).toString().contains(query, ignoreCase = true )
52
- })
53
-
54
- // Group contacts by name
55
- val groupedContacts = contactsList.filter {
56
- it.contains(query, ignoreCase = true )
57
- }.groupBy { it }
58
-
59
- // Create contact options
60
- groupedContacts.forEach { (contactName, contactList) ->
61
- // Create WhatsApp contacts for each contact in the group
62
- val filteredWhatsAppContacts = contactList.map { createWhatsAppContactOption(it) }
63
-
64
- // Add WhatsApp contacts first
65
- newFilteredList.addAll(filteredWhatsAppContacts)
66
-
67
- // Add SMS and phone options for each contact
68
- contactList.forEach { contact ->
69
- newFilteredList.add(createContactOption(contact))
70
- newFilteredList.add(createSmsOption(contact))
71
- }
72
- }
73
-
74
- // Fallback if no results found
75
- if (newFilteredList.isEmpty()) {
76
- newFilteredList.add(createPlayStoreSearchOption(query))
77
- newFilteredList.add(createGoogleMapsSearchOption(query))
78
- newFilteredList.add(createYoutubeSearchOption(query))
79
- newFilteredList.add(createBrowserSearchOption(query))
80
- }
81
- } else {
82
- // If query is empty, show all apps sorted
83
- newFilteredList.addAll(fullAppList.sortedBy { it.loadLabel(packageManager).toString().lowercase() })
84
- }
85
-
86
- // Update the actual list
87
- appList.clear()
88
- appList.addAll(newFilteredList)
89
- adapter.notifyDataSetChanged() // Ensure UI updates
90
- }
91
-
92
- private fun createWhatsAppContactOption (contact : String ): ResolveInfo {
93
- return ResolveInfo ().apply {
94
- activityInfo = ActivityInfo ().apply {
95
- packageName = " whatsapp_contact"
96
- name = contact // Display contact name
67
+ fun updateAppLabels () {
68
+ appLabels.clear()
69
+ CoroutineScope (Dispatchers .IO ).launch {
70
+ allApps.forEach { app ->
71
+ appLabels[app] = app.loadLabel(pm).toString().lowercase()
97
72
}
98
73
}
99
74
}
100
75
101
- private fun createGoogleMapsSearchOption (query : String ): ResolveInfo {
102
- return ResolveInfo ().apply {
103
- activityInfo = ActivityInfo ().apply {
104
- packageName = " maps_search"
105
- name = query
106
- }
107
- }
108
- }
76
+ private suspend fun filter (query : String ) = withContext(Dispatchers .Default ) {
77
+ val filtered = mutableListOf<ResolveInfo >()
78
+ if (query.isNotEmpty()) {
79
+ val lowerQuery = query.lowercase()
109
80
110
- private fun createYoutubeSearchOption (query : String ): ResolveInfo {
111
- return ResolveInfo ().apply {
112
- activityInfo = ActivityInfo ().apply {
113
- packageName = " yt_search"
114
- name = query
115
- }
116
- }
117
- }
81
+ val validApps = allApps.filter { appLabels[it]?.contains(lowerQuery) == true } // Filter valid apps.
82
+ filtered.addAll(validApps)
118
83
119
- private fun createFileSearchOption (query : String ): ResolveInfo {
120
- return ResolveInfo ().apply {
121
- activityInfo = ActivityInfo ().apply {
122
- packageName = " file_search"
123
- name = query
84
+ contacts.filter { it.lowercase().contains(lowerQuery) }.groupBy { it }.forEach { (_, list) ->
85
+ list.map { createWhatsApp(it) }.let { filtered.addAll(it) }
86
+ list.forEach { filtered.add(createContact(it)); filtered.add(createSms(it)) }
124
87
}
125
- }
126
- }
127
-
128
- private fun createPlayStoreSearchOption (query : String ): ResolveInfo {
129
- return ResolveInfo ().apply {
130
- activityInfo = ActivityInfo ().apply {
131
- packageName = " play_store_search"
132
- name = query
88
+ if (filtered.isEmpty()) {
89
+ filtered.add(createPlayStore(query)); filtered.add(createMaps(query)); filtered.add(createYoutube(query)); filtered.add(createBrowser(query))
133
90
}
91
+ } else {
92
+ // Show only the app list when search is empty
93
+ filtered.addAll(allApps.sortedBy { appLabels[it] })
134
94
}
135
- }
136
95
137
- private fun createBrowserSearchOption (query : String ): ResolveInfo {
138
- return ResolveInfo ().apply {
139
- activityInfo = ActivityInfo ().apply {
140
- packageName = " browser_search"
141
- name = query
142
-
143
- }
96
+ withContext(Dispatchers .Main ) {
97
+ apps.clear()
98
+ apps.addAll(filtered)
99
+ adapter.notifyDataSetChanged()
144
100
}
145
101
}
146
102
147
- private fun createSmsOption (contact : String ): ResolveInfo {
148
- return ResolveInfo ().apply {
149
- activityInfo = ActivityInfo ().apply {
150
- packageName = " sms_contact"
151
- name = contact
103
+ fun removeInvalidApps () {
104
+ val iterator = allApps.iterator()
105
+ while (iterator.hasNext()) {
106
+ val app = iterator.next()
107
+ try {
108
+ pm.getApplicationLabel(app.activityInfo.applicationInfo)
109
+ } catch (e: Exception ) {
110
+ iterator.remove()
152
111
}
153
112
}
113
+ updateAppLabels()
154
114
}
155
115
156
- private fun createContactOption (contact : String ): ResolveInfo {
157
- return ResolveInfo ().apply {
158
- activityInfo = ActivityInfo ().apply {
159
- packageName = " contact_search"
160
- name = contact // Display contact name or number
161
- }
162
- }
163
- }
164
- }
116
+ private fun createWhatsApp (contact : String ) = ResolveInfo ().apply { activityInfo = ActivityInfo ().apply { packageName = " whatsapp_contact" ; name = contact } }
117
+ private fun createMaps (query : String ) = ResolveInfo ().apply { activityInfo = ActivityInfo ().apply { packageName = " maps_search" ; name = query } }
118
+ private fun createYoutube (query : String ) = ResolveInfo ().apply { activityInfo = ActivityInfo ().apply { packageName = " yt_search" ; name = query } }
119
+ private fun createPlayStore (query : String ) = ResolveInfo ().apply { activityInfo = ActivityInfo ().apply { packageName = " play_store_search" ; name = query } }
120
+ private fun createBrowser (query : String ) = ResolveInfo ().apply { activityInfo = ActivityInfo ().apply { packageName = " browser_search" ; name = query } }
121
+ private fun createSms (contact : String ) = ResolveInfo ().apply { activityInfo = ActivityInfo ().apply { packageName = " sms_contact" ; name = contact } }
122
+ private fun createContact (contact : String ) = ResolveInfo ().apply { activityInfo = ActivityInfo ().apply { packageName = " contact_search" ; name = contact } }
123
+ }
0 commit comments