From b5bc3b96019cf0612d904ee85046fe91d99b0db9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 12:00:04 +0000 Subject: [PATCH] Preview each font option in its own font in the picker The Appearance font picker is a ListPreference dialog whose rows were all drawn in the current app font. Render each option in the font it names (Ubuntu row in Ubuntu, Oxygen in Oxygen, OpenDyslexic in OpenDyslexic; System default stays the device font) so the user previews a font while choosing it. Adds FontListPreferenceDialogFragment, a frosted list-preference dialog that installs a custom single-choice adapter applying the per-option typeface, and routes the "font" preference to it. FontPreference gains typefaceFor(context, value) to resolve any option's typeface. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017QpoubQoCriCnSKSB4Avxx --- .../FontListPreferenceDialogFragment.kt | 79 +++++++++++++++++++ .../preferences/FontPreference.kt | 11 +++ .../preferences/PreferencesActivity.java | 7 +- 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/be/robinj/distrohopper/preferences/FontListPreferenceDialogFragment.kt diff --git a/app/src/main/java/be/robinj/distrohopper/preferences/FontListPreferenceDialogFragment.kt b/app/src/main/java/be/robinj/distrohopper/preferences/FontListPreferenceDialogFragment.kt new file mode 100644 index 00000000..680ff933 --- /dev/null +++ b/app/src/main/java/be/robinj/distrohopper/preferences/FontListPreferenceDialogFragment.kt @@ -0,0 +1,79 @@ +package be.robinj.distrohopper.preferences + +import android.content.DialogInterface +import android.graphics.Typeface +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.preference.ListPreference + +/** + * The Appearance font picker. Behaves like the framework's list-preference dialog + * (single-choice, tap-to-select-and-dismiss) but renders each option in the font + * it names — the "Ubuntu" row is drawn in Ubuntu, "Oxygen" in Oxygen, and so on — + * so the user previews each font while choosing it. "System default" keeps the + * device font. + * + * We can't just let the base class build the list because it wires up a private + * clicked-index field we can't reach, so this replicates its select-and-persist + * flow with a custom adapter (see [onPrepareDialogBuilder] / [onDialogClosed]). + * Extends the frosted variant so the legibility fallback is preserved. + */ +class FontListPreferenceDialogFragment : + PreferencesActivity.FrostedListPreferenceDialogFragment() { + + private var clickedIndex = -1 + + override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) { + val listPreference = this.preference as ListPreference + val entries = listPreference.entries + val entryValues = listPreference.entryValues + this.clickedIndex = listPreference.findIndexOfValue(listPreference.value) + + val adapter = object : ArrayAdapter( + this.requireContext(), + android.R.layout.select_dialog_singlechoice, + android.R.id.text1, + entries, + ) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = super.getView(position, convertView, parent) + val textView = view.findViewById(android.R.id.text1) + textView.typeface = + FontPreference.typefaceFor(this.context, entryValues[position]?.toString()) + ?: Typeface.DEFAULT + return view + } + } + + // Mirror ListPreferenceDialogFragmentCompat: tapping a row selects it and + // immediately closes with a positive result (no separate OK button). // + builder.setSingleChoiceItems(adapter, this.clickedIndex) { dialog, which -> + this.clickedIndex = which + this.onClick(dialog, DialogInterface.BUTTON_POSITIVE) + dialog.dismiss() + } + builder.setPositiveButton(null, null) + } + + override fun onDialogClosed(positiveResult: Boolean) { + if (positiveResult && this.clickedIndex >= 0) { + val listPreference = this.preference as ListPreference + val value = listPreference.entryValues[this.clickedIndex].toString() + if (listPreference.callChangeListener(value)) { + listPreference.value = value + } + } + } + + companion object { + fun newInstance(key: String): FontListPreferenceDialogFragment { + val fragment = FontListPreferenceDialogFragment() + fragment.arguments = Bundle(1).apply { this.putString("key", key) } + return fragment + } + } +} diff --git a/app/src/main/java/be/robinj/distrohopper/preferences/FontPreference.kt b/app/src/main/java/be/robinj/distrohopper/preferences/FontPreference.kt index 061fcc9d..513c2232 100644 --- a/app/src/main/java/be/robinj/distrohopper/preferences/FontPreference.kt +++ b/app/src/main/java/be/robinj/distrohopper/preferences/FontPreference.kt @@ -42,6 +42,17 @@ object FontPreference { return ResourcesCompat.getFont(context, fontRes) } + /** + * The [Typeface] for a given font preference [value], or null when [value] is + * System / unknown (i.e. the system font should be used). Unlike [typeface] + * this ignores the stored preference and resolves whatever value is passed, + * so the picker can render each option in its own font. + */ + fun typefaceFor(context: Context, value: String?): Typeface? { + val fontRes = this.fontResFor(value) ?: return null + return ResourcesCompat.getFont(context, fontRes) + } + private fun current(context: Context): String = Preferences.getSharedPreferences(context) .getString(Preference.FONT.getName(), SYSTEM) ?: SYSTEM diff --git a/app/src/main/java/be/robinj/distrohopper/preferences/PreferencesActivity.java b/app/src/main/java/be/robinj/distrohopper/preferences/PreferencesActivity.java index 5e86b6c5..932b723f 100644 --- a/app/src/main/java/be/robinj/distrohopper/preferences/PreferencesActivity.java +++ b/app/src/main/java/be/robinj/distrohopper/preferences/PreferencesActivity.java @@ -322,8 +322,13 @@ public void onDisplayPreferenceDialog (final Preference preference) if (preference instanceof ListPreference) { + // The font picker previews each option in its own font; every other + // list preference uses the plain frosted dialog. // final DialogFragment fragment = - FrostedListPreferenceDialogFragment.newInstance (preference.getKey ()); + be.robinj.distrohopper.preferences.Preference.FONT.getName () + .equals (preference.getKey ()) + ? FontListPreferenceDialogFragment.newInstance (preference.getKey ()) + : FrostedListPreferenceDialogFragment.newInstance (preference.getKey ()); fragment.setTargetFragment (this, 0); fragment.show (this.getParentFragmentManager (), DIALOG_FRAGMENT_TAG); }