From 2e2139a77989d0df8d6e252f9188aa9f86fa1c1d Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 7 Jun 2021 20:05:21 -0500 Subject: [PATCH 01/28] ADded backing models and view holders. --- .../ui/prefs/language/LocalePickerListItem.kt | 27 ++++++++++++++ .../LocalePickerListItemViewHolder.kt | 19 ++++++++++ .../LocalePickerListSubHeaderViewHolder.kt | 15 ++++++++ .../language/LocalePickerListViewHolder.kt | 6 ++++ .../res/layout/locale_picker_list_item.xml | 35 +++++++++++++++++++ .../layout/locale_picker_list_subheader.xml | 13 +++++++ 6 files changed, 115 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt create mode 100644 WordPress/src/main/res/layout/locale_picker_list_item.xml create mode 100644 WordPress/src/main/res/layout/locale_picker_list_subheader.xml diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt new file mode 100644 index 000000000000..8d7e1ead6766 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt @@ -0,0 +1,27 @@ +package org.wordpress.android.ui.prefs.language + +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.LOCALE +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.SUB_HEADER + +sealed class LocalePickerListItem(val type: LocalePickerListViewType) { + data class SubHeader(val label: String) : LocalePickerListItem(SUB_HEADER) + + data class LocaleRow( + val label: String, + val localizedLabel: String, + val localeCode: String, + val clickAction: ClickAction + ) : LocalePickerListItem(LOCALE) + + enum class LocalePickerListViewType { + SUB_HEADER, + LOCALE; + } + + data class ClickAction( + val localeCode: String, + private val clickItem: (localeCode: String) -> Unit + ) { + fun onClick() = clickItem(localeCode) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt new file mode 100644 index 000000000000..d6af698a0984 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt @@ -0,0 +1,19 @@ +package org.wordpress.android.ui.prefs.language + +import android.view.ViewGroup +import org.wordpress.android.databinding.LocalePickerListItemBinding +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow +import org.wordpress.android.util.viewBinding + +class LocalePickerListItemViewHolder( + parent: ViewGroup +) : LocalePickerListViewHolder(parent.viewBinding(LocalePickerListItemBinding::inflate)) { + fun bind(item: LocaleRow) = with(binding) { + label.text = item.label + localizedLabel.text = item.localizedLabel + + itemView.setOnClickListener { + item.clickAction.onClick() + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt new file mode 100644 index 000000000000..705be6886db2 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt @@ -0,0 +1,15 @@ +package org.wordpress.android.ui.prefs.language + +import android.view.ViewGroup +import org.wordpress.android.databinding.LocalePickerListSubheaderBinding +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.SubHeader +import org.wordpress.android.util.viewBinding + +class LocalePickerListSubHeaderViewHolder( + parent: ViewGroup +) : LocalePickerListViewHolder(parent.viewBinding(LocalePickerListSubheaderBinding::inflate)) { + fun bind(item: SubHeader) = with(binding) { + label.text = item.label + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt new file mode 100644 index 000000000000..2c7fbf2d438e --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt @@ -0,0 +1,6 @@ +package org.wordpress.android.ui.prefs.language + +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding + +abstract class LocalePickerListViewHolder(protected val binding: T) : RecyclerView.ViewHolder(binding.root) diff --git a/WordPress/src/main/res/layout/locale_picker_list_item.xml b/WordPress/src/main/res/layout/locale_picker_list_item.xml new file mode 100644 index 000000000000..ad269c74dbce --- /dev/null +++ b/WordPress/src/main/res/layout/locale_picker_list_item.xml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/WordPress/src/main/res/layout/locale_picker_list_subheader.xml b/WordPress/src/main/res/layout/locale_picker_list_subheader.xml new file mode 100644 index 000000000000..03229c9b81fd --- /dev/null +++ b/WordPress/src/main/res/layout/locale_picker_list_subheader.xml @@ -0,0 +1,13 @@ + + From 0365e99d492912b464265e042aecefc03d8742d7 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 7 Jun 2021 20:05:41 -0500 Subject: [PATCH 02/28] Added adapter. --- .../ui/prefs/language/LocalePickerAdapter.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt new file mode 100644 index 000000000000..86e68068ba82 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt @@ -0,0 +1,45 @@ +package org.wordpress.android.ui.prefs.language + +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.LOCALE +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.SUB_HEADER +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.SubHeader + +class LocalePickerAdapter : ListAdapter>(DIFF_CALLBACK) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalePickerListViewHolder<*> { + return when (viewType) { + SUB_HEADER.ordinal -> LocalePickerListSubHeaderViewHolder(parent) + LOCALE.ordinal -> LocalePickerListItemViewHolder(parent) + else -> throw IllegalArgumentException("Unexpected view holder in LocalePickerAdapter") + } + } + + override fun onBindViewHolder(holder: LocalePickerListViewHolder<*>, position: Int) { + val item = getItem(position) + when (holder) { + is LocalePickerListSubHeaderViewHolder -> holder.bind(item as SubHeader) + is LocalePickerListItemViewHolder -> holder.bind(item as LocaleRow) + } + } + + override fun getItemViewType(position: Int): Int { + return getItem(position)!!.type.ordinal + } + + companion object { + val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: LocalePickerListItem, newItem: LocalePickerListItem): Boolean { + return when { + oldItem is LocaleRow && newItem is LocaleRow -> oldItem.label == newItem.label + else -> false + } + } + + override fun areContentsTheSame(oldItem: LocalePickerListItem, newItem: LocalePickerListItem) = + oldItem == newItem + } + } +} From eab2be2479a6b93de56952f768168775269229ea Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 7 Jun 2021 20:06:40 -0500 Subject: [PATCH 03/28] Updated locale manager to reurn triple with all the required values. --- .../android/ui/prefs/SiteSettingsFragment.java | 10 ++++++---- .../org/wordpress/android/util/LocaleManager.java | 11 +++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java index 3aa1a3ce3326..b9e6e2b3d9ed 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java @@ -1,5 +1,6 @@ package org.wordpress.android.ui.prefs; +import kotlin.Triple; import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; @@ -1631,15 +1632,16 @@ private void sortLanguages() { return; } - Pair pair = LocaleManager + Triple pair = LocaleManager .createSortedLanguageDisplayStrings(mLanguagePref.getEntryValues(), LocaleManager.languageLocale(null)); if (pair != null) { - String[] sortedEntries = pair.first; - String[] sortedValues = pair.second; + String[] sortedEntries = pair.component1(); + String[] sortedValues = pair.component2(); + String[] localizedEntries = pair.component3(); mLanguagePref.setEntries(sortedEntries); mLanguagePref.setEntryValues(sortedValues); - mLanguagePref.setDetails(LocaleManager.createLanguageDetailDisplayStrings(sortedValues)); + mLanguagePref.setDetails(localizedEntries); } } diff --git a/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java b/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java index 244b1792086e..57f79bb730a4 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java +++ b/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java @@ -1,5 +1,6 @@ package org.wordpress.android.util; +import kotlin.Triple; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; @@ -8,7 +9,6 @@ import android.os.Build; import android.preference.PreferenceManager; import android.text.TextUtils; -import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -240,8 +240,8 @@ public static Map generateLanguageMap(Context context) { * Generates display strings for given language codes. Used as entries in language preference. */ @Nullable - public static Pair createSortedLanguageDisplayStrings(CharSequence[] languageCodes, - Locale locale) { + public static Triple createSortedLanguageDisplayStrings(CharSequence[] languageCodes, + Locale locale) { if (languageCodes == null || languageCodes.length < 1) { return null; } @@ -257,15 +257,18 @@ public static Pair createSortedLanguageDisplayStrings(CharSe String[] sortedEntries = new String[languageCodes.length]; String[] sortedValues = new String[languageCodes.length]; + String[] detailStrings = new String[languageCodes.length]; for (int i = 0; i < entryStrings.size(); ++i) { // now, we can split the sorted array to extract the display string and the language code String[] split = entryStrings.get(i).split("__"); sortedEntries[i] = split[0]; sortedValues[i] = split[1]; + detailStrings[i] = + StringUtils.capitalize(getLanguageString(sortedValues[i], languageLocale(sortedValues[i]))); } - return new Pair<>(sortedEntries, sortedValues); + return new Triple<>(sortedEntries, sortedValues, detailStrings); } /** From 850093b0849649134c5b2cab8794388d0c92851e Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 7 Jun 2021 20:07:04 -0500 Subject: [PATCH 04/28] Added locale picker view model --- .../android/modules/ViewModelModule.java | 6 + .../prefs/language/LocalePickerViewModel.kt | 167 ++++++++++++++++++ .../android/viewmodel/ResourceProvider.kt | 5 + 3 files changed, 178 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt diff --git a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java index 9cabdad1e9fe..8e0275c38644 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java @@ -39,6 +39,7 @@ import org.wordpress.android.ui.posts.editor.StorePostViewModel; import org.wordpress.android.ui.posts.prepublishing.PrepublishingPublishSettingsViewModel; import org.wordpress.android.ui.prefs.homepage.HomepageSettingsViewModel; +import org.wordpress.android.ui.prefs.language.LocalePickerViewModel; import org.wordpress.android.ui.prefs.timezone.SiteSettingsTimezoneViewModel; import org.wordpress.android.ui.reader.ReaderCommentListViewModel; import org.wordpress.android.ui.reader.discover.ReaderDiscoverViewModel; @@ -548,4 +549,9 @@ abstract class ViewModelModule { @IntoMap @ViewModelKey(BloggingRemindersViewModel.class) abstract ViewModel bloggingRemindersViewModel(BloggingRemindersViewModel viewModel); + + @Binds + @IntoMap + @ViewModelKey(LocalePickerViewModel.class) + abstract ViewModel localePickerViewModel(LocalePickerViewModel viewModel); } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt new file mode 100644 index 000000000000..f009d336ae1d --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt @@ -0,0 +1,167 @@ +package org.wordpress.android.ui.prefs.language + +import android.content.res.Resources +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import androidx.lifecycle.ViewModel +import androidx.lifecycle.distinctUntilChanged +import org.wordpress.android.R.array +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.ClickAction +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.SubHeader +import org.wordpress.android.util.LocaleManager +import org.wordpress.android.viewmodel.ResourceProvider +import org.wordpress.android.viewmodel.SingleLiveEvent +import javax.inject.Inject + +class LocalePickerViewModel @Inject constructor( + private val resourceProvider: ResourceProvider +) : ViewModel() { + private val localesList = mutableListOf() + + private val _showEmptyView = SingleLiveEvent() + val showEmptyView: LiveData = _showEmptyView + + private val _dismissBottomSheet = SingleLiveEvent() + val dismissBottomSheet: LiveData = _dismissBottomSheet + + private val _suggestedLocale = MutableLiveData() + val suggestedLocale = _suggestedLocale + + private val _selectedLocale = SingleLiveEvent() + val selectedLocale = _selectedLocale + + private val _locales = MutableLiveData>() + + private val searchInput = MutableLiveData() + private val localeSearch: LiveData> = Transformations.switchMap(searchInput) { term -> + filterLocales(term) + } + + val locales = MediatorLiveData>().apply { + addSource(_locales) { + value = it + } + addSource(localeSearch) { + value = it + } + }.distinctUntilChanged() + + fun searchLocales(query: CharSequence) { + searchInput.value = query.toString() + } + + fun onTimezoneSelected(timezone: String) { + _selectedLocale.value = timezone + _dismissBottomSheet.asyncCall() + } + + @VisibleForTesting + fun filterLocales(query: String): LiveData> { + val filteredTimezones = MutableLiveData>() + + localesList.filter { timezone -> + when (timezone) { + is LocaleRow -> { + query.isBlank() or timezone.label.contains(query, true) or timezone.localizedLabel.contains( + query, + true + ) + } + else -> false + } + }.also { + _showEmptyView.value = it.isEmpty() + filteredTimezones.value = it + } + + return filteredTimezones + } + + fun onSearchCancelled() { + _showEmptyView.value = false + _locales.postValue(localesList) + } + + var started = false + + fun start() { + if (started) { + return + } + started = true + _suggestedLocale.postValue(getDeviceLocale()) + loadLocales() + } + + fun getDeviceLocale(): SuggestedLocale { + val deviceLocale = Resources.getSystem().configuration.locale + return SuggestedLocale( + deviceLocale.language + "_" + deviceLocale.country, + Resources.getSystem().configuration.locale.displayName + ) + } + + data class SuggestedLocale( + val label: String, + val localeCode: String + ) + + private fun loadLocales() { + val languageCode = resourceProvider.getConfiguration().locale.toString() + + val languageLocale = LocaleManager.languageLocale(languageCode) + val availableLocales: Array = resourceProvider.getStringArray(array.available_languages) + + val triple = LocaleManager.createSortedLanguageDisplayStrings(availableLocales, languageLocale) ?: return + + val sortedEntries = triple.first + val sortedValues = triple.second + val sortedLocalizedEntries = triple.third + + val appLocale = resourceProvider.getConfiguration().locale.toString() + val indexOfCurrentLanguage = sortedValues.indexOf(appLocale) + + + if (indexOfCurrentLanguage >= 0) { + localesList.add(SubHeader("Current Language")) + localesList.add( + LocaleRow( + sortedEntries[indexOfCurrentLanguage], + sortedLocalizedEntries[indexOfCurrentLanguage], + sortedValues[indexOfCurrentLanguage], + ClickAction(sortedValues[indexOfCurrentLanguage], this::clickItem) + ) + ) + + localesList.add(SubHeader("All Languages")) + } + + + + for (i in triple.first.indices) { + val code = sortedValues[i] + if (code != appLocale) { + localesList.add( + LocaleRow( + sortedEntries[i], + sortedLocalizedEntries[i], + code, + ClickAction(sortedValues[i], this::clickItem) + ) + ) + } + } + + + + _locales.postValue(localesList) + } + + fun clickItem(localeCode: String) { + _selectedLocale.postValue(localeCode) + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt index 8b79f5412963..475107a6d8c8 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt @@ -1,5 +1,6 @@ package org.wordpress.android.viewmodel +import android.content.res.Configuration import android.graphics.drawable.Drawable import androidx.annotation.ColorRes import androidx.annotation.DimenRes @@ -57,4 +58,8 @@ class ResourceProvider @Inject constructor(private val contextProvider: ContextP fun getDrawable(iconId: Int): Drawable? { return ContextCompat.getDrawable(contextProvider.getContext(), iconId) } + + fun getConfiguration(): Configuration { + return contextProvider.getContext().resources.configuration + } } From b612dd91971d2d1ffc0fe2e523f685f39031e3f8 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 7 Jun 2021 20:07:28 -0500 Subject: [PATCH 05/28] Added bottom sheet with picker. --- .../android/modules/AppComponent.java | 3 + .../android/ui/prefs/AppSettingsFragment.java | 69 +++++-- .../prefs/language/LocalePickerBottomSheet.kt | 190 ++++++++++++++++++ .../res/layout/locale_picker_bottom_sheet.xml | 84 ++++++++ WordPress/src/main/res/xml/app_settings.xml | 2 +- 5 files changed, 325 insertions(+), 23 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt create mode 100644 WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml diff --git a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java index 66f952bbcdaa..69cc16b7b7b1 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java @@ -145,6 +145,7 @@ import org.wordpress.android.ui.prefs.SiteSettingsTagDetailFragment; import org.wordpress.android.ui.prefs.SiteSettingsTagListActivity; import org.wordpress.android.ui.prefs.homepage.HomepageSettingsDialog; +import org.wordpress.android.ui.prefs.language.LocalePickerBottomSheet; import org.wordpress.android.ui.prefs.notifications.NotificationsSettingsFragment; import org.wordpress.android.ui.prefs.timezone.SiteSettingsTimezoneBottomSheet; import org.wordpress.android.ui.publicize.PublicizeAccountChooserListAdapter; @@ -685,6 +686,8 @@ public interface AppComponent extends AndroidInjector { void inject(UnifiedCommentListAdapter object); void inject(BloggingReminderBottomSheetFragment object); + + void inject(LocalePickerBottomSheet object); // Allows us to inject the application without having to instantiate any modules, and provides the Application // in the app graph diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java index 858f0f852828..7d2524f10bef 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java @@ -13,7 +13,6 @@ import android.preference.PreferenceScreen; import android.preference.SwitchPreference; import android.text.TextUtils; -import android.util.Pair; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -26,6 +25,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import org.jetbrains.annotations.NotNull; import org.wordpress.android.BuildConfig; import org.wordpress.android.R; import org.wordpress.android.WordPress; @@ -42,6 +42,8 @@ import org.wordpress.android.fluxc.store.WhatsNewStore.OnWhatsNewFetched; import org.wordpress.android.fluxc.store.WhatsNewStore.WhatsNewAppId; import org.wordpress.android.fluxc.store.WhatsNewStore.WhatsNewFetchPayload; +import org.wordpress.android.ui.prefs.language.LocalePickerBottomSheet; +import org.wordpress.android.ui.prefs.language.LocalePickerBottomSheet.LocalePickerCallback; import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic; import org.wordpress.android.ui.reader.services.update.ReaderUpdateServiceStarter; import org.wordpress.android.ui.whatsnew.FeatureAnnouncementDialogFragment; @@ -66,10 +68,10 @@ import javax.inject.Inject; public class AppSettingsFragment extends PreferenceFragment - implements OnPreferenceClickListener, Preference.OnPreferenceChangeListener { + implements OnPreferenceClickListener, Preference.OnPreferenceChangeListener, LocalePickerCallback { public static final int LANGUAGE_CHANGED = 1000; - private DetailListPreference mLanguagePreference; + private WPPreference mLanguagePreference; private ListPreference mAppThemePreference; // This Device settings @@ -121,8 +123,9 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { ); updateAnalyticsSyncUI(); - mLanguagePreference = (DetailListPreference) findPreference(getString(R.string.pref_key_language)); + mLanguagePreference = (WPPreference) findPreference(getString(R.string.pref_key_language)); mLanguagePreference.setOnPreferenceChangeListener(this); + mLanguagePreference.setOnPreferenceClickListener(this); mAppThemePreference = (ListPreference) findPreference(getString(R.string.pref_key_app_theme)); mAppThemePreference.setOnPreferenceChangeListener(this); @@ -260,7 +263,7 @@ public void onStop() { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - updateLanguagePreference(getResources().getConfiguration().locale.toString()); +// updateLanguagePreference(getResources().getConfiguration().locale.toString()); // flush gathered events (if any) AnalyticsTracker.flush(); } @@ -342,6 +345,8 @@ public boolean onPreferenceClick(Preference preference) { return handlePrivacyClick(); } else if (preference == mWhatsNew) { return handleFeatureAnnouncementClick(); + } else if (preference == mLanguagePreference) { + return handleAppLocalePickerClick(); } return false; @@ -447,23 +452,23 @@ private void updateLanguagePreference(String languageCode) { Locale languageLocale = LocaleManager.languageLocale(languageCode); String[] availableLocales = getResources().getStringArray(R.array.available_languages); - Pair pair = - LocaleManager.createSortedLanguageDisplayStrings(availableLocales, languageLocale); - // check for a possible NPE - if (pair == null) { - return; - } - - String[] sortedEntries = pair.first; - String[] sortedValues = pair.second; - - mLanguagePreference.setEntries(sortedEntries); - mLanguagePreference.setEntryValues(sortedValues); - mLanguagePreference.setDetails(LocaleManager.createLanguageDetailDisplayStrings(sortedValues)); - - mLanguagePreference.setValue(languageCode); - mLanguagePreference.setSummary(LocaleManager.getLanguageString(languageCode, languageLocale)); - mLanguagePreference.refreshAdapter(); +// Pair pair = +// LocaleManager.createSortedLanguageDisplayStrings(availableLocales, languageLocale); +// // check for a possible NPE +// if (pair == null) { +// return; +// } +// +// String[] sortedEntries = pair.first; +// String[] sortedValues = pair.second; +// +// mLanguagePreference.setEntries(sortedEntries); +// mLanguagePreference.setEntryValues(sortedValues); +// mLanguagePreference.setDetails(LocaleManager.createLanguageDetailDisplayStrings(sortedValues)); +// +// mLanguagePreference.setValue(languageCode); +// mLanguagePreference.setSummary(LocaleManager.getLanguageString(languageCode, languageLocale)); +// mLanguagePreference.refreshAdapter(); } private boolean handleAboutPreferenceClick() { @@ -576,4 +581,24 @@ private boolean handleFeatureAnnouncementClick() { + "using support fragment manager from AppCompatActivity."); } } + + private boolean handleAppLocalePickerClick() { + + if (getActivity() instanceof AppCompatActivity) { + LocalePickerBottomSheet bottomSheet = LocalePickerBottomSheet.newInstance(); + bottomSheet.setLocalePickerCallback(this); + bottomSheet + .show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), "TIMEZONE_BOTTOM_SHEET_TAG"); + return true; + } else { + throw new IllegalArgumentException( + "Parent activity is not AppCompatActivity. FeatureAnnouncementDialogFragment must be called " + + "using support fragment manager from AppCompatActivity."); + } + } + + @Override + public void onLocaleSelected(@NotNull String languageCode) { + onPreferenceChange(mLanguagePreference, languageCode); + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt new file mode 100644 index 000000000000..b0d9cca583db --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt @@ -0,0 +1,190 @@ +package org.wordpress.android.ui.prefs.language + +import android.content.res.Configuration +import android.os.Bundle +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.FrameLayout +import androidx.core.widget.doOnTextChanged +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.wordpress.android.WordPress +import org.wordpress.android.databinding.LocalePickerBottomSheetBinding +import org.wordpress.android.util.ActivityUtils +import javax.inject.Inject + +class LocalePickerBottomSheet : BottomSheetDialogFragment() { + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + private lateinit var viewModel: LocalePickerViewModel + + private val localeAdapter = LocalePickerAdapter() + + private var binding: LocalePickerBottomSheetBinding? = null + + private var bottomSheet: FrameLayout? = null + + private var callback: LocalePickerCallback? = null + + fun setLocalePickerCallback(callback: LocalePickerCallback?) { + this.callback = callback + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + (requireActivity().applicationContext as WordPress).component().inject(this) + viewModel = ViewModelProvider(requireActivity(), viewModelFactory) + .get(LocalePickerViewModel::class.java) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = LocalePickerBottomSheetBinding.inflate(inflater, container, false) + return binding?.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding?.apply { + setupContentViews() + setupObservers() + } + } + + private fun LocalePickerBottomSheetBinding.setupContentViews() { + val layoutManager = LinearLayoutManager(context) + recyclerView.layoutManager = layoutManager + recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), RecyclerView.VERTICAL)) + recyclerView.adapter = localeAdapter + recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + ActivityUtils.hideKeyboardForced(binding?.searchInputLayout) + } + } + }) + + searchInputLayout.editText?.doOnTextChanged() { _, _, _, _ -> + searchTimezones() + } + + searchInputLayout.editText?.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> + if (hasFocus) expandBottomSheet() + } + + searchInputLayout.editText?.setOnKeyListener { _, keyCode, event -> + if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { + searchTimezones() + true + } else { + false + } + } + + searchInputLayout.editText?.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_GO) { + searchTimezones() + true + } else { + false + } + } + + searchInputLayout.setEndIconOnClickListener { + searchInputLayout.editText?.text?.clear() + searchInputLayout.editText?.clearFocus() + viewModel.onSearchCancelled() + ActivityUtils.hideKeyboardForced(binding?.searchInputLayout) + } + +// btnTimezoneSuggestion.setOnClickListener { it as MaterialButton +// timezoneViewModel.onTimezoneSelected(it.text.toString()) +// } + + dialog?.setOnShowListener { dialogInterface -> + val sheetDialog = dialogInterface as? BottomSheetDialog + + bottomSheet = sheetDialog?.findViewById( + com.google.android.material.R.id.design_bottom_sheet + ) as? FrameLayout + + if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) expandBottomSheet() + } + } + + private fun LocalePickerBottomSheetBinding.setupObservers() { + viewModel.locales.observe(viewLifecycleOwner, { + localeAdapter.submitList(it) + }) + + viewModel.selectedLocale.observe(viewLifecycleOwner, { + callback?.onLocaleSelected(it) + dismiss() + }) + + viewModel.showEmptyView.observe(viewLifecycleOwner, { + emptyView.visibility = if (it) View.VISIBLE else View.GONE + }) + + + viewModel.suggestedLocale.observe(viewLifecycleOwner, { + btnLocaleSuggestion.text = it.localeCode + }) + + + viewModel.dismissBottomSheet.observe(viewLifecycleOwner, { + dismiss() + }) + + viewModel.start() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) expandBottomSheet() + } + + override fun onDestroyView() { + binding = null + callback = null + super.onDestroyView() + } + + private fun expandBottomSheet() { + bottomSheet?.let { + val behavior = BottomSheetBehavior.from(it) + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + + private fun searchTimezones() { + binding?.searchInputLayout?.editText?.text?.trim().let { + if (it?.isNotEmpty() == true) { + viewModel.searchLocales(it) + } else { + viewModel.onSearchCancelled() + } + } + } + + companion object { + @JvmStatic + fun newInstance(): LocalePickerBottomSheet = LocalePickerBottomSheet() + } + + interface LocalePickerCallback { + fun onLocaleSelected(languageCode: String) + } +} diff --git a/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml new file mode 100644 index 000000000000..e6f6f1e2f0ae --- /dev/null +++ b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + diff --git a/WordPress/src/main/res/xml/app_settings.xml b/WordPress/src/main/res/xml/app_settings.xml index b9d55973f2c0..4ab2398f2c49 100644 --- a/WordPress/src/main/res/xml/app_settings.xml +++ b/WordPress/src/main/res/xml/app_settings.xml @@ -4,7 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:key="@string/pref_key_app_settings_root"> - From 3da94edb28e237126d4103184ae87075ce82ee74 Mon Sep 17 00:00:00 2001 From: khaykov Date: Thu, 10 Jun 2021 16:37:29 -0500 Subject: [PATCH 06/28] Reattaching locale picker callback. --- .../android/ui/prefs/AppSettingsFragment.java | 49 ++++++------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java index 7d2524f10bef..5b1b95193525 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java @@ -262,8 +262,7 @@ public void onStop() { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - -// updateLanguagePreference(getResources().getConfiguration().locale.toString()); + reattachLocalePickerCallback(); // flush gathered events (if any) AnalyticsTracker.flush(); } @@ -421,7 +420,6 @@ private void changeLanguage(String languageCode) { LocaleManager.setNewLocale(WordPress.getContext(), languageCode); WordPress.updateContextLocale(); - updateLanguagePreference(languageCode); mContextProvider.refreshContext(); // Track language change on Analytics because we have both the device language and app selected language @@ -444,33 +442,6 @@ private void changeLanguage(String languageCode) { ReaderUpdateServiceStarter.startService(WordPress.getContext(), EnumSet.of(ReaderUpdateLogic.UpdateTask.TAGS)); } - private void updateLanguagePreference(String languageCode) { - if (mLanguagePreference == null || TextUtils.isEmpty(languageCode)) { - return; - } - - Locale languageLocale = LocaleManager.languageLocale(languageCode); - String[] availableLocales = getResources().getStringArray(R.array.available_languages); - -// Pair pair = -// LocaleManager.createSortedLanguageDisplayStrings(availableLocales, languageLocale); -// // check for a possible NPE -// if (pair == null) { -// return; -// } -// -// String[] sortedEntries = pair.first; -// String[] sortedValues = pair.second; -// -// mLanguagePreference.setEntries(sortedEntries); -// mLanguagePreference.setEntryValues(sortedValues); -// mLanguagePreference.setDetails(LocaleManager.createLanguageDetailDisplayStrings(sortedValues)); -// -// mLanguagePreference.setValue(languageCode); -// mLanguagePreference.setSummary(LocaleManager.getLanguageString(languageCode, languageLocale)); -// mLanguagePreference.refreshAdapter(); - } - private boolean handleAboutPreferenceClick() { startActivity(new Intent(getActivity(), AboutActivity.class)); return true; @@ -583,20 +554,30 @@ private boolean handleFeatureAnnouncementClick() { } private boolean handleAppLocalePickerClick() { - if (getActivity() instanceof AppCompatActivity) { LocalePickerBottomSheet bottomSheet = LocalePickerBottomSheet.newInstance(); - bottomSheet.setLocalePickerCallback(this); + bottomSheet.setLocalePickerCallback(this); bottomSheet - .show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), "TIMEZONE_BOTTOM_SHEET_TAG"); + .show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), LocalePickerBottomSheet.TAG); return true; } else { throw new IllegalArgumentException( - "Parent activity is not AppCompatActivity. FeatureAnnouncementDialogFragment must be called " + "Parent activity is not AppCompatActivity. LocalePickerBottomSheet must be called " + "using support fragment manager from AppCompatActivity."); } } + private void reattachLocalePickerCallback() { + if (getActivity() instanceof AppCompatActivity) { + LocalePickerBottomSheet bottomSheet = + (LocalePickerBottomSheet) (((AppCompatActivity) getActivity())) + .getSupportFragmentManager().findFragmentByTag(LocalePickerBottomSheet.TAG); + if (bottomSheet != null) { + bottomSheet.setLocalePickerCallback(this); + } + } + } + @Override public void onLocaleSelected(@NotNull String languageCode) { onPreferenceChange(mLanguagePreference, languageCode); From 0e963160cf813eccd3da83c6d746fc2100fa140e Mon Sep 17 00:00:00 2001 From: khaykov Date: Thu, 10 Jun 2021 16:37:47 -0500 Subject: [PATCH 07/28] Minor visual tweaks. --- .../res/layout/locale_picker_bottom_sheet.xml | 27 ++++++++++--------- .../res/layout/locale_picker_list_item.xml | 5 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml index e6f6f1e2f0ae..7c9e81b834f7 100644 --- a/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml +++ b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml @@ -36,18 +36,6 @@ tools:context=".ui.prefs.timezone.SiteSettingsTimezoneBottomSheet" tools:listitem="@layout/locale_picker_list_item" /> - + + + - - Date: Thu, 10 Jun 2021 16:38:20 -0500 Subject: [PATCH 08/28] Simplified logic. --- .../prefs/language/LocalePickerBottomSheet.kt | 75 +++++----- .../prefs/language/LocalePickerViewModel.kt | 138 ++++++++++-------- .../android/viewmodel/ResourceProvider.kt | 4 - 3 files changed, 111 insertions(+), 106 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt index b0d9cca583db..b8c2ebad8583 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.prefs.language -import android.content.res.Configuration import android.os.Bundle import android.view.KeyEvent import android.view.LayoutInflater @@ -40,8 +39,7 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (requireActivity().applicationContext as WordPress).component().inject(this) - viewModel = ViewModelProvider(requireActivity(), viewModelFactory) - .get(LocalePickerViewModel::class.java) + viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(LocalePickerViewModel::class.java) } override fun onCreateView( @@ -64,28 +62,30 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { private fun LocalePickerBottomSheetBinding.setupContentViews() { val layoutManager = LinearLayoutManager(context) recyclerView.layoutManager = layoutManager - recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), RecyclerView.VERTICAL)) + recyclerView.addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL)) recyclerView.adapter = localeAdapter recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - ActivityUtils.hideKeyboardForced(binding?.searchInputLayout) + viewModel.onListScrolled() } } }) - searchInputLayout.editText?.doOnTextChanged() { _, _, _, _ -> - searchTimezones() + searchInputLayout.editText?.doOnTextChanged { _, _, _, _ -> + onSearchStatusUpdated() } searchInputLayout.editText?.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus -> - if (hasFocus) expandBottomSheet() + if (hasFocus) { + viewModel.onSearchFieldFocused() + } } searchInputLayout.editText?.setOnKeyListener { _, keyCode, event -> if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { - searchTimezones() + onSearchStatusUpdated() true } else { false @@ -94,7 +94,7 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { searchInputLayout.editText?.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_GO) { - searchTimezones() + onSearchStatusUpdated() true } else { false @@ -102,46 +102,46 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { } searchInputLayout.setEndIconOnClickListener { - searchInputLayout.editText?.text?.clear() - searchInputLayout.editText?.clearFocus() - viewModel.onSearchCancelled() - ActivityUtils.hideKeyboardForced(binding?.searchInputLayout) + viewModel.onClearSearchFieldButtonClicked() } -// btnTimezoneSuggestion.setOnClickListener { it as MaterialButton -// timezoneViewModel.onTimezoneSelected(it.text.toString()) -// } + btnLocaleSuggestion.setOnClickListener { + viewModel.onSuggestedLocaleSelected() + } dialog?.setOnShowListener { dialogInterface -> val sheetDialog = dialogInterface as? BottomSheetDialog - bottomSheet = sheetDialog?.findViewById( com.google.android.material.R.id.design_bottom_sheet ) as? FrameLayout - - if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) expandBottomSheet() } } private fun LocalePickerBottomSheetBinding.setupObservers() { - viewModel.locales.observe(viewLifecycleOwner, { - localeAdapter.submitList(it) + viewModel.uiState.observe(viewLifecycleOwner, { uiState -> + uiState?.let { + localeAdapter.submitList(uiState.listData) + btnLocaleSuggestion.text = uiState.suggestedLocale?.label + emptyView.visibility = if (uiState.isEmptyViewVisible) View.VISIBLE else View.GONE + } }) - viewModel.selectedLocale.observe(viewLifecycleOwner, { - callback?.onLocaleSelected(it) - dismiss() + viewModel.hideKeyboard.observe(viewLifecycleOwner, { + ActivityUtils.hideKeyboardForced(binding?.searchInputLayout) }) - viewModel.showEmptyView.observe(viewLifecycleOwner, { - emptyView.visibility = if (it) View.VISIBLE else View.GONE + viewModel.expandBottomSheet.observe(viewLifecycleOwner, { + expandBottomSheet() }) - - viewModel.suggestedLocale.observe(viewLifecycleOwner, { - btnLocaleSuggestion.text = it.localeCode + viewModel.clearSearchField.observe(viewLifecycleOwner, { + searchInputLayout.editText?.text?.clear() + searchInputLayout.editText?.clearFocus() }) + viewModel.selectedLocale.observe(viewLifecycleOwner, { + callback?.onLocaleSelected(it) + }) viewModel.dismissBottomSheet.observe(viewLifecycleOwner, { dismiss() @@ -150,12 +150,6 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { viewModel.start() } - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) expandBottomSheet() - } - override fun onDestroyView() { binding = null callback = null @@ -169,17 +163,14 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { } } - private fun searchTimezones() { + private fun onSearchStatusUpdated() { binding?.searchInputLayout?.editText?.text?.trim().let { - if (it?.isNotEmpty() == true) { - viewModel.searchLocales(it) - } else { - viewModel.onSearchCancelled() - } + viewModel.requestSearch(it) } } companion object { + const val TAG = "TIMEZONE_BOTTOM_SHEET_TAG" @JvmStatic fun newInstance(): LocalePickerBottomSheet = LocalePickerBottomSheet() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt index f009d336ae1d..c267269defbe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt @@ -11,8 +11,9 @@ import androidx.lifecycle.distinctUntilChanged import org.wordpress.android.R.array import org.wordpress.android.ui.prefs.language.LocalePickerListItem.ClickAction import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow -import org.wordpress.android.ui.prefs.language.LocalePickerListItem.SubHeader +import org.wordpress.android.util.LanguageUtils import org.wordpress.android.util.LocaleManager +import org.wordpress.android.util.merge import org.wordpress.android.viewmodel.ResourceProvider import org.wordpress.android.viewmodel.SingleLiveEvent import javax.inject.Inject @@ -20,42 +21,69 @@ import javax.inject.Inject class LocalePickerViewModel @Inject constructor( private val resourceProvider: ResourceProvider ) : ViewModel() { - private val localesList = mutableListOf() + private val cachedLocales = mutableListOf() - private val _showEmptyView = SingleLiveEvent() - val showEmptyView: LiveData = _showEmptyView + private val _expandBottomSheet = SingleLiveEvent() + val expandBottomSheet: LiveData = _expandBottomSheet + + private val _hideKeyboard = SingleLiveEvent() + val hideKeyboard: LiveData = _hideKeyboard + + private val _clearSearchField = SingleLiveEvent() + val clearSearchField: LiveData = _clearSearchField private val _dismissBottomSheet = SingleLiveEvent() val dismissBottomSheet: LiveData = _dismissBottomSheet + private val _isEmptyViewVisible = SingleLiveEvent() private val _suggestedLocale = MutableLiveData() - val suggestedLocale = _suggestedLocale private val _selectedLocale = SingleLiveEvent() val selectedLocale = _selectedLocale - private val _locales = MutableLiveData>() + private val _loadedLocales = MutableLiveData>() private val searchInput = MutableLiveData() - private val localeSearch: LiveData> = Transformations.switchMap(searchInput) { term -> + private val _filteredLocales: LiveData> = Transformations.switchMap(searchInput) { term -> filterLocales(term) } - val locales = MediatorLiveData>().apply { - addSource(_locales) { + private val locales = MediatorLiveData>().apply { + addSource(_loadedLocales) { value = it } - addSource(localeSearch) { + addSource(_filteredLocales) { value = it } }.distinctUntilChanged() - fun searchLocales(query: CharSequence) { - searchInput.value = query.toString() + val uiState: LiveData = merge( + locales, + _suggestedLocale, + _isEmptyViewVisible + ) { locales, suggestedLocale, emptyViewVisible -> + LocalePickerUiState( + locales, + suggestedLocale, + emptyViewVisible ?: false + ) + } + + fun requestSearch(query: CharSequence?) { + if (query.isNullOrBlank()) { + clearSearch() + } else { + searchInput.value = query.toString() + } + } + + private fun clearSearch() { + _isEmptyViewVisible.value = false + _loadedLocales.postValue(cachedLocales) } - fun onTimezoneSelected(timezone: String) { - _selectedLocale.value = timezone + fun onSuggestedLocaleSelected() { + _selectedLocale.value = _suggestedLocale.value?.localeCode _dismissBottomSheet.asyncCall() } @@ -63,7 +91,7 @@ class LocalePickerViewModel @Inject constructor( fun filterLocales(query: String): LiveData> { val filteredTimezones = MutableLiveData>() - localesList.filter { timezone -> + cachedLocales.filter { timezone -> when (timezone) { is LocaleRow -> { query.isBlank() or timezone.label.contains(query, true) or timezone.localizedLabel.contains( @@ -74,18 +102,13 @@ class LocalePickerViewModel @Inject constructor( else -> false } }.also { - _showEmptyView.value = it.isEmpty() + _isEmptyViewVisible.value = it.isEmpty() filteredTimezones.value = it } return filteredTimezones } - fun onSearchCancelled() { - _showEmptyView.value = false - _locales.postValue(localesList) - } - var started = false fun start() { @@ -99,10 +122,9 @@ class LocalePickerViewModel @Inject constructor( fun getDeviceLocale(): SuggestedLocale { val deviceLocale = Resources.getSystem().configuration.locale - return SuggestedLocale( - deviceLocale.language + "_" + deviceLocale.country, - Resources.getSystem().configuration.locale.displayName - ) + val displayLabel = LocaleManager.getLanguageString(deviceLocale.toString(), deviceLocale) + + return SuggestedLocale(displayLabel, deviceLocale.language + "_" + deviceLocale.country) } data class SuggestedLocale( @@ -111,57 +133,53 @@ class LocalePickerViewModel @Inject constructor( ) private fun loadLocales() { - val languageCode = resourceProvider.getConfiguration().locale.toString() + val appLanguageCode = LanguageUtils.getCurrentDeviceLanguageCode() - val languageLocale = LocaleManager.languageLocale(languageCode) - val availableLocales: Array = resourceProvider.getStringArray(array.available_languages) + val languageLocale = LocaleManager.languageLocale(appLanguageCode) + val availableLocales = resourceProvider.getStringArray(array.available_languages).distinct() - val triple = LocaleManager.createSortedLanguageDisplayStrings(availableLocales, languageLocale) ?: return + val triple = LocaleManager.createSortedLanguageDisplayStrings(availableLocales.toTypedArray(), languageLocale) + ?: return val sortedEntries = triple.first val sortedValues = triple.second val sortedLocalizedEntries = triple.third - val appLocale = resourceProvider.getConfiguration().locale.toString() - val indexOfCurrentLanguage = sortedValues.indexOf(appLocale) - - - if (indexOfCurrentLanguage >= 0) { - localesList.add(SubHeader("Current Language")) - localesList.add( + for (i in triple.first.indices) { + cachedLocales.add( LocaleRow( - sortedEntries[indexOfCurrentLanguage], - sortedLocalizedEntries[indexOfCurrentLanguage], - sortedValues[indexOfCurrentLanguage], - ClickAction(sortedValues[indexOfCurrentLanguage], this::clickItem) + sortedEntries[i], + sortedLocalizedEntries[i], + sortedValues[i], + ClickAction(sortedValues[i], this::clickItem) ) ) - - localesList.add(SubHeader("All Languages")) } + _loadedLocales.postValue(cachedLocales) + } + fun clickItem(localeCode: String) { + _selectedLocale.postValue(localeCode) + } - for (i in triple.first.indices) { - val code = sortedValues[i] - if (code != appLocale) { - localesList.add( - LocaleRow( - sortedEntries[i], - sortedLocalizedEntries[i], - code, - ClickAction(sortedValues[i], this::clickItem) - ) - ) - } - } - - + fun onListScrolled() { + _hideKeyboard.call() + } - _locales.postValue(localesList) + fun onSearchFieldFocused() { + _expandBottomSheet.call() } - fun clickItem(localeCode: String) { - _selectedLocale.postValue(localeCode) + fun onClearSearchFieldButtonClicked() { + clearSearch() + _hideKeyboard.call() + _clearSearchField.call() } + + data class LocalePickerUiState( + val listData: List?, + val suggestedLocale: SuggestedLocale?, + val isEmptyViewVisible: Boolean + ) } diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt index 475107a6d8c8..a44e95283d8d 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt @@ -58,8 +58,4 @@ class ResourceProvider @Inject constructor(private val contextProvider: ContextP fun getDrawable(iconId: Int): Drawable? { return ContextCompat.getDrawable(contextProvider.getContext(), iconId) } - - fun getConfiguration(): Configuration { - return contextProvider.getContext().resources.configuration - } } From 3fbab83c54bda93775a7b6f3e5e7819176a95b36 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 12:58:59 -0500 Subject: [PATCH 09/28] Switched suggestion label to current language. --- WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml | 3 ++- WordPress/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml index 7c9e81b834f7..0570f54afbbc 100644 --- a/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml +++ b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml @@ -68,6 +68,7 @@ android:layout_height="wrap_content" android:minHeight="0dp" android:layout_marginTop="@dimen/margin_small_medium" + android:layout_marginEnd="@dimen/margin_extra_large" android:gravity="start" android:textAllCaps="false" app:layout_constraintEnd_toEndOf="parent" @@ -80,7 +81,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/margin_extra_large" - android:text="@string/suggestion" + android:text="@string/locale_picker_current_language" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/search_input_layout" /> diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index b2c027894849..2b9646e2a300 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -890,6 +890,7 @@ @string/app_theme_entry_value_light @string/app_theme_entry_value_dark + Current language: Test feature configuration From e48e4156ab25dc34e2a9a49af30899e6796f3d46 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 13:37:30 -0500 Subject: [PATCH 10/28] Removed subheader. --- .../ui/prefs/language/LocalePickerListItem.kt | 3 --- .../LocalePickerListSubHeaderViewHolder.kt | 15 --------------- .../prefs/language/LocalePickerListViewHolder.kt | 3 ++- .../res/layout/locale_picker_list_subheader.xml | 13 ------------- 4 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt delete mode 100644 WordPress/src/main/res/layout/locale_picker_list_subheader.xml diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt index 8d7e1ead6766..63c0430f302a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt @@ -1,11 +1,8 @@ package org.wordpress.android.ui.prefs.language import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.LOCALE -import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.SUB_HEADER sealed class LocalePickerListItem(val type: LocalePickerListViewType) { - data class SubHeader(val label: String) : LocalePickerListItem(SUB_HEADER) - data class LocaleRow( val label: String, val localizedLabel: String, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt deleted file mode 100644 index 705be6886db2..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListSubHeaderViewHolder.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.wordpress.android.ui.prefs.language - -import android.view.ViewGroup -import org.wordpress.android.databinding.LocalePickerListSubheaderBinding -import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow -import org.wordpress.android.ui.prefs.language.LocalePickerListItem.SubHeader -import org.wordpress.android.util.viewBinding - -class LocalePickerListSubHeaderViewHolder( - parent: ViewGroup -) : LocalePickerListViewHolder(parent.viewBinding(LocalePickerListSubheaderBinding::inflate)) { - fun bind(item: SubHeader) = with(binding) { - label.text = item.label - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt index 2c7fbf2d438e..0838fbdd12b7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListViewHolder.kt @@ -3,4 +3,5 @@ package org.wordpress.android.ui.prefs.language import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -abstract class LocalePickerListViewHolder(protected val binding: T) : RecyclerView.ViewHolder(binding.root) +abstract class LocalePickerListViewHolder(protected val binding: T) : + RecyclerView.ViewHolder(binding.root) diff --git a/WordPress/src/main/res/layout/locale_picker_list_subheader.xml b/WordPress/src/main/res/layout/locale_picker_list_subheader.xml deleted file mode 100644 index 03229c9b81fd..000000000000 --- a/WordPress/src/main/res/layout/locale_picker_list_subheader.xml +++ /dev/null @@ -1,13 +0,0 @@ - - From c990562dbace70ad5bb00fca5826f0c4499e0614 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 13:37:56 -0500 Subject: [PATCH 11/28] Removed subheader from adapter. --- .../android/ui/prefs/language/LocalePickerAdapter.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt index 86e68068ba82..8b65c8b3f2dc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerAdapter.kt @@ -4,14 +4,11 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.LOCALE -import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocalePickerListViewType.SUB_HEADER import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow -import org.wordpress.android.ui.prefs.language.LocalePickerListItem.SubHeader class LocalePickerAdapter : ListAdapter>(DIFF_CALLBACK) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalePickerListViewHolder<*> { return when (viewType) { - SUB_HEADER.ordinal -> LocalePickerListSubHeaderViewHolder(parent) LOCALE.ordinal -> LocalePickerListItemViewHolder(parent) else -> throw IllegalArgumentException("Unexpected view holder in LocalePickerAdapter") } @@ -20,7 +17,6 @@ class LocalePickerAdapter : ListAdapter, position: Int) { val item = getItem(position) when (holder) { - is LocalePickerListSubHeaderViewHolder -> holder.bind(item as SubHeader) is LocalePickerListItemViewHolder -> holder.bind(item as LocaleRow) } } From d4d7965d20e85df32ad27e30f8ed48cc11ec3324 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 13:38:50 -0500 Subject: [PATCH 12/28] Wrapping some language utils into LocaleProvider --- .../wordpress/android/util/LocaleProvider.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 WordPress/src/main/java/org/wordpress/android/util/LocaleProvider.kt diff --git a/WordPress/src/main/java/org/wordpress/android/util/LocaleProvider.kt b/WordPress/src/main/java/org/wordpress/android/util/LocaleProvider.kt new file mode 100644 index 000000000000..7ccb72651432 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/util/LocaleProvider.kt @@ -0,0 +1,21 @@ +package org.wordpress.android.util + +import java.util.Locale +import javax.inject.Inject + +class LocaleProvider @Inject constructor() { + fun getAppLocale(): Locale { + return LanguageUtils.getCurrentDeviceLanguage() + } + + fun getLanguageDisplayString(languageCode: String, displayLocale: Locale): String { + return LocaleManager.getLanguageString(languageCode, displayLocale) + } + + fun createSortedLocalizedLanguageDisplayStrings( + availableLocales: Array, + targetLocale: Locale + ): Triple, Array, Array>? { + return LocaleManager.createSortedLanguageDisplayStrings(availableLocales, targetLocale) + } +} From 0c61febbf69f5fd73cae68438bd4e13c97b76fbe Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 13:39:07 -0500 Subject: [PATCH 13/28] Added tests. --- .../prefs/language/LocalePickerBottomSheet.kt | 6 +- .../prefs/language/LocalePickerViewModel.kt | 123 ++++++----- .../prefs/locale/LocalePickerViewModelTest.kt | 201 ++++++++++++++++++ 3 files changed, 264 insertions(+), 66 deletions(-) create mode 100644 WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt index b8c2ebad8583..f10c9eafd865 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt @@ -106,7 +106,7 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { } btnLocaleSuggestion.setOnClickListener { - viewModel.onSuggestedLocaleSelected() + viewModel.onCurrentLocaleSelected() } dialog?.setOnShowListener { dialogInterface -> @@ -121,7 +121,7 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { viewModel.uiState.observe(viewLifecycleOwner, { uiState -> uiState?.let { localeAdapter.submitList(uiState.listData) - btnLocaleSuggestion.text = uiState.suggestedLocale?.label + btnLocaleSuggestion.text = uiState.currentLocale?.label emptyView.visibility = if (uiState.isEmptyViewVisible) View.VISIBLE else View.GONE } }) @@ -165,7 +165,7 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { private fun onSearchStatusUpdated() { binding?.searchInputLayout?.editText?.text?.trim().let { - viewModel.requestSearch(it) + viewModel.onSearchQueryChanged(it) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt index c267269defbe..333d6758585d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt @@ -1,7 +1,5 @@ package org.wordpress.android.ui.prefs.language -import android.content.res.Resources -import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData @@ -11,15 +9,15 @@ import androidx.lifecycle.distinctUntilChanged import org.wordpress.android.R.array import org.wordpress.android.ui.prefs.language.LocalePickerListItem.ClickAction import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow -import org.wordpress.android.util.LanguageUtils -import org.wordpress.android.util.LocaleManager +import org.wordpress.android.util.LocaleProvider import org.wordpress.android.util.merge import org.wordpress.android.viewmodel.ResourceProvider import org.wordpress.android.viewmodel.SingleLiveEvent import javax.inject.Inject class LocalePickerViewModel @Inject constructor( - private val resourceProvider: ResourceProvider + private val resourceProvider: ResourceProvider, + private val localeProvider: LocaleProvider ) : ViewModel() { private val cachedLocales = mutableListOf() @@ -36,7 +34,7 @@ class LocalePickerViewModel @Inject constructor( val dismissBottomSheet: LiveData = _dismissBottomSheet private val _isEmptyViewVisible = SingleLiveEvent() - private val _suggestedLocale = MutableLiveData() + private val _suggestedLocale = MutableLiveData() private val _selectedLocale = SingleLiveEvent() val selectedLocale = _selectedLocale @@ -44,9 +42,10 @@ class LocalePickerViewModel @Inject constructor( private val _loadedLocales = MutableLiveData>() private val searchInput = MutableLiveData() - private val _filteredLocales: LiveData> = Transformations.switchMap(searchInput) { term -> - filterLocales(term) - } + private val _filteredLocales: LiveData> = + Transformations.switchMap(searchInput) { term -> + filterLocales(term) + } private val locales = MediatorLiveData>().apply { addSource(_loadedLocales) { @@ -69,7 +68,18 @@ class LocalePickerViewModel @Inject constructor( ) } - fun requestSearch(query: CharSequence?) { + private var started = false + + fun start() { + if (started) { + return + } + started = true + + loadLocales() + } + + fun onSearchQueryChanged(query: CharSequence?) { if (query.isNullOrBlank()) { clearSearch() } else { @@ -77,18 +87,38 @@ class LocalePickerViewModel @Inject constructor( } } - private fun clearSearch() { - _isEmptyViewVisible.value = false - _loadedLocales.postValue(cachedLocales) + fun onCurrentLocaleSelected() { + val localeCode = _suggestedLocale.value?.localeCode + localeCode?.let { + clickItem(localeCode) + } + } + + fun onListScrolled() { + _hideKeyboard.call() + } + + fun onSearchFieldFocused() { + _expandBottomSheet.call() + } + + fun onClearSearchFieldButtonClicked() { + clearSearch() + _hideKeyboard.call() + _clearSearchField.call() } - fun onSuggestedLocaleSelected() { - _selectedLocale.value = _suggestedLocale.value?.localeCode + private fun clickItem(localeCode: String) { + _selectedLocale.postValue(localeCode) _dismissBottomSheet.asyncCall() } - @VisibleForTesting - fun filterLocales(query: String): LiveData> { + private fun clearSearch() { + _isEmptyViewVisible.value = false + _loadedLocales.postValue(cachedLocales) + } + + private fun filterLocales(query: String): LiveData> { val filteredTimezones = MutableLiveData>() cachedLocales.filter { timezone -> @@ -99,7 +129,6 @@ class LocalePickerViewModel @Inject constructor( true ) } - else -> false } }.also { _isEmptyViewVisible.value = it.isEmpty() @@ -109,37 +138,18 @@ class LocalePickerViewModel @Inject constructor( return filteredTimezones } - var started = false - - fun start() { - if (started) { - return - } - started = true - _suggestedLocale.postValue(getDeviceLocale()) - loadLocales() - } - - fun getDeviceLocale(): SuggestedLocale { - val deviceLocale = Resources.getSystem().configuration.locale - val displayLabel = LocaleManager.getLanguageString(deviceLocale.toString(), deviceLocale) - - return SuggestedLocale(displayLabel, deviceLocale.language + "_" + deviceLocale.country) - } - - data class SuggestedLocale( - val label: String, - val localeCode: String - ) - private fun loadLocales() { - val appLanguageCode = LanguageUtils.getCurrentDeviceLanguageCode() + val appLocale = localeProvider.getAppLocale() + + val displayLabel = localeProvider.getLanguageDisplayString(appLocale.toString(), appLocale) + _suggestedLocale.postValue(CurrentLocale(displayLabel, appLocale.toString())) - val languageLocale = LocaleManager.languageLocale(appLanguageCode) val availableLocales = resourceProvider.getStringArray(array.available_languages).distinct() - val triple = LocaleManager.createSortedLanguageDisplayStrings(availableLocales.toTypedArray(), languageLocale) - ?: return + val triple = localeProvider.createSortedLocalizedLanguageDisplayStrings( + availableLocales.toTypedArray(), + appLocale + ) ?: return val sortedEntries = triple.first val sortedValues = triple.second @@ -159,27 +169,14 @@ class LocalePickerViewModel @Inject constructor( _loadedLocales.postValue(cachedLocales) } - fun clickItem(localeCode: String) { - _selectedLocale.postValue(localeCode) - } - - fun onListScrolled() { - _hideKeyboard.call() - } - - fun onSearchFieldFocused() { - _expandBottomSheet.call() - } - - fun onClearSearchFieldButtonClicked() { - clearSearch() - _hideKeyboard.call() - _clearSearchField.call() - } + data class CurrentLocale( + val label: String, + val localeCode: String + ) data class LocalePickerUiState( val listData: List?, - val suggestedLocale: SuggestedLocale?, + val currentLocale: CurrentLocale?, val isEmptyViewVisible: Boolean ) } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt new file mode 100644 index 000000000000..01a159964454 --- /dev/null +++ b/WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt @@ -0,0 +1,201 @@ +package org.wordpress.android.ui.prefs.locale + +import android.content.Context +import androidx.lifecycle.Observer +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.wordpress.android.BaseUnitTest +import org.wordpress.android.test +import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow +import org.wordpress.android.ui.prefs.language.LocalePickerViewModel +import org.wordpress.android.ui.prefs.language.LocalePickerViewModel.LocalePickerUiState +import org.wordpress.android.util.LocaleProvider +import org.wordpress.android.viewmodel.ResourceProvider +import java.util.Locale + +class LocalePickerViewModelTest : BaseUnitTest() { + @Mock lateinit var resourceProvider: ResourceProvider + @Mock lateinit var localeProvider: LocaleProvider + @Mock lateinit var context: Context + + @Mock lateinit var currentLocale: Locale + + @Mock lateinit var selectedLocaleObserver: Observer + + @Mock lateinit var dismissBottomSheetObserver: Observer + @Mock lateinit var expandBottomSheetObserver: Observer + @Mock lateinit var hideKeyboardObserver: Observer + @Mock lateinit var clearSearchFieldObserver: Observer + + private lateinit var viewModel: LocalePickerViewModel + + private var uiStates = mutableListOf() + + private val languageNames = arrayOf("English (United States)", "Italian", "Russian") + private val languageCodes = arrayOf("en_US", "it", "ru") + private val localizedLanguageNames = arrayOf("English (United States)", "Italiano", "Русский") + + private val dummyLocales = Triple( + languageNames, + languageCodes, + localizedLanguageNames + ) + + @Before + fun setUp() { + uiStates.clear() + + viewModel = LocalePickerViewModel(resourceProvider, localeProvider) + + viewModel.uiState.observeForever { + if (it != null) { + uiStates.add(it) + } + } + + whenever(currentLocale.toString()).thenReturn("en_US") + whenever(localeProvider.getAppLocale()).thenReturn(currentLocale) + whenever(localeProvider.getLanguageDisplayString(any(), any())).thenReturn("English (United States)") + whenever(localeProvider.createSortedLocalizedLanguageDisplayStrings(any(), any())).thenReturn(dummyLocales) + whenever(resourceProvider.getStringArray(any())).thenReturn(languageCodes) + + viewModel.selectedLocale.observeForever(selectedLocaleObserver) + viewModel.dismissBottomSheet.observeForever(dismissBottomSheetObserver) + viewModel.expandBottomSheet.observeForever(expandBottomSheetObserver) + viewModel.hideKeyboard.observeForever(hideKeyboardObserver) + viewModel.clearSearchField.observeForever(clearSearchFieldObserver) + } + + @Test + fun `starting VM displays current locale and loads supported locales`() = test { + viewModel.start() + assertThat(uiStates).hasSize(3) + + val lastState = uiStates.last() + + assertThat(lastState.currentLocale).isNotNull + assertThat(lastState.currentLocale?.label).isEqualTo("English (United States)") + assertThat(lastState.currentLocale?.localeCode).isEqualTo("en_US") + + assertThat(lastState.isEmptyViewVisible).isFalse() + + assertThat(lastState.listData).isNotNull + assertThat(lastState.listData?.size).isEqualTo(3) + + val firstLocale = lastState.listData?.get(0) + + assertThat(firstLocale is LocaleRow).isTrue() + assertThat((firstLocale as LocaleRow).label).isEqualTo("English (United States)") + assertThat(firstLocale.localeCode).isEqualTo("en_US") + assertThat(firstLocale.localizedLabel).isEqualTo("English (United States)") + + val secondLocale = lastState.listData?.get(1) + assertThat(secondLocale is LocaleRow).isTrue() + assertThat((secondLocale as LocaleRow).label).isEqualTo("Italian") + assertThat(secondLocale.localeCode).isEqualTo("it") + assertThat(secondLocale.localizedLabel).isEqualTo("Italiano") + + val thirdLocale = lastState.listData?.get(2) + assertThat(thirdLocale is LocaleRow).isTrue() + assertThat((thirdLocale as LocaleRow).label).isEqualTo("Russian") + assertThat(thirdLocale.localeCode).isEqualTo("ru") + assertThat(thirdLocale.localizedLabel).isEqualTo("Русский") + } + + @Test + fun `onSearchQueryChanged produces filtered results based on the query`() = test { + viewModel.start() + + viewModel.onSearchQueryChanged("Ita") + val lastState = uiStates.last() + + assertThat(lastState.listData?.size).isEqualTo(1) + + val localeInTheList = lastState.listData?.get(0) + assertThat(localeInTheList is LocaleRow).isTrue() + assertThat((localeInTheList as LocaleRow).label).isEqualTo("Italian") + assertThat(localeInTheList.localeCode).isEqualTo("it") + assertThat(localeInTheList.localizedLabel).isEqualTo("Italiano") + } + + @Test + fun `onSearchQueryChanged shows empty view if no results are found`() = test { + viewModel.start() + + viewModel.onSearchQueryChanged("Span") + val lastState = uiStates.last() + + assertThat(lastState.listData?.size).isEqualTo(0) + assertThat(lastState.isEmptyViewVisible).isTrue() + } + + @Test + fun `onClearSearchFieldButtonClicked hides keyboard and clears search field`() = test { + viewModel.start() + viewModel.onClearSearchFieldButtonClicked() + + verify(hideKeyboardObserver).onChanged(null) + verify(clearSearchFieldObserver).onChanged(null) + } + + @Test + fun `when no results found onClearSearchFieldButtonClicked hides empty view and displays original list`() = test { + viewModel.start() + + viewModel.onSearchQueryChanged("Span") + val stateAfterSearch = uiStates.last() + + assertThat(stateAfterSearch.listData?.size).isEqualTo(0) + assertThat(stateAfterSearch.isEmptyViewVisible).isTrue() + + viewModel.onClearSearchFieldButtonClicked() + + val stateAfterSearchCancelled = uiStates.last() + + assertThat(stateAfterSearchCancelled.listData?.size).isEqualTo(3) + assertThat(stateAfterSearchCancelled.isEmptyViewVisible).isFalse() + } + + @Test + fun `onSearchFieldFocused expands bottom sheet`() = test { + viewModel.start() + viewModel.onSearchFieldFocused() + + verify(expandBottomSheetObserver).onChanged(null) + } + + @Test + fun `onCurrentLocaleSelected selects current locale and dismisses the bottom sheet`() = test { + viewModel.start() + + viewModel.onCurrentLocaleSelected() + + verify(selectedLocaleObserver).onChanged("en_US") + verify(dismissBottomSheetObserver).onChanged(null) + } + + @Test + fun `clicking on a locale selects current locale and dismisses the bottom sheet`() = test { + viewModel.start() + + val lastState = uiStates.last() + val italianLocale = lastState.listData?.get(1) + (italianLocale as LocaleRow).clickAction.onClick() + + verify(selectedLocaleObserver).onChanged("it") + verify(dismissBottomSheetObserver).onChanged(null) + } + + @Test + fun `onListScrolled hides a keyboard`() = test { + viewModel.start() + viewModel.onListScrolled() + + verify(hideKeyboardObserver).onChanged(null) + } +} From ec5ebe678e84852a2007fdc732ba7544af22bd56 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 13:39:29 -0500 Subject: [PATCH 14/28] Fixed checkstyle errors. --- .../org/wordpress/android/ui/prefs/SiteSettingsFragment.java | 4 ++-- .../main/java/org/wordpress/android/util/LocaleManager.java | 3 ++- .../java/org/wordpress/android/viewmodel/ResourceProvider.kt | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java index b9e6e2b3d9ed..b60a30752c06 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java @@ -1,6 +1,5 @@ package org.wordpress.android.ui.prefs; -import kotlin.Triple; import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; @@ -17,7 +16,6 @@ import android.preference.PreferenceScreen; import android.provider.ContactsContract; import android.text.TextUtils; -import android.util.Pair; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.HapticFeedbackConstants; @@ -111,6 +109,8 @@ import static org.wordpress.android.ui.prefs.WPComSiteSettings.supportsJetpackSiteAcceleratorSettings; +import kotlin.Triple; + /** * Allows interfacing with WordPress site settings. Works with WP.com and WP.org v4.5+ (pending). *

diff --git a/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java b/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java index 57f79bb730a4..c416a56fdd05 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java +++ b/WordPress/src/main/java/org/wordpress/android/util/LocaleManager.java @@ -1,6 +1,5 @@ package org.wordpress.android.util; -import kotlin.Triple; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; @@ -21,6 +20,8 @@ import java.util.Map; import java.util.regex.Pattern; +import kotlin.Triple; + /** * Helper class for working with localized strings. Ensures updates to the users * selected language is properly saved and resources appropriately updated for the diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt index a44e95283d8d..8b79f5412963 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/ResourceProvider.kt @@ -1,6 +1,5 @@ package org.wordpress.android.viewmodel -import android.content.res.Configuration import android.graphics.drawable.Drawable import androidx.annotation.ColorRes import androidx.annotation.DimenRes From 23fe646b01dcb3e64bc4117f7fbf56f0f98558ab Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 14:41:28 -0500 Subject: [PATCH 15/28] Added localized locale name search test. --- .../prefs/locale/LocalePickerViewModelTest.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt index 01a159964454..bfef643f0849 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/prefs/locale/LocalePickerViewModelTest.kt @@ -112,15 +112,25 @@ class LocalePickerViewModelTest : BaseUnitTest() { viewModel.start() viewModel.onSearchQueryChanged("Ita") - val lastState = uiStates.last() + val filteredItalianLocaleState = uiStates.last() + + assertThat(filteredItalianLocaleState.listData?.size).isEqualTo(1) + + val italianLocale = filteredItalianLocaleState.listData?.get(0) + assertThat(italianLocale is LocaleRow).isTrue() + assertThat((italianLocale as LocaleRow).label).isEqualTo("Italian") + assertThat(italianLocale.localeCode).isEqualTo("it") + assertThat(italianLocale.localizedLabel).isEqualTo("Italiano") + + // searching localized label + viewModel.onSearchQueryChanged("Русс") - assertThat(lastState.listData?.size).isEqualTo(1) + val filteredRussianLocaleState = uiStates.last() - val localeInTheList = lastState.listData?.get(0) - assertThat(localeInTheList is LocaleRow).isTrue() - assertThat((localeInTheList as LocaleRow).label).isEqualTo("Italian") - assertThat(localeInTheList.localeCode).isEqualTo("it") - assertThat(localeInTheList.localizedLabel).isEqualTo("Italiano") + val russianLocale = filteredRussianLocaleState.listData?.get(0) + assertThat(russianLocale is LocaleRow).isTrue() + assertThat((russianLocale as LocaleRow).label).isEqualTo("Russian") + assertThat(russianLocale.localeCode).isEqualTo("ru") } @Test From ade3644a1272255b10b2ac99e08feb6cb6228982 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 14:41:49 -0500 Subject: [PATCH 16/28] Cleaning up. --- .../android/ui/prefs/AppSettingsFragment.java | 4 ++-- .../ui/prefs/SiteSettingsFragment.java | 10 ++++----- .../prefs/language/LocalePickerBottomSheet.kt | 4 ++-- .../prefs/language/LocalePickerViewModel.kt | 22 +++++++++---------- .../res/layout/locale_picker_bottom_sheet.xml | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java index 5b1b95193525..6f20a3fbf4b0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java @@ -557,8 +557,8 @@ private boolean handleAppLocalePickerClick() { if (getActivity() instanceof AppCompatActivity) { LocalePickerBottomSheet bottomSheet = LocalePickerBottomSheet.newInstance(); bottomSheet.setLocalePickerCallback(this); - bottomSheet - .show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), LocalePickerBottomSheet.TAG); + bottomSheet.show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), + LocalePickerBottomSheet.TAG); return true; } else { throw new IllegalArgumentException( diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java index b60a30752c06..4a6b76341db6 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/SiteSettingsFragment.java @@ -1632,12 +1632,12 @@ private void sortLanguages() { return; } - Triple pair = LocaleManager + Triple supportedLocales = LocaleManager .createSortedLanguageDisplayStrings(mLanguagePref.getEntryValues(), LocaleManager.languageLocale(null)); - if (pair != null) { - String[] sortedEntries = pair.component1(); - String[] sortedValues = pair.component2(); - String[] localizedEntries = pair.component3(); + if (supportedLocales != null) { + String[] sortedEntries = supportedLocales.component1(); + String[] sortedValues = supportedLocales.component2(); + String[] localizedEntries = supportedLocales.component3(); mLanguagePref.setEntries(sortedEntries); mLanguagePref.setEntryValues(sortedValues); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt index f10c9eafd865..0490dbdef821 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt @@ -151,9 +151,9 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { } override fun onDestroyView() { + super.onDestroyView() binding = null callback = null - super.onDestroyView() } private fun expandBottomSheet() { @@ -170,7 +170,7 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { } companion object { - const val TAG = "TIMEZONE_BOTTOM_SHEET_TAG" + const val TAG = "LOCALE_PICKER_TAG" @JvmStatic fun newInstance(): LocalePickerBottomSheet = LocalePickerBottomSheet() } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt index 333d6758585d..f7313f76e25f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerViewModel.kt @@ -119,12 +119,12 @@ class LocalePickerViewModel @Inject constructor( } private fun filterLocales(query: String): LiveData> { - val filteredTimezones = MutableLiveData>() + val filteredLocales = MutableLiveData>() - cachedLocales.filter { timezone -> - when (timezone) { + cachedLocales.filter { locale -> + when (locale) { is LocaleRow -> { - query.isBlank() or timezone.label.contains(query, true) or timezone.localizedLabel.contains( + query.isBlank() or locale.label.contains(query, true) or locale.localizedLabel.contains( query, true ) @@ -132,10 +132,10 @@ class LocalePickerViewModel @Inject constructor( } }.also { _isEmptyViewVisible.value = it.isEmpty() - filteredTimezones.value = it + filteredLocales.value = it } - return filteredTimezones + return filteredLocales } private fun loadLocales() { @@ -146,16 +146,16 @@ class LocalePickerViewModel @Inject constructor( val availableLocales = resourceProvider.getStringArray(array.available_languages).distinct() - val triple = localeProvider.createSortedLocalizedLanguageDisplayStrings( + val availableLocalesData = localeProvider.createSortedLocalizedLanguageDisplayStrings( availableLocales.toTypedArray(), appLocale ) ?: return - val sortedEntries = triple.first - val sortedValues = triple.second - val sortedLocalizedEntries = triple.third + val sortedEntries = availableLocalesData.first + val sortedValues = availableLocalesData.second + val sortedLocalizedEntries = availableLocalesData.third - for (i in triple.first.indices) { + for (i in availableLocalesData.first.indices) { cachedLocales.add( LocaleRow( sortedEntries[i], diff --git a/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml index 0570f54afbbc..33f6d967727f 100644 --- a/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml +++ b/WordPress/src/main/res/layout/locale_picker_bottom_sheet.xml @@ -33,7 +33,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/label_locale_suggestion" - tools:context=".ui.prefs.timezone.SiteSettingsTimezoneBottomSheet" + tools:context=".ui.prefs.language.LocalePickerBottomSheet" tools:listitem="@layout/locale_picker_list_item" /> From 70d37e5755fcb992d41e2fe9e171c9b473d1f160 Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 14:48:16 -0500 Subject: [PATCH 17/28] Removed unused view type. --- .../wordpress/android/ui/prefs/language/LocalePickerListItem.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt index 63c0430f302a..55b7145647a1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItem.kt @@ -11,7 +11,6 @@ sealed class LocalePickerListItem(val type: LocalePickerListViewType) { ) : LocalePickerListItem(LOCALE) enum class LocalePickerListViewType { - SUB_HEADER, LOCALE; } From 0ce9eb5b34c235369f7693074dbaf17214f2b65c Mon Sep 17 00:00:00 2001 From: khaykov Date: Mon, 14 Jun 2021 14:48:25 -0500 Subject: [PATCH 18/28] Udpated release notes. --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index f501d652c100..eabb9826252a 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -2,7 +2,7 @@ 17.7 ----- - +* [*] App Settings: updated look and functionality of Locale Picker [#14836] 17.6 ----- From 767f889eb96485bf422e2d0b64bd023b3f13e7c6 Mon Sep 17 00:00:00 2001 From: khaykov Date: Wed, 16 Jun 2021 21:26:17 -0500 Subject: [PATCH 19/28] Removed layout manager from code. --- .../android/ui/prefs/language/LocalePickerBottomSheet.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt index 0490dbdef821..585d9a1f25aa 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt @@ -60,8 +60,6 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { } private fun LocalePickerBottomSheetBinding.setupContentViews() { - val layoutManager = LinearLayoutManager(context) - recyclerView.layoutManager = layoutManager recyclerView.addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL)) recyclerView.adapter = localeAdapter recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { From c4fd366b1e728e87bdfcd052f6428f6a766c2b83 Mon Sep 17 00:00:00 2001 From: khaykov Date: Wed, 16 Jun 2021 21:45:30 -0500 Subject: [PATCH 20/28] Removed unused import. --- .../android/ui/prefs/language/LocalePickerBottomSheet.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt index 585d9a1f25aa..12a9dca69b25 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt @@ -10,7 +10,6 @@ import android.widget.FrameLayout import androidx.core.widget.doOnTextChanged import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog From 2adc724a559864f71c1cd637965aac5dd08e6eca Mon Sep 17 00:00:00 2001 From: Siobhan Date: Thu, 14 Apr 2022 21:41:40 +0100 Subject: [PATCH 21/28] Fix out-of-date import --- .../android/ui/prefs/language/LocalePickerListItemViewHolder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt index d6af698a0984..c4991c0dbc22 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerListItemViewHolder.kt @@ -3,7 +3,7 @@ package org.wordpress.android.ui.prefs.language import android.view.ViewGroup import org.wordpress.android.databinding.LocalePickerListItemBinding import org.wordpress.android.ui.prefs.language.LocalePickerListItem.LocaleRow -import org.wordpress.android.util.viewBinding +import org.wordpress.android.util.extensions.viewBinding class LocalePickerListItemViewHolder( parent: ViewGroup From 4a833a1ba4452456c9bdc4a7510952a504349418 Mon Sep 17 00:00:00 2001 From: Siobhan Date: Thu, 14 Apr 2022 21:43:07 +0100 Subject: [PATCH 22/28] Update RELEASE-NOTES --- RELEASE-NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index b0580e25ed27..19133636f53f 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -4,6 +4,7 @@ ----- * [***] My Site: your My Site screen now has two tabs, "Site Menu" and "Home". Under "Home", you'll find contextual cards with some highlights of whats going on with your site. Check your drafts or scheduled posts, your today's stats or go directly to another section of the app. [https://github.com/wordpress-mobile/WordPress-Android/issues/15989] * [*] Site creation: Adds a new screen asking the user the intent of the site [https://github.com/wordpress-mobile/WordPress-Android/pull/16306] +* [*] App Settings: updated look and functionality of Locale Picker [#16331] 19.6 ----- @@ -182,7 +183,6 @@ 17.7 ----- -* [*] App Settings: updated look and functionality of Locale Picker [#14836] * [*] Block editor: Tablet view fixes for inserter button. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/3602] * [**] Block editor: Fixed an issue where pressing enter inside a text-based block was not creating a new block when using Gboard [https://github.com/wordpress-mobile/gutenberg-mobile/pull/3590] * [*] Block editor: Tweaks to the badge component's styling, including change of background color and reduced padding. [https://github.com/wordpress-mobile/gutenberg-mobile/pull/3642] From 873234ffc75034ba5da302cf3f81da5003b561e6 Mon Sep 17 00:00:00 2001 From: Siobhan Date: Tue, 19 Apr 2022 13:39:50 +0100 Subject: [PATCH 23/28] Bring @inject annotations inline for consistency --- .../android/ui/prefs/AppSettingsFragment.java | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java index 79e8b42ec101..6ac648119537 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java @@ -92,24 +92,15 @@ public class AppSettingsFragment extends PreferenceFragment private Preference mWhatsNew; - @Inject - SiteStore mSiteStore; - @Inject - AccountStore mAccountStore; - @Inject - Dispatcher mDispatcher; - @Inject - ContextProvider mContextProvider; - @Inject - FeatureAnnouncementProvider mFeatureAnnouncementProvider; - @Inject - BuildConfigWrapper mBuildConfigWrapper; - @Inject - UnifiedAboutFeatureConfig mUnifiedAboutFeatureConfig; - @Inject - MySiteDashboardTabsFeatureConfig mMySiteDashboardTabsFeatureConfig; - @Inject - MySiteDefaultTabExperiment mMySiteDefaultTabExperiment; + @Inject SiteStore mSiteStore; + @Inject AccountStore mAccountStore; + @Inject Dispatcher mDispatcher; + @Inject ContextProvider mContextProvider; + @Inject FeatureAnnouncementProvider mFeatureAnnouncementProvider; + @Inject BuildConfigWrapper mBuildConfigWrapper; + @Inject UnifiedAboutFeatureConfig mUnifiedAboutFeatureConfig; + @Inject MySiteDashboardTabsFeatureConfig mMySiteDashboardTabsFeatureConfig; + @Inject MySiteDefaultTabExperiment mMySiteDefaultTabExperiment; @Override public void onCreate(Bundle savedInstanceState) { From a373e7a0659d03a6d7bc7d37431c23af3d440c56 Mon Sep 17 00:00:00 2001 From: Siobhan Date: Tue, 19 Apr 2022 22:51:36 +0100 Subject: [PATCH 24/28] Revert unintentional changes to formatting --- .../android/ui/prefs/AppSettingsFragment.java | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java index 6ac648119537..35995ca2a8b9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java @@ -152,24 +152,30 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { findPreference(getString(R.string.pref_key_oss_licenses)) .setOnPreferenceClickListener(this); - mOptimizedImage = (WPSwitchPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_optimize_image, this); + mOptimizedImage = + (WPSwitchPreference) WPPrefUtils + .getPrefAndSetChangeListener(this, R.string.pref_key_optimize_image, this); mImageMaxSizePref = (DetailListPreference) WPPrefUtils .getPrefAndSetChangeListener(this, R.string.pref_key_site_image_width, this); - mImageQualityPref = (DetailListPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_site_image_quality, this); - mOptimizedVideo = (WPSwitchPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_optimize_video, this); - - mVideoWidthPref = (DetailListPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_site_video_width, this); - mVideoEncorderBitratePref = (DetailListPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_site_video_encoder_bitrate, this); + mImageQualityPref = + (DetailListPreference) WPPrefUtils + .getPrefAndSetChangeListener(this, R.string.pref_key_site_image_quality, this); + mOptimizedVideo = + (WPSwitchPreference) WPPrefUtils + .getPrefAndSetChangeListener(this, R.string.pref_key_optimize_video, this); + + mVideoWidthPref = + (DetailListPreference) WPPrefUtils + .getPrefAndSetChangeListener(this, R.string.pref_key_site_video_width, this); + mVideoEncorderBitratePref = + (DetailListPreference) WPPrefUtils + .getPrefAndSetChangeListener(this, R.string.pref_key_site_video_encoder_bitrate, this); mPrivacySettings = (PreferenceScreen) WPPrefUtils .getPrefAndSetClickListener(this, R.string.pref_key_privacy_settings, this); - mStripImageLocation = (WPSwitchPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_strip_image_location, this); + mStripImageLocation = + (WPSwitchPreference) WPPrefUtils + .getPrefAndSetChangeListener(this, R.string.pref_key_strip_image_location, this); // Set Local settings mOptimizedImage.setChecked(AppPrefs.isImageOptimize()); @@ -214,7 +220,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + @Nullable Bundle savedInstanceState) { View view = super.onCreateView(inflater, container, savedInstanceState); final ListView listOfPreferences = view.findViewById(android.R.id.list); @@ -225,44 +231,46 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, } private void removeExperimentalCategory() { - PreferenceCategory experimentalPreferenceCategory = (PreferenceCategory) findPreference( - getString(R.string.pref_key_experimental_section)); - PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference( - getString(R.string.pref_key_app_settings_root)); + PreferenceCategory experimentalPreferenceCategory = + (PreferenceCategory) findPreference(getString(R.string.pref_key_experimental_section)); + PreferenceScreen preferenceScreen = + (PreferenceScreen) findPreference(getString(R.string.pref_key_app_settings_root)); preferenceScreen.removePreference(experimentalPreferenceCategory); } private void removeDebugSettingsCategory() { - Preference experimentalPreference = findPreference(getString(R.string.pref_key_debug_settings)); - PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference( - getString(R.string.pref_key_app_settings_root)); + Preference experimentalPreference = + findPreference(getString(R.string.pref_key_debug_settings)); + PreferenceScreen preferenceScreen = + (PreferenceScreen) findPreference(getString(R.string.pref_key_app_settings_root)); preferenceScreen.removePreference(experimentalPreference); } private void removeAboutCategory() { - PreferenceCategory aboutPreferenceCategory = (PreferenceCategory) findPreference( - getString(R.string.pref_key_about_section)); - PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference( - getString(R.string.pref_key_app_settings_root)); + PreferenceCategory aboutPreferenceCategory = + (PreferenceCategory) findPreference(getString(R.string.pref_key_about_section)); + PreferenceScreen preferenceScreen = + (PreferenceScreen) findPreference(getString(R.string.pref_key_app_settings_root)); preferenceScreen.removePreference(aboutPreferenceCategory); } private void removeWhatsNewPreference() { - PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference( - getString(R.string.pref_key_app_settings_root)); + PreferenceScreen preferenceScreen = + (PreferenceScreen) findPreference(getString(R.string.pref_key_app_settings_root)); preferenceScreen.removePreference(mWhatsNew); } private void addWhatsNewPreference() { - PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference( - getString(R.string.pref_key_app_settings_root)); + PreferenceScreen preferenceScreen = + (PreferenceScreen) findPreference(getString(R.string.pref_key_app_settings_root)); preferenceScreen.addPreference(mWhatsNew); } private void removeInitialScreen() { - Preference initialScreenPreference = findPreference(getString(R.string.pref_key_initial_screen)); - PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference( - getString(R.string.pref_key_app_settings_root)); + Preference initialScreenPreference = + findPreference(getString(R.string.pref_key_initial_screen)); + PreferenceScreen preferenceScreen = + (PreferenceScreen) findPreference(getString(R.string.pref_key_app_settings_root)); preferenceScreen.removePreference(initialScreenPreference); } @@ -337,8 +345,7 @@ public void onAccountChanged(OnAccountChanged event) { break; } } else if (event.causeOfChange == AccountAction.FETCH_SETTINGS) { - // no need to sync with remote here, or do anything else here, since the logic - // is already in WordPress.java + // no need to sync with remote here, or do anything else here, since the logic is already in WordPress.java updateAnalyticsSyncUI(); } } @@ -349,8 +356,8 @@ private void updateAnalyticsSyncUI() { return; } if (mAccountStore.hasAccessToken()) { - SwitchPreference tracksOptOutPreference = (SwitchPreference) findPreference( - getString(R.string.pref_key_send_usage)); + SwitchPreference tracksOptOutPreference = + (SwitchPreference) findPreference(getString(R.string.pref_key_send_usage)); tracksOptOutPreference.setChecked(!mAccountStore.getAccount().getTracksOptOut()); } } @@ -457,8 +464,7 @@ private void changeLanguage(String languageCode) { WordPress.updateContextLocale(); mContextProvider.refreshContext(); - // Track language change on Analytics because we have both the device language - // and app selected language + // Track language change on Analytics because we have both the device language and app selected language // data in Tracks metadata. Map properties = new HashMap<>(); properties.put("app_locale", Locale.getDefault()); @@ -604,7 +610,7 @@ private boolean handleAppLocalePickerClick() { } else { throw new IllegalArgumentException( "Parent activity is not AppCompatActivity. LocalePickerBottomSheet must be called " - + "using support fragment manager from AppCompatActivity."); + + "using support fragment manager from AppCompatActivity."); } } From 7e9fd51c73912612e1b3319c1072abc8bc053d4c Mon Sep 17 00:00:00 2001 From: Siobhan Date: Tue, 19 Apr 2022 22:56:40 +0100 Subject: [PATCH 25/28] Revert unintentional changes to formatting --- .../org/wordpress/android/ui/prefs/AppSettingsFragment.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java index 35995ca2a8b9..1b32b101e80f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java @@ -128,7 +128,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { return true; } - }); + } + ); updateAnalyticsSyncUI(); mLanguagePreference = (WPPreference) findPreference(getString(R.string.pref_key_language)); @@ -596,7 +597,7 @@ private boolean handleFeatureAnnouncementClick() { } else { throw new IllegalArgumentException( "Parent activity is not AppCompatActivity. FeatureAnnouncementDialogFragment must be called " - + "using support fragment manager from AppCompatActivity."); + + "using support fragment manager from AppCompatActivity."); } } From 158d11e75258532d4125866344e655dca869e5da Mon Sep 17 00:00:00 2001 From: Siobhan Date: Thu, 28 Apr 2022 16:54:17 +0100 Subject: [PATCH 26/28] Move parenthesis positioning for increased readability --- .../prefs/language/LocalePickerBottomSheet.kt | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt index 12a9dca69b25..9903119e7761 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/language/LocalePickerBottomSheet.kt @@ -115,34 +115,34 @@ class LocalePickerBottomSheet : BottomSheetDialogFragment() { } private fun LocalePickerBottomSheetBinding.setupObservers() { - viewModel.uiState.observe(viewLifecycleOwner, { uiState -> + viewModel.uiState.observe(viewLifecycleOwner) { uiState -> uiState?.let { localeAdapter.submitList(uiState.listData) btnLocaleSuggestion.text = uiState.currentLocale?.label emptyView.visibility = if (uiState.isEmptyViewVisible) View.VISIBLE else View.GONE } - }) + } - viewModel.hideKeyboard.observe(viewLifecycleOwner, { + viewModel.hideKeyboard.observe(viewLifecycleOwner) { ActivityUtils.hideKeyboardForced(binding?.searchInputLayout) - }) + } - viewModel.expandBottomSheet.observe(viewLifecycleOwner, { + viewModel.expandBottomSheet.observe(viewLifecycleOwner) { expandBottomSheet() - }) + } - viewModel.clearSearchField.observe(viewLifecycleOwner, { + viewModel.clearSearchField.observe(viewLifecycleOwner) { searchInputLayout.editText?.text?.clear() searchInputLayout.editText?.clearFocus() - }) + } - viewModel.selectedLocale.observe(viewLifecycleOwner, { + viewModel.selectedLocale.observe(viewLifecycleOwner) { callback?.onLocaleSelected(it) - }) + } - viewModel.dismissBottomSheet.observe(viewLifecycleOwner, { + viewModel.dismissBottomSheet.observe(viewLifecycleOwner) { dismiss() - }) + } viewModel.start() } From f375f751ecd0148f4b8c2c9a1d658d31a84ebf6e Mon Sep 17 00:00:00 2001 From: Siobhan Date: Thu, 28 Apr 2022 18:37:54 +0100 Subject: [PATCH 27/28] Add missing binding notation to method This notation was mistakenly removed following previous updates from trunks. --- .../java/org/wordpress/android/modules/ViewModelModule.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java index 129bc8079492..7734a0e19542 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java @@ -588,6 +588,8 @@ abstract class ViewModelModule { @ViewModelKey(LocalePickerViewModel.class) abstract ViewModel localePickerViewModel(LocalePickerViewModel viewModel); + @Binds + @IntoMap @ViewModelKey(CategoryDetailViewModel.class) abstract ViewModel categoryDetailViewModel(CategoryDetailViewModel viewModel); From ea45494a6e3ef7103f455bf6e0bbf429c559d0c3 Mon Sep 17 00:00:00 2001 From: Siobhan Date: Thu, 28 Apr 2022 18:43:28 +0100 Subject: [PATCH 28/28] Update RELEASE-NOTES --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index cecf17c57d0c..405b6e27be85 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ 19.8 ----- * [*] App Settings: updated look and functionality of Locale Picker [https://github.com/wordpress-mobile/WordPress-Android/pull/16331] +* [*] Fix issue that prevented Jetpack customers with standalone VideoPress subs uploading >5min videos [https://github.com/wordpress-mobile/WordPress-Android/pull/16322] * [*] On self-hosted sites, allow previewing unsaved changes to draft posts [https://github.com/wordpress-mobile/WordPress-Android/pull/16296] * [*] Editor: Fix issue that caused previews on atomic sites to sometimes be out-of-date. [https://github.com/wordpress-mobile/WordPress-Android/pull/16332] * [*] Fixes Media Picker video selection Accessibility issue [https://github.com/wordpress-mobile/WordPress-Android/pull/16426]