Skip to content

Commit 3da2514

Browse files
committed
Added dynamic assistant manager option
- Added chip with assistants count in category - Fix A-Z order case insensitive - Remove "Auto" button in manager dialog (similar behavior in dynamic mode but not better :D)
1 parent 8910b9c commit 3da2514

11 files changed

Lines changed: 173 additions & 99 deletions

File tree

app/src/main/java/com/wstxda/switchai/fragment/SettingsFragment.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.wstxda.switchai.ui.component.AssistantTutorialBottomSheet
2323
import com.wstxda.switchai.ui.component.DigitalAssistantSetupDialog
2424
import com.wstxda.switchai.ui.utils.AssistantResourcesManager
2525
import com.wstxda.switchai.utils.Constants
26+
import com.wstxda.switchai.viewmodel.AssistantSelectorViewModel
2627
import com.wstxda.switchai.viewmodel.SettingsViewModel
2728
import com.wstxda.switchai.widget.utils.AssistantWidgetUpdater
2829
import kotlinx.coroutines.launch
@@ -33,6 +34,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
3334
ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application)
3435
}
3536

37+
private val assistantSelectorViewModel: AssistantSelectorViewModel by viewModels {
38+
ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application)
39+
}
40+
3641
private lateinit var assistantResourcesManager: AssistantResourcesManager
3742
private val widgetManager by lazy { WidgetManager(requireContext()) }
3843
private val digitalAssistantPreference by lazy { DigitalAssistantPreference(this) }
@@ -61,6 +66,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
6166
findPreference<Preference>(Constants.DIGITAL_ASSISTANT_SETUP_PREF_KEY)?.isVisible =
6267
!isDone
6368
}
69+
assistantSelectorViewModel.isDynamicModeEnabled.observe(this) { isDynamic ->
70+
findPreference<MultiSelectListPreference>(Constants.ASSISTANT_MANAGER_MANUAL_PREF_KEY)?.isVisible =
71+
!isDynamic
72+
}
6473
}
6574

6675
override fun onResume() {

app/src/main/java/com/wstxda/switchai/ui/adapter/AssistantSelectorRecyclerView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.wstxda.switchai.ui.adapter
33
import com.wstxda.switchai.data.AssistantItem
44

55
sealed interface AssistantSelectorRecyclerView {
6-
data class CategoryHeader(val title: String) : AssistantSelectorRecyclerView
6+
data class CategoryHeader(val title: String, val count: Int) : AssistantSelectorRecyclerView
77
data class AssistantSelector(val assistantItem: AssistantItem) : AssistantSelectorRecyclerView
88
object ReorderTip : AssistantSelectorRecyclerView
99
}

app/src/main/java/com/wstxda/switchai/ui/component/AssistantManagerDialog.kt

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,14 @@ package com.wstxda.switchai.ui.component
33
import android.os.Bundle
44
import android.widget.Toast
55
import androidx.appcompat.app.AlertDialog
6-
import androidx.lifecycle.lifecycleScope
76
import androidx.preference.PreferenceDialogFragmentCompat
87
import com.wstxda.switchai.R
98
import com.wstxda.switchai.fragment.preferences.MultiSelectListPreference
10-
import com.wstxda.switchai.logic.PackageChecker
119
import com.wstxda.switchai.utils.Constants
12-
import kotlinx.coroutines.launch
1310

1411
class AssistantManagerDialog : PreferenceDialogFragmentCompat() {
1512

1613
private lateinit var pref: MultiSelectListPreference
17-
private lateinit var packageChecker: PackageChecker
1814
private val currentSelections = HashSet<String>()
1915
private var toastShownForInvalidSelection = false
2016

@@ -28,7 +24,6 @@ class AssistantManagerDialog : PreferenceDialogFragmentCompat() {
2824
super.onCreate(savedInstanceState)
2925

3026
pref = preference as MultiSelectListPreference
31-
packageChecker = PackageChecker(requireContext())
3227

3328
currentSelections.clear()
3429
val restored = savedInstanceState?.getStringArray(Constants.ASSISTANT_MANAGER_DIALOG)
@@ -51,52 +46,22 @@ class AssistantManagerDialog : PreferenceDialogFragmentCompat() {
5146

5247
val entries = pref.entries
5348
val entryValues = pref.entryValues
54-
5549
val checkedItems = BooleanArray(entryValues.size) { index ->
5650
currentSelections.contains(entryValues[index].toString())
5751
}
5852

5953
builder.setMultiChoiceItems(entries, checkedItems) { _, which, isChecked ->
6054
val value = entryValues[which].toString()
61-
if (isChecked) {
62-
currentSelections.add(value)
63-
} else {
64-
currentSelections.remove(value)
65-
}
55+
if (isChecked) currentSelections.add(value) else currentSelections.remove(value)
6656
updatePositiveButtonState()
6757
}
68-
69-
builder.setNeutralButton(R.string.auto_select_assistant) { _, _ ->
70-
applyAutomaticSelection()
71-
}
7258
}
7359

7460
override fun onStart() {
7561
super.onStart()
7662
updatePositiveButtonState()
7763
}
7864

79-
private fun applyAutomaticSelection() {
80-
lifecycleScope.launch {
81-
val installedAssistants = packageChecker.installedAssistants()
82-
val allAvailableAssistantsCount = pref.entryValues.size
83-
val selectedCount = installedAssistants.size
84-
val hiddenCount = allAvailableAssistantsCount - selectedCount
85-
86-
currentSelections.clear()
87-
currentSelections.addAll(installedAssistants)
88-
89-
saveSelections()
90-
dialog?.dismiss()
91-
92-
val msg = getString(
93-
R.string.auto_select_assistant_result, selectedCount, hiddenCount
94-
)
95-
96-
Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
97-
}
98-
}
99-
10065
private fun updatePositiveButtonState() {
10166
val okButton = (dialog as? AlertDialog)?.getButton(AlertDialog.BUTTON_POSITIVE) ?: return
10267

@@ -112,7 +77,6 @@ class AssistantManagerDialog : PreferenceDialogFragmentCompat() {
11277
if (!toastShownForInvalidSelection) {
11378
val limit: Int
11479
val msgRes: Int
115-
11680
when {
11781
count < pref.minSelection -> {
11882
limit = pref.minSelection
@@ -126,19 +90,13 @@ class AssistantManagerDialog : PreferenceDialogFragmentCompat() {
12690

12791
else -> return
12892
}
129-
130-
Toast.makeText(
131-
requireContext(), getString(msgRes, limit), Toast.LENGTH_SHORT
132-
).show()
133-
93+
Toast.makeText(requireContext(), getString(msgRes, limit), Toast.LENGTH_SHORT).show()
13494
toastShownForInvalidSelection = true
13595
}
13696
}
13797

13898
override fun onDialogClosed(positiveResult: Boolean) {
139-
if (positiveResult) {
140-
saveSelections()
141-
}
99+
if (positiveResult) saveSelections()
142100
}
143101

144102
private fun saveSelections() {

app/src/main/java/com/wstxda/switchai/ui/viewholder/AssistantSelectorCategoryViewHolder.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ class AssistantSelectorCategoryViewHolder(
1010

1111
fun bind(categoryHeader: AssistantSelectorRecyclerView.CategoryHeader) {
1212
binding.categoryTitleTextView.text = categoryHeader.title
13+
binding.categoryCountChip.text = categoryHeader.count.toString()
1314
}
1415
}

app/src/main/java/com/wstxda/switchai/utils/Constants.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ object Constants {
88
const val DIGITAL_ASSISTANT_SELECT_PREF_KEY = "digital_assistant_select"
99
const val ASSISTANT_SELECTOR_DIALOG_PREF_KEY = "assistant_selector_dialog"
1010
const val ASSISTANT_SEARCH_BAR_PREF_KEY = "assistant_search_bar"
11-
const val ASSISTANT_MANAGER_DIALOG_PREF_KEY = "assistant_selector_manager"
11+
const val ASSISTANT_MANAGER_MANUAL_PREF_KEY = "assistant_manager_manual"
12+
const val ASSISTANT_MANAGER_DYNAMIC_PREF_KEY = "assistant_manager_dynamic"
1213
const val OPEN_ASSISTANT_TILE_PREF_KEY = "open_assistant_tile"
1314
const val OPEN_ASSISTANT_WIDGET_PREF_KEY = "open_assistant_widget"
1415
const val ASSISTANT_VIBRATION_PREF_KEY = "assistant_vibration"

app/src/main/java/com/wstxda/switchai/viewmodel/AssistantSelectorViewModel.kt

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.wstxda.switchai.viewmodel
22

33
import android.app.Application
4+
import android.content.BroadcastReceiver
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.content.IntentFilter
48
import android.content.SharedPreferences
59
import androidx.core.content.edit
610
import androidx.lifecycle.AndroidViewModel
@@ -36,6 +40,9 @@ class AssistantSelectorViewModel(application: Application) : AndroidViewModel(ap
3640
private val _isLoading = MutableLiveData<Boolean>()
3741
val isLoading: LiveData<Boolean> = _isLoading
3842

43+
private val _isDynamicModeEnabled = MutableLiveData<Boolean>()
44+
val isDynamicModeEnabled: LiveData<Boolean> = _isDynamicModeEnabled
45+
3946
private val pinnedAssistantKeys = mutableListOf<String>()
4047
private val recentlyUsedAssistants = mutableListOf<Pair<String, Long>>()
4148

@@ -56,8 +63,30 @@ class AssistantSelectorViewModel(application: Application) : AndroidViewModel(ap
5663

5764
private val packageChecker: PackageChecker = PackageChecker(application.applicationContext)
5865

66+
private val packageChangeReceiver = object : BroadcastReceiver() {
67+
override fun onReceive(context: Context?, intent: Intent?) {
68+
if (isDynamicMode) {
69+
loadAssistants()
70+
}
71+
}
72+
}
73+
74+
private val isDynamicMode: Boolean
75+
get() = defaultSharedPreferences.getBoolean(
76+
Constants.ASSISTANT_MANAGER_DYNAMIC_PREF_KEY, false
77+
)
78+
5979
init {
6080
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this)
81+
_isDynamicModeEnabled.value = isDynamicMode
82+
83+
val intentFilter = IntentFilter().apply {
84+
addAction(Intent.ACTION_PACKAGE_ADDED)
85+
addAction(Intent.ACTION_PACKAGE_REMOVED)
86+
addDataScheme("package")
87+
}
88+
getApplication<Application>().registerReceiver(packageChangeReceiver, intentFilter)
89+
6190
loadAssistants()
6291
}
6392

@@ -83,7 +112,8 @@ class AssistantSelectorViewModel(application: Application) : AndroidViewModel(ap
83112
return
84113
}
85114

86-
val filteredList = allAssistantItems.filterIsInstance<AssistantSelectorRecyclerView.AssistantSelector>()
115+
val filteredList =
116+
allAssistantItems.filterIsInstance<AssistantSelectorRecyclerView.AssistantSelector>()
87117
.filter { assistant ->
88118
assistant.assistantItem.name.contains(query, ignoreCase = true)
89119
}
@@ -189,28 +219,35 @@ class AssistantSelectorViewModel(application: Application) : AndroidViewModel(ap
189219

190220
private fun getVisibleAssistantDetails(installedKeys: Set<String>): List<AssistantItem> {
191221
val context = getApplication<Application>().applicationContext
192-
val defaultVisibleAssistants =
193-
context.resources.getStringArray(R.array.assistant_values).toSet()
194-
val visibleAssistantKeys = preferenceHelper.getStringSet(
195-
Constants.ASSISTANT_MANAGER_DIALOG_PREF_KEY, defaultVisibleAssistants
196-
)
197222

198-
return AssistantsMap.assistantActivity.filterKeys { it in visibleAssistantKeys }
199-
.map { (key, _) ->
200-
AssistantItem(
201-
key = key,
202-
name = assistantResourcesManager.getAssistantName(key),
203-
iconRes = assistantResourcesManager.getAssistantIcon(key),
204-
isInstalled = key in installedKeys,
205-
isPinned = key in pinnedAssistantKeys,
206-
lastUsedTime = recentlyUsedAssistants.find { it.first == key }?.second ?: 0L
207-
)
208-
}
223+
val keysToShow: Set<String> = if (isDynamicMode) {
224+
installedKeys
225+
} else {
226+
val defaultVisibleAssistants =
227+
context.resources.getStringArray(R.array.assistant_values).toSet()
228+
preferenceHelper.getStringSet(
229+
Constants.ASSISTANT_MANAGER_MANUAL_PREF_KEY, defaultVisibleAssistants
230+
)
231+
}
232+
233+
return AssistantsMap.assistantActivity.filterKeys { it in keysToShow }.map { (key, _) ->
234+
AssistantItem(
235+
key = key,
236+
name = assistantResourcesManager.getAssistantName(key),
237+
iconRes = assistantResourcesManager.getAssistantIcon(key),
238+
isInstalled = key in installedKeys,
239+
isPinned = key in pinnedAssistantKeys,
240+
lastUsedTime = recentlyUsedAssistants.find { it.first == key }?.second ?: 0L
241+
)
242+
}
209243
}
210244

211245
private fun buildCategorizedList(assistants: List<AssistantItem>): List<AssistantSelectorRecyclerView> {
212246
val context = getApplication<Application>().applicationContext
213247
val (installedAssistants, notInstalledAssistants) = assistants.partition { it.isInstalled }
248+
val nameComparator =
249+
compareBy(String.CASE_INSENSITIVE_ORDER) { it: AssistantItem -> it.name }
250+
214251
return buildList {
215252
val pinnedAssistants = installedAssistants.filter { it.isPinned }
216253
val unpinnedItems = installedAssistants.filterNot { it.isPinned }
@@ -223,7 +260,12 @@ class AssistantSelectorViewModel(application: Application) : AndroidViewModel(ap
223260
val (recentItems, otherItems) = unpinnedItems.partition { it.key in recentKeys }
224261

225262
if (pinnedItems.isNotEmpty()) {
226-
add(AssistantSelectorRecyclerView.CategoryHeader(context.getString(R.string.assistant_category_pin)))
263+
add(
264+
AssistantSelectorRecyclerView.CategoryHeader(
265+
title = context.getString(R.string.assistant_category_pin),
266+
count = pinnedItems.size
267+
)
268+
)
227269
val tipDismissed = assistantStatePreferences.getBoolean(
228270
Constants.REORDER_TIP_DISMISSED_KEY, false
229271
)
@@ -233,20 +275,35 @@ class AssistantSelectorViewModel(application: Application) : AndroidViewModel(ap
233275
addAll(pinnedItems)
234276
}
235277
if (recentItems.isNotEmpty()) {
236-
add(AssistantSelectorRecyclerView.CategoryHeader(context.getString(R.string.assistant_category_recent)))
278+
add(
279+
AssistantSelectorRecyclerView.CategoryHeader(
280+
title = context.getString(R.string.assistant_category_recent),
281+
count = recentItems.size
282+
)
283+
)
237284
val sortedRecent = recentItems.sortedByDescending { it.lastUsedTime }
238285
.map { AssistantSelectorRecyclerView.AssistantSelector(it) }
239286
addAll(sortedRecent)
240287
}
241288
if (otherItems.isNotEmpty()) {
242-
add(AssistantSelectorRecyclerView.CategoryHeader(context.getString(R.string.assistant_category_all)))
243-
val sortedOthers = otherItems.sortedBy { it.name }
289+
add(
290+
AssistantSelectorRecyclerView.CategoryHeader(
291+
title = context.getString(R.string.assistant_category_all),
292+
count = otherItems.size
293+
)
294+
)
295+
val sortedOthers = otherItems.sortedWith(nameComparator)
244296
.map { AssistantSelectorRecyclerView.AssistantSelector(it) }
245297
addAll(sortedOthers)
246298
}
247299
if (notInstalledAssistants.isNotEmpty()) {
248-
add(AssistantSelectorRecyclerView.CategoryHeader(context.getString(R.string.assistant_category_not_installed)))
249-
val sortedNotInstalled = notInstalledAssistants.sortedBy { it.name }
300+
add(
301+
AssistantSelectorRecyclerView.CategoryHeader(
302+
title = context.getString(R.string.assistant_category_not_installed),
303+
count = notInstalledAssistants.size
304+
)
305+
)
306+
val sortedNotInstalled = notInstalledAssistants.sortedWith(nameComparator)
250307
.map { AssistantSelectorRecyclerView.AssistantSelector(it) }
251308
addAll(sortedNotInstalled)
252309
}
@@ -277,14 +334,19 @@ class AssistantSelectorViewModel(application: Application) : AndroidViewModel(ap
277334
}
278335

279336
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
280-
if (key == Constants.ASSISTANT_MANAGER_DIALOG_PREF_KEY) {
281-
loadAssistants()
337+
when (key) {
338+
Constants.ASSISTANT_MANAGER_MANUAL_PREF_KEY -> loadAssistants()
339+
Constants.ASSISTANT_MANAGER_DYNAMIC_PREF_KEY -> {
340+
_isDynamicModeEnabled.value = isDynamicMode
341+
loadAssistants()
342+
}
282343
}
283344
}
284345

285346
override fun onCleared() {
286347
super.onCleared()
287348
defaultSharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
349+
getApplication<Application>().unregisterReceiver(packageChangeReceiver)
288350
}
289351

290352
fun dismissReorderTip() {

0 commit comments

Comments
 (0)