Merge pull request #1997 from vector-im/feature/switch_language

Switch language and font size settings fixes
This commit is contained in:
Benoit Marty 2020-08-26 17:34:26 +02:00 committed by GitHub
commit b3edc439cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 148 additions and 88 deletions

View File

@ -9,6 +9,8 @@ Improvements 🙌:
Bugfix 🐛:
- Display name not shown under Settings/General (#1926)
- Fix changing language issue
- Fix FontSize issue (#1483, #1787)
- Fix bad color for settings icon on Android < 24 (#1786)
- Change user or room avatar: when selecting Gallery, I'm not proposed to crop the selected image (#1590)

View File

@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.session.room.membership
import android.content.Context
import io.realm.Realm
import org.matrix.android.sdk.R
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
@ -34,14 +34,15 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrNull
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.UserId
import io.realm.Realm
import org.matrix.android.sdk.internal.util.StringProvider
import javax.inject.Inject
/**
* This class computes room display name
*/
internal class RoomDisplayNameResolver @Inject constructor(private val context: Context,
@UserId private val userId: String
internal class RoomDisplayNameResolver @Inject constructor(
private val stringProvider: StringProvider,
@UserId private val userId: String
) {
/**
@ -89,7 +90,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
.findFirst()
?.displayName
} else {
context.getString(R.string.room_displayname_room_invite)
stringProvider.getString(R.string.room_displayname_room_invite)
}
} else if (roomEntity?.membership == Membership.JOIN) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
@ -108,13 +109,13 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}
val otherMembersCount = otherMembersSubset.count()
name = when (otherMembersCount) {
0 -> context.getString(R.string.room_displayname_empty_room)
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
2 -> context.getString(R.string.room_displayname_two_members,
0 -> stringProvider.getString(R.string.room_displayname_empty_room)
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
2 -> stringProvider.getString(R.string.room_displayname_two_members,
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
)
else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members,
else -> stringProvider.getQuantityString(R.plurals.room_displayname_three_and_more_members,
roomMembers.getNumberOfJoinedMembers() - 1,
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
roomMembers.getNumberOfJoinedMembers() - 1)

View File

@ -18,8 +18,8 @@
package org.matrix.android.sdk.internal.util
import android.content.res.Resources
import androidx.annotation.ArrayRes
import androidx.annotation.NonNull
import androidx.annotation.PluralsRes
import androidx.annotation.StringRes
import dagger.Reusable
import javax.inject.Inject
@ -56,8 +56,8 @@ internal class StringProvider @Inject constructor(private val resources: Resourc
return resources.getString(resId, *formatArgs)
}
@Throws(Resources.NotFoundException::class)
fun getStringArray(@ArrayRes id: Int): Array<String> {
return resources.getStringArray(id)
@NonNull
fun getQuantityString(@PluralsRes resId: Int, quantity: Int, vararg formatArgs: Any?): String {
return resources.getQuantityString(resId, quantity, *formatArgs)
}
}

View File

@ -32,10 +32,6 @@ import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyController
import com.facebook.stetho.Stetho
import com.gabrielittner.threetenbp.LazyThreeTen
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.DaggerVectorComponent
import im.vector.app.core.di.HasVectorInjector
@ -50,16 +46,21 @@ import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.pin.PinLocker
import im.vector.app.features.popup.PopupAlertManager
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.settings.VectorLocale
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.features.version.VersionProvider
import im.vector.app.push.fcm.FcmHelper
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.Executors
import javax.inject.Inject
import androidx.work.Configuration as WorkConfiguration
class VectorApplication :
@ -119,7 +120,9 @@ class VectorApplication :
R.array.com_google_android_gms_fonts_certs
)
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
vectorConfiguration.initConfiguration()
VectorLocale.init(this)
ThemeUtils.init(this)
vectorConfiguration.applyToApplicationContext()
emojiCompatWrapper.init(fontRequest)

View File

@ -102,6 +102,7 @@ import im.vector.app.features.settings.devtools.OutgoingKeyRequestListFragment
import im.vector.app.features.settings.ignored.VectorSettingsIgnoredUsersFragment
import im.vector.app.features.settings.locale.LocalePickerFragment
import im.vector.app.features.settings.push.PushGatewaysFragment
import im.vector.app.features.settings.push.PushRulesFragment
import im.vector.app.features.share.IncomingShareFragment
import im.vector.app.features.signout.soft.SoftLogoutFragment
import im.vector.app.features.terms.ReviewTermsFragment
@ -282,6 +283,11 @@ interface FragmentModule {
@FragmentKey(VectorSettingsLabsFragment::class)
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(PushRulesFragment::class)
fun bindPushRulesFragment(fragment: PushRulesFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VectorSettingsPreferencesFragment::class)

View File

@ -60,6 +60,7 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.extensions.observeEvent
import im.vector.app.core.extensions.observeNotNull
import im.vector.app.core.extensions.restart
import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
@ -75,14 +76,15 @@ 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.VectorPreferences
import im.vector.app.features.themes.ActivityOtherThemes
import im.vector.app.features.themes.ThemeUtils
import im.vector.app.receivers.DebugReceiver
import org.matrix.android.sdk.api.failure.GlobalError
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber
import kotlin.system.measureTimeMillis
@ -198,8 +200,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
configurationViewModel.activityRestarter.observe(this, Observer {
if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed
startActivity(intent)
finish()
restart()
}
})
pinLocker.getLiveState().observeNotNull(this) {
@ -219,6 +220,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
doBeforeSetContentView()
// Hack for font size
applyFontSize()
if (getLayoutRes() != -1) {
setContentView(getLayoutRes())
}
@ -239,6 +243,16 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
}
}
/**
* This method has to be called for the font size setting be supported correctly.
*/
private fun applyFontSize() {
resources.configuration.fontScale = FontScale.getFontScaleValue(this).scale
@Suppress("DEPRECATION")
resources.updateConfiguration(resources.configuration, resources.displayMetrics)
}
private fun handleGlobalError(globalError: GlobalError) {
when (globalError) {
is GlobalError.InvalidToken ->
@ -302,10 +316,10 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
when (resultCode) {
Activity.RESULT_OK -> {
Activity.RESULT_OK -> {
pinLocker.unlock()
}
else -> {
else -> {
pinLocker.block()
moveTaskToBack(true)
}

View File

@ -17,8 +17,10 @@
package im.vector.app.core.services
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import im.vector.app.core.extensions.vectorComponent
import timber.log.Timber
/**
@ -31,6 +33,10 @@ abstract class VectorService : Service() {
*/
private var mIsSelfDestroyed = false
override fun attachBaseContext(base: Context) {
super.attachBaseContext(vectorComponent().vectorConfiguration().getLocalisedContext(base))
}
override fun onCreate() {
super.onCreate()

View File

@ -21,8 +21,6 @@ import android.content.Context
import android.content.Intent
import im.vector.app.core.di.HasVectorInjector
import im.vector.app.features.call.WebRtcPeerConnectionManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.settings.VectorLocale.context
import timber.log.Timber
class CallHeadsUpActionReceiver : BroadcastReceiver() {
@ -32,20 +30,14 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
const val CALL_ACTION_REJECT = 0
}
private lateinit var peerConnectionManager: WebRtcPeerConnectionManager
private lateinit var notificationUtils: NotificationUtils
init {
val appContext = context.applicationContext
if (appContext is HasVectorInjector) {
peerConnectionManager = appContext.injector().webRtcPeerConnectionManager()
notificationUtils = appContext.injector().notificationUtils()
}
}
override fun onReceive(context: Context, intent: Intent?) {
val peerConnectionManager = (context.applicationContext as? HasVectorInjector)
?.injector()
?.webRtcPeerConnectionManager()
?: return
when (intent?.getIntExtra(EXTRA_CALL_ACTION_KEY, 0)) {
CALL_ACTION_REJECT -> onCallRejectClicked()
CALL_ACTION_REJECT -> onCallRejectClicked(peerConnectionManager)
}
// Not sure why this should be needed
@ -56,7 +48,7 @@ class CallHeadsUpActionReceiver : BroadcastReceiver() {
// context.stopService(Intent(context, CallHeadsUpService::class.java))
}
private fun onCallRejectClicked() {
private fun onCallRejectClicked(peerConnectionManager: WebRtcPeerConnectionManager) {
Timber.d("onCallRejectClicked")
peerConnectionManager.endCall()
}

View File

@ -16,10 +16,11 @@
package im.vector.app.features.configuration
import android.annotation.SuppressLint
import android.content.Context
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.VectorLocale
import im.vector.app.features.themes.ThemeUtils
@ -40,14 +41,9 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
}
}
/**
* Init the configuration from the saved one
*/
fun initConfiguration() {
VectorLocale.init(context)
fun applyToApplicationContext() {
val locale = VectorLocale.applicationLocale
val fontScale = FontScale.getFontScaleValue(context)
val theme = ThemeUtils.getApplicationTheme(context)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
@ -56,9 +52,6 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
config.fontScale = fontScale.scale
@Suppress("DEPRECATION")
context.resources.updateConfiguration(config, context.resources.displayMetrics)
// init the theme
ThemeUtils.setApplicationTheme(context, theme)
}
/**
@ -67,26 +60,22 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
* @param context the context
* @return the localised context
*/
@SuppressLint("NewApi")
fun getLocalisedContext(context: Context): Context {
try {
val resources = context.resources
val locale = VectorLocale.applicationLocale
val configuration = resources.configuration
// create new configuration passing old configuration from original Context
val configuration = Configuration(context.resources.configuration)
configuration.fontScale = FontScale.getFontScaleValue(context).scale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
setLocaleForApi24(configuration, locale)
} else {
@Suppress("DEPRECATION")
configuration.locale = locale
configuration.setLayoutDirection(locale)
@Suppress("DEPRECATION")
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
configuration.setLocale(locale)
}
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
} catch (e: Exception) {
Timber.e(e, "## getLocalisedContext() failed")
}
@ -94,6 +83,20 @@ class VectorConfiguration @Inject constructor(private val context: Context) {
return context
}
@RequiresApi(Build.VERSION_CODES.N)
private fun setLocaleForApi24(config: Configuration, locale: Locale) {
val set: MutableSet<Locale> = LinkedHashSet()
// bring the user locale to the front of the list
set.add(locale)
val all = LocaleList.getDefault()
for (i in 0 until all.size()) {
// append other locales supported by the user
set.add(all[i])
}
val locales = set.toTypedArray()
config.setLocales(LocaleList(*locales))
}
/**
* Compute the locale status value
* @return the local status value

View File

@ -52,7 +52,7 @@ object VectorLocale {
var applicationLocale = defaultLocale
private set
lateinit var context: Context
private lateinit var context: Context
/**
* Init this object

View File

@ -168,6 +168,7 @@ class VectorSettingsPreferencesFragment @Inject constructor(
v.setOnClickListener {
dialog.dismiss()
FontScale.updateFontScale(activity, i)
vectorConfiguration.applyToApplicationContext()
activity.restart()
}
}

View File

@ -26,11 +26,13 @@ import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.settings.VectorLocale
import kotlinx.coroutines.launch
class LocalePickerViewModel @AssistedInject constructor(
@Assisted initialState: LocalePickerViewState
@Assisted initialState: LocalePickerViewState,
private val vectorConfiguration: VectorConfiguration
) : VectorViewModel<LocalePickerViewState, LocalePickerAction, LocalePickerViewEvents>(initialState) {
@AssistedInject.Factory
@ -70,6 +72,7 @@ class LocalePickerViewModel @AssistedInject constructor(
private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) {
VectorLocale.saveApplicationLocale(action.locale)
vectorConfiguration.applyToApplicationContext()
_viewEvents.post(LocalePickerViewEvents.RestartActivity)
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.app.features.settings.push
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import javax.inject.Inject
class PushRulesController @Inject constructor(
private val stringProvider: StringProvider
) : TypedEpoxyController<PushRulesViewState>() {
override fun buildModels(data: PushRulesViewState?) {
data?.let {
it.rules.forEach {
pushRuleItem {
id(it.ruleId)
pushRule(it)
}
}
} ?: run {
genericFooterItem {
id("footer")
text(stringProvider.getString(R.string.settings_push_rules_no_rules))
}
}
}
}

View File

@ -17,7 +17,6 @@ package im.vector.app.features.settings.push
import android.os.Bundle
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
@ -25,19 +24,18 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.ui.list.genericFooterItem
import kotlinx.android.synthetic.main.fragment_generic_recycler.*
import javax.inject.Inject
// Referenced in vector_settings_notifications.xml
class PushRulesFragment : VectorBaseFragment() {
class PushRulesFragment @Inject constructor(
private val epoxyController: PushRulesController
) : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_generic_recycler
private val viewModel: PushRulesViewModel by fragmentViewModel(PushRulesViewModel::class)
private val epoxyController by lazy { PushRulesController(StringProvider(requireContext().resources)) }
override fun onResume() {
super.onResume()
(activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_push_rules)
@ -56,23 +54,4 @@ class PushRulesFragment : VectorBaseFragment() {
override fun invalidate() = withState(viewModel) { state ->
epoxyController.setData(state)
}
class PushRulesController(private val stringProvider: StringProvider) : TypedEpoxyController<PushRulesViewState>() {
override fun buildModels(data: PushRulesViewState?) {
data?.let {
it.rules.forEach {
pushRuleItem {
id(it.ruleId)
pushRule(it)
}
}
} ?: run {
genericFooterItem {
id("footer")
text(stringProvider.getString(R.string.settings_push_rules_no_rules))
}
}
}
}
}

View File

@ -44,6 +44,12 @@ object ThemeUtils {
private val mColorByAttr = HashMap<Int, Int>()
// init the theme
fun init(context: Context) {
val theme = getApplicationTheme(context)
setApplicationTheme(context, theme)
}
/**
* @return true if current theme is Light or Status
*/