diff --git a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt index b8dc136c4e..3c7a6fcfc3 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/buildAndRun.kt @@ -46,6 +46,7 @@ import com.itsaky.androidide.tasks.executeAsync import com.itsaky.androidide.utils.Environment.GRADLE_USER_HOME import com.itsaky.androidide.utils.flashError import com.itsaky.androidide.utils.flashSuccess +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import java.io.File @@ -112,15 +113,18 @@ private class GradleCommands( ) } - override fun onItemSelected(position: Int, isSelected: Boolean) { - when (position) { - 0 -> isStacktraceEnabled = isSelected - 1 -> isInfoEnabled = isSelected - 2 -> isDebugEnabled = isSelected - 3 -> isScanEnabled = isSelected - 4 -> isWarningModeAllEnabled = isSelected - 5 -> isBuildCacheEnabled = isSelected - 6 -> isOfflineEnabled = isSelected + override fun onChoicesConfirmed(selectedPositions: List) { + for (position in selectedPositions) { + when (position) { + 0 -> ::isStacktraceEnabled + 1 -> ::isInfoEnabled + 2 -> ::isDebugEnabled + 3 -> ::isScanEnabled + 4 -> ::isWarningModeAllEnabled + 5 -> ::isBuildCacheEnabled + 6 -> ::isOfflineEnabled + else -> null + }?.set(true) } } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/editor.kt b/app/src/main/java/com/itsaky/androidide/preferences/editor.kt index 1eca5b9526..75ab07687c 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/editor.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/editor.kt @@ -183,21 +183,21 @@ private class TabSize( return choices } - override fun onItemSelected(position: Int, isSelected: Boolean) { - var size = (position + 1) * 2 - if (size < 2 || size > 8) { - size = 4 - } - tabSize = size - } - - override fun getSelectedItem(context: Context): Int { + override fun getInitiallySelectionItemPosition(context: Context): Int { var current = tabSize / 2 - 1 if (current < 0 || current >= choices.size) { current = 1 } return current } + + override fun onChoiceConfirmed(position: Int) { + var size = (position + 1) * 2 + if (size < 2 || size > 8) { + size = 4 + } + tabSize = size + } } @Parcelize @@ -217,14 +217,12 @@ private class ColorSchemePreference( return schemes.map { it.name }.toTypedArray() } - override fun getSelectedItem(context: Context): Int { + override fun getInitiallySelectionItemPosition(context: Context): Int { return schemes.indexOfFirst { it.key == colorScheme } } - override fun onItemSelected(position: Int, isSelected: Boolean) { - if (isSelected) { - colorScheme = schemes[position].key - } + override fun onChoiceConfirmed(position: Int) { + colorScheme = schemes[position].key } } @@ -240,16 +238,6 @@ private class NonPrintablePaintingFlags( return arrayOf("Leading", "Trailing", "Inner", "Empty lines", "Line breaks") } - override fun onItemSelected(position: Int, isSelected: Boolean) { - when (position) { - 0 -> drawLeadingWs = isSelected - 1 -> drawTrailingWs = isSelected - 2 -> drawInnerWs = isSelected - 3 -> drawEmptyLineWs = isSelected - 4 -> drawLineBreak = isSelected - } - } - override fun getCheckedItems(): BooleanArray { return booleanArrayOf( drawLeadingWs, @@ -259,6 +247,19 @@ private class NonPrintablePaintingFlags( drawLineBreak ) } + + override fun onChoicesConfirmed(selectedPositions: List) { + for (position in selectedPositions) { + when (position) { + 0 -> ::drawLeadingWs + 1 -> ::drawTrailingWs + 2 -> ::drawInnerWs + 3 -> ::drawEmptyLineWs + 4 -> ::drawLineBreak + else -> null + }?.set(true) + } + } } @Parcelize diff --git a/app/src/main/java/com/itsaky/androidide/preferences/general.kt b/app/src/main/java/com/itsaky/androidide/preferences/general.kt index 366fe54316..81f71277f1 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/general.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/general.kt @@ -110,7 +110,7 @@ class UiMode( ) } - override fun getSelectedItem(context: Context): Int { + override fun getInitiallySelectionItemPosition(context: Context): Int { return when (uiMode) { AppCompatDelegate.MODE_NIGHT_NO -> 0 AppCompatDelegate.MODE_NIGHT_YES -> 1 @@ -118,15 +118,11 @@ class UiMode( } } - override fun onItemSelected(position: Int, isSelected: Boolean) { - if (isSelected) { - val mode = - when (position) { - 0 -> AppCompatDelegate.MODE_NIGHT_NO - 1 -> AppCompatDelegate.MODE_NIGHT_YES - else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } - uiMode = mode + override fun onChoiceConfirmed(position: Int) { + uiMode = when (position) { + 0 -> AppCompatDelegate.MODE_NIGHT_NO + 1 -> AppCompatDelegate.MODE_NIGHT_YES + else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM } } } @@ -146,14 +142,12 @@ class ThemeSelector( return themes.map { context.getString(it.title) }.toTypedArray() } - override fun getSelectedItem(context: Context): Int { + override fun getInitiallySelectionItemPosition(context: Context): Int { return themes.indexOf(ThemeManager.getCurrentTheme()) } - override fun onItemSelected(position: Int, isSelected: Boolean) { - if (isSelected) { - selectedTheme = themes[position].name - } + override fun onChoiceConfirmed(position: Int) { + selectedTheme = themes[position].name } } diff --git a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefs.kt b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefs.kt index 795135abad..79354062ec 100644 --- a/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefs.kt +++ b/app/src/main/java/com/itsaky/androidide/preferences/xmlPrefs.kt @@ -227,7 +227,7 @@ private class EmptyElementsBehavior( ) : SingleChoicePreference() { override val dialogCancellable = true - override fun getSelectedItem(context: Context): Int { + override fun getInitiallySelectionItemPosition(context: Context): Int { return EmptyElements.valueOf(emptyElementsBehavior).ordinal } @@ -235,7 +235,7 @@ private class EmptyElementsBehavior( return EmptyElements.values().map { it.toString() }.toTypedArray() } - override fun onItemSelected(position: Int, isSelected: Boolean) { + override fun onChoiceConfirmed(position: Int) { emptyElementsBehavior = EmptyElements.values()[position].toString() } } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt new file mode 100644 index 0000000000..a3b51abdd4 --- /dev/null +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/ChoiceBasedDialogPreference.kt @@ -0,0 +1,73 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + +package com.itsaky.androidide.preferences + +import androidx.preference.Preference +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +/** + * Base class for dialog preferences which allows users to choose from multiple items. + * + * @author Akash Yadav + */ +abstract class ChoiceBasedDialogPreference : DialogPreference(), PreferenceChoices { + + protected open var selectedPositions: MutableList? = null + + final override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { + val choices = getChoices(preference.context) + selectedPositions = MutableList(size = choices.size) { -1 } + + onConfigureDialogChoices(preference, dialog, choices) + + dialog.setPositiveButton(android.R.string.ok) { dialogInterface, _ -> + dialogInterface.dismiss() + + val positions = selectedPositions?.also { positions -> + positions.removeIf { idx -> idx == -1 } + } + onChoicesConfirmed(positions ?: emptyList()) + } + + dialog.setNegativeButton(android.R.string.cancel) { dialogInterface, _ -> + dialogInterface.dismiss() + onChoicesCancelled() + } + } + + override fun onSelectionChanged(position: Int, isSelected: Boolean) { + if (isSelected) { + selectedPositions!!.add(position) + } else { + selectedPositions!!.removeAt(position) + } + } + + /** + * Configure the dialog choices. + */ + protected abstract fun onConfigureDialogChoices( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + choices: Array + ) + + override fun onChoicesConfirmed(selectedPositions: List) {} + + override fun onChoicesCancelled() {} +} \ No newline at end of file diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/MultiChoicePreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/MultiChoicePreference.kt index a4afdb19e7..6cf834d7b0 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/MultiChoicePreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/MultiChoicePreference.kt @@ -23,21 +23,32 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder /** * A preference with multiple choices to select from. * + * The [onSelectionChanged] is called whenever the + * [DialogInterface.OnMultiChoiceClickListener][android.content.DialogInterface.OnMultiChoiceClickListener] + * is called. + * * @author Akash Yadav */ -abstract class MultiChoicePreference : DialogPreference(), PreferenceChoices { +abstract class MultiChoicePreference : ChoiceBasedDialogPreference(), PreferenceChoices { /** - * Get the index of all the items that should be selected by default. + * Get the index of all the checked and unchecked items. + * * @see MaterialAlertDialogBuilder.setMultiChoiceItems */ abstract fun getCheckedItems(): BooleanArray - override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { - super.onConfigureDialog(preference, dialog) - dialog.setMultiChoiceItems(getChoices(preference.context), getCheckedItems()) { _, which, checked -> - onItemSelected(which, checked) + override fun onConfigureDialogChoices( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + choices: Array + ) { + + dialog.setMultiChoiceItems( + getChoices(preference.context), + getCheckedItems() + ) { _, which, checked -> + onSelectionChanged(which, checked) } - dialog.setPositiveButton(android.R.string.ok, null) } } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt index a2637270a3..f682d740a1 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/PreferenceChoices.kt @@ -28,11 +28,24 @@ interface PreferenceChoices { /** Get the choices to show in the preference. */ fun getChoices(context: Context): Array - + /** * Called when an item is selected from the single choice list. * * @param position The position of the selected item. + * @param isSelected Whether the item is selected. + */ + fun onSelectionChanged(position: Int, isSelected: Boolean) + + /** + * Called when the user confirms the selections. + * + * @param selectedPositions The positions of the selected items. + */ + fun onChoicesConfirmed(selectedPositions: List) + + /** + * Called when the user cancels the selections. */ - fun onItemSelected(position: Int, isSelected: Boolean = true) + fun onChoicesCancelled() } diff --git a/preferences/src/main/java/com/itsaky/androidide/preferences/SingleChoicePreference.kt b/preferences/src/main/java/com/itsaky/androidide/preferences/SingleChoicePreference.kt index 42d9d4de17..c2e5c22aa6 100644 --- a/preferences/src/main/java/com/itsaky/androidide/preferences/SingleChoicePreference.kt +++ b/preferences/src/main/java/com/itsaky/androidide/preferences/SingleChoicePreference.kt @@ -24,22 +24,49 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder /** * A preference which allows selecting a single value from a list of values. * + * The [onSelectionChanged] method is called exactly two times when the user changes the selection, first call for the previously + * selected item and second call for the newly selected item. + * + * The [onChoicesConfirmed] is always called with a singleton list. + * * @author Akash Yadav */ -abstract class SingleChoicePreference : DialogPreference(), PreferenceChoices { +abstract class SingleChoicePreference : ChoiceBasedDialogPreference(), PreferenceChoices { + + /** + * The currently selected item in the dialog. + */ + protected open var currentSelection: Int = -1 /** * Get the index of the selected item. * @see MaterialAlertDialogBuilder.setSingleChoiceItems */ - abstract fun getSelectedItem(context: Context): Int - - override fun onConfigureDialog(preference: Preference, dialog: MaterialAlertDialogBuilder) { - super.onConfigureDialog(preference, dialog) - dialog.setSingleChoiceItems(getChoices(preference.context), - getSelectedItem(preference.context)) { dialogInterface, position -> - dialogInterface.dismiss() - onItemSelected(position) + abstract fun getInitiallySelectionItemPosition(context: Context): Int + + override fun onConfigureDialogChoices( + preference: Preference, + dialog: MaterialAlertDialogBuilder, + choices: Array + ) { + + dialog.setSingleChoiceItems( + getChoices(preference.context), + getInitiallySelectionItemPosition(preference.context)) + { _, position -> + + if (currentSelection != -1) { + onSelectionChanged(currentSelection, false) + } + + currentSelection = position + onSelectionChanged(position, true) } } + + final override fun onChoicesConfirmed(selectedPositions: List) { + selectedPositions.firstOrNull()?.let { onChoiceConfirmed(it) } + } + + protected open fun onChoiceConfirmed(position: Int) {} }