From 79762d91334f00a7206286fbc63aa47a3f8d025e Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Mon, 18 Jul 2022 09:49:57 +0200 Subject: [PATCH] font scale setting screen (#6453) --- changelog.d/5687.feature | 1 + .../res/color/radio_button_tint_selector.xml | 6 + .../vector_content_primary_tint_selector.xml | 5 + .../settings/SettingsPreferencesRobot.kt | 6 +- vector/src/main/AndroidManifest.xml | 1 + .../im/vector/app/core/di/FragmentModule.kt | 6 + .../app/core/di/MavericksViewModelModule.kt | 6 + .../di/{NamedGlobalScope.kt => Qualifiers.kt} | 5 +- .../im/vector/app/core/di/SingletonModule.kt | 18 +++ .../im/vector/app/core/epoxy/FontScaleItem.kt | 64 +++++++++ .../app/core/epoxy/FontScaleSectionItem.kt | 37 +++++ .../epoxy/FontScaleUseSystemSettingsItem.kt | 45 ++++++ .../app/core/platform/VectorBaseActivity.kt | 14 +- .../app/core/utils/SystemSettingsProvider.kt | 41 ++++++ .../configuration/VectorConfiguration.kt | 13 +- .../vector/app/features/settings/FontScale.kt | 88 ------------ .../features/settings/FontScalePreferences.kt | 133 ++++++++++++++++++ .../VectorSettingsPreferencesFragment.kt | 42 +----- .../settings/font/FontScaleSettingAction.kt | 25 ++++ .../settings/font/FontScaleSettingActivity.kt | 34 +++++ .../font/FontScaleSettingController.kt | 83 +++++++++++ .../settings/font/FontScaleSettingFragment.kt | 80 +++++++++++ .../font/FontScaleSettingViewEvents.kt | 23 +++ .../font/FontScaleSettingViewModel.kt | 84 +++++++++++ .../font/FontScaleSettingViewState.kt | 26 ++++ .../res/layout/dialog_select_text_size.xml | 87 ------------ .../layout/fragment_settings_font_scaling.xml | 38 +++++ .../src/main/res/layout/item_font_scale.xml | 33 +++++ .../res/layout/item_font_scale_section.xml | 24 ++++ .../res/layout/item_font_scale_system.xml | 32 +++++ vector/src/main/res/values/strings.xml | 4 + .../settings/font/FontScalePreferencesTest.kt | 57 ++++++++ .../font/FontScaleSettingViewModelTest.kt | 105 ++++++++++++++ .../app/test/fakes/FakeConfiguration.kt | 28 ++++ .../im/vector/app/test/fakes/FakeContext.kt | 1 + .../test/fakes/FakeFontScalePreferences.kt | 58 ++++++++ .../app/test/fakes/FakeSharedPreferences.kt | 35 +++++ .../test/fakes/FakeSystemSettingsProvider.kt | 28 ++++ 38 files changed, 1195 insertions(+), 221 deletions(-) create mode 100644 changelog.d/5687.feature create mode 100644 library/ui-styles/src/main/res/color/radio_button_tint_selector.xml create mode 100644 library/ui-styles/src/main/res/color/vector_content_primary_tint_selector.xml rename vector/src/main/java/im/vector/app/core/di/{NamedGlobalScope.kt => Qualifiers.kt} (89%) create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/FontScaleItem.kt create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/FontScaleSectionItem.kt create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/FontScaleUseSystemSettingsItem.kt create mode 100644 vector/src/main/java/im/vector/app/core/utils/SystemSettingsProvider.kt delete mode 100644 vector/src/main/java/im/vector/app/features/settings/FontScale.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingController.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewState.kt delete mode 100644 vector/src/main/res/layout/dialog_select_text_size.xml create mode 100644 vector/src/main/res/layout/fragment_settings_font_scaling.xml create mode 100644 vector/src/main/res/layout/item_font_scale.xml create mode 100644 vector/src/main/res/layout/item_font_scale_section.xml create mode 100644 vector/src/main/res/layout/item_font_scale_system.xml create mode 100644 vector/src/test/java/im/vector/app/features/settings/font/FontScalePreferencesTest.kt create mode 100644 vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeConfiguration.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeFontScalePreferences.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeSharedPreferences.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeSystemSettingsProvider.kt diff --git a/changelog.d/5687.feature b/changelog.d/5687.feature new file mode 100644 index 0000000000..4450b4e457 --- /dev/null +++ b/changelog.d/5687.feature @@ -0,0 +1 @@ +Adds settings screen to change app font scale or enable using system setting diff --git a/library/ui-styles/src/main/res/color/radio_button_tint_selector.xml b/library/ui-styles/src/main/res/color/radio_button_tint_selector.xml new file mode 100644 index 0000000000..527738b306 --- /dev/null +++ b/library/ui-styles/src/main/res/color/radio_button_tint_selector.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/library/ui-styles/src/main/res/color/vector_content_primary_tint_selector.xml b/library/ui-styles/src/main/res/color/vector_content_primary_tint_selector.xml new file mode 100644 index 0000000000..26b911391f --- /dev/null +++ b/library/ui-styles/src/main/res/color/vector_content_primary_tint_selector.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt index 438bbac5a5..bb09ee30f2 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/robot/settings/SettingsPreferencesRobot.kt @@ -21,7 +21,9 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaDialogInteractions.clickDialogNegativeButton import im.vector.app.R +import im.vector.app.espresso.tools.waitUntilActivityVisible import im.vector.app.espresso.tools.waitUntilViewVisible +import im.vector.app.features.settings.font.FontScaleSettingActivity class SettingsPreferencesRobot { @@ -32,6 +34,8 @@ class SettingsPreferencesRobot { clickOn(R.string.settings_theme) clickDialogNegativeButton() clickOn(R.string.font_size) - clickDialogNegativeButton() + waitUntilActivityVisible { + pressBack() + } } } diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 9c010d12f0..1c104f3bbf 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -347,6 +347,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index bb11ec0dc5..675df69f23 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -162,6 +162,7 @@ import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailFragmen import im.vector.app.features.settings.devtools.IncomingKeyRequestListFragment import im.vector.app.features.settings.devtools.KeyRequestsFragment import im.vector.app.features.settings.devtools.OutgoingKeyRequestListFragment +import im.vector.app.features.settings.font.FontScaleSettingFragment import im.vector.app.features.settings.homeserver.HomeserverSettingsFragment import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragment import im.vector.app.features.settings.legals.LegalsFragment @@ -586,6 +587,11 @@ interface FragmentModule { @FragmentKey(HomeserverSettingsFragment::class) fun bindHomeserverSettingsFragment(fragment: HomeserverSettingsFragment): Fragment + @Binds + @IntoMap + @FragmentKey(FontScaleSettingFragment::class) + fun bindFontScaleSettingFragment(fragment: FontScaleSettingFragment): Fragment + @Binds @IntoMap @FragmentKey(VectorSettingsPinFragment::class) diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 313fd7b98c..a3e08036ff 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -90,6 +90,7 @@ import im.vector.app.features.settings.devtools.AccountDataViewModel import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel import im.vector.app.features.settings.devtools.KeyRequestListViewModel import im.vector.app.features.settings.devtools.KeyRequestViewModel +import im.vector.app.features.settings.font.FontScaleSettingViewModel import im.vector.app.features.settings.homeserver.HomeserverSettingsViewModel import im.vector.app.features.settings.ignored.IgnoredUsersViewModel import im.vector.app.features.settings.legals.LegalsViewModel @@ -606,4 +607,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(LocationLiveMapViewModel::class) fun locationLiveMapViewModelFactory(factory: LocationLiveMapViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(FontScaleSettingViewModel::class) + fun fontScaleSettingViewModelFactory(factory: FontScaleSettingViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/core/di/NamedGlobalScope.kt b/vector/src/main/java/im/vector/app/core/di/Qualifiers.kt similarity index 89% rename from vector/src/main/java/im/vector/app/core/di/NamedGlobalScope.kt rename to vector/src/main/java/im/vector/app/core/di/Qualifiers.kt index cc1ac829a1..9b07b2c865 100644 --- a/vector/src/main/java/im/vector/app/core/di/NamedGlobalScope.kt +++ b/vector/src/main/java/im/vector/app/core/di/Qualifiers.kt @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package im.vector.app.core.di import javax.inject.Qualifier +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class DefaultPreferences + @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class NamedGlobalScope diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt index cbd34fa05b..602fd73034 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import android.content.res.Resources +import androidx.preference.PreferenceManager import com.google.i18n.phonenumbers.PhoneNumberUtil import dagger.Binds import dagger.Module @@ -37,6 +38,8 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.BuildMeta import im.vector.app.core.time.Clock import im.vector.app.core.time.DefaultClock +import im.vector.app.core.utils.AndroidSystemSettingsProvider +import im.vector.app.core.utils.SystemSettingsProvider import im.vector.app.features.analytics.AnalyticsConfig import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.VectorAnalytics @@ -48,6 +51,8 @@ import im.vector.app.features.navigation.Navigator import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.SharedPrefPinCodeStore import im.vector.app.features.room.VectorRoomDisplayNameFallbackProvider +import im.vector.app.features.settings.FontScalePreferences +import im.vector.app.features.settings.FontScalePreferencesImpl import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository @@ -97,6 +102,12 @@ abstract class VectorBindModule { @Binds abstract fun bindEmojiSpanify(emojiCompatWrapper: EmojiCompatWrapper): EmojiSpanify + + @Binds + abstract fun bindFontScale(fontScale: FontScalePreferencesImpl): FontScalePreferences + + @Binds + abstract fun bindSystemSettingsProvide(provider: AndroidSystemSettingsProvider): SystemSettingsProvider } @InstallIn(SingletonComponent::class) @@ -200,4 +211,11 @@ object VectorStaticModule { @Provides @Singleton fun providesBuildMeta() = BuildMeta() + + @Provides + @Singleton + @DefaultPreferences + fun providesDefaultSharedPreferences(context: Context): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(context.applicationContext) + } } diff --git a/vector/src/main/java/im/vector/app/core/epoxy/FontScaleItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/FontScaleItem.kt new file mode 100644 index 0000000000..045a503def --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/FontScaleItem.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 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.app.core.epoxy + +import android.util.TypedValue +import android.widget.CompoundButton +import android.widget.RadioButton +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.features.settings.FontScaleValue + +@EpoxyModelClass +abstract class FontScaleItem : VectorEpoxyModel(R.layout.item_font_scale) { + + companion object { + const val MINIMAL_TEXT_SIZE_DP = 10f + } + + @EpoxyAttribute var fontScale: FontScaleValue? = null + @EpoxyAttribute var selected: Boolean = true + @EpoxyAttribute var enabled: Boolean = true + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + val context = holder.view.context + holder.textView.text = fontScale?.let { + context.resources.getString(it.nameResId) + } + val index = fontScale?.index ?: 0 + holder.textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, MINIMAL_TEXT_SIZE_DP + index * 2) + holder.textView.isEnabled = enabled + holder.button.isChecked = selected + holder.button.isEnabled = enabled + holder.button.isClickable = enabled + holder.view.onClick { + holder.button.performClick() + } + holder.button.setOnCheckedChangeListener(checkChangeListener) + } + + class Holder : VectorEpoxyHolder() { + val button by bind(R.id.font_scale_radio_button) + val textView by bind(R.id.font_scale_text) + } +} diff --git a/vector/src/main/java/im/vector/app/core/epoxy/FontScaleSectionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/FontScaleSectionItem.kt new file mode 100644 index 0000000000..6ac0906346 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/FontScaleSectionItem.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 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.app.core.epoxy + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R + +@EpoxyModelClass +abstract class FontScaleSectionItem : VectorEpoxyModel(R.layout.item_font_scale_section) { + + @EpoxyAttribute var sectionName: String = "" + + override fun bind(holder: Holder) { + super.bind(holder) + holder.textView.text = sectionName + } + + class Holder : VectorEpoxyHolder() { + val textView by bind(R.id.font_scale_section_name) + } +} diff --git a/vector/src/main/java/im/vector/app/core/epoxy/FontScaleUseSystemSettingsItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/FontScaleUseSystemSettingsItem.kt new file mode 100644 index 0000000000..95915d5372 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/FontScaleUseSystemSettingsItem.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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.app.core.epoxy + +import android.widget.CheckBox +import android.widget.CompoundButton +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R + +@EpoxyModelClass +abstract class FontScaleUseSystemSettingsItem : VectorEpoxyModel(R.layout.item_font_scale_system) { + + @EpoxyAttribute var useSystemSettings: Boolean = true + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.checkBox.isChecked = useSystemSettings + holder.checkBox.setOnCheckedChangeListener(checkChangeListener) + holder.view.onClick { + holder.checkBox.performClick() + } + } + + class Holder : VectorEpoxyHolder() { + val checkBox by bind(R.id.font_scale_use_system_checkbox) + } +} diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 9bad0f8e90..5ca5b62d1e 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -45,6 +45,7 @@ import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding import com.airbnb.mvrx.MavericksView import com.bumptech.glide.util.Util @@ -66,6 +67,7 @@ import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.extensions.toMvRxBundle +import im.vector.app.core.utils.AndroidSystemSettingsProvider import im.vector.app.core.utils.ToolbarConfig import im.vector.app.core.utils.toast import im.vector.app.features.MainActivity @@ -82,7 +84,8 @@ import im.vector.app.features.rageshake.BugReportActivity import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.rageshake.RageShake import im.vector.app.features.session.SessionListener -import im.vector.app.features.settings.FontScale +import im.vector.app.features.settings.FontScalePreferences +import im.vector.app.features.settings.FontScalePreferencesImpl import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils @@ -154,6 +157,10 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver @Inject lateinit var rageShake: RageShake + + @Inject + lateinit var fontScalePreferences: FontScalePreferences + lateinit var navigator: Navigator private set private lateinit var fragmentFactory: FragmentFactory @@ -172,7 +179,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver private val restorables = ArrayList() override fun attachBaseContext(base: Context) { - val vectorConfiguration = VectorConfiguration(this) + val fontScalePreferences = FontScalePreferencesImpl(PreferenceManager.getDefaultSharedPreferences(base), AndroidSystemSettingsProvider(base)) + val vectorConfiguration = VectorConfiguration(this, fontScalePreferences) super.attachBaseContext(vectorConfiguration.getLocalisedContext(base)) } @@ -285,7 +293,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver * This method has to be called for the font size setting be supported correctly. */ private fun applyFontSize() { - resources.configuration.fontScale = FontScale.getFontScaleValue(this).scale + resources.configuration.fontScale = fontScalePreferences.getResolvedFontScaleValue().scale @Suppress("DEPRECATION") resources.updateConfiguration(resources.configuration, resources.displayMetrics) diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemSettingsProvider.kt b/vector/src/main/java/im/vector/app/core/utils/SystemSettingsProvider.kt new file mode 100644 index 0000000000..1a0b73fd21 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/utils/SystemSettingsProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 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.app.core.utils + +import android.content.Context +import android.provider.Settings +import javax.inject.Inject + +/** + * A helper to get system settings. + */ +interface SystemSettingsProvider { + + /** + * @return system setting for font scale + */ + fun getSystemFontScale(): Float +} + +class AndroidSystemSettingsProvider @Inject constructor( + private val context: Context, +) : SystemSettingsProvider { + + override fun getSystemFontScale(): Float { + return Settings.System.getFloat(context.contentResolver, Settings.System.FONT_SCALE) + } +} diff --git a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt index d209adecc8..a3d801e534 100644 --- a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt +++ b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt @@ -21,7 +21,7 @@ import android.content.res.Configuration import android.os.Build import android.os.LocaleList import androidx.annotation.RequiresApi -import im.vector.app.features.settings.FontScale +import im.vector.app.features.settings.FontScalePreferences import im.vector.app.features.settings.VectorLocale import im.vector.app.features.themes.ThemeUtils import timber.log.Timber @@ -31,7 +31,10 @@ 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) { +class VectorConfiguration @Inject constructor( + private val context: Context, + private val fontScalePreferences: FontScalePreferences +) { fun onConfigurationChanged() { if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) { @@ -45,7 +48,7 @@ class VectorConfiguration @Inject constructor(private val context: Context) { fun applyToApplicationContext() { val locale = VectorLocale.applicationLocale - val fontScale = FontScale.getFontScaleValue(context) + val fontScale = fontScalePreferences.getResolvedFontScaleValue() Locale.setDefault(locale) val config = Configuration(context.resources.configuration) @@ -69,7 +72,7 @@ class VectorConfiguration @Inject constructor(private val context: Context) { // create new configuration passing old configuration from original Context val configuration = Configuration(context.resources.configuration) - configuration.fontScale = FontScale.getFontScaleValue(context).scale + configuration.fontScale = fontScalePreferences.getResolvedFontScaleValue().scale if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { setLocaleForApi24(configuration, locale) @@ -105,7 +108,7 @@ class VectorConfiguration @Inject constructor(private val context: Context) { */ fun getHash(): String { return (VectorLocale.applicationLocale.toString() + - "_" + FontScale.getFontScaleValue(context).preferenceValue + + "_" + fontScalePreferences.getResolvedFontScaleValue().preferenceValue + "_" + ThemeUtils.getApplicationTheme(context)) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt b/vector/src/main/java/im/vector/app/features/settings/FontScale.kt deleted file mode 100644 index a1acef7d35..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2018 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.app.features.settings - -import android.content.Context -import androidx.annotation.StringRes -import androidx.core.content.edit -import im.vector.app.R -import im.vector.app.core.di.DefaultSharedPreferences - -/** - * Object to manage the Font Scale choice of the user. - */ -object FontScale { - // Key for the SharedPrefs - private const val APPLICATION_FONT_SCALE_KEY = "APPLICATION_FONT_SCALE_KEY" - - data class FontScaleValue( - val index: Int, - // Possible values for the SharedPrefs - val preferenceValue: String, - val scale: Float, - @StringRes - val nameResId: Int - ) - - private val fontScaleValues = listOf( - FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny), - FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small), - FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal), - FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large), - FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger), - FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest), - FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge) - ) - - private val normalFontScaleValue = fontScaleValues[2] - - /** - * Get the font scale value from SharedPrefs. Init the SharedPrefs if necessary. - * - * @return the font scale value - */ - fun getFontScaleValue(context: Context): FontScaleValue { - val preferences = DefaultSharedPreferences.getInstance(context) - - return if (APPLICATION_FONT_SCALE_KEY !in preferences) { - val fontScale = context.resources.configuration.fontScale - - (fontScaleValues.firstOrNull { it.scale == fontScale } ?: normalFontScaleValue) - .also { preferences.edit { putString(APPLICATION_FONT_SCALE_KEY, it.preferenceValue) } } - } else { - val pref = preferences.getString(APPLICATION_FONT_SCALE_KEY, null) - fontScaleValues.firstOrNull { it.preferenceValue == pref } ?: normalFontScaleValue - } - } - - fun updateFontScale(context: Context, index: Int) { - fontScaleValues.getOrNull(index)?.let { - saveFontScaleValue(context, it) - } - } - - /** - * Store the font scale value. - * - * @param context the Android context - * @param fontScaleValue the font scale value to store - */ - private fun saveFontScaleValue(context: Context, fontScaleValue: FontScaleValue) { - DefaultSharedPreferences.getInstance(context) - .edit { putString(APPLICATION_FONT_SCALE_KEY, fontScaleValue.preferenceValue) } - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt b/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt new file mode 100644 index 0000000000..292d0107ba --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2018 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.app.features.settings + +import android.content.SharedPreferences +import androidx.annotation.StringRes +import androidx.core.content.edit +import im.vector.app.R +import im.vector.app.core.di.DefaultPreferences +import im.vector.app.core.utils.SystemSettingsProvider +import javax.inject.Inject + +/** + * Stores and returns font scale settings using shared preferences. + */ +interface FontScalePreferences { + /** Defines whether to use system settings for font scale or not. + * @param useSystem true to use system settings, false to use app settings + */ + fun setUseSystemScale(useSystem: Boolean) + + /** Returns whether to use system settings for font scale or not. + * @return useSystem true to use system settings, false to use app settings + */ + fun getUseSystemScale(): Boolean + + /** Returns font scale taking in account [useSystemScale] setting. + * @return App setting for font scale if [getUseSystemScale] returns false, system setting otherwise + */ + fun getResolvedFontScaleValue(): FontScaleValue + + /** Returns persisted app font scale. + * @return app setting for font scale + */ + fun getAppFontScaleValue(): FontScaleValue + + /** Sets and stores app font scale setting value. + * @param fontScaleValue value to be set and saved + */ + fun setFontScaleValue(fontScaleValue: FontScaleValue) + + /** Returns list of all available font scale values. + * @return list of values + */ + fun getAvailableScales(): List +} + +/** + * Object to manage the Font Scale choice of the user. + */ +class FontScalePreferencesImpl @Inject constructor( + @DefaultPreferences private val preferences: SharedPreferences, + private val systemSettingsProvider: SystemSettingsProvider, +) : FontScalePreferences { + + companion object { + private const val APPLICATION_FONT_SCALE_KEY = "APPLICATION_FONT_SCALE_KEY" + private const val APPLICATION_USE_SYSTEM_FONT_SCALE_KEY = "APPLICATION_USE_SYSTEM_FONT_SCALE_KEY" + } + + private val fontScaleValues = listOf( + FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny), + FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small), + FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal), + FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large), + FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger), + FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest), + FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge) + ) + + private val normalFontScaleValue = fontScaleValues[2] + + override fun getAppFontScaleValue(): FontScaleValue { + return if (APPLICATION_FONT_SCALE_KEY !in preferences) { + normalFontScaleValue + } else { + val pref = preferences.getString(APPLICATION_FONT_SCALE_KEY, null) + fontScaleValues.firstOrNull { it.preferenceValue == pref } ?: normalFontScaleValue + } + } + + override fun getResolvedFontScaleValue(): FontScaleValue { + val useSystem = getUseSystemScale() + + return if (useSystem) { + val fontScale = systemSettingsProvider.getSystemFontScale() + fontScaleValues.firstOrNull { it.scale == fontScale } ?: normalFontScaleValue + } else { + getAppFontScaleValue() + } + } + + override fun setFontScaleValue(fontScaleValue: FontScaleValue) { + preferences + .edit() + .putString(APPLICATION_FONT_SCALE_KEY, fontScaleValue.preferenceValue) + .apply() + } + + override fun getAvailableScales(): List = fontScaleValues + + override fun getUseSystemScale(): Boolean { + return preferences.getBoolean(APPLICATION_USE_SYSTEM_FONT_SCALE_KEY, true) + } + + override fun setUseSystemScale(useSystem: Boolean) { + preferences + .edit { putBoolean(APPLICATION_USE_SYSTEM_FONT_SCALE_KEY, useSystem) } + } +} + +data class FontScaleValue( + val index: Int, + // Possible values for the SharedPrefs + val preferenceValue: String, + val scale: Float, + @StringRes + val nameResId: Int +) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt index 9f9afc8ef1..ac7d29ab7a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt @@ -16,11 +16,9 @@ package im.vector.app.features.settings -import android.app.Activity import android.content.Context +import android.content.Intent import android.os.Bundle -import android.widget.CheckedTextView -import androidx.core.view.children import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -30,19 +28,18 @@ import im.vector.app.core.extensions.restart import im.vector.app.core.preference.VectorListPreference import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorSwitchPreference -import im.vector.app.databinding.DialogSelectTextSizeBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import im.vector.app.features.analytics.plan.MobileScreen -import im.vector.app.features.configuration.VectorConfiguration +import im.vector.app.features.settings.font.FontScaleSettingActivity import im.vector.app.features.themes.ThemeUtils import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.presence.model.PresenceEnum import javax.inject.Inject class VectorSettingsPreferencesFragment @Inject constructor( - private val vectorConfiguration: VectorConfiguration, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val fontScalePreferences: FontScalePreferences, ) : VectorSettingsBaseFragment() { override var titleRes = R.string.settings_preferences @@ -194,38 +191,11 @@ class VectorSettingsPreferencesFragment @Inject constructor( selectedLanguagePreference.summary = VectorLocale.localeToLocalisedString(VectorLocale.applicationLocale) // Text size - textSizePreference.summary = getString(FontScale.getFontScaleValue(requireActivity()).nameResId) + textSizePreference.summary = getString(fontScalePreferences.getResolvedFontScaleValue().nameResId) textSizePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.let { displayTextSizeSelection(it) } + startActivity(Intent(activity, FontScaleSettingActivity::class.java)) true } } - - private fun displayTextSizeSelection(activity: Activity) { - val layout = layoutInflater.inflate(R.layout.dialog_select_text_size, null) - val views = DialogSelectTextSizeBinding.bind(layout) - - val dialog = MaterialAlertDialogBuilder(activity) - .setTitle(R.string.font_size) - .setView(layout) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.action_cancel, null) - .show() - - val index = FontScale.getFontScaleValue(activity).index - - views.textSelectionGroupView.children - .filterIsInstance(CheckedTextView::class.java) - .forEachIndexed { i, v -> - v.isChecked = i == index - - v.debouncedClicks { - dialog.dismiss() - FontScale.updateFontScale(activity, i) - vectorConfiguration.applyToApplicationContext() - activity.restart() - } - } - } } diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingAction.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingAction.kt new file mode 100644 index 0000000000..270303b059 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingAction.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.features.settings.FontScaleValue + +sealed class FontScaleSettingAction : VectorViewModelAction { + data class UseSystemSettingChangedAction(val useSystemSettings: Boolean) : FontScaleSettingAction() + data class FontScaleChangedAction(val fontScale: FontScaleValue) : FontScaleSettingAction() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt new file mode 100644 index 0000000000..999622039b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingActivity.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding + +@AndroidEntryPoint +class FontScaleSettingActivity : VectorBaseActivity() { + + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + + override fun initUiAndData() { + if (isFirstCreation()) { + addFragment(views.simpleFragmentContainer, FontScaleSettingFragment::class.java) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingController.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingController.kt new file mode 100644 index 0000000000..d9a1361ee8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingController.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.epoxy.fontScaleItem +import im.vector.app.core.epoxy.fontScaleSectionItem +import im.vector.app.core.epoxy.fontScaleUseSystemSettingsItem +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.settings.FontScaleValue +import javax.inject.Inject + +class FontScaleSettingController @Inject constructor( + val stringProvider: StringProvider +) : TypedEpoxyController() { + + var callback: Callback? = null + + override fun buildModels(data: FontScaleSettingViewState?) { + data?.let { + buildAutomaticallySection(data.useSystemSettings) + buildFontScaleItems(data.availableScaleOptions, data.persistedSettingIndex, data.useSystemSettings) + } + } + + private fun buildAutomaticallySection(useSystemSettings: Boolean) { + val host = this + fontScaleSectionItem { + id("section_automatically") + sectionName(host.stringProvider.getString(R.string.font_size_section_auto)) + } + + fontScaleUseSystemSettingsItem { + id("use_system_settings") + useSystemSettings(useSystemSettings) + checkChangeListener { _, isChecked -> + host.callback?.onUseSystemSettingChanged(useSystemSettings = isChecked) + } + } + } + + private fun buildFontScaleItems(scales: List, persistedSettingIndex: Int, useSystemSettings: Boolean) { + val host = this + fontScaleSectionItem { + id("section_manually") + sectionName(host.stringProvider.getString(R.string.font_size_section_manually)) + } + + scales.forEachIndexed { index, scaleItem -> + fontScaleItem { + id(scaleItem.index) + fontScale(scaleItem) + selected(index == persistedSettingIndex) + enabled(!useSystemSettings) + checkChangeListener { _, isChecked -> + if (isChecked) { + host.callback?.oFontScaleSelected(fonScale = scaleItem) + } + } + } + } + } + + interface Callback { + fun onUseSystemSettingChanged(useSystemSettings: Boolean) + fun oFontScaleSelected(fonScale: FontScaleValue) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt new file mode 100644 index 0000000000..d553c1a8ce --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingFragment.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.extensions.restart +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentSettingsFontScalingBinding +import im.vector.app.features.settings.FontScaleValue +import javax.inject.Inject + +class FontScaleSettingFragment @Inject constructor( + private val fontListController: FontScaleSettingController +) : VectorBaseFragment(), FontScaleSettingController.Callback { + + private val viewModel: FontScaleSettingViewModel by fragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsFontScalingBinding { + return FragmentSettingsFontScalingBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar(views.fontScaleToolbar) + .allowBack() + + fontListController.callback = this + setupRecyclerView() + + viewModel.observeViewEvents { + when (it) { + is FontScaleSettingViewEvents.RestartActivity -> { + requireActivity().restart() + } + } + } + } + + private fun setupRecyclerView() { + views.fonsScaleRecycler.configureWith(fontListController) + } + + override fun invalidate() = withState(viewModel) { state -> + fontListController.setData(state) + } + + override fun onDestroyView() { + super.onDestroyView() + fontListController.callback = null + } + + override fun onUseSystemSettingChanged(useSystemSettings: Boolean) { + viewModel.handle(FontScaleSettingAction.UseSystemSettingChangedAction(useSystemSettings)) + } + + override fun oFontScaleSelected(fonScale: FontScaleValue) { + viewModel.handle(FontScaleSettingAction.FontScaleChangedAction(fonScale)) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewEvents.kt new file mode 100644 index 0000000000..80b2b389bd --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewEvents.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import im.vector.app.core.platform.VectorViewEvents + +sealed class FontScaleSettingViewEvents : VectorViewEvents { + object RestartActivity : FontScaleSettingViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewModel.kt new file mode 100644 index 0000000000..8eb144a8be --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewModel.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.configuration.VectorConfiguration +import im.vector.app.features.settings.FontScalePreferences + +class FontScaleSettingViewModel @AssistedInject constructor( + @Assisted initialState: FontScaleSettingViewState, + private val vectorConfiguration: VectorConfiguration, + private val fontScalePreferences: FontScalePreferences, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: FontScaleSettingViewState): FontScaleSettingViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + init { + setState { + copy( + availableScaleOptions = fontScalePreferences.getAvailableScales(), + useSystemSettings = fontScalePreferences.getUseSystemScale(), + persistedSettingIndex = fontScalePreferences.getAppFontScaleValue().index + ) + } + } + + override fun handle(action: FontScaleSettingAction) { + when (action) { + is FontScaleSettingAction.UseSystemSettingChangedAction -> handleUseSystemScale(action) + is FontScaleSettingAction.FontScaleChangedAction -> handleFontScaleChange(action) + } + } + + private fun handleFontScaleChange(action: FontScaleSettingAction.FontScaleChangedAction) { + setState { + copy(persistedSettingIndex = fontScalePreferences.getAvailableScales().indexOf(action.fontScale)) + } + + fontScalePreferences.setFontScaleValue(action.fontScale) + vectorConfiguration.applyToApplicationContext() + + _viewEvents.post(FontScaleSettingViewEvents.RestartActivity) + } + + private fun handleUseSystemScale(action: FontScaleSettingAction.UseSystemSettingChangedAction) { + setState { + copy(useSystemSettings = action.useSystemSettings) + } + + val oldScale = fontScalePreferences.getResolvedFontScaleValue() + fontScalePreferences.setUseSystemScale(action.useSystemSettings) + val newScale = fontScalePreferences.getResolvedFontScaleValue() + + if (oldScale != newScale) { + vectorConfiguration.applyToApplicationContext() + _viewEvents.post(FontScaleSettingViewEvents.RestartActivity) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewState.kt b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewState.kt new file mode 100644 index 0000000000..173fa4714d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/font/FontScaleSettingViewState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import com.airbnb.mvrx.MavericksState +import im.vector.app.features.settings.FontScaleValue + +data class FontScaleSettingViewState( + val availableScaleOptions: List = emptyList(), + val persistedSettingIndex: Int = 0, + val useSystemSettings: Boolean = true, +) : MavericksState diff --git a/vector/src/main/res/layout/dialog_select_text_size.xml b/vector/src/main/res/layout/dialog_select_text_size.xml deleted file mode 100644 index e93f524c1d..0000000000 --- a/vector/src/main/res/layout/dialog_select_text_size.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/vector/src/main/res/layout/fragment_settings_font_scaling.xml b/vector/src/main/res/layout/fragment_settings_font_scaling.xml new file mode 100644 index 0000000000..19d289f3d1 --- /dev/null +++ b/vector/src/main/res/layout/fragment_settings_font_scaling.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_font_scale.xml b/vector/src/main/res/layout/item_font_scale.xml new file mode 100644 index 0000000000..b918bde867 --- /dev/null +++ b/vector/src/main/res/layout/item_font_scale.xml @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/vector/src/main/res/layout/item_font_scale_section.xml b/vector/src/main/res/layout/item_font_scale_section.xml new file mode 100644 index 0000000000..512a006b45 --- /dev/null +++ b/vector/src/main/res/layout/item_font_scale_section.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/vector/src/main/res/layout/item_font_scale_system.xml b/vector/src/main/res/layout/item_font_scale_system.xml new file mode 100644 index 0000000000..fac4646fa1 --- /dev/null +++ b/vector/src/main/res/layout/item_font_scale_system.xml @@ -0,0 +1,32 @@ + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index eed6269e65..f52a344922 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1263,6 +1263,10 @@ %1$s: %2$s %3$s + Choose font size + Set automatically + Choose manually + Use system default Font size Tiny Small diff --git a/vector/src/test/java/im/vector/app/features/settings/font/FontScalePreferencesTest.kt b/vector/src/test/java/im/vector/app/features/settings/font/FontScalePreferencesTest.kt new file mode 100644 index 0000000000..2ce19d0833 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/font/FontScalePreferencesTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import im.vector.app.features.settings.FontScalePreferencesImpl +import im.vector.app.test.fakes.FakeSharedPreferences +import im.vector.app.test.fakes.FakeSystemSettingsProvider +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class FontScalePreferencesTest { + + private val fakeSharedPreferences = FakeSharedPreferences() + private val fakeSystemSettingsProvider = FakeSystemSettingsProvider() + private val fontScalePreferences = FontScalePreferencesImpl( + preferences = fakeSharedPreferences, + systemSettingsProvider = fakeSystemSettingsProvider + ) + + @Test + fun `given app setting is different from system setting and useSystemSetting is set to true, then returns system-level setting`() { + val scaleOptions = fontScalePreferences.getAvailableScales() + val tinyScale = scaleOptions[0] + val normalScale = scaleOptions[2] + fakeSharedPreferences.givenFontScaleIsSetTo(tinyScale) + fakeSharedPreferences.givenUseSystemFontScaleIsSetTo(true) + fakeSystemSettingsProvider.givenSystemFontScaleIs(normalScale.scale) + + fontScalePreferences.getResolvedFontScaleValue() shouldBeEqualTo normalScale + } + + @Test + fun `given app setting is different from system setting and useSystemSetting is set to false, then returns app-level setting`() { + val scaleOptions = fontScalePreferences.getAvailableScales() + val tinyScale = scaleOptions[0] + val normalScale = scaleOptions[2] + fakeSharedPreferences.givenFontScaleIsSetTo(tinyScale) + fakeSharedPreferences.givenUseSystemFontScaleIsSetTo(false) + fakeSystemSettingsProvider.givenSystemFontScaleIs(normalScale.scale) + + fontScalePreferences.getResolvedFontScaleValue() shouldBeEqualTo tinyScale + } +} diff --git a/vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt new file mode 100644 index 0000000000..f21cc86572 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 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.app.features.settings.font + +import com.airbnb.mvrx.test.MvRxTestRule +import im.vector.app.features.settings.FontScaleValue +import im.vector.app.test.fakes.FakeConfiguration +import im.vector.app.test.fakes.FakeFontScalePreferences +import im.vector.app.test.test +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +private val A_SELECTION = aFontScaleValue(index = 1) +private val A_SCALE_OPTIONS_WITH_SELECTION = listOf( + aFontScaleValue(index = 0), + A_SELECTION, +) + +// our tests only make use of the index +private fun aFontScaleValue(index: Int) = FontScaleValue(index, "foo", -1f, 0) + +class FontScaleSettingViewModelTest { + + @get:Rule + val mvrxTestRule = MvRxTestRule() + + private val fakeConfiguration = FakeConfiguration() + private val fakeFontScalePreferences = FakeFontScalePreferences() + + private var initialState = FontScaleSettingViewState() + private lateinit var viewModel: FontScaleSettingViewModel + + @Before + fun setUp() { + viewModelWith(initialState) + } + + private fun viewModelWith(state: FontScaleSettingViewState) { + FontScaleSettingViewModel( + state, + fakeConfiguration.instance, + fakeFontScalePreferences + ).also { + viewModel = it + initialState = state + } + } + + @Test + fun `given useSystemSetting is false when handling FontScaleChangedAction, then changes state and emits RestartActivity event`() = + runTest { + fakeFontScalePreferences.givenAvailableScaleOptions(A_SCALE_OPTIONS_WITH_SELECTION) + viewModelWith(initialState) + val test = viewModel.test() + + viewModel.handle(FontScaleSettingAction.FontScaleChangedAction(A_SELECTION)) + + test + .assertStatesChanges( + initialState.copy(availableScaleOptions = A_SCALE_OPTIONS_WITH_SELECTION), + { copy(persistedSettingIndex = A_SELECTION.index) } + ) + .assertEvents(FontScaleSettingViewEvents.RestartActivity) + .finish() + + fakeFontScalePreferences.verifyAppScaleFontValue(A_SELECTION) + } + + @Test + fun `given app and system font scale are different when handling UseSystemSettingChangedAction, then changes state and emits RestartActivity event`() = + runTest { + fakeFontScalePreferences.givenAvailableScaleOptions(A_SCALE_OPTIONS_WITH_SELECTION) + viewModelWith(initialState) + val test = viewModel.test() + + fakeFontScalePreferences.givenAppSettingIsDifferentFromSystemSetting() + val newValue = false + + viewModel.handle(FontScaleSettingAction.UseSystemSettingChangedAction(newValue)) + + test + .assertStatesChanges( + initialState.copy(availableScaleOptions = A_SCALE_OPTIONS_WITH_SELECTION), + { copy(useSystemSettings = newValue) } + ) + .assertEvents(FontScaleSettingViewEvents.RestartActivity) + .finish() + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeConfiguration.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeConfiguration.kt new file mode 100644 index 0000000000..8fcbca5134 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeConfiguration.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 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.app.test.fakes + +import im.vector.app.features.configuration.VectorConfiguration +import io.mockk.every +import io.mockk.mockk + +class FakeConfiguration { + + val instance = mockk { + every { applyToApplicationContext() } returns Unit + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt index 226f6458de..329ac1bdae 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt @@ -33,6 +33,7 @@ class FakeContext( init { every { instance.contentResolver } returns contentResolver + every { instance.applicationContext } returns instance } fun givenFileDescriptor(uri: Uri, mode: String, factory: () -> ParcelFileDescriptor?) { diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFontScalePreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeFontScalePreferences.kt new file mode 100644 index 0000000000..4e8abce74a --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeFontScalePreferences.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 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.app.test.fakes + +import im.vector.app.R +import im.vector.app.features.settings.FontScalePreferences +import im.vector.app.features.settings.FontScaleValue +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify + +class FakeFontScalePreferences : FontScalePreferences by mockk(relaxUnitFun = true) { + + private val fontScaleValues = listOf( + FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny), + FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small), + FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal), + FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large), + FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger), + FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest), + FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge) + ) + + init { + every { getAvailableScales() } returns fontScaleValues + every { getUseSystemScale() } returns true + every { getAppFontScaleValue() } returns fontScaleValues[0] + every { getResolvedFontScaleValue() } returns fontScaleValues[0] + } + + fun givenAppSettingIsDifferentFromSystemSetting() { + every { getResolvedFontScaleValue() } returns fontScaleValues[2] andThen fontScaleValues[0] + } + + fun verifyAppScaleFontValue(value: FontScaleValue) { + verify { + setFontScaleValue(value) + } + } + + fun givenAvailableScaleOptions(availableFontScales: List) { + every { getAvailableScales() } returns availableFontScales + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSharedPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSharedPreferences.kt new file mode 100644 index 0000000000..f9d525fd13 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSharedPreferences.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 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.app.test.fakes + +import android.content.SharedPreferences +import im.vector.app.features.settings.FontScaleValue +import io.mockk.every +import io.mockk.mockk + +class FakeSharedPreferences : SharedPreferences by mockk() { + + fun givenFontScaleIsSetTo(fontScaleValue: FontScaleValue) { + every { contains("APPLICATION_FONT_SCALE_KEY") } returns true + every { getString("APPLICATION_FONT_SCALE_KEY", any()) } returns fontScaleValue.preferenceValue + } + + fun givenUseSystemFontScaleIsSetTo(useSystemScale: Boolean) { + every { contains("APPLICATION_USE_SYSTEM_FONT_SCALE_KEY") } returns true + every { getBoolean("APPLICATION_USE_SYSTEM_FONT_SCALE_KEY", any()) } returns useSystemScale + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSystemSettingsProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSystemSettingsProvider.kt new file mode 100644 index 0000000000..d89cd19c1e --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSystemSettingsProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 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.app.test.fakes + +import im.vector.app.core.utils.SystemSettingsProvider +import io.mockk.every +import io.mockk.mockk + +class FakeSystemSettingsProvider : SystemSettingsProvider by mockk() { + + fun givenSystemFontScaleIs(scale: Float) { + every { getSystemFontScale() } returns scale + } +}