From 61106968cbdf96a4fa578d16aff03865b985e547 Mon Sep 17 00:00:00 2001 From: Norbel Ambanumben Date: Mon, 7 Oct 2024 21:31:33 +0000 Subject: feat: add language selection --- app/src/main/AndroidManifest.xml | 12 +- .../base/fragments/LanguageSelectionFragment.java | 166 +++++++++++++++++++++ .../base/fragments/NavigationDrawerFragment.java | 25 ++++ app/src/main/res/drawable/translate.xml | 5 + app/src/main/res/layout/f_drawer_main.xml | 8 + app/src/main/res/layout/f_language_selection.xml | 17 +++ .../main/res/layout/v_select_text_list_item.xml | 3 +- app/src/main/res/resources.properties | 1 + app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/untranslatable.xml | 43 ++++++ app/src/main/res/xml/locales_config.xml | 44 ++++++ 11 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/fragments/LanguageSelectionFragment.java create mode 100644 app/src/main/res/drawable/translate.xml create mode 100644 app/src/main/res/layout/f_language_selection.xml create mode 100644 app/src/main/res/resources.properties create mode 100644 app/src/main/res/xml/locales_config.xml 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"> + + + + \ 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 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 { + + private final List languages; + private final LanguageClickListener clickListener; + private final Locale selectedLocale; + + public LanguageSelectionAdapter(List 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 @@ + + + + + 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 @@ -79,6 +79,14 @@ android:visibility="visible" /> + + + + + + \ 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"> Click the button below to connect Permission request rejected. The current app version doesn\'t support logins, which you need to update your VPN certificate for this provider. + Select Language 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 @@ --- + + ar + az + bg + bn + br + ca + cs + de + el + en + es + es-AR + es-CU + et + eu + fa-IR + fi + fr + gl + he + hr + hu + id + it + ja + lt + my + nl + no + pl + pt + pt-BR + pt-PT + ro + ru + tr + ug + uk + vi + zh + zh-TW + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3