From 087e3f5931e6c5981b7ec2fa8d68c30426796685 Mon Sep 17 00:00:00 2001 From: Rick Brown Date: Wed, 11 Oct 2023 15:31:39 -0400 Subject: [PATCH] Allow "System language" to be set as language option Populate "System language" as a first class item in the language preference list so that users can select it in scenarios where they have previously selected a specific language, and wish to go back to the default. --- src/keepass2android/Utils/Util.cs | 66 +++++++- .../settings/DatabaseSettingsActivity.cs | 144 ++++++++++++------ 2 files changed, 163 insertions(+), 47 deletions(-) diff --git a/src/keepass2android/Utils/Util.cs b/src/keepass2android/Utils/Util.cs index 5ffb0156..b849df3b 100644 --- a/src/keepass2android/Utils/Util.cs +++ b/src/keepass2android/Utils/Util.cs @@ -78,8 +78,8 @@ namespace keepass2android { if (!languageLoaded) { - var language = PreferenceManager.GetDefaultSharedPreferences(c).GetString(c.GetString(Resource.String.app_language_pref_key), null); - Language = language; + var langPref = PreferenceManager.GetDefaultSharedPreferences(c).GetString(c.GetString(Resource.String.app_language_pref_key), null); + Language = LanguageEntry.PrefCodeToLanguage(langPref); } return Language; } @@ -109,8 +109,68 @@ namespace keepass2android } } + /// + /// A small container/wrapper that helps with the building and sorting of the + /// Language Preference list, and interoperability between it and LocaleManager + /// + class LanguageEntry + { + // "System language" preference code; maps to LocaleManager.Language = null + private const string SYS_LANG_CODE = "SysLang"; - public class Util { + // Preference code (2-char lowercase, or SYS_LANG_CODE) + public readonly string Code; + // Localized display name + public readonly string Name; + // True if this LanguageEntry is the "System language", false otherwise + public readonly bool IsSystem; + + /// + /// Creates a LanguageEntry from a Locale + /// + /// + /// + public static LanguageEntry OfLocale(Java.Util.Locale from) + { + return new LanguageEntry(from.Language, from.DisplayLanguage, false); + } + + /// + /// Creates the "System language" LanguageEntry with the given localized display name, + /// special preference code, marked as a System entry. + /// + /// + /// + public static LanguageEntry SystemDefault(string displayName) + { + return new LanguageEntry(SYS_LANG_CODE, displayName, true); + } + + private LanguageEntry(string code, string name, bool isSystem) + { + this.Code = code; + this.Name = name; + this.IsSystem = isSystem; + } + + /// + /// Converts a language preference code to a code that is compatible with LocaleManager.Language + /// + /// + /// A converted code, possibly null + public static string PrefCodeToLanguage(string code) + { + return string.IsNullOrEmpty(code) || SYS_LANG_CODE.Equals(code) ? null : code; + } + + public override string ToString() + { + return "{" + this.Code + ":" + this.Name + "}"; + } + } + + + public class Util { public const String KeyFilename = "fileName"; diff --git a/src/keepass2android/settings/DatabaseSettingsActivity.cs b/src/keepass2android/settings/DatabaseSettingsActivity.cs index d857824a..608154da 100644 --- a/src/keepass2android/settings/DatabaseSettingsActivity.cs +++ b/src/keepass2android/settings/DatabaseSettingsActivity.cs @@ -41,10 +41,12 @@ using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Interfaces; using System.Collections.Generic; + namespace keepass2android { //http://stackoverflow.com/a/27422401/292233 - public class SettingsFragment : PreferenceFragment +#pragma warning disable CS0618 // Type or member is obsolete + public class SettingsFragment : PreferenceFragment { @@ -177,36 +179,8 @@ namespace keepass2android FindPreference(GetString(Resource.String.DebugLog_send_key)).PreferenceClick += OnSendDebug; HashSet supportedLocales = new HashSet() { "en", "af", "ar", "az", "be", "bg", "ca", "cs", "da", "de", "el", "es", "eu", "fa", "fi", "fr", "gl", "he", "hr", "hu", "id", "in", "it", "iw", "ja", "ko", "ml", "nb", "nl", "nn", "no", "pl", "pt", "ro", "ru", "si", "sk", "sl", "sr", "sv", "tr", "uk", "vi", "zh" }; - - ListPreference appLanguagePref = (ListPreference)FindPreference(GetString(Resource.String.app_language_pref_key)); - - var localesByCode = new System.Collections.Generic.Dictionary>(); - foreach (var loc in Java.Util.Locale.GetAvailableLocales()) - { - if (!supportedLocales.Contains(loc.Language)) - continue; - if (!localesByCode.ContainsKey(loc.Language)) - { - localesByCode[loc.Language] = new List(); - } - localesByCode[loc.Language].Add(loc); - - } - var localesByCodeUnique = localesByCode.Select(l => new KeyValuePair(l.Key, l.Value.First())).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - List>> codesWithMultiple = localesByCode.Where(l => l.Value.Count > 1).ToList(); - List> localesByLanguageList = localesByCodeUnique - .OrderBy(kvp => kvp.Value.DisplayLanguage).ToList(); - appLanguagePref.SetEntries(localesByLanguageList.Select(kvp => kvp.Value.DisplayLanguage).ToArray()); - appLanguagePref.SetEntryValues(localesByLanguageList.Select(kvp => kvp.Value.Language).ToArray()); - string languageCode = appLanguagePref.Value; - string summary = GetDisplayLanguage(localesByCodeUnique, languageCode); - ((ListPreference)FindPreference(GetString(Resource.String.app_language_pref_key))).Summary = summary; - appLanguagePref.PreferenceChange += (sender, args) => - { - ((ListPreference)FindPreference(GetString(Resource.String.app_language_pref_key))).Summary = GetDisplayLanguage(localesByCodeUnique, (string)args.NewValue); - LocaleManager.Language = (string)args.NewValue; - }; - + var languagePref = (ListPreference)FindPreference(GetString(Resource.String.app_language_pref_key)); + new AppLanguageManager(this, languagePref, supportedLocales); UpdateAutofillPref(); @@ -347,16 +321,6 @@ namespace keepass2android } - private string GetDisplayLanguage(Dictionary localesByCode, string languageCode) - { - return languageCode != null && localesByCode.ContainsKey(languageCode) ? localesByCode[languageCode]?.DisplayLanguage : GetString(Resource.String.SystemLanguage); - } - - private void AppLanguagePref_PreferenceChange(object sender, Preference.PreferenceChangeEventArgs e) - { - throw new NotImplementedException(); - } - private void UpdateAutofillPref() { var autofillScreen = FindPreference(GetString(Resource.String.AutoFill_prefs_screen_key)); @@ -897,10 +861,102 @@ namespace keepass2android } - /// - /// Activity to configure the application and database settings. The database must be unlocked, and this activity will close if it becomes locked. - /// + /// + /// A helper class that manages language preference display and selection. + /// + /// + /// The idea is to provide a ListPreference with a "System language" item at the top, followed by + /// the localized list of supported language names. The items are backed by their corresponding "code". + /// For a langauge that's the 2-char lowercase language code, which is exactly the same code that + /// LocaleManager.Language expects. + /// + /// + /// "System language" is a special case. LocaleManager.Language expects null, but ListPreference + /// does not support null as a valid code. To work around this, LanguageEntry.SYS_LANG_CODE + /// is used as the preference code. LanguageEntry.PrefCodeToLanguage(string) is used to convert the + /// preference codes to language codes as needed. + /// + /// + internal class AppLanguageManager + { + private readonly PreferenceFragment _fragment; + private readonly ListPreference _langPref; + private readonly Dictionary _langEntriesByCodeUnique; + + public AppLanguageManager(PreferenceFragment fragment, ListPreference langPref, HashSet supportedLocales) + { + this._fragment = fragment; + this._langPref = langPref; + this._langEntriesByCodeUnique = CreateCodeToEntryMapping(fragment, supportedLocales); + + ConfigureLanguageList(); + } + + private static Dictionary CreateCodeToEntryMapping(PreferenceFragment fragment, HashSet supportedLocales) + { + var localesByCode = new Dictionary>(); + foreach (var loc in Java.Util.Locale.GetAvailableLocales()) + { + if (!supportedLocales.Contains(loc.Language)) + continue; + if (!localesByCode.ContainsKey(loc.Language)) + { + localesByCode[loc.Language] = new List(); + } + localesByCode[loc.Language].Add(loc); + } + + var langEntriesByCodeUnique = localesByCode + .Select(l => new KeyValuePair(l.Key, LanguageEntry.OfLocale(l.Value.First()))) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + var sysLangEntry = LanguageEntry.SystemDefault(fragment.GetString(Resource.String.SystemLanguage)); + langEntriesByCodeUnique.Add(sysLangEntry.Code, sysLangEntry); + + return langEntriesByCodeUnique; + } + + private void ConfigureLanguageList() + { + List> langEntriesList = _langEntriesByCodeUnique + .OrderByDescending(kvp => kvp.Value.IsSystem) + .ThenBy(kvp => kvp.Value.Name) + .ToList(); + + _langPref.SetEntries(langEntriesList + .Select(kvp => kvp.Value.Name) + .ToArray()); + _langPref.SetEntryValues(langEntriesList + .Select(kvp => kvp.Value.Code) + .ToArray()); + + _langPref.Summary = GetDisplayLanguage(LanguageEntry.PrefCodeToLanguage(_langPref.Value)); + _langPref.PreferenceChange += AppLanguagePrefChange; + } + + private string GetDisplayLanguage(string languageCode) + { + if (languageCode != null && this._langEntriesByCodeUnique.ContainsKey(languageCode)) + return this._langEntriesByCodeUnique[languageCode]?.Name; + else + return _fragment.GetString(Resource.String.SystemLanguage); + } + + private void AppLanguagePrefChange(object sender, Preference.PreferenceChangeEventArgs args) + { + string langCode = LanguageEntry.PrefCodeToLanguage((string)args.NewValue); + LocaleManager.Language = langCode; + _langPref.Summary = GetDisplayLanguage(langCode); + } + } + +#pragma warning restore CS0618 // Type or member is obsolete + + + /// + /// Activity to configure the application and database settings. The database must be unlocked, and this activity will close if it becomes locked. + /// [Activity(Label = "@string/app_name", Theme = "@style/MyTheme", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)] public class DatabaseSettingsActivity : LockCloseActivity {