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