Skip to content

Commit 0654a1c

Browse files
committed
refactor + fix bugs + add modify group option in dock
1 parent d4fc5b8 commit 0654a1c

File tree

11 files changed

+349
-673
lines changed

11 files changed

+349
-673
lines changed

app/release/app-release.apk

9.24 KB
Binary file not shown.
-9 Bytes
Binary file not shown.
-7 Bytes
Binary file not shown.

app/src/main/java/com/guruswarupa/launch/AppAdapter.kt

Lines changed: 65 additions & 191 deletions
Large diffs are not rendered by default.

app/src/main/java/com/guruswarupa/launch/AppDockManager.kt

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.widget.LinearLayout
2121
import android.widget.PopupWindow
2222
import android.widget.TextView
2323
import android.widget.Toast
24+
import androidx.core.content.ContextCompat
2425
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2526
import com.google.android.material.textfield.TextInputEditText
2627
import com.google.android.material.textfield.TextInputLayout
@@ -330,22 +331,28 @@ class AppDockManager(
330331
}
331332

332333
private fun showRemoveDockAppDialog(item: String) {
334+
// Options for the dialog
333335
val options = if (item.startsWith("group:")) {
334-
arrayOf("Remove", "Rename")
336+
arrayOf("Remove", "Rename", "Modify Group")
335337
} else {
336338
arrayOf("Remove")
337339
}
338340

339-
MaterialAlertDialogBuilder(context)
341+
// Create the dialog
342+
MaterialAlertDialogBuilder(context, R.style.CustomDialogTheme)
340343
.setTitle("Manage Dock Item")
341344
.setItems(options) { _, which ->
342345
when (which) {
343346
0 -> removeDockItem(item)
344347
1 -> if (item.startsWith("group:")) showRenameGroupDialog(item)
348+
2 -> if (item.startsWith("group:")) showModifyGroupDialog(item)
345349
}
346350
refreshDock()
347351
}
348-
.setNegativeButton("Cancel", null)
352+
.setNegativeButton("Cancel") { dialog, _ ->
353+
dialog.dismiss()
354+
}
355+
.setBackground(ContextCompat.getDrawable(context, R.drawable.dialog_background)) // Custom background
349356
.show()
350357
}
351358

@@ -387,6 +394,53 @@ class AppDockManager(
387394
.show()
388395
}
389396

397+
private fun showModifyGroupDialog(item: String) {
398+
if (!item.startsWith("group:")) return
399+
400+
val parts = item.split(":", limit = 3)
401+
if (parts.size != 3) return
402+
403+
val groupName = parts[1]
404+
val currentPackageNames = parts[2].split(',').toMutableList()
405+
406+
val intent = Intent(Intent.ACTION_MAIN).apply {
407+
addCategory(Intent.CATEGORY_LAUNCHER)
408+
}
409+
val apps = packageManager.queryIntentActivities(intent, 0)
410+
val sortedApps = apps.sortedBy { it.loadLabel(packageManager).toString().lowercase() }
411+
val appNames = sortedApps.map { it.loadLabel(packageManager).toString() }
412+
val appPackageNames = sortedApps.map { it.activityInfo.packageName }
413+
414+
val checkedItems = BooleanArray(appNames.size) { false }
415+
appPackageNames.forEachIndexed { index, packageName ->
416+
if (currentPackageNames.contains(packageName)) {
417+
checkedItems[index] = true
418+
}
419+
}
420+
421+
AlertDialog.Builder(context)
422+
.setTitle("Modify Group: $groupName")
423+
.setMultiChoiceItems(appNames.toTypedArray(), checkedItems) { _, which, isChecked ->
424+
val packageName = appPackageNames[which]
425+
if (isChecked) {
426+
currentPackageNames.add(packageName)
427+
} else {
428+
currentPackageNames.remove(packageName)
429+
}
430+
}
431+
.setPositiveButton("Save") { _, _ ->
432+
if (currentPackageNames.size >= 2) {
433+
removeDockItem(item)
434+
addGroupToDock(currentPackageNames, groupName)
435+
refreshDock()
436+
} else {
437+
Toast.makeText(context, "A group must have at least 2 apps", Toast.LENGTH_SHORT).show()
438+
}
439+
}
440+
.setNegativeButton("Cancel", null)
441+
.show()
442+
}
443+
390444
private fun showGroupNameDialog(selectedPackages: List<String>) {
391445
val editText = EditText(context)
392446
AlertDialog.Builder(context)

app/src/main/java/com/guruswarupa/launch/AppSearchManager.kt

Lines changed: 77 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -8,157 +8,116 @@ import android.net.Uri
88
import android.widget.EditText
99
import android.widget.Toast
1010
import androidx.recyclerview.widget.RecyclerView
11+
import kotlinx.coroutines.*
12+
import java.util.concurrent.ConcurrentHashMap
1113

1214
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>,
1618
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>
2022
) {
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
2127

2228
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+
}
2540

2641
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+
}
2851
}
2952

30-
override fun afterTextChanged(s: android.text.Editable?) {}
53+
override fun afterTextChanged(s: android.text.Editable?) {
54+
}
3155
})
3256

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 {
3658
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))
3860
} 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()
4062
}
4163
true
4264
}
4365
}
4466

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()
9772
}
9873
}
9974
}
10075

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()
10980

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)
11883

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)) }
12487
}
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))
13390
}
91+
} else {
92+
// Show only the app list when search is empty
93+
filtered.addAll(allApps.sortedBy { appLabels[it] })
13494
}
135-
}
13695

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()
144100
}
145101
}
146102

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()
152111
}
153112
}
113+
updateAppLabels()
154114
}
155115

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

Comments
 (0)