diff --git a/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java index 5e99eba79..015a96d98 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java @@ -12,7 +12,7 @@ import java.util.Locale; import javax.annotation.Nullable; import static android.os.Build.VERSION.SDK_INT; -import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE; +import static org.briarproject.briar.android.settings.DisplayFragment.PREF_LANGUAGE; @NotNullByDefault public class Localizer { @@ -25,7 +25,7 @@ public class Localizer { private Localizer(SharedPreferences sharedPreferences) { this(Locale.getDefault(), getLocaleFromTag( - sharedPreferences.getString(LANGUAGE, "default"))); + sharedPreferences.getString(PREF_LANGUAGE, "default"))); } private Localizer(Locale systemLocale, @Nullable Locale userLocale) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java new file mode 100644 index 000000000..6ef75cefb --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/DisplayFragment.java @@ -0,0 +1,160 @@ +package org.briarproject.briar.android.settings; + +import android.app.AlertDialog; +import android.content.Intent; +import android.os.Bundle; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.R; +import org.briarproject.briar.android.Localizer; +import org.briarproject.briar.android.util.UiUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.logging.Logger; + +import androidx.core.text.TextUtilsCompat; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.Build.VERSION.SDK_INT; +import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; +import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI; + +@NotNullByDefault +public class DisplayFragment extends PreferenceFragmentCompat { + + public static final String PREF_LANGUAGE = "pref_key_language"; + private static final String PREF_THEME = "pref_key_theme"; + + private static final Logger LOG = + getLogger(DisplayFragment.class.getName()); + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + addPreferencesFromResource(R.xml.settings_display); + + ListPreference language = findPreference(PREF_LANGUAGE); + setLanguageEntries(language); + language.setOnPreferenceChangeListener(this::onLanguageChanged); + + ListPreference theme = findPreference(PREF_THEME); + setThemeEntries(theme); + theme.setOnPreferenceChangeListener(this::onThemeChanged); + } + + @Override + public void onStart() { + super.onStart(); + requireActivity().setTitle(R.string.display_settings_title); + } + + private void setLanguageEntries(ListPreference language) { + CharSequence[] tags = language.getEntryValues(); + List entries = new ArrayList<>(tags.length); + List entryValues = new ArrayList<>(tags.length); + for (CharSequence cs : tags) { + String tag = cs.toString(); + if (tag.equals("default")) { + entries.add(getString(R.string.pref_language_default)); + entryValues.add(tag); + continue; + } + Locale locale = Localizer.getLocaleFromTag(tag); + if (locale == null) + throw new IllegalStateException(); + // Exclude RTL locales on API < 17, they won't be laid out correctly + if (SDK_INT < 17 && !isLeftToRight(locale)) { + if (LOG.isLoggable(INFO)) + LOG.info("Skipping RTL locale " + tag); + continue; + } + String nativeName = locale.getDisplayName(locale); + // Fallback to English if the name is unknown in both native and + // current locale. + if (nativeName.equals(tag)) { + String tmp = locale.getDisplayLanguage(Locale.ENGLISH); + if (!tmp.isEmpty() && !tmp.equals(nativeName)) + nativeName = tmp; + } + // Prefix with LRM marker to prevent any RTL direction + entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase() + + nativeName.substring(1)); + entryValues.add(tag); + } + language.setEntries(entries.toArray(new CharSequence[0])); + language.setEntryValues(entryValues.toArray(new CharSequence[0])); + } + + private boolean isLeftToRight(Locale locale) { + // TextUtilsCompat returns the wrong direction for Hebrew on some phones + String language = locale.getLanguage(); + if (language.equals("iw") || language.equals("he")) return false; + int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale); + return direction == LAYOUT_DIRECTION_LTR; + } + + private void setThemeEntries(ListPreference theme) { + if (SDK_INT < 27) { + // remove System Default Theme option from preference entries + // as it is not functional on this API anyway + List entries = + new ArrayList<>(Arrays.asList(theme.getEntries())); + entries.remove(getString(R.string.pref_theme_system)); + theme.setEntries(entries.toArray(new CharSequence[0])); + // also remove corresponding value + List values = + new ArrayList<>(Arrays.asList(theme.getEntryValues())); + values.remove(getString(R.string.pref_theme_system_value)); + theme.setEntryValues(values.toArray(new CharSequence[0])); + } + } + + private boolean onThemeChanged(Preference preference, Object newValue) { + // activate new theme + UiUtils.setTheme(getActivity(), (String) newValue); + // bring up parent activity, so it can change its theme as well + // upstream bug: https://issuetracker.google.com/issues/38352704 + Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY); + intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + // bring this activity back to the foreground + // TODO maybe tell the activity here to relaunch this fragment? + intent = new Intent(getActivity(), getActivity().getClass()); + startActivity(intent); + getActivity().finish(); + return true; + } + + private boolean onLanguageChanged(Preference preference, Object newValue) { + ListPreference language = (ListPreference) preference; + if (!language.getValue().equals(newValue)) { + AlertDialog.Builder builder = + new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.pref_language_title); + builder.setMessage(R.string.pref_language_changed); + builder.setPositiveButton(R.string.sign_out_button, (d, i) -> { + language.setValue((String) newValue); + Intent intent = new Intent(getContext(), ENTRY_ACTIVITY); + intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP); + intent.setData(SIGN_OUT_URI); + requireActivity().startActivity(intent); + requireActivity().finish(); + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setCancelable(false); + builder.show(); + } + return false; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java index 747bc9895..6991e24c0 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java @@ -9,6 +9,8 @@ import android.widget.TextView; import android.widget.Toast; import org.briarproject.bramble.api.FeatureFlags; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; @@ -19,23 +21,39 @@ import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentFactory; +import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback; import de.hdodenhof.circleimageview.CircleImageView; import static android.widget.Toast.LENGTH_LONG; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_AVATAR_IMAGE; -public class SettingsActivity extends BriarActivity { +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class SettingsActivity extends BriarActivity + implements OnPreferenceStartFragmentCallback { @Inject ViewModelProvider.Factory viewModelFactory; - private SettingsViewModel settingsViewModel; - @Inject FeatureFlags featureFlags; + private SettingsViewModel settingsViewModel; + @Override - public void onCreate(Bundle bundle) { + public void injectActivity(ActivityComponent component) { + component.inject(this); + settingsViewModel = new ViewModelProvider(this, viewModelFactory) + .get(SettingsViewModel.class); + } + + @Override + public void onCreate(@Nullable Bundle bundle) { super.onCreate(bundle); ActionBar actionBar = getSupportActionBar(); @@ -47,10 +65,6 @@ public class SettingsActivity extends BriarActivity { setContentView(R.layout.activity_settings); if (featureFlags.shouldEnableProfilePictures()) { - ViewModelProvider provider = - new ViewModelProvider(this, viewModelFactory); - settingsViewModel = provider.get(SettingsViewModel.class); - TextView textViewUserName = findViewById(R.id.username); CircleImageView imageViewAvatar = findViewById(R.id.avatarImage); @@ -78,11 +92,6 @@ public class SettingsActivity extends BriarActivity { } } - @Override - public void injectActivity(ActivityComponent component) { - component.inject(this); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -118,4 +127,23 @@ public class SettingsActivity extends BriarActivity { ConfirmAvatarDialogFragment.TAG); } + @Override + public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, + Preference pref) { + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentFactory fragmentFactory = fragmentManager.getFragmentFactory(); + Fragment fragment = fragmentFactory + .instantiate(getClassLoader(), pref.getFragment()); + fragment.setTargetFragment(caller, 0); + // Replace the existing Fragment with the new Fragment + fragmentManager.beginTransaction() + .setCustomAnimations(R.anim.step_next_in, + R.anim.step_previous_out, R.anim.step_previous_in, + R.anim.step_next_out) + .replace(R.id.fragmentContainer, fragment) + .addToBackStack(null) + .commit(); + return true; + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index 7412b213f..f6609adbb 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -1,7 +1,6 @@ package org.briarproject.briar.android.settings; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.graphics.drawable.ColorDrawable; @@ -30,13 +29,7 @@ import org.briarproject.bramble.api.system.LocationUtils; import org.briarproject.bramble.plugin.tor.CircumventionProvider; import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.R; -import org.briarproject.briar.android.Localizer; -import org.briarproject.briar.android.util.UiUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; import java.util.logging.Logger; import javax.inject.Inject; @@ -44,7 +37,6 @@ import javax.inject.Inject; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; -import androidx.core.text.TextUtilsCompat; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; @@ -53,9 +45,6 @@ import androidx.preference.PreferenceGroup; import androidx.preference.SwitchPreference; import static android.app.Activity.RESULT_OK; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; -import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; -import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.media.RingtoneManager.ACTION_RINGTONE_PICKER; import static android.media.RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI; import static android.media.RingtoneManager.EXTRA_RINGTONE_EXISTING_URI; @@ -70,9 +59,7 @@ import static android.provider.Settings.EXTRA_APP_PACKAGE; import static android.provider.Settings.EXTRA_CHANNEL_ID; import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; import static android.widget.Toast.LENGTH_SHORT; -import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_LTR; import static java.util.Objects.requireNonNull; -import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_MOBILE; @@ -86,10 +73,8 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHE import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.now; -import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; -import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI; import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName; import static org.briarproject.briar.android.util.UiUtils.hasScreenLock; import static org.briarproject.briar.android.util.UiUtils.triggerFeedback; @@ -112,7 +97,6 @@ public class SettingsFragment extends PreferenceFragmentCompat implements EventListener, OnPreferenceChangeListener { public static final String SETTINGS_NAMESPACE = "android-ui"; - public static final String LANGUAGE = "pref_key_language"; public static final String PREF_SCREEN_LOCK = "pref_key_lock"; public static final String PREF_SCREEN_LOCK_TIMEOUT = "pref_key_lock_timeout"; @@ -136,7 +120,6 @@ public class SettingsFragment extends PreferenceFragmentCompat Logger.getLogger(SettingsFragment.class.getName()); private SettingsActivity listener; - private ListPreference language; private SwitchPreference enableBluetooth; private SwitchPreference enableWifi; private SwitchPreference enableTor; @@ -177,9 +160,6 @@ public class SettingsFragment extends PreferenceFragmentCompat public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); - language = findPreference(LANGUAGE); - setLanguageEntries(); - ListPreference theme = findPreference("pref_key_theme"); enableBluetooth = findPreference(BT_ENABLE); enableWifi = findPreference(WIFI_ENABLE); enableTor = findPreference(TOR_ENABLE); @@ -196,24 +176,6 @@ public class SettingsFragment extends PreferenceFragmentCompat notifyVibration = findPreference("pref_key_notify_vibration"); notifySound = findPreference("pref_key_notify_sound"); - language.setOnPreferenceChangeListener(this); - theme.setOnPreferenceChangeListener((preference, newValue) -> { - if (getActivity() != null) { - // activate new theme - UiUtils.setTheme(getActivity(), (String) newValue); - // bring up parent activity, so it can change its theme as well - // upstream bug: https://issuetracker.google.com/issues/38352704 - Intent intent = new Intent(getActivity(), ENTRY_ACTIVITY); - intent.setFlags( - FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - // bring this activity back to the foreground - intent = new Intent(getActivity(), getActivity().getClass()); - startActivity(intent); - getActivity().finish(); - } - return true; - }); enableBluetooth.setOnPreferenceChangeListener(this); enableWifi.setOnPreferenceChangeListener(this); enableTor.setOnPreferenceChangeListener(this); @@ -230,19 +192,6 @@ public class SettingsFragment extends PreferenceFragmentCompat return true; }); - if (SDK_INT < 27) { - // remove System Default Theme option from preference entries - // as it is not functional on this API anyway - List entries = - new ArrayList<>(Arrays.asList(theme.getEntries())); - entries.remove(getString(R.string.pref_theme_system)); - theme.setEntries(entries.toArray(new CharSequence[0])); - // also remove corresponding value - List values = - new ArrayList<>(Arrays.asList(theme.getEntryValues())); - values.remove(getString(R.string.pref_theme_system_value)); - theme.setEntryValues(values.toArray(new CharSequence[0])); - } Preference explode = requireNonNull(findPreference("pref_key_explode")); if (IS_DEBUG_BUILD) { explode.setOnPreferenceClickListener(preference -> { @@ -271,6 +220,7 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public void onStart() { super.onStart(); + requireActivity().setTitle(R.string.settings_button); eventBus.addListener(this); setSettingsEnabled(false); loadSettings(); @@ -282,51 +232,6 @@ public class SettingsFragment extends PreferenceFragmentCompat eventBus.removeListener(this); } - private void setLanguageEntries() { - CharSequence[] tags = language.getEntryValues(); - List entries = new ArrayList<>(tags.length); - List entryValues = new ArrayList<>(tags.length); - for (CharSequence cs : tags) { - String tag = cs.toString(); - if (tag.equals("default")) { - entries.add(getString(R.string.pref_language_default)); - entryValues.add(tag); - continue; - } - Locale locale = Localizer.getLocaleFromTag(tag); - if (locale == null) - throw new IllegalStateException(); - // Exclude RTL locales on API < 17, they won't be laid out correctly - if (SDK_INT < 17 && !isLeftToRight(locale)) { - if (LOG.isLoggable(INFO)) - LOG.info("Skipping RTL locale " + tag); - continue; - } - String nativeName = locale.getDisplayName(locale); - // Fallback to English if the name is unknown in both native and - // current locale. - if (nativeName.equals(tag)) { - String tmp = locale.getDisplayLanguage(Locale.ENGLISH); - if (!tmp.isEmpty() && !tmp.equals(nativeName)) - nativeName = tmp; - } - // Prefix with LRM marker to prevent any RTL direction - entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase() - + nativeName.substring(1)); - entryValues.add(tag); - } - language.setEntries(entries.toArray(new CharSequence[0])); - language.setEntryValues(entryValues.toArray(new CharSequence[0])); - } - - private boolean isLeftToRight(Locale locale) { - // TextUtilsCompat returns the wrong direction for Hebrew on some phones - String language = locale.getLanguage(); - if (language.equals("iw") || language.equals("he")) return false; - int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale); - return direction == LAYOUT_DIRECTION_LTR; - } - private void setTorNetworkSummary(int torNetworkSetting) { if (torNetworkSetting != PREF_TOR_NETWORK_AUTOMATIC) { torNetwork.setSummary("%s"); // use setting value @@ -573,11 +478,7 @@ public class SettingsFragment extends PreferenceFragmentCompat @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - if (preference == language) { - if (!language.getValue().equals(newValue)) - languageChanged((String) newValue); - return false; - } else if (preference == enableBluetooth) { + if (preference == enableBluetooth) { boolean btSetting = (Boolean) newValue; storeBluetoothSetting(btSetting); } else if (preference == enableWifi) { @@ -630,25 +531,6 @@ public class SettingsFragment extends PreferenceFragmentCompat return true; } - private void languageChanged(String newValue) { - AlertDialog.Builder builder = - new AlertDialog.Builder(getActivity()); - builder.setTitle(R.string.pref_language_title); - builder.setMessage(R.string.pref_language_changed); - builder.setPositiveButton(R.string.sign_out_button, - (dialogInterface, i) -> { - language.setValue(newValue); - Intent intent = new Intent(getContext(), ENTRY_ACTIVITY); - intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP); - intent.setData(SIGN_OUT_URI); - requireActivity().startActivity(intent); - requireActivity().finish(); - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setCancelable(false); - builder.show(); - } - private void storeTorEnabledSetting(boolean torEnabledSetting) { Settings s = new Settings(); s.putBoolean(PREF_PLUGIN_ENABLE, torEnabledSetting); diff --git a/briar-android/src/main/res/drawable/ic_settings_brightness.xml b/briar-android/src/main/res/drawable/ic_settings_brightness.xml new file mode 100644 index 000000000..a0b720816 --- /dev/null +++ b/briar-android/src/main/res/drawable/ic_settings_brightness.xml @@ -0,0 +1,10 @@ + + + diff --git a/briar-android/src/main/res/layout/activity_settings.xml b/briar-android/src/main/res/layout/activity_settings.xml index 88bf95130..180e2728e 100644 --- a/briar-android/src/main/res/layout/activity_settings.xml +++ b/briar-android/src/main/res/layout/activity_settings.xml @@ -68,8 +68,8 @@ - Light Dark Automatic (Daytime) - System Default + System default Connections diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 21acdd98f..5c2a93520 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -1,29 +1,11 @@ - - - - - - - + + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> @@ -97,7 +79,7 @@ android:summary="@string/pref_lock_summary" android:title="@string/pref_lock_title" android:widgetLayout="@layout/preference_switch_compat" - app:iconSpaceReserved="false"/> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + android:targetPackage="@string/app_package" /> @@ -133,7 +115,7 @@ + android:targetPackage="@string/app_package" /> @@ -149,7 +131,7 @@ android:summary="@string/notify_sign_in_summary" android:title="@string/notify_sign_in_title" android:widgetLayout="@layout/preference_switch_compat" - app:iconSpaceReserved="false"/> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> + app:iconSpaceReserved="false" /> @@ -209,7 +191,7 @@ + app:iconSpaceReserved="false" /> @@ -224,13 +206,13 @@ + android:targetPackage="@string/app_package" /> + app:iconSpaceReserved="false" /> diff --git a/briar-android/src/main/res/xml/settings_display.xml b/briar-android/src/main/res/xml/settings_display.xml new file mode 100644 index 000000000..4a7d146a9 --- /dev/null +++ b/briar-android/src/main/res/xml/settings_display.xml @@ -0,0 +1,24 @@ + + + + + + + +