Merge pull request #7159 from vector-im/feature/bma/fix_new_lint_warning
Fix lint warning
This commit is contained in:
commit
60bfd0dd42
|
@ -0,0 +1 @@
|
|||
Fix lint warning, and cleanup the code
|
|
@ -14,6 +14,7 @@
|
|||
android:id="@+id/menuDebug2"
|
||||
android:icon="@drawable/ic_debug_icon"
|
||||
android:title="Send"
|
||||
app:showAsAction="always" />
|
||||
app:showAsAction="always"
|
||||
tools:ignore="AlwaysShowAction" />
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
|
|
|
@ -131,11 +131,10 @@ class SecretStoringUtils @Inject constructor(
|
|||
*
|
||||
* The secret is encrypted using the following method: AES/GCM/NoPadding
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Throws(Exception::class)
|
||||
fun securelyStoreBytes(secret: ByteArray, keyAlias: String): ByteArray {
|
||||
return when {
|
||||
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptBytesM(secret, keyAlias)
|
||||
buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) -> encryptBytesM(secret, keyAlias)
|
||||
else -> encryptBytes(secret, keyAlias)
|
||||
}
|
||||
}
|
||||
|
@ -156,10 +155,9 @@ class SecretStoringUtils @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) {
|
||||
when {
|
||||
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any)
|
||||
buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) -> saveSecureObjectM(keyAlias, output, any)
|
||||
else -> saveSecureObject(keyAlias, output, any)
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +187,6 @@ class SecretStoringUtils @Inject constructor(
|
|||
return cipher
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey {
|
||||
val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry)
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.matrix.android.sdk.api.util
|
||||
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
|
||||
interface BuildVersionSdkIntProvider {
|
||||
/**
|
||||
* Return the current version of the Android SDK.
|
||||
|
@ -26,9 +28,13 @@ interface BuildVersionSdkIntProvider {
|
|||
* Checks the if the current OS version is equal or greater than [version].
|
||||
* @return A `non-null` result if true, `null` otherwise.
|
||||
*/
|
||||
@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
|
||||
fun <T> whenAtLeast(version: Int, result: () -> T): T? {
|
||||
return if (get() >= version) {
|
||||
result()
|
||||
} else null
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(parameter = 0)
|
||||
fun isAtLeast(version: Int) = get() >= version
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
<issue id="IconExpectedSize" severity="error" />
|
||||
<issue id="LocaleFolder" severity="error" />
|
||||
|
||||
<!-- AlwaysShowAction is considered as an error to force ignoring the issue when detected -->
|
||||
<issue id="AlwaysShowAction" severity="error" />
|
||||
|
||||
<issue id="TooManyViews" severity="warning">
|
||||
<!-- Ignore TooManyViews in debug build type -->
|
||||
<ignore path="**/src/debug/**" />
|
||||
|
@ -77,6 +80,7 @@
|
|||
<issue id="KotlinPropertyAccess" severity="error" />
|
||||
<issue id="DefaultLocale" severity="error" />
|
||||
<issue id="CheckResult" severity="error" />
|
||||
<issue id="StaticFieldLeak" severity="error" />
|
||||
|
||||
<issue id="InvalidPackage">
|
||||
<!-- Ignore error from HtmlCompressor lib -->
|
||||
|
@ -105,6 +109,9 @@
|
|||
<issue id="TypographyDashes" severity="error" />
|
||||
<issue id="PluralsCandidate" severity="error" />
|
||||
|
||||
<!-- Notification -->
|
||||
<issue id="LaunchActivityFromNotification" severity="error" />
|
||||
|
||||
<!-- DI -->
|
||||
<issue id="JvmStaticProvidesInObjectDetector" severity="error" />
|
||||
</lint>
|
||||
|
|
|
@ -23,7 +23,7 @@ import android.content.IntentFilter
|
|||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import im.vector.app.core.debug.DebugReceiver
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.utils.lsFiles
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -31,7 +31,10 @@ import javax.inject.Inject
|
|||
/**
|
||||
* Receiver to handle some command from ADB
|
||||
*/
|
||||
class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugReceiver {
|
||||
class VectorDebugReceiver @Inject constructor(
|
||||
@DefaultPreferences
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
) : BroadcastReceiver(), DebugReceiver {
|
||||
|
||||
override fun register(context: Context) {
|
||||
context.registerReceiver(this, getIntentFilter(context))
|
||||
|
@ -47,14 +50,14 @@ class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugRece
|
|||
intent.action?.let {
|
||||
when {
|
||||
it.endsWith(DEBUG_ACTION_DUMP_FILESYSTEM) -> lsFiles(context)
|
||||
it.endsWith(DEBUG_ACTION_DUMP_PREFERENCES) -> dumpPreferences(context)
|
||||
it.endsWith(DEBUG_ACTION_ALTER_SCALAR_TOKEN) -> alterScalarToken(context)
|
||||
it.endsWith(DEBUG_ACTION_DUMP_PREFERENCES) -> dumpPreferences()
|
||||
it.endsWith(DEBUG_ACTION_ALTER_SCALAR_TOKEN) -> alterScalarToken()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpPreferences(context: Context) {
|
||||
logPrefs("DefaultSharedPreferences", DefaultSharedPreferences.getInstance(context))
|
||||
private fun dumpPreferences() {
|
||||
logPrefs("DefaultSharedPreferences", sharedPreferences)
|
||||
}
|
||||
|
||||
private fun logPrefs(name: String, sharedPreferences: SharedPreferences?) {
|
||||
|
@ -67,8 +70,8 @@ class VectorDebugReceiver @Inject constructor() : BroadcastReceiver(), DebugRece
|
|||
}
|
||||
}
|
||||
|
||||
private fun alterScalarToken(context: Context) {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
private fun alterScalarToken() {
|
||||
sharedPreferences.edit {
|
||||
// putString("SCALAR_TOKEN_PREFERENCE_KEY" + Matrix.getInstance(context).defaultSession.myUserId, "bad_token")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package im.vector.app.push.fcm
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
|
@ -24,7 +25,7 @@ import com.google.android.gms.common.GoogleApiAvailability
|
|||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.pushers.FcmHelper
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import timber.log.Timber
|
||||
|
@ -35,14 +36,13 @@ import javax.inject.Inject
|
|||
* It has an alter ego in the fdroid variant.
|
||||
*/
|
||||
class GoogleFcmHelper @Inject constructor(
|
||||
context: Context,
|
||||
@DefaultPreferences
|
||||
private val sharedPrefs: SharedPreferences,
|
||||
) : FcmHelper {
|
||||
companion object {
|
||||
private const val PREFS_KEY_FCM_TOKEN = "FCM_TOKEN"
|
||||
}
|
||||
|
||||
private val sharedPrefs = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
override fun isFirebaseAvailable(): Boolean = true
|
||||
|
||||
override fun getFcmToken(): String? {
|
||||
|
|
|
@ -53,7 +53,7 @@ import im.vector.app.core.resources.BuildMeta
|
|||
import im.vector.app.features.analytics.VectorAnalytics
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.configuration.VectorConfiguration
|
||||
import im.vector.app.features.disclaimer.doNotShowDisclaimerDialog
|
||||
import im.vector.app.features.disclaimer.DisclaimerDialog
|
||||
import im.vector.app.features.invite.InvitesAcceptor
|
||||
import im.vector.app.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
|
@ -109,6 +109,8 @@ class VectorApplication :
|
|||
@Inject lateinit var fcmHelper: FcmHelper
|
||||
@Inject lateinit var buildMeta: BuildMeta
|
||||
@Inject lateinit var leakDetector: LeakDetector
|
||||
@Inject lateinit var vectorLocale: VectorLocale
|
||||
@Inject lateinit var disclaimerDialog: DisclaimerDialog
|
||||
|
||||
// font thread handler
|
||||
private var fontThreadHandler: Handler? = null
|
||||
|
@ -159,7 +161,7 @@ class VectorApplication :
|
|||
R.array.com_google_android_gms_fonts_certs
|
||||
)
|
||||
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
|
||||
VectorLocale.init(this, buildMeta)
|
||||
vectorLocale.init()
|
||||
ThemeUtils.init(this)
|
||||
vectorConfiguration.applyToApplicationContext()
|
||||
|
||||
|
@ -171,7 +173,7 @@ class VectorApplication :
|
|||
val sessionImported = legacySessionImporter.process()
|
||||
if (!sessionImported) {
|
||||
// Do not display the name change popup
|
||||
doNotShowDisclaimerDialog(this)
|
||||
disclaimerDialog.doNotShowDisclaimerDialog()
|
||||
}
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import android.os.Build
|
||||
import java.lang.reflect.Field
|
||||
|
||||
/**
|
||||
* Used to override [Build.VERSION.SDK_INT]. Ideally an interface should be used instead, but that approach forces us to either add suppress lint annotations
|
||||
* and potentially miss an API version issue or write a custom lint rule, which seems like an overkill.
|
||||
*/
|
||||
object AndroidVersionTestOverrider {
|
||||
|
||||
private var initialValue: Int? = null
|
||||
|
||||
fun override(newVersion: Int) {
|
||||
if (initialValue == null) {
|
||||
initialValue = Build.VERSION.SDK_INT
|
||||
}
|
||||
val field = Build.VERSION::class.java.getField("SDK_INT")
|
||||
setStaticField(field, newVersion)
|
||||
}
|
||||
|
||||
fun restore() {
|
||||
initialValue?.let { override(it) }
|
||||
}
|
||||
|
||||
private fun setStaticField(field: Field, value: Any) {
|
||||
field.isAccessible = true
|
||||
field.set(null, value)
|
||||
}
|
||||
}
|
|
@ -18,8 +18,6 @@ package im.vector.app
|
|||
|
||||
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
|
||||
|
||||
class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider {
|
||||
var value: Int = 0
|
||||
|
||||
class TestBuildVersionSdkIntProvider(var value: Int = 0) : BuildVersionSdkIntProvider {
|
||||
override fun get() = value
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.security.keystore.KeyProperties
|
|||
import android.util.Base64
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import im.vector.app.TestBuildVersionSdkIntProvider
|
||||
import im.vector.app.features.pin.PinCodeStore
|
||||
import im.vector.app.features.pin.SharedPrefPinCodeStore
|
||||
import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants.ANDROID_KEY_STORE
|
||||
|
@ -32,7 +33,6 @@ import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants.LE
|
|||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -42,7 +42,6 @@ import org.amshove.kluent.shouldBeEqualTo
|
|||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.securestorage.SecretStoringUtils
|
||||
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
|
@ -66,9 +65,7 @@ class LegacyPinCodeMigratorTests {
|
|||
SharedPrefPinCodeStore(PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().context))
|
||||
)
|
||||
private val keyStore: KeyStore = spyk(KeyStore.getInstance(ANDROID_KEY_STORE)).also { it.load(null) }
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider = mockk {
|
||||
every { get() } returns Build.VERSION_CODES.M
|
||||
}
|
||||
private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider(Build.VERSION_CODES.M)
|
||||
private val secretStoringUtils: SecretStoringUtils = spyk(
|
||||
SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider)
|
||||
)
|
||||
|
@ -125,26 +122,18 @@ class LegacyPinCodeMigratorTests {
|
|||
|
||||
@Test
|
||||
fun migratePinCodeM() = runTest {
|
||||
val pinCode = "1234"
|
||||
saveLegacyPinCode(pinCode)
|
||||
|
||||
legacyPinCodeMigrator.migrate()
|
||||
|
||||
coVerify { legacyPinCodeMigrator.getDecryptedPinCode() }
|
||||
verify { secretStoringUtils.securelyStoreBytes(any(), any()) }
|
||||
coVerify { pinCodeStore.savePinCode(any()) }
|
||||
verify { keyStore.deleteEntry(LEGACY_PIN_CODE_KEY_ALIAS) }
|
||||
|
||||
val decodedPinCode = String(secretStoringUtils.loadSecureSecretBytes(Base64.decode(pinCodeStore.getPinCode().orEmpty(), Base64.NO_WRAP), alias))
|
||||
decodedPinCode shouldBeEqualTo pinCode
|
||||
keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS) shouldBe false
|
||||
keyStore.containsAlias(alias) shouldBe true
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||
migratePinCode()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun migratePinCodeL() = runTest {
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||
migratePinCode()
|
||||
}
|
||||
|
||||
private suspend fun migratePinCode() {
|
||||
val pinCode = "1234"
|
||||
every { buildVersionSdkIntProvider.get() } returns Build.VERSION_CODES.LOLLIPOP
|
||||
saveLegacyPinCode(pinCode)
|
||||
|
||||
legacyPinCodeMigrator.migrate()
|
||||
|
@ -163,7 +152,7 @@ class LegacyPinCodeMigratorTests {
|
|||
private fun generateLegacyKey() {
|
||||
if (keyStore.containsAlias(LEGACY_PIN_CODE_KEY_ALIAS)) return
|
||||
|
||||
if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) {
|
||||
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) {
|
||||
generateLegacyKeyM()
|
||||
} else {
|
||||
generateLegacyKeyL()
|
||||
|
@ -206,7 +195,7 @@ class LegacyPinCodeMigratorTests {
|
|||
generateLegacyKey()
|
||||
val publicKey = keyStore.getCertificate(LEGACY_PIN_CODE_KEY_ALIAS).publicKey
|
||||
val cipher = getLegacyCipher()
|
||||
if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) {
|
||||
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) {
|
||||
val unrestrictedKey = KeyFactory.getInstance(publicKey.algorithm).generatePublic(X509EncodedKeySpec(publicKey.encoded))
|
||||
val spec = OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT)
|
||||
cipher.init(Cipher.ENCRYPT_MODE, unrestrictedKey, spec)
|
||||
|
@ -219,14 +208,15 @@ class LegacyPinCodeMigratorTests {
|
|||
}
|
||||
|
||||
private fun getLegacyCipher(): Cipher {
|
||||
return when (buildVersionSdkIntProvider.get()) {
|
||||
Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.LOLLIPOP_MR1 -> getCipherL()
|
||||
else -> getCipherM()
|
||||
return if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) {
|
||||
getCipherM()
|
||||
} else {
|
||||
getCipherL()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCipherL(): Cipher {
|
||||
val provider = if (buildVersionSdkIntProvider.get() < Build.VERSION_CODES.M) "AndroidOpenSSL" else "AndroidKeyStoreBCWorkaround"
|
||||
val provider = if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) "AndroidKeyStoreBCWorkaround" else "AndroidOpenSSL"
|
||||
val transformation = "RSA/ECB/PKCS1Padding"
|
||||
return Cipher.getInstance(transformation, provider)
|
||||
}
|
||||
|
|
|
@ -18,41 +18,36 @@ package im.vector.app.features.voice
|
|||
|
||||
import android.os.Build
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import im.vector.app.AndroidVersionTestOverrider
|
||||
import im.vector.app.TestBuildVersionSdkIntProvider
|
||||
import im.vector.app.features.DefaultVectorFeatures
|
||||
import io.mockk.every
|
||||
import io.mockk.spyk
|
||||
import org.amshove.kluent.shouldBeInstanceOf
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
|
||||
class VoiceRecorderProviderTests {
|
||||
|
||||
private val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
private val provider = spyk(VoiceRecorderProvider(context, DefaultVectorFeatures()))
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
AndroidVersionTestOverrider.restore()
|
||||
}
|
||||
private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider()
|
||||
private val provider = spyk(VoiceRecorderProvider(context, DefaultVectorFeatures(), buildVersionSdkIntProvider))
|
||||
|
||||
@Test
|
||||
fun provideVoiceRecorderOnAndroidQAndCodecReturnsQRecorder() {
|
||||
AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q)
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.Q
|
||||
every { provider.hasOpusEncoder() } returns true
|
||||
provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderQ::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun provideVoiceRecorderOnAndroidQButNoCodecReturnsLRecorder() {
|
||||
AndroidVersionTestOverrider.override(Build.VERSION_CODES.Q)
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.Q
|
||||
every { provider.hasOpusEncoder() } returns false
|
||||
provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderL::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun provideVoiceRecorderOnOlderAndroidVersionReturnsLRecorder() {
|
||||
AndroidVersionTestOverrider.override(Build.VERSION_CODES.LOLLIPOP)
|
||||
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||
provider.provideVoiceRecorder().shouldBeInstanceOf(VoiceRecorderL::class)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.core.extensions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.ConnectivityManager
|
||||
|
@ -91,10 +90,9 @@ fun Context.safeOpenOutputStream(uri: Uri): OutputStream? {
|
|||
*
|
||||
* @return true if no active connection is found
|
||||
*/
|
||||
@SuppressLint("NewApi") // false positive
|
||||
fun Context.inferNoConnectivity(sdkIntProvider: BuildVersionSdkIntProvider): Boolean {
|
||||
val connectivityManager = getSystemService<ConnectivityManager>()!!
|
||||
return if (sdkIntProvider.get() > Build.VERSION_CODES.M) {
|
||||
return if (sdkIntProvider.isAtLeast(Build.VERSION_CODES.M)) {
|
||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||
when {
|
||||
networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true -> false
|
||||
|
|
|
@ -84,6 +84,7 @@ import im.vector.app.features.rageshake.RageShake
|
|||
import im.vector.app.features.session.SessionListener
|
||||
import im.vector.app.features.settings.FontScalePreferences
|
||||
import im.vector.app.features.settings.FontScalePreferencesImpl
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.themes.ActivityOtherThemes
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
@ -155,6 +156,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
@Inject lateinit var rageShake: RageShake
|
||||
@Inject lateinit var buildMeta: BuildMeta
|
||||
@Inject lateinit var fontScalePreferences: FontScalePreferences
|
||||
@Inject lateinit var vectorLocale: VectorLocaleProvider
|
||||
|
||||
// For debug only
|
||||
@Inject lateinit var debugReceiver: DebugReceiver
|
||||
|
@ -176,8 +178,10 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
|
|||
private val restorables = ArrayList<Restorable>()
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
val fontScalePreferences = FontScalePreferencesImpl(PreferenceManager.getDefaultSharedPreferences(base), AndroidSystemSettingsProvider(base))
|
||||
val vectorConfiguration = VectorConfiguration(this, fontScalePreferences)
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(base)
|
||||
val fontScalePreferences = FontScalePreferencesImpl(preferences, AndroidSystemSettingsProvider(base))
|
||||
val vectorLocaleProvider = VectorLocaleProvider(preferences)
|
||||
val vectorConfiguration = VectorConfiguration(this, fontScalePreferences, vectorLocaleProvider)
|
||||
super.attachBaseContext(vectorConfiguration.getLocalisedContext(base))
|
||||
}
|
||||
|
||||
|
|
|
@ -17,16 +17,17 @@
|
|||
package im.vector.app.core.pushers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import javax.inject.Inject
|
||||
|
||||
class UnifiedPushStore @Inject constructor(
|
||||
val context: Context,
|
||||
val fcmHelper: FcmHelper
|
||||
val fcmHelper: FcmHelper,
|
||||
@DefaultPreferences
|
||||
private val defaultPrefs: SharedPreferences,
|
||||
) {
|
||||
private val defaultPrefs = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
/**
|
||||
* Retrieves the UnifiedPush Endpoint.
|
||||
*
|
||||
|
|
|
@ -20,11 +20,10 @@ import android.content.Context
|
|||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.databinding.ViewKeysBackupBannerBinding
|
||||
import im.vector.app.features.workers.signout.BannerState
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
@ -38,16 +37,12 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnClickListener {
|
||||
|
||||
var delegate: Delegate? = null
|
||||
private var state: State = State.Initial
|
||||
private var state: BannerState = BannerState.Initial
|
||||
|
||||
private lateinit var views: ViewKeysBackupBannerBinding
|
||||
|
||||
init {
|
||||
setupView()
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, "")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,7 +51,7 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
* @param newState the newState representing the view
|
||||
* @param force true to force the rendering of the view
|
||||
*/
|
||||
fun render(newState: State, force: Boolean = false) {
|
||||
fun render(newState: BannerState, force: Boolean = false) {
|
||||
if (newState == state && !force) {
|
||||
Timber.v("State unchanged")
|
||||
return
|
||||
|
@ -67,48 +62,26 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
|
||||
hideAll()
|
||||
when (newState) {
|
||||
State.Initial -> renderInitial()
|
||||
State.Hidden -> renderHidden()
|
||||
is State.Setup -> renderSetup(newState.numberOfKeys)
|
||||
is State.Recover -> renderRecover(newState.version)
|
||||
is State.Update -> renderUpdate(newState.version)
|
||||
State.BackingUp -> renderBackingUp()
|
||||
BannerState.Initial -> renderInitial()
|
||||
BannerState.Hidden -> renderHidden()
|
||||
is BannerState.Setup -> renderSetup(newState)
|
||||
is BannerState.Recover -> renderRecover(newState)
|
||||
is BannerState.Update -> renderUpdate(newState)
|
||||
BannerState.BackingUp -> renderBackingUp()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
when (state) {
|
||||
is State.Setup -> delegate?.setupKeysBackup()
|
||||
is State.Update,
|
||||
is State.Recover -> delegate?.recoverKeysBackup()
|
||||
is BannerState.Setup -> delegate?.setupKeysBackup()
|
||||
is BannerState.Update,
|
||||
is BannerState.Recover -> delegate?.recoverKeysBackup()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCloseClicked() {
|
||||
state.let {
|
||||
when (it) {
|
||||
is State.Setup -> {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, true)
|
||||
}
|
||||
}
|
||||
is State.Recover -> {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, it.version)
|
||||
}
|
||||
}
|
||||
is State.Update -> {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, it.version)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Should not happen, close button is not displayed in other cases
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate?.onCloseClicked()
|
||||
// Force refresh
|
||||
render(state, true)
|
||||
}
|
||||
|
@ -133,9 +106,8 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
isVisible = false
|
||||
}
|
||||
|
||||
private fun renderSetup(nbOfKeys: Int) {
|
||||
if (nbOfKeys == 0 ||
|
||||
DefaultSharedPreferences.getInstance(context).getBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)) {
|
||||
private fun renderSetup(state: BannerState.Setup) {
|
||||
if (state.numberOfKeys == 0 || state.doNotShowAgain) {
|
||||
// Do not display the setup banner if there is no keys to backup, or if the user has already closed it
|
||||
isVisible = false
|
||||
} else {
|
||||
|
@ -148,8 +120,8 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun renderRecover(version: String) {
|
||||
if (version == DefaultSharedPreferences.getInstance(context).getString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
private fun renderRecover(state: BannerState.Recover) {
|
||||
if (state.version == state.doNotShowForVersion) {
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
|
@ -161,8 +133,8 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun renderUpdate(version: String) {
|
||||
if (version == DefaultSharedPreferences.getInstance(context).getString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
private fun renderUpdate(state: BannerState.Update) {
|
||||
if (state.version == state.doNotShowForVersion) {
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
|
@ -191,61 +163,12 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
views.viewKeysBackupBannerLoading.isVisible = false
|
||||
}
|
||||
|
||||
/**
|
||||
* The state representing the view.
|
||||
* It can take one state at a time.
|
||||
*/
|
||||
sealed class State {
|
||||
// Not yet rendered
|
||||
object Initial : State()
|
||||
|
||||
// View will be Gone
|
||||
object Hidden : State()
|
||||
|
||||
// Keys backup is not setup, numberOfKeys is the number of locally stored keys
|
||||
data class Setup(val numberOfKeys: Int) : State()
|
||||
|
||||
// Keys backup can be recovered, with version from the server
|
||||
data class Recover(val version: String) : State()
|
||||
|
||||
// Keys backup can be updated
|
||||
data class Update(val version: String) : State()
|
||||
|
||||
// Keys are backing up
|
||||
object BackingUp : State()
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface to delegate some actions to another object.
|
||||
*/
|
||||
interface Delegate {
|
||||
fun onCloseClicked()
|
||||
fun setupKeysBackup()
|
||||
fun recoverKeysBackup()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Preference key for setup. Value is a boolean.
|
||||
*/
|
||||
private const val BANNER_SETUP_DO_NOT_SHOW_AGAIN = "BANNER_SETUP_DO_NOT_SHOW_AGAIN"
|
||||
|
||||
/**
|
||||
* Preference key for recover. Value is a backup version (String).
|
||||
*/
|
||||
private const val BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION = "BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION"
|
||||
|
||||
/**
|
||||
* Preference key for update. Value is a backup version (String).
|
||||
*/
|
||||
private const val BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION = "BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION"
|
||||
|
||||
/**
|
||||
* Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version.
|
||||
*/
|
||||
fun onRecoverDoneForVersion(context: Context, version: String) {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,103 +17,109 @@
|
|||
package im.vector.app.core.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.media.Ringtone
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import androidx.core.content.edit
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This file manages the sound ringtone for calls.
|
||||
* It allows you to use the default Riot Ringtone, or the standard ringtone or set a different one from the available choices
|
||||
* This class manages the sound ringtone for calls.
|
||||
* It allows you to use the default Element Ringtone, or the standard ringtone or set a different one from the available choices
|
||||
* in Android.
|
||||
*/
|
||||
class RingtoneUtils @Inject constructor(
|
||||
@DefaultPreferences
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val context: Context,
|
||||
) {
|
||||
/**
|
||||
* Returns a Uri object that points to a specific Ringtone.
|
||||
*
|
||||
* If no Ringtone was explicitly set using Riot, it will return the Uri for the current system
|
||||
* ringtone for calls.
|
||||
*
|
||||
* @return the [Uri] of the currently set [Ringtone]
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun getCallRingtoneUri(): Uri? {
|
||||
val callRingtone: String? = sharedPreferences
|
||||
.getString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, null)
|
||||
|
||||
/**
|
||||
* Returns a Uri object that points to a specific Ringtone.
|
||||
*
|
||||
* If no Ringtone was explicitly set using Riot, it will return the Uri for the current system
|
||||
* ringtone for calls.
|
||||
*
|
||||
* @return the [Uri] of the currently set [Ringtone]
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun getCallRingtoneUri(context: Context): Uri? {
|
||||
val callRingtone: String? = DefaultSharedPreferences.getInstance(context)
|
||||
.getString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, null)
|
||||
callRingtone?.let {
|
||||
return Uri.parse(it)
|
||||
}
|
||||
|
||||
callRingtone?.let {
|
||||
return Uri.parse(it)
|
||||
return try {
|
||||
// Use current system notification sound for incoming calls per default (note that it can return null)
|
||||
RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE)
|
||||
} catch (e: SecurityException) {
|
||||
// Ignore for now
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return try {
|
||||
// Use current system notification sound for incoming calls per default (note that it can return null)
|
||||
RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE)
|
||||
} catch (e: SecurityException) {
|
||||
// Ignore for now
|
||||
null
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns a Ringtone object that can then be played.
|
||||
*
|
||||
* If no Ringtone was explicitly set using Riot, it will return the current system ringtone
|
||||
* for calls.
|
||||
*
|
||||
* @return the currently set [Ringtone]
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun getCallRingtone(): Ringtone? {
|
||||
getCallRingtoneUri()?.let {
|
||||
// Note that it can also return null
|
||||
return RingtoneManager.getRingtone(context, it)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Ringtone object that can then be played.
|
||||
*
|
||||
* If no Ringtone was explicitly set using Riot, it will return the current system ringtone
|
||||
* for calls.
|
||||
*
|
||||
* @return the currently set [Ringtone]
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun getCallRingtone(context: Context): Ringtone? {
|
||||
getCallRingtoneUri(context)?.let {
|
||||
// Note that it can also return null
|
||||
return RingtoneManager.getRingtone(context, it)
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
/**
|
||||
* Returns a String with the name of the current Ringtone.
|
||||
*
|
||||
* If no Ringtone was explicitly set using Riot, it will return the name of the current system
|
||||
* ringtone for calls.
|
||||
*
|
||||
* @return the name of the currently set [Ringtone], or null
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun getCallRingtoneName(): String? {
|
||||
return getCallRingtone()?.getTitle(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String with the name of the current Ringtone.
|
||||
*
|
||||
* If no Ringtone was explicitly set using Riot, it will return the name of the current system
|
||||
* ringtone for calls.
|
||||
*
|
||||
* @return the name of the currently set [Ringtone], or null
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun getCallRingtoneName(context: Context): String? {
|
||||
return getCallRingtone(context)?.getTitle(context)
|
||||
}
|
||||
/**
|
||||
* Sets the selected ringtone for riot calls.
|
||||
*
|
||||
* @param ringtoneUri
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun setCallRingtoneUri(ringtoneUri: Uri) {
|
||||
sharedPreferences
|
||||
.edit {
|
||||
putString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, ringtoneUri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected ringtone for riot calls.
|
||||
*
|
||||
* @param context Android context
|
||||
* @param ringtoneUri
|
||||
* @see Ringtone
|
||||
*/
|
||||
fun setCallRingtoneUri(context: Context, ringtoneUri: Uri) {
|
||||
DefaultSharedPreferences.getInstance(context)
|
||||
.edit {
|
||||
putString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, ringtoneUri.toString())
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set using Riot default ringtone.
|
||||
*/
|
||||
fun useRiotDefaultRingtone(): Boolean {
|
||||
return sharedPreferences.getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set using Riot default ringtone.
|
||||
*/
|
||||
fun useRiotDefaultRingtone(context: Context): Boolean {
|
||||
return DefaultSharedPreferences.getInstance(context).getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask if default Riot ringtone has to be used.
|
||||
*/
|
||||
fun setUseRiotDefaultRingtone(context: Context, useRiotDefault: Boolean) {
|
||||
DefaultSharedPreferences.getInstance(context)
|
||||
.edit {
|
||||
putBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, useRiotDefault)
|
||||
}
|
||||
/**
|
||||
* Ask if default Riot ringtone has to be used.
|
||||
*/
|
||||
fun setUseRiotDefaultRingtone(useRiotDefault: Boolean) {
|
||||
sharedPreferences
|
||||
.edit {
|
||||
putBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, useRiotDefault)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import im.vector.app.core.utils.toBase32String
|
|||
import im.vector.app.features.call.conference.jwt.JitsiJWTFactory
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import im.vector.app.features.themes.ThemeProvider
|
||||
import okhttp3.Request
|
||||
import org.jitsi.meet.sdk.JitsiMeetUserInfo
|
||||
|
@ -49,6 +49,7 @@ class JitsiService @Inject constructor(
|
|||
private val themeProvider: ThemeProvider,
|
||||
private val jitsiJWTFactory: JitsiJWTFactory,
|
||||
private val clock: Clock,
|
||||
private val vectorLocale: VectorLocaleProvider,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
@ -163,7 +164,7 @@ class JitsiService @Inject constructor(
|
|||
if (widgetSessionId.length > 8) {
|
||||
widgetSessionId = widgetSessionId.substring(0, 7)
|
||||
}
|
||||
roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.lowercase(VectorLocale.applicationLocale)
|
||||
roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.lowercase(vectorLocale.applicationLocale)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,15 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.addChildFragment
|
||||
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
|
||||
import im.vector.app.databinding.BottomSheetCallDialPadBinding
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCallDialPadBinding>() {
|
||||
|
||||
companion object {
|
||||
|
@ -41,6 +44,8 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCa
|
|||
}
|
||||
}
|
||||
|
||||
@Inject lateinit var vectorLocale: VectorLocaleProvider
|
||||
|
||||
override val showExpanded = true
|
||||
|
||||
var callback: DialPadFragment.Callback? = null
|
||||
|
@ -62,7 +67,7 @@ class CallDialPadBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetCa
|
|||
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, showActions)
|
||||
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, showActions)
|
||||
putBoolean(DialPadFragment.EXTRA_CURSOR_VISIBLE, false)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, vectorLocale.applicationLocale.country)
|
||||
}
|
||||
callback = DialPadFragmentCallbackWrapper(this@CallDialPadBottomSheet.callback)
|
||||
}.also {
|
||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.app.core.extensions.addFragment
|
|||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.createdirect.DirectRoomHelper
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -78,7 +77,7 @@ class PstnDialActivity : SimpleFragmentActivity() {
|
|||
arguments = Bundle().apply {
|
||||
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true)
|
||||
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, vectorLocale.applicationLocale.country)
|
||||
}
|
||||
callback = object : DialPadFragment.Callback {
|
||||
override fun onOkClicked(formatted: String?, raw: String?) {
|
||||
|
|
|
@ -59,7 +59,7 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
sectionsPagerAdapter = CallTransferPagerAdapter(this)
|
||||
sectionsPagerAdapter = CallTransferPagerAdapter(this, vectorLocale)
|
||||
views.callTransferViewPager.adapter = sectionsPagerAdapter
|
||||
|
||||
TabLayoutMediator(views.callTransferTabLayout, views.callTransferViewPager) { tab, position ->
|
||||
|
|
|
@ -22,12 +22,13 @@ import androidx.fragment.app.FragmentActivity
|
|||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import im.vector.app.core.extensions.toMvRxBundle
|
||||
import im.vector.app.features.call.dialpad.DialPadFragment
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import im.vector.app.features.userdirectory.UserListFragment
|
||||
import im.vector.app.features.userdirectory.UserListFragmentArgs
|
||||
|
||||
class CallTransferPagerAdapter(
|
||||
private val fragmentActivity: FragmentActivity
|
||||
private val fragmentActivity: FragmentActivity,
|
||||
private val vectorLocale: VectorLocaleProvider,
|
||||
) : FragmentStateAdapter(fragmentActivity) {
|
||||
|
||||
companion object {
|
||||
|
@ -61,7 +62,7 @@ class CallTransferPagerAdapter(
|
|||
arguments = Bundle().apply {
|
||||
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true)
|
||||
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, false)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, vectorLocale.applicationLocale.country)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import android.os.Build
|
|||
import android.os.LocaleList
|
||||
import androidx.annotation.RequiresApi
|
||||
import im.vector.app.features.settings.FontScalePreferences
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
|
@ -33,21 +33,22 @@ import javax.inject.Inject
|
|||
*/
|
||||
class VectorConfiguration @Inject constructor(
|
||||
private val context: Context,
|
||||
private val fontScalePreferences: FontScalePreferences
|
||||
private val fontScalePreferences: FontScalePreferences,
|
||||
private val vectorLocale: VectorLocaleProvider,
|
||||
) {
|
||||
|
||||
fun onConfigurationChanged() {
|
||||
if (Locale.getDefault().toString() != VectorLocale.applicationLocale.toString()) {
|
||||
if (Locale.getDefault().toString() != vectorLocale.applicationLocale.toString()) {
|
||||
Timber.v("## onConfigurationChanged(): the locale has been updated to ${Locale.getDefault()}")
|
||||
Timber.v("## onConfigurationChanged(): restore the expected value ${VectorLocale.applicationLocale}")
|
||||
Locale.setDefault(VectorLocale.applicationLocale)
|
||||
Timber.v("## onConfigurationChanged(): restore the expected value ${vectorLocale.applicationLocale}")
|
||||
Locale.setDefault(vectorLocale.applicationLocale)
|
||||
}
|
||||
// Night mode may have changed
|
||||
ThemeUtils.init(context)
|
||||
}
|
||||
|
||||
fun applyToApplicationContext() {
|
||||
val locale = VectorLocale.applicationLocale
|
||||
val locale = vectorLocale.applicationLocale
|
||||
val fontScale = fontScalePreferences.getResolvedFontScaleValue()
|
||||
|
||||
Locale.setDefault(locale)
|
||||
|
@ -67,7 +68,7 @@ class VectorConfiguration @Inject constructor(
|
|||
*/
|
||||
fun getLocalisedContext(context: Context): Context {
|
||||
try {
|
||||
val locale = VectorLocale.applicationLocale
|
||||
val locale = vectorLocale.applicationLocale
|
||||
|
||||
// create new configuration passing old configuration from original Context
|
||||
val configuration = Configuration(context.resources.configuration)
|
||||
|
@ -107,7 +108,7 @@ class VectorConfiguration @Inject constructor(
|
|||
* @return the local status value
|
||||
*/
|
||||
fun getHash(): String {
|
||||
return (VectorLocale.applicationLocale.toString() +
|
||||
return (vectorLocale.applicationLocale.toString() +
|
||||
"_" + fontScalePreferences.getResolvedFontScaleValue().preferenceValue +
|
||||
"_" + ThemeUtils.getApplicationTheme(context))
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.crypto.keysbackup.restore
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.airbnb.mvrx.viewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
|
@ -27,8 +28,9 @@ import im.vector.app.core.extensions.observeEvent
|
|||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||
import im.vector.app.core.ui.views.KeysBackupBanner
|
||||
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusAction
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -46,6 +48,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
|
|||
override fun getTitleRes() = R.string.title_activity_keys_backup_restore
|
||||
|
||||
private lateinit var viewModel: KeysBackupRestoreSharedViewModel
|
||||
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by viewModel()
|
||||
|
||||
override fun onBackPressed() {
|
||||
hideWaitingView()
|
||||
|
@ -95,7 +98,8 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
|
|||
}
|
||||
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_SUCCESS -> {
|
||||
viewModel.keyVersionResult.value?.version?.let {
|
||||
KeysBackupBanner.onRecoverDoneForVersion(this, it)
|
||||
// Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version.
|
||||
serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnRecoverDoneForVersion(it))
|
||||
}
|
||||
replaceFragment(views.container, KeysBackupRestoreSuccessFragment::class.java, allowStateLoss = true)
|
||||
}
|
||||
|
|
|
@ -29,9 +29,10 @@ import im.vector.app.R
|
|||
import im.vector.app.core.extensions.hidePassword
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentKeysBackupSetupStep2Binding
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class KeysBackupSetupStep2Fragment :
|
||||
|
@ -43,6 +44,8 @@ class KeysBackupSetupStep2Fragment :
|
|||
|
||||
private val zxcvbn = Zxcvbn()
|
||||
|
||||
@Inject lateinit var vectorLocale: VectorLocaleProvider
|
||||
|
||||
private fun onPassphraseChanged() {
|
||||
viewModel.passphrase.value = views.keysBackupSetupStep2PassphraseEnterEdittext.text.toString()
|
||||
viewModel.confirmPassphraseError.value = null
|
||||
|
@ -78,12 +81,12 @@ class KeysBackupSetupStep2Fragment :
|
|||
views.keysBackupSetupStep2PassphraseStrengthLevel.strength = score
|
||||
|
||||
if (score in 1..3) {
|
||||
val warning = strength.feedback?.getWarning(VectorLocale.applicationLocale)
|
||||
val warning = strength.feedback?.getWarning(vectorLocale.applicationLocale)
|
||||
if (warning != null) {
|
||||
views.keysBackupSetupStep2PassphraseEnterTil.error = warning
|
||||
}
|
||||
|
||||
val suggestions = strength.feedback?.getSuggestions(VectorLocale.applicationLocale)
|
||||
val suggestions = strength.feedback?.getSuggestions(vectorLocale.applicationLocale)
|
||||
if (suggestions != null) {
|
||||
views.keysBackupSetupStep2PassphraseEnterTil.error = suggestions.firstOrNull()
|
||||
}
|
||||
|
|
|
@ -28,12 +28,13 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.databinding.FragmentBootstrapEnterPassphraseBinding
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import im.vector.lib.core.utils.flow.throttleFirst
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.widget.editorActionEvents
|
||||
import reactivecircus.flowbinding.android.widget.textChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BootstrapEnterPassphraseFragment :
|
||||
|
@ -43,6 +44,8 @@ class BootstrapEnterPassphraseFragment :
|
|||
return FragmentBootstrapEnterPassphraseBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
@Inject lateinit var vectorLocale: VectorLocaleProvider
|
||||
|
||||
val sharedViewModel: BootstrapSharedViewModel by parentFragmentViewModel()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -105,8 +108,8 @@ class BootstrapEnterPassphraseFragment :
|
|||
views.ssssPassphraseSecurityProgress.strength = score
|
||||
if (score in 1..3) {
|
||||
val hint =
|
||||
strength.feedback?.getWarning(VectorLocale.applicationLocale)?.takeIf { it.isNotBlank() }
|
||||
?: strength.feedback?.getSuggestions(VectorLocale.applicationLocale)?.firstOrNull()
|
||||
strength.feedback?.getWarning(vectorLocale.applicationLocale)?.takeIf { it.isNotBlank() }
|
||||
?: strength.feedback?.getSuggestions(vectorLocale.applicationLocale)?.firstOrNull()
|
||||
if (hint != null && hint != views.ssssPassphraseEnterTil.error.toString()) {
|
||||
views.ssssPassphraseEnterTil.error = hint
|
||||
}
|
||||
|
|
|
@ -17,44 +17,46 @@
|
|||
package im.vector.app.features.disclaimer
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import im.vector.app.features.settings.VectorSettingsUrls
|
||||
import javax.inject.Inject
|
||||
|
||||
// Increase this value to show again the disclaimer dialog after an upgrade of the application
|
||||
private const val CURRENT_DISCLAIMER_VALUE = 2
|
||||
|
||||
const val SHARED_PREF_KEY = "LAST_DISCLAIMER_VERSION_VALUE"
|
||||
|
||||
fun showDisclaimerDialog(activity: Activity) {
|
||||
val sharedPrefs = DefaultSharedPreferences.getInstance(activity)
|
||||
class DisclaimerDialog @Inject constructor(
|
||||
@DefaultPreferences
|
||||
private val sharedPrefs: SharedPreferences,
|
||||
) {
|
||||
fun showDisclaimerDialog(activity: Activity) {
|
||||
if (sharedPrefs.getInt(SHARED_PREF_KEY, 0) < CURRENT_DISCLAIMER_VALUE) {
|
||||
sharedPrefs.edit {
|
||||
putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE)
|
||||
}
|
||||
|
||||
if (sharedPrefs.getInt(SHARED_PREF_KEY, 0) < CURRENT_DISCLAIMER_VALUE) {
|
||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_disclaimer_content, null)
|
||||
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setView(dialogLayout)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(R.string.disclaimer_negative_button, null)
|
||||
.setPositiveButton(R.string.disclaimer_positive_button) { _, _ ->
|
||||
openUrlInChromeCustomTab(activity, null, VectorSettingsUrls.DISCLAIMER_URL)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun doNotShowDisclaimerDialog() {
|
||||
sharedPrefs.edit {
|
||||
putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE)
|
||||
}
|
||||
|
||||
val dialogLayout = activity.layoutInflater.inflate(R.layout.dialog_disclaimer_content, null)
|
||||
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setView(dialogLayout)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(R.string.disclaimer_negative_button, null)
|
||||
.setPositiveButton(R.string.disclaimer_positive_button) { _, _ ->
|
||||
openUrlInChromeCustomTab(activity, null, VectorSettingsUrls.DISCLAIMER_URL)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun doNotShowDisclaimerDialog(context: Context) {
|
||||
val sharedPrefs = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
sharedPrefs.edit {
|
||||
putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewMode
|
|||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import im.vector.app.features.analytics.plan.ViewRoom
|
||||
import im.vector.app.features.crypto.recover.SetupMode
|
||||
import im.vector.app.features.disclaimer.showDisclaimerDialog
|
||||
import im.vector.app.features.disclaimer.DisclaimerDialog
|
||||
import im.vector.app.features.home.room.list.actions.RoomListSharedAction
|
||||
import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel
|
||||
import im.vector.app.features.home.room.list.home.layout.HomeLayoutSettingBottomDialogFragment
|
||||
|
@ -141,6 +141,7 @@ class HomeActivity :
|
|||
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
|
||||
@Inject lateinit var fcmHelper: FcmHelper
|
||||
@Inject lateinit var nightlyProxy: NightlyProxy
|
||||
@Inject lateinit var disclaimerDialog: DisclaimerDialog
|
||||
|
||||
private var isNewAppLayoutEnabled: Boolean = false // delete once old app layout is removed
|
||||
|
||||
|
@ -570,7 +571,7 @@ class HomeActivity :
|
|||
.setNegativeButton(R.string.no) { _, _ -> bugReporter.deleteCrashFile() }
|
||||
.show()
|
||||
} else {
|
||||
showDisclaimerDialog(this)
|
||||
disclaimerDialog.showDisclaimerDialog(this)
|
||||
}
|
||||
|
||||
// Force remote backup state update to update the banner if needed
|
||||
|
|
|
@ -51,11 +51,12 @@ import im.vector.app.features.home.room.list.RoomListParams
|
|||
import im.vector.app.features.home.room.list.UnreadCounterBadgeView
|
||||
import im.vector.app.features.popup.PopupAlertManager
|
||||
import im.vector.app.features.popup.VerificationVectorAlert
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.workers.signout.BannerState
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusAction
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
@ -75,6 +76,7 @@ class HomeDetailFragment :
|
|||
@Inject lateinit var callManager: WebRtcCallManager
|
||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
@Inject lateinit var spaceStateHandler: SpaceStateHandler
|
||||
@Inject lateinit var vectorLocale: VectorLocaleProvider
|
||||
|
||||
private val viewModel: HomeDetailViewModel by fragmentViewModel()
|
||||
private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel()
|
||||
|
@ -288,13 +290,15 @@ class HomeDetailFragment :
|
|||
}
|
||||
|
||||
private fun setupKeysBackupBanner() {
|
||||
serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerDisplayed)
|
||||
serverBackupStatusViewModel
|
||||
.onEach {
|
||||
when (val banState = it.bannerState.invoke()) {
|
||||
is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
|
||||
BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
|
||||
null,
|
||||
BannerState.Hidden -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
|
||||
is BannerState.Setup,
|
||||
BannerState.BackingUp,
|
||||
BannerState.Hidden -> views.homeKeysBackupBanner.render(banState, false)
|
||||
null -> views.homeKeysBackupBanner.render(BannerState.Hidden, false)
|
||||
else -> Unit /* No op? */
|
||||
}
|
||||
}
|
||||
views.homeKeysBackupBanner.delegate = this
|
||||
|
@ -378,7 +382,7 @@ class HomeDetailFragment :
|
|||
arguments = Bundle().apply {
|
||||
putBoolean(DialPadFragment.EXTRA_ENABLE_DELETE, true)
|
||||
putBoolean(DialPadFragment.EXTRA_ENABLE_OK, true)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, VectorLocale.applicationLocale.country)
|
||||
putString(DialPadFragment.EXTRA_REGION_CODE, vectorLocale.applicationLocale.country)
|
||||
}
|
||||
applyCallback()
|
||||
}
|
||||
|
@ -401,6 +405,10 @@ class HomeDetailFragment :
|
|||
* KeysBackupBanner Listener
|
||||
* ========================================================================================== */
|
||||
|
||||
override fun onCloseClicked() {
|
||||
serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed)
|
||||
}
|
||||
|
||||
override fun setupKeysBackup() {
|
||||
navigator.openKeysBackupSetup(requireActivity(), false)
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ import im.vector.app.features.settings.VectorPreferences
|
|||
import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS
|
||||
import im.vector.app.features.spaces.SpaceListBottomSheet
|
||||
import im.vector.app.features.workers.signout.BannerState
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusAction
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
@ -300,13 +301,15 @@ class NewHomeDetailFragment :
|
|||
}
|
||||
|
||||
private fun setupKeysBackupBanner() {
|
||||
serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerDisplayed)
|
||||
serverBackupStatusViewModel
|
||||
.onEach {
|
||||
when (val banState = it.bannerState.invoke()) {
|
||||
is BannerState.Setup -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Setup(banState.numberOfKeys), false)
|
||||
BannerState.BackingUp -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.BackingUp, false)
|
||||
null,
|
||||
BannerState.Hidden -> views.homeKeysBackupBanner.render(KeysBackupBanner.State.Hidden, false)
|
||||
is BannerState.Setup,
|
||||
BannerState.BackingUp,
|
||||
BannerState.Hidden -> views.homeKeysBackupBanner.render(banState, false)
|
||||
null -> views.homeKeysBackupBanner.render(BannerState.Hidden, false)
|
||||
else -> Unit /* No op? */
|
||||
}
|
||||
}
|
||||
views.homeKeysBackupBanner.delegate = this
|
||||
|
@ -348,6 +351,10 @@ class NewHomeDetailFragment :
|
|||
* KeysBackupBanner Listener
|
||||
* ========================================================================================== */
|
||||
|
||||
override fun onCloseClicked() {
|
||||
serverBackupStatusViewModel.handle(ServerBackupStatusAction.OnBannerClosed)
|
||||
}
|
||||
|
||||
override fun setupKeysBackup() {
|
||||
navigator.openKeysBackupSetup(requireActivity(), false)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
|||
import android.content.pm.ShortcutInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
|
@ -32,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
|
||||
private val useAdaptiveIcon = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
private const val adaptiveIconSizeDp = 108
|
||||
private const val adaptiveIconOuterSidesDp = 18
|
||||
|
|
|
@ -1124,6 +1124,7 @@ class TimelineFragment :
|
|||
.findViewById<ImageView>(R.id.action_view_icon_image)
|
||||
.setColorFilter(colorProvider.getColorFromAttribute(R.attr.colorPrimary))
|
||||
actionView.findViewById<TextView>(R.id.cart_badge).setTextOrHide("$widgetsCount")
|
||||
@Suppress("AlwaysShowAction")
|
||||
matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,29 +16,36 @@
|
|||
|
||||
package im.vector.app.features.homeserver
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Object to store and retrieve home and identity server urls.
|
||||
*/
|
||||
object ServerUrlsRepository {
|
||||
class ServerUrlsRepository @Inject constructor(
|
||||
@DefaultPreferences
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
) {
|
||||
companion object {
|
||||
// Keys used to store default servers urls from the referrer
|
||||
private const val DEFAULT_REFERRER_HOME_SERVER_URL_PREF = "default_referrer_home_server_url"
|
||||
private const val DEFAULT_REFERRER_IDENTITY_SERVER_URL_PREF = "default_referrer_identity_server_url"
|
||||
|
||||
// Keys used to store default servers urls from the referrer
|
||||
private const val DEFAULT_REFERRER_HOME_SERVER_URL_PREF = "default_referrer_home_server_url"
|
||||
private const val DEFAULT_REFERRER_IDENTITY_SERVER_URL_PREF = "default_referrer_identity_server_url"
|
||||
|
||||
// Keys used to store current homeserver url and identity url
|
||||
const val HOME_SERVER_URL_PREF = "home_server_url"
|
||||
const val IDENTITY_SERVER_URL_PREF = "identity_server_url"
|
||||
// Keys used to store current homeserver url and identity url
|
||||
const val HOME_SERVER_URL_PREF = "home_server_url"
|
||||
const val IDENTITY_SERVER_URL_PREF = "identity_server_url"
|
||||
}
|
||||
|
||||
/**
|
||||
* Save home and identity sever urls received by the Referrer receiver.
|
||||
*/
|
||||
fun setDefaultUrlsFromReferrer(context: Context, homeServerUrl: String, identityServerUrl: String) {
|
||||
DefaultSharedPreferences.getInstance(context)
|
||||
fun setDefaultUrlsFromReferrer(homeServerUrl: String, identityServerUrl: String) {
|
||||
sharedPreferences
|
||||
.edit {
|
||||
if (homeServerUrl.isNotEmpty()) {
|
||||
putString(DEFAULT_REFERRER_HOME_SERVER_URL_PREF, homeServerUrl)
|
||||
|
@ -53,8 +60,8 @@ object ServerUrlsRepository {
|
|||
/**
|
||||
* Save home and identity sever urls entered by the user. May be custom or default value.
|
||||
*/
|
||||
fun saveServerUrls(context: Context, homeServerUrl: String, identityServerUrl: String) {
|
||||
DefaultSharedPreferences.getInstance(context)
|
||||
fun saveServerUrls(homeServerUrl: String, identityServerUrl: String) {
|
||||
sharedPreferences
|
||||
.edit {
|
||||
putString(HOME_SERVER_URL_PREF, homeServerUrl)
|
||||
putString(IDENTITY_SERVER_URL_PREF, identityServerUrl)
|
||||
|
@ -64,14 +71,12 @@ object ServerUrlsRepository {
|
|||
/**
|
||||
* Return last used homeserver url, or the default one from referrer or the default one from resources.
|
||||
*/
|
||||
fun getLastHomeServerUrl(context: Context): String {
|
||||
val prefs = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
return prefs.getString(
|
||||
fun getLastHomeServerUrl(): String {
|
||||
return sharedPreferences.getString(
|
||||
HOME_SERVER_URL_PREF,
|
||||
prefs.getString(
|
||||
sharedPreferences.getString(
|
||||
DEFAULT_REFERRER_HOME_SERVER_URL_PREF,
|
||||
getDefaultHomeServerUrl(context)
|
||||
getDefaultHomeServerUrl()
|
||||
)!!
|
||||
)!!
|
||||
}
|
||||
|
@ -79,10 +84,10 @@ object ServerUrlsRepository {
|
|||
/**
|
||||
* Return true if url is the default homeserver url form resources.
|
||||
*/
|
||||
fun isDefaultHomeServerUrl(context: Context, url: String) = url == getDefaultHomeServerUrl(context)
|
||||
fun isDefaultHomeServerUrl(url: String) = url == getDefaultHomeServerUrl()
|
||||
|
||||
/**
|
||||
* Return default homeserver url from resources.
|
||||
*/
|
||||
fun getDefaultHomeServerUrl(context: Context): String = context.getString(R.string.matrix_org_server_url)
|
||||
fun getDefaultHomeServerUrl() = stringProvider.getString(R.string.matrix_org_server_url)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.lifecycle
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.ActivityManager
|
||||
import android.app.Application
|
||||
|
@ -91,7 +90,6 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager
|
|||
*
|
||||
* @return true if an app task is corrupted by a potentially malicious activity
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private suspend fun isTaskCorrupted(activity: Activity): Boolean = withContext(Dispatchers.Default) {
|
||||
val context = activity.applicationContext
|
||||
val packageManager: PackageManager = context.packageManager
|
||||
|
|
|
@ -144,7 +144,6 @@ class LoginCaptchaFragment :
|
|||
// runOnUiThread(Runnable { finish() })
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) {
|
||||
super.onReceivedHttpError(view, request, errorResponse)
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package im.vector.app.features.notifications
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
|
@ -34,6 +33,7 @@ import android.text.Spannable
|
|||
import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
@ -102,6 +102,7 @@ class NotificationUtils @Inject constructor(
|
|||
const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2"
|
||||
private const val CALL_NOTIFICATION_CHANNEL_ID = "CALL_NOTIFICATION_CHANNEL_ID_V2"
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
|
||||
fun supportNotificationChannels() = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
|
||||
fun openSystemSettingsForSilentCategory(fragment: Fragment) {
|
||||
|
@ -126,7 +127,6 @@ class NotificationUtils @Inject constructor(
|
|||
/**
|
||||
* Create notification channels.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
fun createNotificationChannels() {
|
||||
if (!supportNotificationChannels()) {
|
||||
return
|
||||
|
@ -218,7 +218,6 @@ class NotificationUtils @Inject constructor(
|
|||
* @param withProgress true to show indeterminate progress on the notification
|
||||
* @return the polling thread listener notification
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
|
||||
// build the pending intent go to the home screen if this is clicked.
|
||||
val i = HomeActivity.newIntent(context, firstStartMainActivity = false)
|
||||
|
@ -287,7 +286,6 @@ class NotificationUtils @Inject constructor(
|
|||
* @param fromBg true if the app is in background when posting the notification
|
||||
* @return the call notification.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun buildIncomingCallNotification(
|
||||
call: WebRtcCall,
|
||||
title: String,
|
||||
|
@ -420,7 +418,6 @@ class NotificationUtils @Inject constructor(
|
|||
* @param title title of the notification
|
||||
* @return the call notification.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun buildPendingCallNotification(
|
||||
call: WebRtcCall,
|
||||
title: String
|
||||
|
@ -966,6 +963,7 @@ class NotificationUtils @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("LaunchActivityFromNotification")
|
||||
fun displayDiagnosticNotification() {
|
||||
val testActionIntent = Intent(context, TestNotificationReceiver::class.java)
|
||||
testActionIntent.action = actionIds.diagnostic
|
||||
|
|
|
@ -92,7 +92,6 @@ class CaptchaWebview @Inject constructor(
|
|||
Timber.e("## onError() : $errorMessage")
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) {
|
||||
super.onReceivedHttpError(view, request, errorResponse)
|
||||
when {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.pin.lockscreen.biometrics
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.MainThread
|
||||
|
@ -156,7 +155,6 @@ class BiometricHelper @AssistedInject constructor(
|
|||
return authenticate(activity)
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun authenticateInternal(
|
||||
activity: FragmentActivity,
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.pin.lockscreen.crypto
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
|
@ -55,7 +54,6 @@ class KeyStoreCrypto @AssistedInject constructor(
|
|||
* Ensures a [Key] for the [alias] exists and validates it.
|
||||
* @throws KeyPermanentlyInvalidatedException if key is not valid.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@Throws(KeyPermanentlyInvalidatedException::class)
|
||||
fun ensureKey() = secretStoringUtils.ensureKey(alias).also {
|
||||
// Check validity of Key by initializing an encryption Cipher
|
||||
|
@ -109,10 +107,9 @@ class KeyStoreCrypto @AssistedInject constructor(
|
|||
/**
|
||||
* Check if the key associated with the [alias] is valid.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun hasValidKey(): Boolean {
|
||||
val keyExists = hasKey()
|
||||
return if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && keyExists) {
|
||||
return if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) && keyExists) {
|
||||
val initializedKey = tryOrNull("Error validating lockscreen system key.") { ensureKey() }
|
||||
initializedKey != null
|
||||
} else {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.pin.lockscreen.crypto
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import im.vector.app.features.pin.lockscreen.crypto.migrations.LegacyPinCodeMigrator
|
||||
import im.vector.app.features.pin.lockscreen.crypto.migrations.MissingSystemKeyMigrator
|
||||
|
@ -36,14 +35,13 @@ class LockScreenKeysMigrator @Inject constructor(
|
|||
/**
|
||||
* Performs any needed migrations in order.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
suspend fun migrateIfNeeded() {
|
||||
if (legacyPinCodeMigrator.isMigrationNeeded()) {
|
||||
legacyPinCodeMigrator.migrate()
|
||||
missingSystemKeyMigrator.migrateIfNeeded()
|
||||
}
|
||||
|
||||
if (systemKeyV1Migrator.isMigrationNeeded() && versionProvider.get() >= Build.VERSION_CODES.M) {
|
||||
if (systemKeyV1Migrator.isMigrationNeeded() && versionProvider.isAtLeast(Build.VERSION_CODES.M)) {
|
||||
systemKeyV1Migrator.migrate()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.pin.lockscreen.crypto.migrations
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto
|
||||
import im.vector.app.features.pin.lockscreen.di.BiometricKeyAlias
|
||||
|
@ -38,9 +37,9 @@ class MissingSystemKeyMigrator @Inject constructor(
|
|||
/**
|
||||
* If user had biometric auth enabled, ensure system key exists, creating one if needed.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun migrateIfNeeded() {
|
||||
if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && vectorPreferences.useBiometricsToUnlock()) {
|
||||
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.M) &&
|
||||
vectorPreferences.useBiometricsToUnlock()) {
|
||||
val systemKeyStoreCrypto = keystoreCryptoFactory.provide(systemKeyAlias, true)
|
||||
runCatching {
|
||||
systemKeyStoreCrypto.ensureKey()
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.pin.lockscreen.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.KeyguardManager
|
||||
import android.os.Build
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
|
@ -139,12 +138,12 @@ class LockScreenViewModel @AssistedInject constructor(
|
|||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private fun showBiometricPrompt(activity: FragmentActivity) = flow {
|
||||
emitAll(biometricHelper.authenticate(activity))
|
||||
}.catch { error ->
|
||||
when {
|
||||
versionProvider.get() >= Build.VERSION_CODES.M && error is KeyPermanentlyInvalidatedException -> {
|
||||
versionProvider.isAtLeast(Build.VERSION_CODES.M) &&
|
||||
error is KeyPermanentlyInvalidatedException -> {
|
||||
onBiometricKeyInvalidated()
|
||||
}
|
||||
else -> {
|
||||
|
@ -168,15 +167,14 @@ class LockScreenViewModel @AssistedInject constructor(
|
|||
_viewEvents.post(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage)
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
private suspend fun updateStateWithBiometricInfo() {
|
||||
// This is a terrible hack, but I found no other way to ensure this would be called only after the device is considered unlocked on Android 12+
|
||||
waitUntilKeyguardIsUnlocked()
|
||||
setState {
|
||||
val isBiometricKeyInvalidated = biometricHelper.hasSystemKey && !biometricHelper.isSystemKeyValid
|
||||
val canUseBiometricAuth = lockScreenConfiguration.mode == LockScreenMode.VERIFY &&
|
||||
!isSystemAuthTemporarilyDisabledByBiometricPrompt &&
|
||||
biometricHelper.isSystemAuthEnabledAndValid
|
||||
!isSystemAuthTemporarilyDisabledByBiometricPrompt &&
|
||||
biometricHelper.isSystemAuthEnabledAndValid
|
||||
val showBiometricPromptAutomatically = canUseBiometricAuth && lockScreenConfiguration.autoStartBiometric
|
||||
copy(
|
||||
canUseBiometricAuth = canUseBiometricAuth,
|
||||
|
@ -191,12 +189,12 @@ class LockScreenViewModel @AssistedInject constructor(
|
|||
* after an Activity's `onResume` method. If we mix that with the system keys needing the device to be unlocked before they're used, we get crashes.
|
||||
* See issue [#6768](https://github.com/vector-im/element-android/issues/6768).
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private suspend fun waitUntilKeyguardIsUnlocked() {
|
||||
if (versionProvider.get() < Build.VERSION_CODES.S) return
|
||||
withTimeoutOrNull(5.seconds) {
|
||||
while (keyguardManager.isDeviceLocked) {
|
||||
delay(50.milliseconds)
|
||||
if (versionProvider.isAtLeast(Build.VERSION_CODES.S)) {
|
||||
withTimeoutOrNull(5.seconds) {
|
||||
while (keyguardManager.isDeviceLocked) {
|
||||
delay(50.milliseconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||
import im.vector.app.core.extensions.getAllChildFragments
|
||||
import im.vector.app.core.extensions.toOnOff
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorLocaleProvider
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.settings.devtools.GossipingEventsSerializer
|
||||
import im.vector.app.features.settings.locale.SystemLocaleProvider
|
||||
|
@ -80,6 +80,7 @@ class BugReporter @Inject constructor(
|
|||
private val buildMeta: BuildMeta,
|
||||
private val processInfo: ProcessInfo,
|
||||
private val sdkIntProvider: BuildVersionSdkIntProvider,
|
||||
private val vectorLocale: VectorLocaleProvider,
|
||||
) {
|
||||
var inMultiWindowMode = false
|
||||
|
||||
|
@ -294,7 +295,7 @@ class BugReporter @Inject constructor(
|
|||
Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME
|
||||
)
|
||||
.addFormDataPart("locale", Locale.getDefault().toString())
|
||||
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
|
||||
.addFormDataPart("app_language", vectorLocale.applicationLocale.toString())
|
||||
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
|
||||
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
|
||||
.addFormDataPart("server_version", serverVersion)
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
package im.vector.app.features.rageshake
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import androidx.core.content.edit
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.resources.VersionCodeProvider
|
||||
import im.vector.app.features.version.VersionProvider
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
|
@ -31,10 +31,11 @@ import javax.inject.Singleton
|
|||
|
||||
@Singleton
|
||||
class VectorUncaughtExceptionHandler @Inject constructor(
|
||||
context: Context,
|
||||
@DefaultPreferences
|
||||
private val preferences: SharedPreferences,
|
||||
private val bugReporter: BugReporter,
|
||||
private val versionProvider: VersionProvider,
|
||||
private val versionCodeProvider: VersionCodeProvider
|
||||
private val versionCodeProvider: VersionCodeProvider,
|
||||
) : Thread.UncaughtExceptionHandler {
|
||||
|
||||
// key to save the crash status
|
||||
|
@ -44,8 +45,6 @@ class VectorUncaughtExceptionHandler @Inject constructor(
|
|||
|
||||
private var previousHandler: Thread.UncaughtExceptionHandler? = null
|
||||
|
||||
private val preferences = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
/**
|
||||
* Activate this handler.
|
||||
*/
|
||||
|
|
|
@ -17,30 +17,40 @@
|
|||
package im.vector.app.features.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import androidx.core.content.edit
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.util.IllformedLocaleException
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Object to manage the Locale choice of the user.
|
||||
*/
|
||||
object VectorLocale {
|
||||
private const val APPLICATION_LOCALE_COUNTRY_KEY = "APPLICATION_LOCALE_COUNTRY_KEY"
|
||||
private const val APPLICATION_LOCALE_VARIANT_KEY = "APPLICATION_LOCALE_VARIANT_KEY"
|
||||
private const val APPLICATION_LOCALE_LANGUAGE_KEY = "APPLICATION_LOCALE_LANGUAGE_KEY"
|
||||
private const val APPLICATION_LOCALE_SCRIPT_KEY = "APPLICATION_LOCALE_SCRIPT_KEY"
|
||||
@Singleton
|
||||
class VectorLocale @Inject constructor(
|
||||
private val context: Context,
|
||||
private val buildMeta: BuildMeta,
|
||||
@DefaultPreferences
|
||||
private val preferences: SharedPreferences,
|
||||
) {
|
||||
companion object {
|
||||
const val APPLICATION_LOCALE_COUNTRY_KEY = "APPLICATION_LOCALE_COUNTRY_KEY"
|
||||
const val APPLICATION_LOCALE_VARIANT_KEY = "APPLICATION_LOCALE_VARIANT_KEY"
|
||||
const val APPLICATION_LOCALE_LANGUAGE_KEY = "APPLICATION_LOCALE_LANGUAGE_KEY"
|
||||
private const val APPLICATION_LOCALE_SCRIPT_KEY = "APPLICATION_LOCALE_SCRIPT_KEY"
|
||||
private const val ISO_15924_LATN = "Latn"
|
||||
}
|
||||
|
||||
private val defaultLocale = Locale("en", "US")
|
||||
|
||||
private const val ISO_15924_LATN = "Latn"
|
||||
|
||||
/**
|
||||
* The cache of supported application languages.
|
||||
*/
|
||||
|
@ -52,17 +62,10 @@ object VectorLocale {
|
|||
var applicationLocale = defaultLocale
|
||||
private set
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var buildMeta: BuildMeta
|
||||
|
||||
/**
|
||||
* Init this object.
|
||||
* Init this singleton.
|
||||
*/
|
||||
fun init(context: Context, buildMeta: BuildMeta) {
|
||||
this.context = context
|
||||
this.buildMeta = buildMeta
|
||||
val preferences = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
fun init() {
|
||||
if (preferences.contains(APPLICATION_LOCALE_LANGUAGE_KEY)) {
|
||||
applicationLocale = Locale(
|
||||
preferences.getString(APPLICATION_LOCALE_LANGUAGE_KEY, "")!!,
|
||||
|
@ -88,7 +91,7 @@ object VectorLocale {
|
|||
fun saveApplicationLocale(locale: Locale) {
|
||||
applicationLocale = locale
|
||||
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
preferences.edit {
|
||||
val language = locale.language
|
||||
if (language.isEmpty()) {
|
||||
remove(APPLICATION_LOCALE_LANGUAGE_KEY)
|
||||
|
|
|
@ -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.features.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Class to provide the Locale choice of the user.
|
||||
*/
|
||||
class VectorLocaleProvider @Inject constructor(
|
||||
@DefaultPreferences
|
||||
private val preferences: SharedPreferences,
|
||||
) {
|
||||
/**
|
||||
* Get the current local.
|
||||
* SharedPref values has been initialized in [VectorLocale.init]
|
||||
*/
|
||||
val applicationLocale: Locale
|
||||
get() = Locale(
|
||||
preferences.getString(VectorLocale.APPLICATION_LOCALE_LANGUAGE_KEY, "")!!,
|
||||
preferences.getString(VectorLocale.APPLICATION_LOCALE_COUNTRY_KEY, "")!!,
|
||||
preferences.getString(VectorLocale.APPLICATION_LOCALE_VARIANT_KEY, "")!!
|
||||
)
|
||||
}
|
|
@ -24,8 +24,9 @@ import androidx.annotation.BoolRes
|
|||
import androidx.core.content.edit
|
||||
import com.squareup.seismic.ShakeDetector
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.time.Clock
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import im.vector.app.features.disclaimer.SHARED_PREF_KEY
|
||||
|
@ -41,6 +42,9 @@ class VectorPreferences @Inject constructor(
|
|||
private val clock: Clock,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val vectorFeatures: VectorFeatures,
|
||||
@DefaultPreferences
|
||||
private val defaultPrefs: SharedPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
@ -289,8 +293,6 @@ class VectorPreferences @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private val defaultPrefs = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
/**
|
||||
* Allow subscribing and unsubscribing to configuration changes. This is
|
||||
* particularly useful when you need to be notified of a configuration change
|
||||
|
@ -716,10 +718,10 @@ class VectorPreferences @Inject constructor(
|
|||
*/
|
||||
fun getSelectedMediasSavingPeriodString(): String {
|
||||
return when (getSelectedMediasSavingPeriod()) {
|
||||
MEDIA_SAVING_3_DAYS -> context.getString(R.string.media_saving_period_3_days)
|
||||
MEDIA_SAVING_1_WEEK -> context.getString(R.string.media_saving_period_1_week)
|
||||
MEDIA_SAVING_1_MONTH -> context.getString(R.string.media_saving_period_1_month)
|
||||
MEDIA_SAVING_FOREVER -> context.getString(R.string.media_saving_period_forever)
|
||||
MEDIA_SAVING_3_DAYS -> stringProvider.getString(R.string.media_saving_period_3_days)
|
||||
MEDIA_SAVING_1_WEEK -> stringProvider.getString(R.string.media_saving_period_1_week)
|
||||
MEDIA_SAVING_1_MONTH -> stringProvider.getString(R.string.media_saving_period_1_month)
|
||||
MEDIA_SAVING_FOREVER -> stringProvider.getString(R.string.media_saving_period_forever)
|
||||
else -> "?"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ class VectorSettingsPreferencesFragment :
|
|||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
@Inject lateinit var fontScalePreferences: FontScalePreferences
|
||||
@Inject lateinit var vectorFeatures: VectorFeatures
|
||||
@Inject lateinit var vectorLocale: VectorLocale
|
||||
|
||||
override var titleRes = R.string.settings_preferences
|
||||
override val preferenceXmlRes = R.xml.vector_settings_preferences
|
||||
|
@ -198,7 +199,7 @@ class VectorSettingsPreferencesFragment :
|
|||
|
||||
private fun setUserInterfacePreferences() {
|
||||
// Selected language
|
||||
selectedLanguagePreference.summary = VectorLocale.localeToLocalisedString(VectorLocale.applicationLocale)
|
||||
selectedLanguagePreference.summary = vectorLocale.localeToLocalisedString(vectorLocale.applicationLocale)
|
||||
|
||||
// Text size
|
||||
textSizePreference.summary = getString(fontScalePreferences.getResolvedFontScaleValue().nameResId)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package im.vector.app.features.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
@ -448,7 +447,6 @@ class VectorSettingsSecurityPrivacyFragment :
|
|||
/**
|
||||
* Manage the e2e keys import.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private fun importKeys() {
|
||||
openFileSelection(
|
||||
requireActivity(),
|
||||
|
|
|
@ -23,17 +23,19 @@ import android.net.Uri
|
|||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.SwitchPreference
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||
import im.vector.app.core.preference.VectorPreference
|
||||
import im.vector.app.core.utils.getCallRingtoneName
|
||||
import im.vector.app.core.utils.getCallRingtoneUri
|
||||
import im.vector.app.core.utils.setCallRingtoneUri
|
||||
import im.vector.app.core.utils.setUseRiotDefaultRingtone
|
||||
import im.vector.app.core.utils.RingtoneUtils
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
@Inject lateinit var ringtoneUtils: RingtoneUtils
|
||||
|
||||
override var titleRes = R.string.preference_voice_and_video
|
||||
override val preferenceXmlRes = R.xml.vector_settings_voice_video
|
||||
|
||||
|
@ -52,12 +54,12 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() {
|
|||
override fun bindPref() {
|
||||
// Incoming call sounds
|
||||
mUseRiotCallRingtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
activity?.let { setUseRiotDefaultRingtone(it, mUseRiotCallRingtonePreference.isChecked) }
|
||||
ringtoneUtils.setUseRiotDefaultRingtone(mUseRiotCallRingtonePreference.isChecked)
|
||||
false
|
||||
}
|
||||
|
||||
mCallRingtonePreference.let {
|
||||
activity?.let { activity -> it.summary = getCallRingtoneName(activity) }
|
||||
it.summary = ringtoneUtils.getCallRingtoneName()
|
||||
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
displayRingtonePicker()
|
||||
false
|
||||
|
@ -68,10 +70,9 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() {
|
|||
private val ringtoneStartForActivityResult = registerStartForActivityResult { activityResult ->
|
||||
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||
val callRingtoneUri: Uri? = activityResult.data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
|
||||
val thisActivity = activity
|
||||
if (callRingtoneUri != null && thisActivity != null) {
|
||||
setCallRingtoneUri(thisActivity, callRingtoneUri)
|
||||
mCallRingtonePreference.summary = getCallRingtoneName(thisActivity)
|
||||
if (callRingtoneUri != null) {
|
||||
ringtoneUtils.setCallRingtoneUri(callRingtoneUri)
|
||||
mCallRingtonePreference.summary = ringtoneUtils.getCallRingtoneName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +83,7 @@ class VectorSettingsVoiceVideoFragment : VectorSettingsBaseFragment() {
|
|||
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false)
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE)
|
||||
activity?.let { putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, getCallRingtoneUri(it)) }
|
||||
putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUtils.getCallRingtoneUri())
|
||||
}
|
||||
ringtoneStartForActivityResult.launch(intent)
|
||||
}
|
||||
|
|
|
@ -37,13 +37,15 @@ import javax.inject.Inject
|
|||
class LocalePickerController @Inject constructor(
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
private val errorFormatter: ErrorFormatter
|
||||
private val errorFormatter: ErrorFormatter,
|
||||
private val vectorLocale: VectorLocale,
|
||||
) : TypedEpoxyController<LocalePickerViewState>() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
override fun buildModels(data: LocalePickerViewState?) {
|
||||
val list = data?.locales ?: return
|
||||
val currentLocale = data.currentLocale ?: return
|
||||
val host = this
|
||||
|
||||
profileSectionItem {
|
||||
|
@ -51,10 +53,10 @@ class LocalePickerController @Inject constructor(
|
|||
title(host.stringProvider.getString(R.string.choose_locale_current_locale_title))
|
||||
}
|
||||
localeItem {
|
||||
id(data.currentLocale.toString())
|
||||
title(VectorLocale.localeToLocalisedString(data.currentLocale).safeCapitalize(data.currentLocale))
|
||||
id(currentLocale.toString())
|
||||
title(host.vectorLocale.localeToLocalisedString(currentLocale).safeCapitalize(currentLocale))
|
||||
if (host.vectorPreferences.developerMode()) {
|
||||
subtitle(VectorLocale.localeToLocalisedStringInfo(data.currentLocale))
|
||||
subtitle(host.vectorLocale.localeToLocalisedStringInfo(currentLocale))
|
||||
}
|
||||
clickListener { host.listener?.onUseCurrentClicked() }
|
||||
}
|
||||
|
@ -78,13 +80,13 @@ class LocalePickerController @Inject constructor(
|
|||
}
|
||||
} else {
|
||||
list()
|
||||
.filter { it.toString() != data.currentLocale.toString() }
|
||||
.filter { it.toString() != currentLocale.toString() }
|
||||
.forEach { locale ->
|
||||
localeItem {
|
||||
id(locale.toString())
|
||||
title(VectorLocale.localeToLocalisedString(locale).safeCapitalize(locale))
|
||||
title(host.vectorLocale.localeToLocalisedString(locale).safeCapitalize(locale))
|
||||
if (host.vectorPreferences.developerMode()) {
|
||||
subtitle(VectorLocale.localeToLocalisedStringInfo(locale))
|
||||
subtitle(host.vectorLocale.localeToLocalisedStringInfo(locale))
|
||||
}
|
||||
clickListener { host.listener?.onLocaleClicked(locale) }
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class LocalePickerViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: LocalePickerViewState,
|
||||
private val vectorConfiguration: VectorConfiguration
|
||||
private val vectorConfiguration: VectorConfiguration,
|
||||
private val vectorLocale: VectorLocale,
|
||||
) : VectorViewModel<LocalePickerViewState, LocalePickerAction, LocalePickerViewEvents>(initialState) {
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -39,8 +40,13 @@ class LocalePickerViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
init {
|
||||
setState {
|
||||
copy(
|
||||
currentLocale = vectorLocale.applicationLocale
|
||||
)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val result = VectorLocale.getSupportedLocales()
|
||||
val result = vectorLocale.getSupportedLocales()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
|
@ -59,7 +65,7 @@ class LocalePickerViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleSelectLocale(action: LocalePickerAction.SelectLocale) {
|
||||
VectorLocale.saveApplicationLocale(action.locale)
|
||||
vectorLocale.saveApplicationLocale(action.locale)
|
||||
vectorConfiguration.applyToApplicationContext()
|
||||
_viewEvents.post(LocalePickerViewEvents.RestartActivity)
|
||||
}
|
||||
|
|
|
@ -19,10 +19,9 @@ package im.vector.app.features.settings.locale
|
|||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import java.util.Locale
|
||||
|
||||
data class LocalePickerViewState(
|
||||
val currentLocale: Locale = VectorLocale.applicationLocale,
|
||||
val currentLocale: Locale? = null,
|
||||
val locales: Async<List<Locale>> = Uninitialized
|
||||
) : MavericksState
|
||||
|
|
|
@ -27,8 +27,8 @@ import androidx.annotation.ColorInt
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
|
@ -84,7 +84,7 @@ object ThemeUtils {
|
|||
fun getApplicationTheme(context: Context): String {
|
||||
val currentTheme = this.currentTheme.get()
|
||||
return if (currentTheme == null) {
|
||||
val prefs = DefaultSharedPreferences.getInstance(context)
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
|
||||
var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, DEFAULT_THEME) ?: DEFAULT_THEME
|
||||
if (themeFromPref == "status") {
|
||||
// Migrate to the default theme
|
||||
|
|
|
@ -20,25 +20,31 @@ import android.content.Context
|
|||
import android.media.MediaCodecList
|
||||
import android.media.MediaFormat
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import im.vector.app.features.VectorFeatures
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
class VoiceRecorderProvider @Inject constructor(
|
||||
private val context: Context,
|
||||
private val vectorFeatures: VectorFeatures,
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) {
|
||||
fun provideVoiceRecorder(): VoiceRecorder {
|
||||
return if (useFallbackRecorder()) {
|
||||
VoiceRecorderL(context, Dispatchers.IO)
|
||||
} else {
|
||||
return if (useNativeRecorder()) {
|
||||
VoiceRecorderQ(context)
|
||||
} else {
|
||||
VoiceRecorderL(context, Dispatchers.IO)
|
||||
}
|
||||
}
|
||||
|
||||
private fun useFallbackRecorder(): Boolean {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || !hasOpusEncoder() || vectorFeatures.forceUsageOfOpusEncoder()
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
|
||||
private fun useNativeRecorder(): Boolean {
|
||||
return buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.Q &&
|
||||
hasOpusEncoder() &&
|
||||
!vectorFeatures.forceUsageOfOpusEncoder()
|
||||
}
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.app.features.widgets.webview
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.CookieManager
|
||||
|
@ -29,7 +28,6 @@ import im.vector.app.features.themes.ThemeUtils
|
|||
import im.vector.app.features.webview.VectorWebViewClient
|
||||
import im.vector.app.features.webview.WebEventListener
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
fun WebView.setupForWidget(activity: Activity,
|
||||
checkWebViewPermissionsUseCase: CheckWebViewPermissionsUseCase,
|
||||
eventListener: WebEventListener,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* 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.
|
||||
|
@ -14,18 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.di
|
||||
package im.vector.app.features.workers.signout
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
object DefaultSharedPreferences {
|
||||
|
||||
@Volatile private var INSTANCE: SharedPreferences? = null
|
||||
|
||||
fun getInstance(context: Context): SharedPreferences =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: PreferenceManager.getDefaultSharedPreferences(context.applicationContext).also { INSTANCE = it }
|
||||
}
|
||||
sealed interface ServerBackupStatusAction : VectorViewModelAction {
|
||||
data class OnRecoverDoneForVersion(val version: String) : ServerBackupStatusAction
|
||||
object OnBannerDisplayed : ServerBackupStatusAction
|
||||
object OnBannerClosed : ServerBackupStatusAction
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package im.vector.app.features.workers.signout
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MavericksState
|
||||
|
@ -24,9 +26,9 @@ import com.airbnb.mvrx.Uninitialized
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.DefaultPreferences
|
||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||
import im.vector.app.core.platform.EmptyAction
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
@ -51,29 +53,55 @@ data class ServerBackupStatusViewState(
|
|||
* The state representing the view.
|
||||
* It can take one state at a time.
|
||||
*/
|
||||
sealed class BannerState {
|
||||
sealed interface BannerState {
|
||||
// Not yet rendered
|
||||
object Initial : BannerState
|
||||
|
||||
object Hidden : BannerState()
|
||||
// View will be Gone
|
||||
object Hidden : BannerState
|
||||
|
||||
// Keys backup is not setup, numberOfKeys is the number of locally stored keys
|
||||
data class Setup(val numberOfKeys: Int) : BannerState()
|
||||
data class Setup(val numberOfKeys: Int, val doNotShowAgain: Boolean) : BannerState
|
||||
|
||||
// Keys backup can be recovered, with version from the server
|
||||
data class Recover(val version: String, val doNotShowForVersion: String) : BannerState
|
||||
|
||||
// Keys backup can be updated
|
||||
data class Update(val version: String, val doNotShowForVersion: String) : BannerState
|
||||
|
||||
// Keys are backing up
|
||||
object BackingUp : BannerState()
|
||||
object BackingUp : BannerState
|
||||
}
|
||||
|
||||
class ServerBackupStatusViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: ServerBackupStatusViewState,
|
||||
private val session: Session
|
||||
private val session: Session,
|
||||
@DefaultPreferences
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
) :
|
||||
VectorViewModel<ServerBackupStatusViewState, EmptyAction, EmptyViewEvents>(initialState), KeysBackupStateListener {
|
||||
VectorViewModel<ServerBackupStatusViewState, ServerBackupStatusAction, EmptyViewEvents>(initialState), KeysBackupStateListener {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MavericksAssistedViewModelFactory<ServerBackupStatusViewModel, ServerBackupStatusViewState> {
|
||||
override fun create(initialState: ServerBackupStatusViewState): ServerBackupStatusViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<ServerBackupStatusViewModel, ServerBackupStatusViewState> by hiltMavericksViewModelFactory()
|
||||
companion object : MavericksViewModelFactory<ServerBackupStatusViewModel, ServerBackupStatusViewState> by hiltMavericksViewModelFactory() {
|
||||
/**
|
||||
* Preference key for setup. Value is a boolean.
|
||||
*/
|
||||
private const val BANNER_SETUP_DO_NOT_SHOW_AGAIN = "BANNER_SETUP_DO_NOT_SHOW_AGAIN"
|
||||
|
||||
/**
|
||||
* Preference key for recover. Value is a backup version (String).
|
||||
*/
|
||||
private const val BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION = "BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION"
|
||||
|
||||
/**
|
||||
* Preference key for update. Value is a backup version (String).
|
||||
*/
|
||||
private const val BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION = "BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION"
|
||||
}
|
||||
|
||||
// Keys exported manually
|
||||
val keysExportedToFile = MutableLiveData<Boolean>()
|
||||
|
@ -105,7 +133,10 @@ class ServerBackupStatusViewModel @AssistedInject constructor(
|
|||
pInfo.getOrNull()?.allKnown().orFalse())
|
||||
) {
|
||||
// So 4S is not setup and we have local secrets,
|
||||
return@combine BannerState.Setup(numberOfKeys = getNumberOfKeysToBackup())
|
||||
return@combine BannerState.Setup(
|
||||
numberOfKeys = getNumberOfKeysToBackup(),
|
||||
doNotShowAgain = sharedPreferences.getBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)
|
||||
)
|
||||
}
|
||||
BannerState.Hidden
|
||||
}
|
||||
|
@ -161,5 +192,47 @@ class ServerBackupStatusViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun handle(action: EmptyAction) {}
|
||||
override fun handle(action: ServerBackupStatusAction) {
|
||||
when (action) {
|
||||
is ServerBackupStatusAction.OnRecoverDoneForVersion -> handleOnRecoverDoneForVersion(action)
|
||||
ServerBackupStatusAction.OnBannerDisplayed -> handleOnBannerDisplayed()
|
||||
ServerBackupStatusAction.OnBannerClosed -> handleOnBannerClosed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnRecoverDoneForVersion(action: ServerBackupStatusAction.OnRecoverDoneForVersion) {
|
||||
sharedPreferences.edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, action.version)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnBannerDisplayed() {
|
||||
sharedPreferences.edit {
|
||||
putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOnBannerClosed() = withState { state ->
|
||||
when (val bannerState = state.bannerState()) {
|
||||
is BannerState.Setup -> {
|
||||
sharedPreferences.edit {
|
||||
putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, true)
|
||||
}
|
||||
}
|
||||
is BannerState.Recover -> {
|
||||
sharedPreferences.edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, bannerState.version)
|
||||
}
|
||||
}
|
||||
is BannerState.Update -> {
|
||||
sharedPreferences.edit {
|
||||
putString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, bannerState.version)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Should not happen, close button is not displayed in other cases
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
android:icon="@drawable/ic_filter"
|
||||
android:title="@string/home_filter_placeholder_home"
|
||||
app:iconTint="?vctr_content_secondary"
|
||||
app:showAsAction="always" />
|
||||
app:showAsAction="always"
|
||||
tools:ignore="AlwaysShowAction" />
|
||||
|
||||
</menu>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/ic_delete_unsent_messages"
|
||||
android:title="@string/action_delete"
|
||||
app:showAsAction="always" />
|
||||
app:showAsAction="always"
|
||||
tools:ignore="AlwaysShowAction" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_mark_as_suggested"
|
||||
|
@ -18,4 +20,4 @@
|
|||
android:title="@string/space_mark_as_not_suggested"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
|
|
Loading…
Reference in New Issue