From a5b11ba99a643c1bb274e2f4b527426498160ba1 Mon Sep 17 00:00:00 2001 From: Michatec Date: Sat, 28 Mar 2026 21:55:38 +0100 Subject: [PATCH] feat(ui): implement theme selection dialog with TV support --- app/src/main/java/com/michatec/radio/Keys.kt | 1 + .../com/michatec/radio/SettingsFragment.kt | 87 +++++++++++---- .../radio/dialogs/ThemeSelectionDialog.kt | 101 ++++++++++++++++++ .../dialog_theme_selection.xml | 64 +++++++++++ 4 files changed, 231 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/michatec/radio/dialogs/ThemeSelectionDialog.kt create mode 100644 app/src/main/res/layout-television/dialog_theme_selection.xml diff --git a/app/src/main/java/com/michatec/radio/Keys.kt b/app/src/main/java/com/michatec/radio/Keys.kt index fa14716..b8224e8 100644 --- a/app/src/main/java/com/michatec/radio/Keys.kt +++ b/app/src/main/java/com/michatec/radio/Keys.kt @@ -84,6 +84,7 @@ object Keys { const val DIALOG_REMOVE_STATION: Int = 2 const val DIALOG_UPDATE_STATION_IMAGES: Int = 4 const val DIALOG_RESTORE_COLLECTION: Int = 5 + const val DIALOG_THEME_SELECTION: Int = 6 // dialog results const val DIALOG_EMPTY_PAYLOAD_STRING: String = "" diff --git a/app/src/main/java/com/michatec/radio/SettingsFragment.kt b/app/src/main/java/com/michatec/radio/SettingsFragment.kt index 148d101..0d488eb 100644 --- a/app/src/main/java/com/michatec/radio/SettingsFragment.kt +++ b/app/src/main/java/com/michatec/radio/SettingsFragment.kt @@ -5,6 +5,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -16,8 +17,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.net.toUri import androidx.navigation.fragment.findNavController import androidx.preference.* +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.michatec.radio.dialogs.ErrorDialog +import com.michatec.radio.dialogs.ThemeSelectionDialog import com.michatec.radio.dialogs.YesNoDialog import com.michatec.radio.helpers.* import com.michatec.radio.helpers.AppThemeHelper.getColor @@ -31,7 +34,7 @@ import java.util.* /* * SettingsFragment class */ -class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogListener { +class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogListener, ThemeSelectionDialog.ThemeSelectionDialogListener { /* Define log tag */ @@ -55,35 +58,24 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList val screen = preferenceManager.createPreferenceScreen(context) // set up "App Theme" preference - val preferenceThemeSelection = ListPreference(activity as Context) + val preferenceThemeSelection = Preference(activity as Context) preferenceThemeSelection.title = getString(R.string.pref_theme_selection_title) preferenceThemeSelection.setIcon(R.drawable.ic_brush_24dp) preferenceThemeSelection.key = Keys.PREF_THEME_SELECTION preferenceThemeSelection.summary = "${getString(R.string.pref_theme_selection_summary)} ${ AppThemeHelper.getCurrentTheme(activity as Context) }" - preferenceThemeSelection.entries = arrayOf( - getString(R.string.pref_theme_selection_mode_device_default), - getString(R.string.pref_theme_selection_mode_light), - getString(R.string.pref_theme_selection_mode_dark) - ) - preferenceThemeSelection.entryValues = arrayOf( - Keys.STATE_THEME_FOLLOW_SYSTEM, - Keys.STATE_THEME_LIGHT_MODE, - Keys.STATE_THEME_DARK_MODE - ) - preferenceThemeSelection.setDefaultValue(Keys.STATE_THEME_FOLLOW_SYSTEM) - preferenceThemeSelection.setOnPreferenceChangeListener { preference, newValue -> - if (preference is ListPreference) { - val index: Int = preference.entryValues.indexOf(newValue) - preferenceThemeSelection.summary = - "${getString(R.string.pref_theme_selection_summary)} ${preference.entries[index]}" - - AppThemeHelper.setTheme(newValue as String) - return@setOnPreferenceChangeListener true + preferenceThemeSelection.setOnPreferenceClickListener { + // check if device is a TV + val isTv = requireContext().packageManager.hasSystemFeature(android.content.pm.PackageManager.FEATURE_LEANBACK) + if (isTv) { + // show TV-specific theme selection dialog + ThemeSelectionDialog(this).show(activity as Context) } else { - return@setOnPreferenceChangeListener false + // show standard theme selection dialog for non-TV devices + showThemeSelectionDialog() } + return@setOnPreferenceClickListener true } // set up "Update Station Images" preference @@ -306,6 +298,57 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList } + /* Shows theme selection dialog for non-TV devices */ + private fun showThemeSelectionDialog() { + val themes = arrayOf( + getString(R.string.pref_theme_selection_mode_device_default), + getString(R.string.pref_theme_selection_mode_light), + getString(R.string.pref_theme_selection_mode_dark) + ) + val themeValues = arrayOf( + Keys.STATE_THEME_FOLLOW_SYSTEM, + Keys.STATE_THEME_LIGHT_MODE, + Keys.STATE_THEME_DARK_MODE + ) + val currentTheme = AppThemeHelper.getCurrentTheme(activity as Context) + val currentIndex = themes.indexOf(currentTheme) + + val builder = MaterialAlertDialogBuilder(activity as Context) + builder.setTitle(getString(R.string.pref_theme_selection_title)) + builder.setSingleChoiceItems(themes, currentIndex) { dialog, which -> + val selectedTheme = themeValues[which] + AppThemeHelper.setTheme(selectedTheme) + // update summary + val preferenceThemeSelection = findPreference(Keys.PREF_THEME_SELECTION) + preferenceThemeSelection?.summary = "${getString(R.string.pref_theme_selection_summary)} ${themes[which]}" + dialog.dismiss() + } + builder.setNegativeButton(R.string.dialog_generic_button_cancel, null) + builder.show() + } + + + /* Overrides onThemeSelectionDialog from ThemeSelectionDialogListener */ + override fun onThemeSelectionDialog(dialogResult: Boolean, selectedTheme: String) { + if (dialogResult) { + // update summary + val themes = arrayOf( + getString(R.string.pref_theme_selection_mode_device_default), + getString(R.string.pref_theme_selection_mode_light), + getString(R.string.pref_theme_selection_mode_dark) + ) + val themeValues = arrayOf( + Keys.STATE_THEME_FOLLOW_SYSTEM, + Keys.STATE_THEME_LIGHT_MODE, + Keys.STATE_THEME_DARK_MODE + ) + val index = themeValues.indexOf(selectedTheme) + val preferenceThemeSelection = findPreference(Keys.PREF_THEME_SELECTION) + preferenceThemeSelection?.summary = "${getString(R.string.pref_theme_selection_summary)} ${themes[index]}" + } + } + + /* Overrides onYesNoDialog from YesNoDialogListener */ override fun onYesNoDialog( type: Int, diff --git a/app/src/main/java/com/michatec/radio/dialogs/ThemeSelectionDialog.kt b/app/src/main/java/com/michatec/radio/dialogs/ThemeSelectionDialog.kt new file mode 100644 index 0000000..032e308 --- /dev/null +++ b/app/src/main/java/com/michatec/radio/dialogs/ThemeSelectionDialog.kt @@ -0,0 +1,101 @@ +package com.michatec.radio.dialogs + +import android.content.Context +import android.view.LayoutInflater +import android.widget.RadioButton +import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.michatec.radio.Keys +import com.michatec.radio.R +import com.michatec.radio.helpers.AppThemeHelper + + +/* + * ThemeSelectionDialog class + */ +class ThemeSelectionDialog(private var themeSelectionDialogListener: ThemeSelectionDialogListener) { + + /* Interface used to communicate back to activity */ + interface ThemeSelectionDialogListener { + fun onThemeSelectionDialog(dialogResult: Boolean, selectedTheme: String) { + } + } + + + /* Main class variables */ + private lateinit var dialog: AlertDialog + + + /* Construct and show dialog */ + fun show(context: Context) { + // prepare dialog builder + val builder = MaterialAlertDialogBuilder(context) + + // inflate custom layout + val inflater = LayoutInflater.from(context) + val view = inflater.inflate(R.layout.dialog_theme_selection, null) + + // find radio buttons + val radioGroup = view.findViewById(R.id.theme_radio_group) + val radioFollowSystem = view.findViewById(R.id.radio_theme_follow_system) + val radioLight = view.findViewById(R.id.radio_theme_light) + val radioDark = view.findViewById(R.id.radio_theme_dark) + + // set current selection + val currentTheme = AppThemeHelper.getCurrentTheme(context) + when (currentTheme) { + context.getString(R.string.pref_theme_selection_mode_device_default) -> { + radioFollowSystem.isChecked = true + } + context.getString(R.string.pref_theme_selection_mode_light) -> { + radioLight.isChecked = true + } + context.getString(R.string.pref_theme_selection_mode_dark) -> { + radioDark.isChecked = true + } + } + + // set up radio group listener + radioGroup.setOnCheckedChangeListener { _, checkedId -> + val selectedTheme = when (checkedId) { + R.id.radio_theme_follow_system -> Keys.STATE_THEME_FOLLOW_SYSTEM + R.id.radio_theme_light -> Keys.STATE_THEME_LIGHT_MODE + R.id.radio_theme_dark -> Keys.STATE_THEME_DARK_MODE + else -> Keys.STATE_THEME_FOLLOW_SYSTEM + } + // apply theme immediately + AppThemeHelper.setTheme(selectedTheme) + } + + // set custom view + builder.setView(view) + + // add OK button + builder.setPositiveButton(R.string.dialog_generic_button_ok) { _, _ -> + // get selected theme + val selectedTheme = when (radioGroup.checkedRadioButtonId) { + R.id.radio_theme_follow_system -> Keys.STATE_THEME_FOLLOW_SYSTEM + R.id.radio_theme_light -> Keys.STATE_THEME_LIGHT_MODE + R.id.radio_theme_dark -> Keys.STATE_THEME_DARK_MODE + else -> Keys.STATE_THEME_FOLLOW_SYSTEM + } + // notify listener + themeSelectionDialogListener.onThemeSelectionDialog(true, selectedTheme) + } + + // add cancel button + builder.setNegativeButton(R.string.dialog_generic_button_cancel) { _, _ -> + // notify listener + themeSelectionDialogListener.onThemeSelectionDialog(false, Keys.STATE_THEME_FOLLOW_SYSTEM) + } + + // handle outside-click as cancel + builder.setOnCancelListener { + themeSelectionDialogListener.onThemeSelectionDialog(false, Keys.STATE_THEME_FOLLOW_SYSTEM) + } + + // display dialog + dialog = builder.create() + dialog.show() + } +} diff --git a/app/src/main/res/layout-television/dialog_theme_selection.xml b/app/src/main/res/layout-television/dialog_theme_selection.xml new file mode 100644 index 0000000..e525edc --- /dev/null +++ b/app/src/main/res/layout-television/dialog_theme_selection.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + +