Feature/#220 language menu (#7070)
* For #220 - Added advanced header + locale settings item in the settings fragment * For #220 - Added locale selection page with lib state + handling of locale changes * For #220 - Removed registering for locale changes in the manifest, allow system to restart activity in that scenario * For #220 - Added unit tests for locale settings page * For #220: fixed an outdated unit test ga-a Co-authored-by: Severin Rudie <Baron-Severin@users.noreply.github.com>master
parent
9cbc3f7a4a
commit
ea2411a88b
|
@ -429,6 +429,7 @@ dependencies {
|
|||
implementation Deps.mozilla_support_ktx
|
||||
implementation Deps.mozilla_support_rustlog
|
||||
implementation Deps.mozilla_support_utils
|
||||
implementation Deps.mozilla_support_locale
|
||||
|
||||
// We only care about support-migration in builds that will be overwriting Fennec.
|
||||
fennecProductionImplementation Deps.mozilla_support_migration
|
||||
|
@ -602,6 +603,23 @@ task printVariants {
|
|||
}
|
||||
}
|
||||
|
||||
task buildTranslationArray {
|
||||
def foundLocales = new StringBuilder()
|
||||
foundLocales.append("new String[]{")
|
||||
|
||||
fileTree("src/main/res").visit { FileVisitDetails details ->
|
||||
if(details.file.path.endsWith("/strings.xml")){
|
||||
def languageCode = details.file.parent.tokenize('/').last().replaceAll('values-','').replaceAll('-r','-')
|
||||
languageCode = (languageCode == "values") ? "en-US" : languageCode
|
||||
foundLocales.append("\"").append(languageCode).append("\"").append(",")
|
||||
}
|
||||
}
|
||||
|
||||
foundLocales.append("}")
|
||||
def foundLocalesString = foundLocales.toString().replaceAll(',}','}')
|
||||
android.defaultConfig.buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", foundLocalesString
|
||||
}
|
||||
|
||||
def glean_android_components_tag = (
|
||||
Versions.mozilla_android_components.endsWith('-SNAPSHOT') ?
|
||||
'master' :
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".HomeActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
|
||||
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|layoutDirection|smallestScreenSize|screenLayout"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
|
@ -81,7 +81,7 @@
|
|||
<activity
|
||||
android:name=".customtabs.ExternalAppBrowserActivity"
|
||||
android:autoRemoveFromRecents="false"
|
||||
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
|
||||
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|layoutDirection|smallestScreenSize|screenLayout"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:persistableMode="persistNever"
|
||||
|
@ -151,7 +151,7 @@
|
|||
<activity
|
||||
android:name=".settings.account.AuthCustomTabActivity"
|
||||
android:autoRemoveFromRecents="false"
|
||||
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|locale|layoutDirection|smallestScreenSize|screenLayout"
|
||||
android:configChanges="keyboard|keyboardHidden|mcc|mnc|orientation|screenSize|layoutDirection|smallestScreenSize|screenLayout"
|
||||
android:exported="false"
|
||||
android:taskAffinity=""
|
||||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden" />
|
||||
|
|
|
@ -5,19 +5,18 @@
|
|||
package org.mozilla.fenix
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import android.os.StrictMode
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.getSystemService
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import mozilla.appservices.Megazord
|
||||
import mozilla.components.concept.push.PushProcessor
|
||||
import mozilla.components.service.experiments.Experiments
|
||||
|
@ -29,6 +28,7 @@ import mozilla.components.support.base.log.logger.Logger
|
|||
import mozilla.components.support.base.log.sink.AndroidLogSink
|
||||
import mozilla.components.support.ktx.android.content.isMainProcess
|
||||
import mozilla.components.support.ktx.android.content.runOnlyInMainProcess
|
||||
import mozilla.components.support.locale.LocaleAwareApplication
|
||||
import mozilla.components.support.rusthttp.RustHttpConfig
|
||||
import mozilla.components.support.rustlog.RustLog
|
||||
import org.mozilla.fenix.GleanMetrics.ExperimentsMetrics
|
||||
|
@ -40,7 +40,7 @@ import java.io.File
|
|||
|
||||
@SuppressLint("Registered")
|
||||
@Suppress("TooManyFunctions")
|
||||
open class FenixApplication : Application() {
|
||||
open class FenixApplication : LocaleAwareApplication() {
|
||||
lateinit var fretboard: Fretboard
|
||||
lateinit var experimentLoader: Deferred<Boolean>
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import androidx.annotation.CallSuper
|
|||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.VisibleForTesting.PROTECTED
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavDestination
|
||||
|
@ -30,6 +29,7 @@ import mozilla.components.service.fxa.sync.SyncReason
|
|||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import mozilla.components.support.ktx.kotlin.isUrl
|
||||
import mozilla.components.support.ktx.kotlin.toNormalizedUrl
|
||||
import mozilla.components.support.locale.LocaleAwareAppCompatActivity
|
||||
import mozilla.components.support.utils.SafeIntent
|
||||
import mozilla.components.support.utils.toSafeIntent
|
||||
import org.mozilla.fenix.browser.UriOpenedObserver
|
||||
|
@ -62,7 +62,7 @@ import org.mozilla.fenix.theme.DefaultThemeManager
|
|||
import org.mozilla.fenix.theme.ThemeManager
|
||||
|
||||
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
||||
open class HomeActivity : AppCompatActivity() {
|
||||
open class HomeActivity : LocaleAwareAppCompatActivity() {
|
||||
|
||||
lateinit var themeManager: ThemeManager
|
||||
lateinit var browsingModeManager: BrowsingModeManager
|
||||
|
@ -239,8 +239,9 @@ open class HomeActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
fun openToBrowser(from: BrowserDirection, customTabSessionId: String? = null) {
|
||||
if (sessionObserver == null)
|
||||
if (sessionObserver == null) {
|
||||
sessionObserver = UriOpenedObserver(this)
|
||||
}
|
||||
|
||||
if (navHost.navController.alreadyOnDestination(R.id.browserFragment)) return
|
||||
@IdRes val fragmentId = if (from.fragmentId != 0) from.fragmentId else null
|
||||
|
|
|
@ -11,9 +11,9 @@ import mozilla.components.lib.state.State
|
|||
import mozilla.components.lib.state.Store
|
||||
|
||||
class BookmarkFragmentStore(
|
||||
initalState: BookmarkFragmentState
|
||||
initialState: BookmarkFragmentState
|
||||
) : Store<BookmarkFragmentState, BookmarkFragmentAction>(
|
||||
initalState, ::bookmarkFragmentStateReducer
|
||||
initialState, ::bookmarkFragmentStateReducer
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,7 +62,6 @@ import org.mozilla.fenix.ext.settings
|
|||
import org.mozilla.fenix.ext.showToolbar
|
||||
import org.mozilla.fenix.settings.account.AccountAuthErrorPreference
|
||||
import org.mozilla.fenix.settings.account.AccountPreference
|
||||
import org.mozilla.fenix.utils.ItsNotBrokenSnack
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
@ -210,9 +209,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
SettingsFragmentDirections.actionSettingsFragmentToAccessibilityFragment()
|
||||
}
|
||||
resources.getString(pref_key_language) -> {
|
||||
// TODO #220
|
||||
ItsNotBrokenSnack(requireContext()).showSnackbar(issueNumber = "220")
|
||||
null
|
||||
SettingsFragmentDirections.actionSettingsFragmentToLocaleSettingsFragment()
|
||||
}
|
||||
resources.getString(pref_key_make_default_browser) -> {
|
||||
SettingsFragmentDirections.actionSettingsFragmentToDefaultBrowserSettingsFragment()
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import java.util.Locale
|
||||
|
||||
interface LocaleSettingsController {
|
||||
fun handleLocaleSelected(locale: Locale)
|
||||
fun handleSearchQueryTyped(query: String)
|
||||
fun handleDefaultLocaleSelected()
|
||||
}
|
||||
|
||||
class DefaultLocaleSettingsController(
|
||||
private val context: Context,
|
||||
private val localeSettingsStore: LocaleSettingsStore
|
||||
) : LocaleSettingsController {
|
||||
|
||||
override fun handleLocaleSelected(locale: Locale) {
|
||||
if (localeSettingsStore.state.selectedLocale == locale) {
|
||||
return
|
||||
}
|
||||
localeSettingsStore.dispatch(LocaleSettingsAction.Select(locale))
|
||||
LocaleManager.setNewLocale(context, locale.toLanguageTag())
|
||||
(context as Activity).recreate()
|
||||
}
|
||||
|
||||
override fun handleDefaultLocaleSelected() {
|
||||
localeSettingsStore.dispatch(LocaleSettingsAction.Select(localeSettingsStore.state.localeList[0]))
|
||||
LocaleManager.resetToSystemDefault(context)
|
||||
(context as Activity).recreate()
|
||||
}
|
||||
|
||||
override fun handleSearchQueryTyped(query: String) {
|
||||
localeSettingsStore.dispatch(LocaleSettingsAction.Search(query))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.locale_settings_item.view.locale_selected_icon
|
||||
import kotlinx.android.synthetic.main.locale_settings_item.view.locale_subtitle_text
|
||||
import kotlinx.android.synthetic.main.locale_settings_item.view.locale_title_text
|
||||
import org.mozilla.fenix.R
|
||||
import java.util.Locale
|
||||
|
||||
class LocaleAdapter(private val interactor: LocaleSettingsViewInteractor) :
|
||||
RecyclerView.Adapter<BaseLocaleViewHolder>() {
|
||||
|
||||
private var localeList: List<Locale> = listOf()
|
||||
private var selectedLocale: Locale = Locale.getDefault()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseLocaleViewHolder {
|
||||
val view =
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.locale_settings_item, parent, false)
|
||||
|
||||
return when (viewType) {
|
||||
ItemType.DEFAULT.ordinal -> SystemLocaleViewHolder(
|
||||
view,
|
||||
interactor,
|
||||
selectedLocale
|
||||
)
|
||||
ItemType.LOCALE.ordinal -> LocaleViewHolder(
|
||||
view,
|
||||
interactor,
|
||||
selectedLocale
|
||||
)
|
||||
else -> throw IllegalStateException("ViewType $viewType does not match to a ViewHolder")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return localeList.size
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: BaseLocaleViewHolder, position: Int) {
|
||||
holder.bind(localeList[position])
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when (position) {
|
||||
0 -> ItemType.DEFAULT
|
||||
else -> ItemType.LOCALE
|
||||
}.ordinal
|
||||
}
|
||||
|
||||
fun updateData(localeList: List<Locale>, selectedLocale: Locale) {
|
||||
val diffUtil = DiffUtil.calculateDiff(
|
||||
LocaleDiffUtil(
|
||||
this.localeList,
|
||||
localeList,
|
||||
this.selectedLocale,
|
||||
selectedLocale
|
||||
)
|
||||
)
|
||||
this.localeList = localeList
|
||||
this.selectedLocale = selectedLocale
|
||||
|
||||
diffUtil.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
inner class LocaleDiffUtil(
|
||||
private val old: List<Locale>,
|
||||
private val new: List<Locale>,
|
||||
private val oldSelectedLocale: Locale,
|
||||
private val newSelectedLocale: Locale
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
val selectionChanged =
|
||||
old[oldItemPosition] == oldSelectedLocale && oldSelectedLocale != newSelectedLocale
|
||||
return old[oldItemPosition] == new[newItemPosition] && !selectionChanged
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
old[oldItemPosition].toLanguageTag() == new[newItemPosition].toLanguageTag()
|
||||
|
||||
override fun getOldListSize(): Int = old.size
|
||||
override fun getNewListSize(): Int = new.size
|
||||
}
|
||||
|
||||
enum class ItemType {
|
||||
DEFAULT, LOCALE;
|
||||
}
|
||||
}
|
||||
|
||||
class LocaleViewHolder(
|
||||
view: View,
|
||||
private val interactor: LocaleSettingsViewInteractor,
|
||||
private val selectedLocale: Locale
|
||||
) : BaseLocaleViewHolder(view) {
|
||||
private val icon = view.locale_selected_icon
|
||||
private val title = view.locale_title_text
|
||||
private val subtitle = view.locale_subtitle_text
|
||||
|
||||
override fun bind(locale: Locale) {
|
||||
// capitalisation is done using the rules of the appropriate locale (endonym and exonym)
|
||||
title.text = locale.getDisplayName(locale).capitalize(locale)
|
||||
subtitle.text = locale.displayName.capitalize(Locale.getDefault())
|
||||
icon.isVisible = locale === selectedLocale
|
||||
|
||||
itemView.setOnClickListener {
|
||||
interactor.onLocaleSelected(locale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SystemLocaleViewHolder(
|
||||
view: View,
|
||||
private val interactor: LocaleSettingsViewInteractor,
|
||||
private val selectedLocale: Locale
|
||||
) : BaseLocaleViewHolder(view) {
|
||||
private val icon = view.locale_selected_icon
|
||||
private val title = view.locale_title_text
|
||||
private val subtitle = view.locale_subtitle_text
|
||||
|
||||
override fun bind(locale: Locale) {
|
||||
title.text = itemView.context.getString(R.string.default_locale_text)
|
||||
subtitle.visibility = View.GONE
|
||||
icon.isVisible = locale === selectedLocale
|
||||
|
||||
itemView.setOnClickListener {
|
||||
interactor.onDefaultLocaleSelected()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseLocaleViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
abstract fun bind(locale: Locale)
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to Kotlin's capitalize with locale parameter, but that method is currently experimental
|
||||
*/
|
||||
private fun String.capitalize(locale: Locale): String {
|
||||
return substring(0, 1).toUpperCase(locale) + substring(1)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import mozilla.components.support.locale.toLocale
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Returns a list of currently supported locales, with the system default set as the first one
|
||||
*/
|
||||
fun LocaleManager.getSupportedLocales(): List<Locale> {
|
||||
val resultLocaleList: MutableList<Locale> = ArrayList()
|
||||
resultLocaleList.add(0, getSystemDefault() ?: Locale.getDefault())
|
||||
|
||||
resultLocaleList.addAll(BuildConfig.SUPPORTED_LOCALE_ARRAY
|
||||
.toList()
|
||||
.map {
|
||||
it.toLocale()
|
||||
}.sortedWith(compareBy(
|
||||
{ it.displayLanguage },
|
||||
{ it.displayCountry }
|
||||
)))
|
||||
return resultLocaleList
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locale that corresponds to the language stored locally by us. If no suitable one is found,
|
||||
* return default.
|
||||
*/
|
||||
fun LocaleManager.getSelectedLocale(
|
||||
context: Context,
|
||||
localeList: List<Locale> = getSupportedLocales()
|
||||
): Locale {
|
||||
val selectedLocale = getCurrentLocale(context)?.toLanguageTag()
|
||||
val defaultLocale = getSystemDefault() ?: Locale.getDefault()
|
||||
|
||||
return if (selectedLocale == null) {
|
||||
defaultLocale
|
||||
} else {
|
||||
val supportedMatch = localeList
|
||||
.firstOrNull { it.toLanguageTag() == selectedLocale }
|
||||
supportedMatch ?: defaultLocale
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import kotlinx.android.synthetic.main.fragment_locale_settings.view.locale_container
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import mozilla.components.support.ktx.android.view.hideKeyboard
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.StoreProvider
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
|
||||
class LocaleSettingsFragment : Fragment() {
|
||||
|
||||
private lateinit var store: LocaleSettingsStore
|
||||
private lateinit var interactor: LocaleSettingsInteractor
|
||||
private lateinit var localeView: LocaleSettingsView
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_locale_settings, container, false)
|
||||
|
||||
store = getStore()
|
||||
interactor = LocaleSettingsInteractor(
|
||||
controller = DefaultLocaleSettingsController(
|
||||
context = requireContext(),
|
||||
localeSettingsStore = store
|
||||
)
|
||||
)
|
||||
localeView = LocaleSettingsView(view.locale_container, interactor)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
localeView.onResume()
|
||||
showToolbar(getString(R.string.preferences_language))
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
view?.hideKeyboard()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
consumeFrom(store) {
|
||||
localeView.update(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStore(): LocaleSettingsStore {
|
||||
val supportedLocales = LocaleManager.getSupportedLocales()
|
||||
val selectedLocale = LocaleManager.getSelectedLocale(requireContext())
|
||||
|
||||
return StoreProvider.get(this) {
|
||||
LocaleSettingsStore(
|
||||
LocaleSettingsState(
|
||||
supportedLocales,
|
||||
supportedLocales,
|
||||
selectedLocale
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
class LocaleSettingsInteractor(private val controller: LocaleSettingsController) :
|
||||
LocaleSettingsViewInteractor {
|
||||
|
||||
override fun onLocaleSelected(locale: Locale) {
|
||||
controller.handleLocaleSelected(locale)
|
||||
}
|
||||
|
||||
override fun onDefaultLocaleSelected() {
|
||||
controller.handleDefaultLocaleSelected()
|
||||
}
|
||||
|
||||
override fun onSearchQueryTyped(query: String) {
|
||||
controller.handleSearchQueryTyped(query)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import mozilla.components.lib.state.Action
|
||||
import mozilla.components.lib.state.State
|
||||
import mozilla.components.lib.state.Store
|
||||
import java.util.Locale
|
||||
|
||||
class LocaleSettingsStore(
|
||||
initialState: LocaleSettingsState
|
||||
) : Store<LocaleSettingsState, LocaleSettingsAction>(
|
||||
initialState, ::localeSettingsStateReducer
|
||||
)
|
||||
|
||||
/**
|
||||
* The state of the language selection page
|
||||
* @property localeList The full list of locales available
|
||||
* @property searchedLocaleList The list of locales starting with a search query
|
||||
* @property selectedLocale The current selected locale
|
||||
*/
|
||||
data class LocaleSettingsState(
|
||||
val localeList: List<Locale>,
|
||||
val searchedLocaleList: List<Locale>,
|
||||
val selectedLocale: Locale
|
||||
) : State
|
||||
|
||||
/**
|
||||
* Actions to dispatch through the `LocaleSettingsStore` to modify `LocaleSettingsState` through the reducer.
|
||||
*/
|
||||
sealed class LocaleSettingsAction : Action {
|
||||
data class Select(val selectedItem: Locale) : LocaleSettingsAction()
|
||||
data class Search(val query: String) : LocaleSettingsAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the locale state from the current state and an action performed on it.
|
||||
* @param state the current locale state
|
||||
* @param action the action to perform
|
||||
* @return the new locale state
|
||||
*/
|
||||
private fun localeSettingsStateReducer(
|
||||
state: LocaleSettingsState,
|
||||
action: LocaleSettingsAction
|
||||
): LocaleSettingsState {
|
||||
return when (action) {
|
||||
is LocaleSettingsAction.Select -> {
|
||||
state.copy(selectedLocale = action.selectedItem)
|
||||
}
|
||||
is LocaleSettingsAction.Search -> {
|
||||
val searchedItems = state.localeList.filter {
|
||||
it.getDisplayLanguage(it).startsWith(action.query, ignoreCase = true) ||
|
||||
it.displayLanguage.startsWith(action.query, ignoreCase = true) ||
|
||||
it === state.localeList[0]
|
||||
}
|
||||
state.copy(searchedLocaleList = searchedItems)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.SearchView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.component_locale_settings.view.locale_list
|
||||
import kotlinx.android.synthetic.main.component_locale_settings.view.toolbar_container
|
||||
import org.mozilla.fenix.R
|
||||
import java.util.Locale
|
||||
|
||||
interface LocaleSettingsViewInteractor {
|
||||
|
||||
fun onLocaleSelected(locale: Locale)
|
||||
|
||||
fun onDefaultLocaleSelected()
|
||||
|
||||
fun onSearchQueryTyped(query: String)
|
||||
}
|
||||
|
||||
class LocaleSettingsView(
|
||||
container: ViewGroup,
|
||||
val interactor: LocaleSettingsViewInteractor
|
||||
) {
|
||||
|
||||
val view: View = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_locale_settings, container, true)
|
||||
|
||||
private val localeAdapter: LocaleAdapter
|
||||
|
||||
init {
|
||||
view.locale_list.apply {
|
||||
localeAdapter = LocaleAdapter(interactor)
|
||||
adapter = localeAdapter
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
val searchView: SearchView = view.toolbar_container
|
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
interactor.onSearchQueryTyped(newText)
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun update(state: LocaleSettingsState) {
|
||||
localeAdapter.updateData(state.searchedLocaleList, state.selectedLocale)
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
view.requestFocus()
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import android.speech.RecognizerIntent
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.IntentReceiverActivity
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
|
@ -57,6 +58,7 @@ class VoiceSearchActivity : AppCompatActivity() {
|
|||
private fun displaySpeechRecognizer() {
|
||||
val intentSpeech = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
|
||||
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
|
||||
putExtra(RecognizerIntent.EXTRA_LANGUAGE, LocaleManager.getCurrentLocale(this@VoiceSearchActivity))
|
||||
}
|
||||
metrics.track(Event.SearchWidgetVoiceSearchPressed)
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?foundation" />
|
||||
</shape>
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true">
|
||||
|
||||
<SearchView
|
||||
android:id="@+id/toolbar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/locale_search_bar_margin"
|
||||
android:background="@drawable/search_url_background"
|
||||
android:closeIcon="@drawable/ic_close"
|
||||
android:iconifiedByDefault="false"
|
||||
android:paddingStart="@dimen/locale_search_bar_padding_start"
|
||||
android:paddingEnd="0dp"
|
||||
android:queryBackground="@android:color/transparent"
|
||||
android:queryHint="@string/locale_search_hint"
|
||||
android:searchIcon="@drawable/ic_search"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/locale_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="@dimen/locale_list_margin"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar_container" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/locale_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/locale_selected_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/locale_item_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/locale_item_vertical_margin"
|
||||
android:contentDescription="@string/a11y_selected_locale_content_description"
|
||||
android:src="@drawable/mozac_ic_check"
|
||||
android:tint="?primaryText"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/locale_title_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/locale_item_text_margin_start"
|
||||
android:layout_marginTop="@dimen/locale_item_vertical_margin"
|
||||
android:textColor="?primaryText"
|
||||
app:layout_goneMarginStart="@dimen/locale_item_text_margin_gone_start"
|
||||
android:textSize="@dimen/locale_item_title_size"
|
||||
app:layout_constraintBottom_toTopOf="@+id/locale_subtitle_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/locale_selected_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_goneMarginBottom="@dimen/locale_item_vertical_margin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/locale_subtitle_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/locale_item_text_margin_start"
|
||||
android:layout_marginBottom="@dimen/locale_item_vertical_margin"
|
||||
android:textColor="?secondaryText"
|
||||
android:textSize="@dimen/locale_item_subtitle_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_goneMarginStart="@dimen/locale_item_text_margin_gone_start"
|
||||
app:layout_constraintStart_toEndOf="@+id/locale_selected_icon"
|
||||
app:layout_constraintTop_toBottomOf="@+id/locale_title_text"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -397,6 +397,9 @@
|
|||
<action
|
||||
android:id="@+id/action_settingsFragment_to_toolbarSettingsFragment"
|
||||
app:destination="@id/toolbarSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_localeSettingsFragment"
|
||||
app:destination="@id/localeSettingsFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/dataChoicesFragment"
|
||||
|
@ -687,4 +690,8 @@
|
|||
android:id="@+id/toolbarSettingsFragment"
|
||||
android:name="org.mozilla.fenix.settings.ToolbarSettingsFragment"
|
||||
android:label="ToolbarSettingsFragment" />
|
||||
<fragment
|
||||
android:id="@+id/localeSettingsFragment"
|
||||
android:name="org.mozilla.fenix.settings.advanced.LocaleSettingsFragment"
|
||||
android:label="LanguageSettingsFragment" />
|
||||
</navigation>
|
||||
|
|
|
@ -93,4 +93,15 @@
|
|||
<dimen name="about_items_text_size">16sp</dimen>
|
||||
<dimen name="about_header_text_line_spacing_extra">4dp</dimen>
|
||||
|
||||
<!-- Locale Settings Fragment -->
|
||||
<dimen name="locale_search_bar_margin">8dp</dimen>
|
||||
<dimen name="locale_search_bar_padding_start">-12dp</dimen>
|
||||
<dimen name="locale_list_margin">16dp</dimen>
|
||||
<dimen name="locale_item_vertical_margin">8dp</dimen>
|
||||
<dimen name="locale_item_text_margin_start">16dp</dimen>
|
||||
<dimen name="locale_item_text_margin_gone_start">40dp</dimen>
|
||||
<dimen name="locale_item_title_size">16sp</dimen>
|
||||
<dimen name="locale_item_subtitle_size">12sp</dimen>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -108,6 +108,16 @@
|
|||
<!-- Browser menu button to configure reader mode appearance e.g. the used font type and size -->
|
||||
<string name="browser_menu_read_appearance">Appearance</string>
|
||||
|
||||
<!-- Locale Settings Fragment -->
|
||||
<!-- Content description for tick mark on selected language -->
|
||||
<string name="a11y_selected_locale_content_description">Selected language</string>
|
||||
<!-- Content description for search icon -->
|
||||
<string name="a11y_search_icon_content_description">Search</string>
|
||||
<!-- Text for default locale item -->
|
||||
<string name="default_locale_text">Follow device language</string>
|
||||
<!-- Placeholder text shown in the search bar before a user enters text -->
|
||||
<string name="locale_search_hint">Search language</string>
|
||||
|
||||
<!-- Search Fragment -->
|
||||
<!-- Button in the search view that lets a user search by scanning a QR code -->
|
||||
<string name="search_scan_button">Scan</string>
|
||||
|
|
|
@ -101,6 +101,15 @@
|
|||
app:isPreferenceVisible="@bool/IS_DEBUG" />
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/preferences_category_advanced"
|
||||
app:iconSpaceReserved="false">
|
||||
<androidx.preference.Preference
|
||||
android:icon="@drawable/ic_language"
|
||||
android:key="@string/pref_key_language"
|
||||
android:title="@string/preferences_language" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/developer_tools_category"
|
||||
app:iconSpaceReserved="false">
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import android.content.Context
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.TestApplication
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import java.util.Locale
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = TestApplication::class)
|
||||
class LocaleManagerExtensionTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic("org.mozilla.fenix.settings.advanced.LocaleManagerExtensionKt")
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en-rUS")
|
||||
fun `build supported locale list`() {
|
||||
val list = LocaleManager.getSupportedLocales()
|
||||
|
||||
// Expect all supported locales + 'follow default option'
|
||||
val expectedSize = BuildConfig.SUPPORTED_LOCALE_ARRAY.size + 1
|
||||
|
||||
assertEquals(expectedSize, list.size)
|
||||
assertTrue(list.isNotEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en-rUS")
|
||||
fun `match current stored locale string with a Locale from our list`() {
|
||||
val context: Context = mockk()
|
||||
mockkObject(LocaleManager)
|
||||
val otherLocale = Locale("fr")
|
||||
val selectedLocale = Locale("en", "UK")
|
||||
val localeList = ArrayList<Locale>()
|
||||
localeList.add(otherLocale)
|
||||
localeList.add(selectedLocale)
|
||||
|
||||
every { LocaleManager.getCurrentLocale(context) } returns selectedLocale
|
||||
|
||||
assertEquals(selectedLocale, LocaleManager.getSelectedLocale(context, localeList))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en-rUS")
|
||||
fun `match null stored locale with the default Locale from our list`() {
|
||||
val context: Context = mockk()
|
||||
mockkObject(LocaleManager)
|
||||
val firstLocale = Locale("fr")
|
||||
val secondLocale = Locale("en", "UK")
|
||||
val localeList = ArrayList<Locale>()
|
||||
localeList.add(firstLocale)
|
||||
localeList.add(secondLocale)
|
||||
|
||||
every { LocaleManager.getCurrentLocale(context) } returns null
|
||||
|
||||
assertEquals("en-US", LocaleManager.getSelectedLocale(context, localeList).toLanguageTag())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.verify
|
||||
import mozilla.components.support.locale.LocaleManager
|
||||
import mozilla.components.support.test.mock
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Locale
|
||||
|
||||
class LocaleSettingsControllerTest {
|
||||
|
||||
private val context: Context = mockk<Activity>(relaxed = true)
|
||||
private val localeSettingsStore: LocaleSettingsStore = mockk(relaxed = true)
|
||||
|
||||
private lateinit var controller: LocaleSettingsController
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
controller = DefaultLocaleSettingsController(context, localeSettingsStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `set a new locale from the list`() {
|
||||
val selectedLocale = Locale("en", "UK")
|
||||
val otherLocale: Locale = mock()
|
||||
every { localeSettingsStore.state } returns LocaleSettingsState(
|
||||
mockk(),
|
||||
mockk(),
|
||||
otherLocale
|
||||
)
|
||||
mockkObject(LocaleManager)
|
||||
every {
|
||||
LocaleManager.setNewLocale(
|
||||
context,
|
||||
selectedLocale.toLanguageTag()
|
||||
)
|
||||
} returns context
|
||||
|
||||
controller.handleLocaleSelected(selectedLocale)
|
||||
|
||||
verify { localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale)) }
|
||||
verify { LocaleManager.setNewLocale(context, selectedLocale.toLanguageTag()) }
|
||||
verify { (context as Activity).recreate() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `set the default locale as the new locale`() {
|
||||
val selectedLocale = Locale("en", "UK")
|
||||
val localeList = ArrayList<Locale>()
|
||||
localeList.add(selectedLocale)
|
||||
every { localeSettingsStore.state } returns LocaleSettingsState(
|
||||
localeList,
|
||||
mockk(),
|
||||
mockk()
|
||||
)
|
||||
mockkObject(LocaleManager)
|
||||
every { LocaleManager.resetToSystemDefault(context) } just Runs
|
||||
|
||||
controller.handleDefaultLocaleSelected()
|
||||
|
||||
verify { localeSettingsStore.dispatch(LocaleSettingsAction.Select(selectedLocale)) }
|
||||
verify { LocaleManager.resetToSystemDefault(context) }
|
||||
verify { (context as Activity).recreate() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `handle search query typed`() {
|
||||
val query = "Eng"
|
||||
|
||||
controller.handleSearchQueryTyped(query)
|
||||
|
||||
verify { localeSettingsStore.dispatch(LocaleSettingsAction.Search(query)) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Locale
|
||||
|
||||
class LocaleSettingsInteractorTest {
|
||||
|
||||
private lateinit var interactor: LocaleSettingsInteractor
|
||||
private val controller: LocaleSettingsController = mockk(relaxed = true)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
interactor = LocaleSettingsInteractor(controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `locale was selected from list`() {
|
||||
val locale: Locale = mockk()
|
||||
|
||||
interactor.onLocaleSelected(locale)
|
||||
|
||||
verify { controller.handleLocaleSelected(locale) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `default locale was selected from list`() {
|
||||
interactor.onDefaultLocaleSelected()
|
||||
|
||||
verify { controller.handleDefaultLocaleSelected() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `search query was typed`() {
|
||||
val query = "Eng"
|
||||
|
||||
interactor.onSearchQueryTyped(query)
|
||||
|
||||
verify { controller.handleSearchQueryTyped(query) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.settings.advanced
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Locale
|
||||
|
||||
class LocaleSettingsStoreTest {
|
||||
|
||||
private lateinit var localeSettingsStore: LocaleSettingsStore
|
||||
private val selectedLocale = Locale("en", "UK")
|
||||
private val otherLocale = Locale("fr")
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val localeList = ArrayList<Locale>()
|
||||
localeList.add(Locale("fr")) // default
|
||||
localeList.add(otherLocale)
|
||||
localeList.add(selectedLocale)
|
||||
|
||||
localeSettingsStore =
|
||||
LocaleSettingsStore(LocaleSettingsState(localeList, localeList, selectedLocale))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `change selected locale`() = runBlocking {
|
||||
localeSettingsStore.dispatch(LocaleSettingsAction.Select(otherLocale)).join()
|
||||
|
||||
assertEquals(otherLocale, localeSettingsStore.state.selectedLocale)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `change selected list by search query`() = runBlocking {
|
||||
localeSettingsStore.dispatch(LocaleSettingsAction.Search("Eng")).join()
|
||||
|
||||
assertEquals(2, (localeSettingsStore.state.searchedLocaleList as ArrayList).size)
|
||||
assertEquals(selectedLocale, localeSettingsStore.state.searchedLocaleList[1])
|
||||
}
|
||||
}
|
|
@ -147,6 +147,7 @@ object Deps {
|
|||
const val mozilla_support_utils = "org.mozilla.components:support-utils:${Versions.mozilla_android_components}"
|
||||
const val mozilla_support_test = "org.mozilla.components:support-test:${Versions.mozilla_android_components}"
|
||||
const val mozilla_support_migration = "org.mozilla.components:support-migration:${Versions.mozilla_android_components}"
|
||||
const val mozilla_support_locale = "org.mozilla.components:support-locale:${Versions.mozilla_android_components}"
|
||||
|
||||
const val sentry = "io.sentry:sentry-android:${Versions.sentry}"
|
||||
const val leakcanary = "com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}"
|
||||
|
|
Loading…
Reference in New Issue