diff --git a/CHANGES.md b/CHANGES.md index 37cdfba1f5..0ff37419cd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.21.0 (2020-XX-XX) =================================================== Features ✨: - - + - Switch language support (#41) Improvements 🙌: - Better connectivity lost indicator when airplane mode is on diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 01709efcac..26a5142961 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -61,8 +61,6 @@ import im.vector.riotx.features.login.LoginSplashFragment import im.vector.riotx.features.login.LoginWaitForEmailFragment import im.vector.riotx.features.login.LoginWebFragment import im.vector.riotx.features.login.terms.LoginTermsFragment -import im.vector.riotx.features.userdirectory.KnownUsersFragment -import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.qrcode.QrCodeScannerFragment import im.vector.riotx.features.reactions.EmojiChooserFragment import im.vector.riotx.features.reactions.EmojiSearchResultFragment @@ -92,9 +90,12 @@ import im.vector.riotx.features.settings.devtools.IncomingKeyRequestListFragment import im.vector.riotx.features.settings.devtools.KeyRequestsFragment import im.vector.riotx.features.settings.devtools.OutgoingKeyRequestListFragment import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment +import im.vector.riotx.features.settings.locale.LocalePickerFragment import im.vector.riotx.features.settings.push.PushGatewaysFragment import im.vector.riotx.features.share.IncomingShareFragment import im.vector.riotx.features.signout.soft.SoftLogoutFragment +import im.vector.riotx.features.userdirectory.KnownUsersFragment +import im.vector.riotx.features.userdirectory.UserDirectoryFragment @Module interface FragmentModule { @@ -109,6 +110,11 @@ interface FragmentModule { @FragmentKey(RoomListFragment::class) fun bindRoomListFragment(fragment: RoomListFragment): Fragment + @Binds + @IntoMap + @FragmentKey(LocalePickerFragment::class) + fun bindLocalePickerFragment(fragment: LocalePickerFragment): Fragment + @Binds @IntoMap @FragmentKey(GroupListFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt index f9f5d3b3d2..b74f143e17 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt @@ -16,6 +16,7 @@ package im.vector.riotx.core.extensions +import android.app.Activity import android.os.Parcelable import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction @@ -59,3 +60,8 @@ fun VectorBaseActivity.addFragmentToBackstack(frameId: Int, fun VectorBaseActivity.hideKeyboard() { currentFocus?.hideKeyboard() } + +fun Activity.restart() { + startActivity(intent) + finish() +} diff --git a/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt b/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt index a4b7ca263d..8ea17fecf6 100644 --- a/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt +++ b/vector/src/main/java/im/vector/riotx/features/configuration/VectorConfiguration.kt @@ -30,7 +30,6 @@ import javax.inject.Inject /** * Handle locale configuration change, such as theme, font size and locale chosen by the user */ - class VectorConfiguration @Inject constructor(private val context: Context) { // TODO Import mLanguageReceiver From Riot? @@ -98,7 +97,6 @@ class VectorConfiguration @Inject constructor(private val context: Context) { * * @param locale */ - // TODO Call from LanguagePickerActivity fun updateApplicationLocale(locale: Locale) { updateApplicationSettings(locale, FontScale.getFontScalePrefValue(context), ThemeUtils.getApplicationTheme(context)) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt index e1a89ab3c4..4d78a30718 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorLocale.kt @@ -19,9 +19,8 @@ package im.vector.riotx.features.settings import android.content.Context import android.content.res.Configuration import android.os.Build -import androidx.preference.PreferenceManager import androidx.core.content.edit -import im.vector.riotx.BuildConfig +import androidx.preference.PreferenceManager import im.vector.riotx.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -75,7 +74,7 @@ object VectorLocale { saveApplicationLocale(context, applicationLocale) } - // init the known locales in background, using kotlin coroutines + // init the known locales in background GlobalScope.launch(Dispatchers.IO) { initApplicationLocales(context) } @@ -144,6 +143,7 @@ object VectorLocale { } else { val resources = context.resources val conf = resources.configuration + @Suppress("DEPRECATION") val savedLocale = conf.locale @Suppress("DEPRECATION") @@ -235,22 +235,29 @@ object VectorLocale { append(locale.getDisplayCountry(locale)) append(")") } + } + } - // In debug mode, also display information about the locale in the current locale. - if (BuildConfig.DEBUG) { - append("\n[") - append(locale.displayLanguage) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && locale.script != "Latn") { - append(" - ") - append(locale.displayScript) - } - if (locale.displayCountry.isNotEmpty()) { - append(" (") - append(locale.displayCountry) - append(")") - } - append("]") + /** + * Information about the locale in the current locale + * + * @param locale the locale to get info from + * @return the string + */ + fun localeToLocalisedStringInfo(locale: Locale): String { + return buildString { + append("[") + append(locale.displayLanguage) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && locale.script != "Latn") { + append(" - ") + append(locale.displayScript) } + if (locale.displayCountry.isNotEmpty()) { + append(" (") + append(locale.displayCountry) + append(")") + } + append("]") } } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsPreferencesFragment.kt index 9c240ad093..a8ba7bcbe6 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsPreferencesFragment.kt @@ -18,7 +18,6 @@ package im.vector.riotx.features.settings import android.app.Activity import android.content.Context -import android.content.Intent import android.widget.CheckedTextView import android.widget.LinearLayout import androidx.appcompat.app.AlertDialog @@ -129,21 +128,6 @@ class VectorSettingsPreferencesFragment @Inject constructor( } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (resultCode == Activity.RESULT_OK) { - when (requestCode) { - REQUEST_LOCALE -> { - activity?.let { - startActivity(it.intent) - it.finish() - } - } - } - } - } - // ============================================================================================================== // user interface management // ============================================================================================================== @@ -152,12 +136,6 @@ class VectorSettingsPreferencesFragment @Inject constructor( // Selected language selectedLanguagePreference.summary = VectorLocale.localeToLocalisedString(VectorLocale.applicationLocale) - selectedLanguagePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - notImplemented() - // TODO startActivityForResult(LanguagePickerActivity.getIntent(activity), REQUEST_LOCALE) - true - } - // Text size textSizePreference.summary = FontScale.getFontScaleDescription(activity!!) @@ -199,8 +177,4 @@ class VectorSettingsPreferencesFragment @Inject constructor( } } } - - companion object { - private const val REQUEST_LOCALE = 777 - } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/locale/LocaleItem.kt b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocaleItem.kt new file mode 100644 index 0000000000..18c5cb2aae --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocaleItem.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.locale + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.ClickListener +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.epoxy.onClick +import im.vector.riotx.core.extensions.setTextOrHide + +@EpoxyModelClass(layout = R.layout.item_locale) +abstract class LocaleItem : VectorEpoxyModel() { + + @EpoxyAttribute var title: String? = null + @EpoxyAttribute var subtitle: String? = null + @EpoxyAttribute var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + + holder.view.onClick { clickListener?.invoke() } + holder.titleView.setTextOrHide(title) + holder.subtitleView.setTextOrHide(subtitle) + } + + class Holder : VectorEpoxyHolder() { + val titleView by bind(R.id.localeTitle) + val subtitleView by bind(R.id.localeSubtitle) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerAction.kt b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerAction.kt new file mode 100644 index 0000000000..0bfc203159 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerAction.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.locale + +import im.vector.riotx.core.platform.VectorViewModelAction +import java.util.Locale + +sealed class LocalePickerAction : VectorViewModelAction { + data class SelectLocale(val locale: Locale) : LocalePickerAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerController.kt b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerController.kt new file mode 100644 index 0000000000..74c5adf98c --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerController.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.locale + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.noResultItem +import im.vector.riotx.core.epoxy.profiles.profileSectionItem +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.settings.VectorLocale +import im.vector.riotx.features.settings.VectorPreferences +import java.util.Locale +import javax.inject.Inject + +class LocalePickerController @Inject constructor( + private val vectorPreferences: VectorPreferences, + private val stringProvider: StringProvider +) : TypedEpoxyController() { + + var listener: Listener? = null + + @ExperimentalStdlibApi + override fun buildModels(data: LocalePickerViewState?) { + val list = data?.locales ?: return + + if (list.isEmpty()) { + noResultItem { + id("noResult") + text(stringProvider.getString(R.string.no_result_placeholder)) + } + } else { + profileSectionItem { + id("currentTitle") + title(stringProvider.getString(R.string.choose_locale_current_locale_title)) + } + localeItem { + id(data.currentLocale.toString()) + title(VectorLocale.localeToLocalisedString(data.currentLocale).capitalize(data.currentLocale)) + if (vectorPreferences.developerMode()) { + subtitle(VectorLocale.localeToLocalisedStringInfo(data.currentLocale)) + } + clickListener { listener?.onUseCurrentClicked() } + } + profileSectionItem { + id("otherTitle") + title(stringProvider.getString(R.string.choose_locale_other_locales_title)) + } + list + .filter { it != data.currentLocale } + .sortedBy { VectorLocale.localeToLocalisedString(it).toLowerCase(it) } + .forEach { + localeItem { + id(it.toString()) + title(VectorLocale.localeToLocalisedString(it).capitalize(it)) + if (vectorPreferences.developerMode()) { + subtitle(VectorLocale.localeToLocalisedStringInfo(it)) + } + clickListener { listener?.onLocaleClicked(it) } + } + } + } + } + + interface Listener { + fun onUseCurrentClicked() + fun onLocaleClicked(locale: Locale) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerFragment.kt new file mode 100644 index 0000000000..75d758aafa --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerFragment.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.locale + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.extensions.restart +import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.synthetic.main.fragment_locale_picker.* +import java.util.Locale +import javax.inject.Inject + +class LocalePickerFragment @Inject constructor( + private val viewModelFactory: LocalePickerViewModel.Factory, + private val controller: LocalePickerController +) : VectorBaseFragment(), LocalePickerViewModel.Factory by viewModelFactory, LocalePickerController.Listener { + + private val viewModel: LocalePickerViewModel by fragmentViewModel() + + override fun getLayoutResId() = R.layout.fragment_locale_picker + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + localeRecyclerView.configureWith(controller) + controller.listener = this + + viewModel.observeViewEvents { + when (it) { + LocalePickerViewEvents.RestartActivity -> { + activity?.restart() + } + }.exhaustive + } + } + + override fun onDestroyView() { + super.onDestroyView() + localeRecyclerView.cleanup() + controller.listener = null + } + + override fun invalidate() = withState(viewModel) { state -> + controller.setData(state) + } + + override fun onUseCurrentClicked() { + // Just close the fragment + parentFragmentManager.popBackStack() + } + + override fun onLocaleClicked(locale: Locale) { + viewModel.handle(LocalePickerAction.SelectLocale(locale)) + } + + override fun onResume() { + super.onResume() + (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_select_language) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewEvents.kt new file mode 100644 index 0000000000..e007f5f036 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewEvents.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.locale + +import im.vector.riotx.core.platform.VectorViewEvents + +sealed class LocalePickerViewEvents : VectorViewEvents { + object RestartActivity : LocalePickerViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewModel.kt new file mode 100644 index 0000000000..1adcad5086 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewModel.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.locale + +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.configuration.VectorConfiguration +import im.vector.riotx.features.settings.VectorLocale + +class LocalePickerViewModel @AssistedInject constructor( + @Assisted initialState: LocalePickerViewState, + private val vectorConfiguration: VectorConfiguration +) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: LocalePickerViewState): LocalePickerViewModel + } + + companion object : MvRxViewModelFactory { + + override fun initialState(viewModelContext: ViewModelContext): LocalePickerViewState? { + return LocalePickerViewState( + locales = VectorLocale.supportedLocales + ) + } + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: LocalePickerViewState): LocalePickerViewModel? { + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") + } + } + + override fun handle(action: LocalePickerAction) { + when (action) { + is LocalePickerAction.SelectLocale -> handleSelectLocale(action) + }.exhaustive + } + + private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) { + vectorConfiguration.updateApplicationLocale(action.locale) + _viewEvents.post(LocalePickerViewEvents.RestartActivity) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewState.kt b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewState.kt new file mode 100644 index 0000000000..6a0f39ab66 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/locale/LocalePickerViewState.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.settings.locale + +import com.airbnb.mvrx.MvRxState +import java.util.Locale + +data class LocalePickerViewState( + val currentLocale: Locale = Locale.getDefault(), + val locales: List = emptyList() +) : MvRxState diff --git a/vector/src/main/res/layout/fragment_locale_picker.xml b/vector/src/main/res/layout/fragment_locale_picker.xml new file mode 100644 index 0000000000..1d7977d19a --- /dev/null +++ b/vector/src/main/res/layout/fragment_locale_picker.xml @@ -0,0 +1,8 @@ + + diff --git a/vector/src/main/res/layout/item_locale.xml b/vector/src/main/res/layout/item_locale.xml new file mode 100644 index 0000000000..a446f23e78 --- /dev/null +++ b/vector/src/main/res/layout/item_locale.xml @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 0158c61fcc..f2e5ea4edc 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2393,4 +2393,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming We could not invite users. Please check the users you want to invite and try again. + Current language + Other available languages + \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index e7217b7394..dde967a283 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -7,9 +7,10 @@ android:title="@string/settings_user_interface"> + android:persistent="false" + android:title="@string/settings_interface_language" + app:fragment="im.vector.riotx.features.settings.locale.LocalePickerFragment" />