diff --git a/documentation/docs/help/en/Advanced preferences.md b/documentation/docs/help/en/Advanced preferences.md index 6787a6604..da98cbf8d 100644 --- a/documentation/docs/help/en/Advanced preferences.md +++ b/documentation/docs/help/en/Advanced preferences.md @@ -74,7 +74,7 @@ Show the menu buttons at the bottom of the screen. Default: _on_. You need to re ### App language -Select a language for the user interface that is different from the device default. On devices running Android 13 and later the app language can be changed in the system settings too. Preset translations can be disabled in the preset configurations. +Select a language for the user interface that is different from the device default, setting the value to _Device language_ will revert to using your preference for the whole device. On devices running Android 13 and later the app language can be changed in the system settings too. Preset translations can be disabled in the preset configurations. ### Max. number of inline values diff --git a/src/androidTest/java/de/blau/android/prefs/AppLocalePrefTest.java b/src/androidTest/java/de/blau/android/prefs/AppLocalePrefTest.java new file mode 100644 index 000000000..6b5f9a5dd --- /dev/null +++ b/src/androidTest/java/de/blau/android/prefs/AppLocalePrefTest.java @@ -0,0 +1,138 @@ +package de.blau.android.prefs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Locale; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.app.Instrumentation; +import android.app.Instrumentation.ActivityMonitor; +import android.view.View; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.os.LocaleListCompat; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.UiObjectNotFoundException; +import androidx.test.uiautomator.UiSelector; +import de.blau.android.LayerUtils; +import de.blau.android.Main; +import de.blau.android.Map; +import de.blau.android.R; +import de.blau.android.TestUtils; +import de.blau.android.prefs.AdvancedPrefDatabase.Geocoder; + +/** + * Note these tests are not mocked + * + * @author simon + * + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class AppLocalePrefTest { + + Main main = null; + View v = null; + ActivityMonitor monitor = null; + Instrumentation instrumentation = null; + UiDevice device = null; + + /** + * Manual start of activity so that we can set up the monitor for main + */ + @Rule + public ActivityTestRule
mActivityRule = new ActivityTestRule<>(Main.class); + + /** + * Pre-test setup + * + * Note that there doesn't seem to be an easy way to set the app locale from the test + */ + @Before + public void setup() { + instrumentation = InstrumentationRegistry.getInstrumentation(); + device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + main = mActivityRule.getActivity(); + Preferences prefs = new Preferences(main); + LayerUtils.removeImageryLayers(main); + Map map = main.getMap(); + map.setPrefs(main, prefs); + + TestUtils.grantPermissons(device); + TestUtils.dismissStartUpDialogs(device, main); + TestUtils.stopEasyEdit(main); + } + + /** + * Post-test teardown + */ + @After + public void teardown() { + instrumentation.removeMonitor(monitor); + } + + /** + * Start prefs, advanced prefs, app locale + */ + @Test + public void setToGerman() { + monitor = instrumentation.addMonitor(PrefEditor.class.getName(), null, false); + assertTrue(TestUtils.clickButton(device, device.getCurrentPackageName() + ":id/menu_config", true)); + instrumentation.waitForMonitorWithTimeout(monitor, 40000); // + + if (!TestUtils.scrollToAndSelect(device, main.getString(R.string.config_advancedprefs), new UiSelector().scrollable(true))) { + fail("Didn't find " + main.getString(R.string.config_advancedprefs)); + } + + assertTrue(TestUtils.clickText(device, false, main.getString(R.string.config_advancedprefs), true)); + assertTrue(TestUtils.findText(device, false, main.getString(R.string.config_advancedprefs))); + + assertTrue(TestUtils.clickText(device, false, main.getString(R.string.config_category_view), true, false)); + assertTrue(TestUtils.findText(device, false, main.getString(R.string.config_category_view))); + + if (!TestUtils.scrollToAndSelect(device, main.getString(R.string.config_appLocale_title), new UiSelector().scrollable(true))) { + fail("Didn't find " + main.getString(R.string.config_appLocale_title)); + } + + assertTrue(TestUtils.clickText(device, false, main.getString(R.string.config_appLocale_title), true, false)); + assertTrue(TestUtils.findText(device, false, main.getString(R.string.config_appLocale_title))); + + UiObject2 deviceLangCheck = TestUtils.findObjectWithText(device, false, main.getString(R.string.config_appLocale_device_language), 500, false); + assertTrue(deviceLangCheck.isChecked()); + + TestUtils.scrollTo(Locale.GERMAN.getDisplayName(Locale.getDefault()), false); + UiObject2 german = TestUtils.findObjectWithText(device, false, Locale.GERMAN.getDisplayName(Locale.getDefault()), 500, false); + assertFalse(german.isChecked()); + german.click(); + + assertTrue(TestUtils.clickText(device, false, main.getString(R.string.config_category_view), true, false)); + assertTrue(TestUtils.findText(device, false, main.getString(R.string.config_category_view))); + + if (!TestUtils.scrollToAndSelect(device, main.getString(R.string.config_appLocale_title), new UiSelector().scrollable(true))) { + fail("Didn't find " + main.getString(R.string.config_appLocale_title)); + } + + assertTrue(TestUtils.clickText(device, false, main.getString(R.string.config_appLocale_title), true, false)); + assertTrue(TestUtils.findText(device, false, main.getString(R.string.config_appLocale_title))); + + TestUtils.scrollTo(main.getString(R.string.config_appLocale_device_language), false); + deviceLangCheck = TestUtils.findObjectWithText(device, false, main.getString(R.string.config_appLocale_device_language), 500, false); + assertFalse(deviceLangCheck.isChecked()); + deviceLangCheck.click(); + + TestUtils.clickHome(device, false); + } +} \ No newline at end of file diff --git a/src/main/assets/help/en/Advanced preferences.html b/src/main/assets/help/en/Advanced preferences.html index f23b2ef4b..c1e4da34d 100644 --- a/src/main/assets/help/en/Advanced preferences.html +++ b/src/main/assets/help/en/Advanced preferences.html @@ -43,7 +43,7 @@

Theme

Enable split action bar

Show the menu buttons at the bottom of the screen. Default: on. You need to restart the app for changes to this setting to take effect.

App language

-

Select a language for the user interface that is different from the device default. On devices running Android 13 and later the app language can be changed in the system settings too. Preset translations can be disabled in the preset configurations.

+

Select a language for the user interface that is different from the device default, setting the value to Device language will revert to using your preference for the whole device. On devices running Android 13 and later the app language can be changed in the system settings too. Preset translations can be disabled in the preset configurations.

Max. number of inline values

Maximum number of values that will directly be displayed in the form based editor for Tags with pre-determined values. Default: 4.

Long string limit

diff --git a/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java b/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java index b875a56c0..9b2e59fba 100644 --- a/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java +++ b/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java @@ -91,37 +91,46 @@ public void onResume() { } /** - * Setup the app local preference + * Setup the app locale preference * * @param appLocalePref the preference * */ private void setupAppLocalePref(@NonNull ListPreference appLocalePref) { + appLocalePref.setPersistent(false); // stored by the device Locale currentLocale = Locale.getDefault(); - LocaleListCompat appLocales = LocaleUtils.getSupportedLocales(getContext()); - LocaleListCompat currentLocales = AppCompatDelegate.getApplicationLocales(); - if (!currentLocales.isEmpty()) { - LocaleListCompat temp = LocaleListCompat.getAdjustedDefault(); - if (!temp.isEmpty()) { - currentLocale = temp.get(0); - } + LocaleListCompat currentAppLocales = AppCompatDelegate.getApplicationLocales(); + boolean hasAppLocale = !currentAppLocales.isEmpty(); + if (hasAppLocale) { + currentLocale = currentAppLocales.get(0); } - String[] entries = new String[appLocales.size()]; - String[] values = new String[appLocales.size()]; + LocaleListCompat appLocales = LocaleUtils.getSupportedLocales(getContext()); + final String[] entries = new String[appLocales.size() + 1]; + String[] values = new String[appLocales.size() + 1]; + entries[0] = getContext().getString(R.string.config_appLocale_device_language); + values[0] = ""; for (int i = 0; i < appLocales.size(); i++) { Locale l = appLocales.get(i); - entries[i] = l.getDisplayName(currentLocale); - values[i] = l.toString(); + entries[i + 1] = l.getDisplayName(currentLocale); + values[i + 1] = l.toString(); } appLocalePref.setEntryValues(values); appLocalePref.setEntries(entries); - appLocalePref.setDefaultValue(currentLocale.toString()); + final String currentLocaleString = currentLocale.toString(); + appLocalePref.setValue(hasAppLocale ? currentLocaleString : ""); + appLocalePref.setSummary(hasAppLocale ? currentLocale.getDisplayName() : entries[0]); OnPreferenceChangeListener p = (preference, newValue) -> { - Log.d(DEBUG_TAG, "onPreferenceChange appLocale " + newValue); - LocaleListCompat newDefaultList = LocaleListCompat.forLanguageTags((String) newValue); - Locale newDefault = newDefaultList.get(0); - AppCompatDelegate.setApplicationLocales(newDefaultList); - preference.setSummary(newDefault.getDisplayName(newDefault)); + // Note google is very confused about Android with _ and proper, with - locale values, we try to circumvent + // that here + if (Util.notEmpty((String) newValue)) { + Locale newLocale = LocaleUtils.localeFromAndroidLocaleTag((String) newValue); + LocaleListCompat newAppList = LocaleListCompat.create(newLocale); + AppCompatDelegate.setApplicationLocales(newAppList); + preference.setSummary(newLocale.getDisplayName()); + } else { + AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList()); + preference.setSummary(entries[0]); + } return true; }; appLocalePref.setOnPreferenceChangeListener(p); diff --git a/src/main/java/de/blau/android/util/LocaleUtils.java b/src/main/java/de/blau/android/util/LocaleUtils.java index 8c327bc4d..5f7057a7c 100644 --- a/src/main/java/de/blau/android/util/LocaleUtils.java +++ b/src/main/java/de/blau/android/util/LocaleUtils.java @@ -23,6 +23,8 @@ public final class LocaleUtils { private static final String DEBUG_TAG = LocaleUtils.class.getSimpleName(); + private static final String ANDROID_LOCALE_SEPARATOR = "_"; + // list of languages that use Latin script from https://gist.github.com/phil-brown/8056700 private static Set latin = new HashSet<>(Arrays.asList("aa", "ace", "ach", "ada", "af", "agq", "ak", "ale", "amo", "an", "arn", "arp", "arw", "asa", "ast", "ay", "az", "bal", "ban", "bas", "bbc", "bem", "bez", "bi", "bik", "bin", "bku", "bla", "bm", "bqv", "br", "bs", "buc", "bug", "bya", "ca", @@ -212,4 +214,16 @@ public static LocaleListCompat getSupportedLocales(@NonNull Context context) { } return LocaleListCompat.forLanguageTags(String.join(",", locales)); } + + /** + * Construct a Locale from an Android format locale string + * + * @param localeString the Android format locale string + * @return a Locale + */ + @NonNull + public static Locale localeFromAndroidLocaleTag(@NonNull String localeString) { + String[] code = localeString.split(ANDROID_LOCALE_SEPARATOR); + return code.length == 1 ? new Locale(code[0]) : new Locale(code[0], code[1]); + } } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index eb6f4a983..5aa3391c2 100755 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1205,6 +1205,7 @@ Show the menu buttons at the bottom of the screen. Requires starting Vespucci again, and Android 4.0 or later. App language Select non-standard app language + Device language Max. number of inline values Maximum number of inline values displayed in tag form. %1$d values diff --git a/src/main/res/xml-v19/advancedpreferences.xml b/src/main/res/xml-v19/advancedpreferences.xml index 8ee517488..22542163b 100644 --- a/src/main/res/xml-v19/advancedpreferences.xml +++ b/src/main/res/xml-v19/advancedpreferences.xml @@ -45,7 +45,7 @@ android:summary="@string/config_useInternalPhotoViewer_summary" android:title="@string/config_useInternalPhotoViewer_title" /> @@ -101,7 +101,7 @@ android:key="@string/config_appLocale_key" android:title="@string/config_appLocale_title" android:summary="@string/config_appLocale_summary" - android:defaultValue="en" /> + android:defaultValue="" /> @@ -101,7 +101,7 @@ android:key="@string/config_appLocale_key" android:title="@string/config_appLocale_title" android:summary="@string/config_appLocale_summary" - android:defaultValue="en" /> + android:defaultValue="" /> @@ -101,7 +101,7 @@ android:key="@string/config_appLocale_key" android:title="@string/config_appLocale_title" android:summary="@string/config_appLocale_summary" - android:defaultValue="en" /> + android:defaultValue="" /> @@ -101,7 +101,7 @@ android:key="@string/config_appLocale_key" android:title="@string/config_appLocale_title" android:summary="@string/config_appLocale_summary" - android:defaultValue="en" /> + android:defaultValue="" />