diff options
Diffstat (limited to 'app/src')
-rw-r--r-- | app/src/main/AndroidManifest.xml | 12 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/fragments/LanguageSelectionFragment.java | 166 | ||||
-rw-r--r-- | app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java | 25 | ||||
-rw-r--r-- | app/src/main/res/drawable/translate.xml | 5 | ||||
-rw-r--r-- | app/src/main/res/layout/f_drawer_main.xml | 8 | ||||
-rw-r--r-- | app/src/main/res/layout/f_language_selection.xml | 17 | ||||
-rw-r--r-- | app/src/main/res/layout/v_select_text_list_item.xml | 3 | ||||
-rw-r--r-- | app/src/main/res/resources.properties | 1 | ||||
-rw-r--r-- | app/src/main/res/values/strings.xml | 1 | ||||
-rw-r--r-- | app/src/main/res/values/untranslatable.xml | 43 | ||||
-rw-r--r-- | app/src/main/res/xml/locales_config.xml | 44 |
11 files changed, 322 insertions, 3 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8cf2e6f4..cc876ecc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,7 +31,8 @@ android:extractNativeLibs="true" android:appCategory="productivity" android:logo="@mipmap/ic_launcher" - android:theme="@style/BitmaskTheme"> + android:theme="@style/BitmaskTheme" + android:localeConfig="@xml/locales_config"> <activity android:name=".providersetup.activities.SetupActivity" android:launchMode="singleInstance" @@ -121,6 +122,15 @@ android:name="android.service.quicksettings.ACTIVE_TILE" android:value="false" /> </service> + <service + android:name="androidx.appcompat.app.AppLocalesMetadataHolderService" + android:enabled="false" + android:exported="false"> + <meta-data + android:name="autoStoreLocales" + android:value="true" /> + </service> + </application> </manifest>
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/LanguageSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/LanguageSelectionFragment.java new file mode 100644 index 00000000..263a8d46 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/LanguageSelectionFragment.java @@ -0,0 +1,166 @@ +package se.leap.bitmaskclient.base.fragments; + +import static se.leap.bitmaskclient.base.utils.ViewHelper.setActionBarSubtitle; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.core.os.LocaleListCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.views.SimpleCheckBox; +import se.leap.bitmaskclient.databinding.FLanguageSelectionBinding; +import se.leap.bitmaskclient.databinding.VSelectTextListItemBinding; + +public class LanguageSelectionFragment extends BottomSheetDialogFragment { + static final String TAG = LanguageSelectionFragment.class.getSimpleName(); + static final String SYSTEM_LOCALE = "systemLocale"; + private FLanguageSelectionBinding binding; + + public static LanguageSelectionFragment newInstance(Locale defaultLocale) { + LanguageSelectionFragment fragment = new LanguageSelectionFragment(); + Bundle args = new Bundle(); + args.putString(SYSTEM_LOCALE, defaultLocale.toLanguageTag()); + fragment.setArguments(args); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FLanguageSelectionBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setActionBarSubtitle(this, R.string.select_language); + + initRecyclerView(Arrays.asList(getResources().getStringArray(R.array.supported_languages))); + } + + private static void customizeSelectionItem(VSelectTextListItemBinding binding) { + binding.title.setVisibility(View.GONE); + binding.bridgeImage.setVisibility(View.GONE); + binding.quality.setVisibility(View.GONE); + } + + private void initRecyclerView(List<String> supportedLanguages) { + Locale defaultLocale = AppCompatDelegate.getApplicationLocales().get(0); + if (defaultLocale == null) { + defaultLocale = LocaleListCompat.getDefault().get(0); + } + // NOTE: Sort the supported languages by their display names. + // This would make updating supported languages easier as we don't have to tip toe around the order + Collections.sort(supportedLanguages, (lang1, lang2) -> { + String displayName1 = Locale.forLanguageTag(lang1).getDisplayName(Locale.ENGLISH); + String displayName2 = Locale.forLanguageTag(lang2).getDisplayName(Locale.ENGLISH); + return displayName1.compareTo(displayName2); + }); + binding.languages.setAdapter( + new LanguageSelectionAdapter(supportedLanguages, this::updateLocale, defaultLocale) + ); + binding.languages.setLayoutManager(new LinearLayoutManager(getContext())); + } + + public static Locale getCurrentLocale() { + Locale defaultLocale = AppCompatDelegate.getApplicationLocales().get(0); + if (defaultLocale == null) { + defaultLocale = LocaleListCompat.getDefault().get(0); + } + return defaultLocale; + } + + /** + * Update the locale of the application + * + * @param languageTag the language tag to set the locale to + */ + private void updateLocale(String languageTag) { + if (languageTag.isEmpty()) { + AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList()); + } else { + AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(languageTag)); + } + } + + /** + * Adapter for the language selection recycler view. + */ + static class LanguageSelectionAdapter extends RecyclerView.Adapter<LanguageSelectionAdapter.LanguageViewHolder> { + + private final List<String> languages; + private final LanguageClickListener clickListener; + private final Locale selectedLocale; + + public LanguageSelectionAdapter(List<String> languages, LanguageClickListener clickListener, Locale defaultLocale) { + this.languages = languages; + this.clickListener = clickListener; + this.selectedLocale = defaultLocale; + } + + @NonNull + @Override + public LanguageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + VSelectTextListItemBinding binding = VSelectTextListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new LanguageViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull LanguageViewHolder holder, int position) { + String languageTag = languages.get(position); + holder.languageName.setText(Locale.forLanguageTag(languageTag).getDisplayName(Locale.ENGLISH)); + if (languages.contains(selectedLocale.toLanguageTag())) { + holder.selected.setChecked(selectedLocale.toLanguageTag().equals(languageTag)); + } else { + holder.selected.setChecked(selectedLocale.getLanguage().equals(languageTag)); + } + holder.itemView.setOnClickListener(v -> clickListener.onLanguageClick(languageTag)); + } + + @Override + public int getItemCount() { + return languages.size(); + } + + /** + * View holder for the language item + */ + static class LanguageViewHolder extends RecyclerView.ViewHolder { + TextView languageName; + SimpleCheckBox selected; + + public LanguageViewHolder(@NonNull VSelectTextListItemBinding binding) { + super(binding.getRoot()); + languageName = binding.location; + selected = binding.selected; + customizeSelectionItem(binding); + } + } + } + + + /** + * Interface for the language click listener + */ + interface LanguageClickListener { + void onLanguageClick(String languageTag); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java index cbab1d32..41e102bb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -46,15 +46,19 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.graphics.drawable.DrawerArrowDrawable; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; +import androidx.core.os.LocaleListCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.Locale; +import java.util.Map; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.R; @@ -211,6 +215,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen initSwitchProviderEntry(); initSaveBatteryEntry(); initManualGatewayEntry(); + initLanguageSettingsEntry(); initAdvancedSettingsEntry(); initDonateEntry(); initLogEntry(); @@ -314,6 +319,26 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen }); } + + private void initLanguageSettingsEntry() { + IconTextEntry languageSwitcher = drawerView.findViewById(R.id.language_switcher); + + Locale currentLocale = LanguageSelectionFragment.getCurrentLocale(); + languageSwitcher.setSubtitle(currentLocale.getDisplayName(Locale.ENGLISH)); + + languageSwitcher.setOnClickListener(v -> { + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + closeDrawer(); + Fragment current = fragmentManager.findFragmentByTag(MainActivity.TAG); + if (current instanceof LanguageSelectionFragment) { + return; + } + Fragment fragment = LanguageSelectionFragment.newInstance(Locale.getDefault()); + setDrawerToggleColor(drawerView.getContext(), ContextCompat.getColor(drawerView.getContext(), R.color.colorActionBarTitleFont)); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + }); + } + private void initDonateEntry() { if (ENABLE_DONATION) { IconTextEntry donate = drawerView.findViewById(R.id.donate); diff --git a/app/src/main/res/drawable/translate.xml b/app/src/main/res/drawable/translate.xml new file mode 100644 index 00000000..7826980c --- /dev/null +++ b/app/src/main/res/drawable/translate.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#828282" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z"/> + +</vector> diff --git a/app/src/main/res/layout/f_drawer_main.xml b/app/src/main/res/layout/f_drawer_main.xml index a948d9ce..ca0406ad 100644 --- a/app/src/main/res/layout/f_drawer_main.xml +++ b/app/src/main/res/layout/f_drawer_main.xml @@ -80,6 +80,14 @@ /> <se.leap.bitmaskclient.base.views.IconTextEntry + android:id="@+id/language_switcher" + app:text="@string/select_language" + app:icon="@drawable/translate" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:visibility="visible"/> + + <se.leap.bitmaskclient.base.views.IconTextEntry android:id="@+id/advancedSettings" app:icon="@drawable/ic_cog" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/f_language_selection.xml b/app/src/main/res/layout/f_language_selection.xml new file mode 100644 index 00000000..0569c7c1 --- /dev/null +++ b/app/src/main/res/layout/f_language_selection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_margin="@dimen/stdpadding" + tools:context=".base.fragments.LanguageSelectionFragment"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/languages" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/v_select_text_list_item.xml b/app/src/main/res/layout/v_select_text_list_item.xml index 47a1f4ad..44e82906 100644 --- a/app/src/main/res/layout/v_select_text_list_item.xml +++ b/app/src/main/res/layout/v_select_text_list_item.xml @@ -4,8 +4,7 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:orientation="vertical" - android:background="?attr/selectableItemBackground" - tools:viewBindingIgnore="true"> + android:background="?attr/selectableItemBackground"> <LinearLayout android:layout_width="match_parent" diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties new file mode 100644 index 00000000..63b46f93 --- /dev/null +++ b/app/src/main/res/resources.properties @@ -0,0 +1 @@ +unqualifiedResLocale=en
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index edbe56bb..b90c4e0f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -238,4 +238,5 @@ <string name="setup_success_description">Click the button below to connect</string> <string name="permission_rejected">Permission request rejected.</string> <string name="login_not_supported">The current app version doesn\'t support logins, which you need to update your VPN certificate for this provider.</string> + <string name="select_language">Select Language</string> </resources> diff --git a/app/src/main/res/values/untranslatable.xml b/app/src/main/res/values/untranslatable.xml index 80cbf7f3..f326e756 100644 --- a/app/src/main/res/values/untranslatable.xml +++ b/app/src/main/res/values/untranslatable.xml @@ -49,4 +49,47 @@ <!-- gateway selector, move to strings.xml, once the wording is clear --> <string name="no_location" translatable="false">---</string> + <string-array name="supported_languages" translatable="false"> + <item>ar</item> + <item>az</item> + <item>bg</item> + <item>bn</item> + <item>br</item> + <item>ca</item> + <item>cs</item> + <item>de</item> + <item>el</item> + <item>en</item> + <item>es</item> + <item>es-AR</item> + <item>es-CU</item> + <item>et</item> + <item>eu</item> + <item>fa-IR</item> + <item>fi</item> + <item>fr</item> + <item>gl</item> + <item>he</item> + <item>hr</item> + <item>hu</item> + <item>id</item> + <item>it</item> + <item>ja</item> + <item>lt</item> + <item>my</item> + <item>nl</item> + <item>no</item> + <item>pl</item> + <item>pt</item> + <item>pt-BR</item> + <item>pt-PT</item> + <item>ro</item> + <item>ru</item> + <item>tr</item> + <item>ug</item> + <item>uk</item> + <item>vi</item> + <item>zh</item> + <item>zh-TW</item> + </string-array> </resources> diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml new file mode 100644 index 00000000..9da2a215 --- /dev/null +++ b/app/src/main/res/xml/locales_config.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<locale-config xmlns:android="http://schemas.android.com/apk/res/android"> + <locale android:name="ar"/> + <locale android:name="az"/> + <locale android:name="bg"/> + <locale android:name="bn"/> + <locale android:name="br"/> + <locale android:name="ca"/> + <locale android:name="cs"/> + <locale android:name="de"/> + <locale android:name="el"/> + <locale android:name="en"/> + <locale android:name="es"/> + <locale android:name="es-AR"/> + <locale android:name="es-CU"/> + <locale android:name="et"/> + <locale android:name="eu"/> + <locale android:name="fa-IR"/> + <locale android:name="fi"/> + <locale android:name="fr"/> + <locale android:name="gl"/> + <locale android:name="he"/> + <locale android:name="hr"/> + <locale android:name="hu"/> + <locale android:name="id"/> + <locale android:name="it"/> + <locale android:name="ja"/> + <locale android:name="lt"/> + <locale android:name="my"/> + <locale android:name="nl"/> + <locale android:name="no"/> + <locale android:name="pl"/> + <locale android:name="pt"/> + <locale android:name="pt-BR"/> + <locale android:name="pt-PT"/> + <locale android:name="ro"/> + <locale android:name="ru"/> + <locale android:name="tr"/> + <locale android:name="ug"/> + <locale android:name="uk"/> + <locale android:name="vi"/> + <locale android:name="zh"/> + <locale android:name="zh-TW"/> +</locale-config>
\ No newline at end of file |