Hot change of theme - WIP

This commit is contained in:
Benoit Marty 2019-06-06 18:34:14 +02:00
parent bfbb29b2cf
commit 2c2f517e52
7 changed files with 260 additions and 17 deletions

View File

@ -18,6 +18,7 @@ package im.vector.riotredesign
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import androidx.multidex.MultiDex
import com.airbnb.epoxy.EpoxyAsyncUtil
import com.airbnb.epoxy.EpoxyController
@ -27,10 +28,12 @@ import com.github.piasy.biv.loader.glide.GlideImageLoader
import com.jakewharton.threetenabp.AndroidThreeTen
import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.core.di.AppModule
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.home.HomeModule
import im.vector.riotredesign.features.rageshake.VectorFileLogger
import im.vector.riotredesign.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule
import org.koin.android.ext.android.inject
import org.koin.log.EmptyLogger
import org.koin.standalone.StandAloneContext.startKoin
import timber.log.Timber
@ -38,6 +41,8 @@ import timber.log.Timber
class VectorApplication : Application() {
val vectorConfiguration: VectorConfiguration by inject()
override fun onCreate() {
super.onCreate()
@ -61,6 +66,8 @@ class VectorApplication : Application() {
startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger())
Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)
vectorConfiguration.initConfiguration()
}
override fun attachBaseContext(base: Context) {
@ -68,4 +75,10 @@ class VectorApplication : Application() {
MultiDex.install(this)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
vectorConfiguration.onConfigurationChanged(newConfig)
}
}

View File

@ -24,6 +24,7 @@ import im.vector.riotredesign.core.error.ErrorFormatter
import im.vector.riotredesign.core.resources.LocaleProvider
import im.vector.riotredesign.core.resources.StringArrayProvider
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.home.HomeRoomListObservableStore
import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.list.AlphabeticalRoomComparator
@ -37,6 +38,10 @@ class AppModule(private val context: Context) {
val definition = module {
single {
VectorConfiguration(context)
}
single {
LocaleProvider(context.resources)
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2019 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.riotredesign.core.platform
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.configuration.VectorConfiguration
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject
import timber.log.Timber
class ConfigurationViewModel : ViewModel(), KoinComponent {
private val vectorConfiguration: VectorConfiguration by inject()
private var currentConfigurationValue: String? = null
private val _activityRestarter = MutableLiveData<LiveEvent<Unit>>()
val activityRestarter: LiveData<LiveEvent<Unit>>
get() = _activityRestarter
fun onActivityResumed() {
if (currentConfigurationValue == null) {
currentConfigurationValue = vectorConfiguration.getHash()
Timber.v("Configuration: init to $currentConfigurationValue")
} else {
val newHash = vectorConfiguration.getHash()
Timber.v("Configuration: newHash $newHash")
if (newHash != currentConfigurationValue) {
Timber.v("Configuration: recreate the Activity")
currentConfigurationValue = newHash
_activityRestarter.postValue(LiveEvent(Unit))
}
}
}
}

View File

@ -16,6 +16,7 @@
package im.vector.riotredesign.core.platform
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.view.Menu
@ -24,6 +25,8 @@ import android.view.View
import androidx.annotation.*
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.Unbinder
@ -33,6 +36,7 @@ import com.google.android.material.snackbar.Snackbar
import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.utils.toast
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.rageshake.BugReportActivity
import im.vector.riotredesign.features.rageshake.BugReporter
import im.vector.riotredesign.features.rageshake.RageShake
@ -41,6 +45,7 @@ import im.vector.riotredesign.receivers.DebugReceiver
import im.vector.ui.themes.ActivityOtherThemes
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import org.koin.android.ext.android.inject
import timber.log.Timber
@ -58,6 +63,10 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
* DATA
* ========================================================================================== */
private val vectorConfiguration: VectorConfiguration by inject()
private lateinit var configurationViewModel: ConfigurationViewModel
private var unBinder: Unbinder? = null
private var savedInstanceState: Bundle? = null
@ -70,6 +79,10 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
private var rageShake: RageShake? = null
override fun attachBaseContext(base: Context) {
super.attachBaseContext(vectorConfiguration.getLocalisedContext(base))
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
restorables.forEach { it.onSaveInstanceState(outState) }
@ -95,6 +108,16 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
configurationViewModel = ViewModelProviders.of(this).get(ConfigurationViewModel::class.java)
configurationViewModel.activityRestarter.observe(this, Observer {
if (!it.hasBeenHandled) {
// Recreate the Activity because configuration has changed
startActivity(intent)
finish()
}
})
// Shake detector
rageShake = RageShake(this)
@ -136,6 +159,8 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
Timber.d("onResume Activity ${this.javaClass.simpleName}")
configurationViewModel.onActivityResumed()
if (this !is BugReportActivity) {
rageShake?.start()
}

View File

@ -0,0 +1,146 @@
/*
* Copyright 2019 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.riotredesign.features.configuration
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import im.vector.riotredesign.features.settings.FontScale
import im.vector.riotredesign.features.settings.VectorLocale
import im.vector.riotredesign.features.themes.ThemeUtils
import timber.log.Timber
import java.util.*
/**
* Handle locale configuration change, such as theme, font size and locale chosen by the user
*/
class VectorConfiguration(private val context: Context) {
// TODO Import mLanguageReceiver From Riot?
fun onConfigurationChanged(newConfig: Configuration?) {
if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) {
Timber.v("## onConfigurationChanged() : the locale has been updated to " + Locale.getDefault().toString()
+ ", restore the expected value " + VectorLocale.applicationLocale.toString())
updateApplicationSettings(VectorLocale.applicationLocale,
FontScale.getFontScalePrefValue(context),
ThemeUtils.getApplicationTheme(context))
}
}
private fun updateApplicationSettings(locale: Locale, textSize: String, theme: String) {
VectorLocale.saveApplicationLocale(context, locale)
FontScale.saveFontScale(context, textSize)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
config.locale = locale
config.fontScale = FontScale.getFontScale(context)
context.resources.updateConfiguration(config, context.resources.displayMetrics)
ThemeUtils.setApplicationTheme(context, theme)
// TODO PhoneNumberUtils.onLocaleUpdate()
}
/**
* Update the application theme
*
* @param theme the new theme
*/
fun updateApplicationTheme(theme: String) {
ThemeUtils.setApplicationTheme(context, theme)
updateApplicationSettings(VectorLocale.applicationLocale,
FontScale.getFontScalePrefValue(context),
theme)
}
/**
* Init the configuration from the saved one
*/
fun initConfiguration() {
VectorLocale.init(context)
val locale = VectorLocale.applicationLocale
val fontScale = FontScale.getFontScale(context)
val theme = ThemeUtils.getApplicationTheme(context)
Locale.setDefault(locale)
val config = Configuration(context.resources.configuration)
config.locale = locale
config.fontScale = fontScale
context.resources.updateConfiguration(config, context.resources.displayMetrics)
// init the theme
ThemeUtils.setApplicationTheme(context, theme)
}
/**
* Update the application locale
*
* @param locale
*/
// TODO Call from LanguagePickerActivity
fun updateApplicationLocale(locale: Locale) {
updateApplicationSettings(locale, FontScale.getFontScalePrefValue(context), ThemeUtils.getApplicationTheme(context))
}
/**
* Compute a localised 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
configuration.fontScale = FontScale.getFontScale(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
} else {
configuration.locale = locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale)
}
resources.updateConfiguration(configuration, resources.displayMetrics)
return context
}
} catch (e: Exception) {
Timber.e(e, "## getLocalisedContext() failed")
}
return context
}
/**
* Compute the locale status value
* @param activity the activity
* @return the local status value
*/
// TODO Create data class for this
fun getHash(): String {
return (VectorLocale.applicationLocale.toString()
+ "_" + FontScale.getFontScalePrefValue(context)
+ "_" + ThemeUtils.getApplicationTheme(context))
}
}

View File

@ -54,6 +54,7 @@ import im.vector.riotredesign.core.preference.UserAvatarPreference
import im.vector.riotredesign.core.preference.VectorPreference
import im.vector.riotredesign.core.utils.*
import im.vector.riotredesign.features.MainActivity
import im.vector.riotredesign.features.configuration.VectorConfiguration
import im.vector.riotredesign.features.themes.ThemeUtils
import org.koin.android.ext.android.inject
import java.lang.ref.WeakReference
@ -99,6 +100,8 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
// used to avoid requesting to enter the password for each deletion
private var mAccountPassword: String? = null
private val vectorConfiguration by inject<VectorConfiguration>()
// current publicised group list
private var mPublicisedGroups: MutableSet<String>? = null
@ -366,8 +369,10 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
findPreference(ThemeUtils.APPLICATION_THEME_KEY)
.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
if (newValue is String) {
// TODO VectorApp.updateApplicationTheme(newValue)
vectorConfiguration.updateApplicationTheme(newValue)
// Restart the Activity
activity?.let {
// Note: recreate does not apply the color correctly
it.startActivity(it.intent)
it.finish()
}
@ -1359,7 +1364,7 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CALL_RINGTONE -> {
REQUEST_CALL_RINGTONE -> {
val callRingtoneUri: Uri? = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
val thisActivity = activity
if (callRingtoneUri != null && thisActivity != null) {
@ -1368,9 +1373,9 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
}
}
REQUEST_E2E_FILE_REQUEST_CODE -> importKeys(data)
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
REQUEST_LOCALE -> {
REQUEST_NEW_PHONE_NUMBER -> refreshPhoneNumbersList()
REQUEST_PHONEBOOK_COUNTRY -> onPhonebookCountryUpdate(data)
REQUEST_LOCALE -> {
activity?.let {
startActivity(it.intent)
it.finish()

View File

@ -55,7 +55,7 @@ object ThemeUtils {
*/
fun getApplicationTheme(context: Context): String {
return PreferenceManager.getDefaultSharedPreferences(context)
.getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE)
.getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE)!!
}
/**
@ -64,20 +64,14 @@ object ThemeUtils {
* @param aTheme the new theme
*/
fun setApplicationTheme(context: Context, aTheme: String) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putString(APPLICATION_THEME_KEY, aTheme)
.apply()
/* TODO
when (aTheme) {
THEME_DARK_VALUE -> VectorApp.getInstance().setTheme(R.style.AppTheme_Dark)
THEME_BLACK_VALUE -> VectorApp.getInstance().setTheme(R.style.AppTheme_Black)
THEME_STATUS_VALUE -> VectorApp.getInstance().setTheme(R.style.AppTheme_Status)
else -> VectorApp.getInstance().setTheme(R.style.AppTheme_Light)
THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark)
THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black)
THEME_STATUS_VALUE -> context.setTheme(R.style.AppTheme_Status)
else -> context.setTheme(R.style.AppTheme_Light)
}
*/
// Clear the cache
mColorByAttr.clear()
}