mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 18:36:21 +03:00
Merge branch 'release/1.0.8'
This commit is contained in:
commit
34760a00be
144 changed files with 1987 additions and 1224 deletions
10
.github/ISSUE_TEMPLATE/matrix-sdk.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/matrix-sdk.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: Matrix SDK
|
||||
about: Report issue or ask for a feature regarding the Android Matrix SDK
|
||||
title: "[SDK] "
|
||||
labels: matrix-sdk
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This issue template should be used by third party application maintainers, to report a bug or to request a feature on the SDK module of the application Element Android-->
|
28
CHANGES.md
28
CHANGES.md
|
@ -1,3 +1,31 @@
|
|||
Changes in Element 1.0.8 (2020-09-25)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- Add "show password" in import Megolm keys dialog
|
||||
- Visually disable call buttons in menu and prohibit calling when permissions are insufficient (#2112)
|
||||
- Better management of requested permissions (#2048)
|
||||
- Add a setting to show timestamp for all messages (#2123)
|
||||
- Use cache for user color
|
||||
- Allow using an outdated homeserver, at user's risk (#1972)
|
||||
- Restore small logo on login screens and fix scrolling issue on those screens
|
||||
- PIN Code Improvements: Add more settings: biometrics, grace period, notification content (#1985)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Long message cannot be sent/takes infinite time & blocks other messages (#1397)
|
||||
- Fix crash when wellknown are malformed, or redirect to some HTML content (reported by rageshakes)
|
||||
- User Verification in DM not working
|
||||
- Manual import of Megolm keys does back up the imported keys
|
||||
- Auto scrolling to the latest message when sending (#2094)
|
||||
- Fix incorrect permission check when creating widgets (#2137)
|
||||
- Pin code: user has to enter pin code twice (#2005)
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- Rename `tryThis` to `tryOrNull`
|
||||
|
||||
Other changes:
|
||||
- Add an advanced action to reset an account data entry
|
||||
|
||||
Changes in Element 1.0.7 (2020-09-17)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto
|
|||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -212,7 +212,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||
mTestHelper.waitWithLatch {
|
||||
mTestHelper.retryPeriodicallyWithLatch(it) {
|
||||
// we should get back the key and be able to decrypt
|
||||
val result = tryThis {
|
||||
val result = tryOrNull {
|
||||
bobSession.cryptoService().decryptEvent(messagesReceivedByBob[0].root, "")
|
||||
}
|
||||
Timber.i("## CRYPTO | testUnwedging: decrypt result ${result?.clearEvent}")
|
||||
|
|
|
@ -20,7 +20,7 @@ import android.util.Log
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -227,7 +227,7 @@ class WithHeldTests : InstrumentedTest {
|
|||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also {
|
||||
// try to decrypt and force key request
|
||||
tryThis { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
|
||||
tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
|
||||
}
|
||||
sessionId = timeLineEvent?.root?.content?.toModel<EncryptedEventContent>()?.sessionId
|
||||
timeLineEvent != null
|
||||
|
|
|
@ -17,13 +17,11 @@
|
|||
|
||||
package org.matrix.android.sdk.api.auth.data
|
||||
|
||||
// Either a list of supported login types, or an error if the homeserver is outdated
|
||||
sealed class LoginFlowResult {
|
||||
data class Success(
|
||||
val supportedLoginTypes: List<String>,
|
||||
val isLoginAndRegistrationSupported: Boolean,
|
||||
val homeServerUrl: String
|
||||
val homeServerUrl: String,
|
||||
val isOutdatedHomeserver: Boolean
|
||||
) : LoginFlowResult()
|
||||
|
||||
object OutdatedHomeserver : LoginFlowResult()
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.extensions
|
|||
|
||||
import timber.log.Timber
|
||||
|
||||
inline fun <A> tryThis(message: String? = null, operation: () -> A): A? {
|
||||
inline fun <A> tryOrNull(message: String? = null, operation: () -> A): A? {
|
||||
return try {
|
||||
operation()
|
||||
} catch (any: Throwable) {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.matrix.android.sdk.api.failure
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import java.io.IOException
|
||||
|
@ -49,7 +49,7 @@ fun Throwable.isInvalidPassword(): Boolean {
|
|||
*/
|
||||
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
|
||||
return if (this is Failure.OtherServerError && this.httpCode == 401) {
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
MoshiProvider.providesMoshi()
|
||||
.adapter(RegistrationFlowResponse::class.java)
|
||||
.fromJson(this.errorBody)
|
||||
|
|
|
@ -273,16 +273,16 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
|
||||
return if (versions.isSupportedBySdk()) {
|
||||
// Get the login flow
|
||||
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
|
||||
apiCall = authAPI.getLoginFlows()
|
||||
}
|
||||
LoginFlowResult.Success(loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
|
||||
} else {
|
||||
// Not supported
|
||||
LoginFlowResult.OutdatedHomeserver
|
||||
// Get the login flow
|
||||
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
|
||||
apiCall = authAPI.getLoginFlows()
|
||||
}
|
||||
return LoginFlowResult.Success(
|
||||
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||
versions.isLoginAndRegistrationSupportedBySdk(),
|
||||
homeServerUrl,
|
||||
!versions.isSupportedBySdk()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getRegistrationWizard(): RegistrationWizard {
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -32,28 +31,29 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class CancelGossipRequestWorker(context: Context,
|
||||
params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
val sessionId: String,
|
||||
override val sessionId: String,
|
||||
val requestId: String,
|
||||
val recipients: Map<String, List<String>>
|
||||
) {
|
||||
val recipients: Map<String, List<String>>,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams {
|
||||
companion object {
|
||||
fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params {
|
||||
return Params(
|
||||
sessionId = sessionId,
|
||||
requestId = request.requestId,
|
||||
recipients = request.recipients
|
||||
recipients = request.recipients,
|
||||
lastFailureMessage = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -64,18 +64,11 @@ internal class CancelGossipRequestWorker(context: Context,
|
|||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var credentials: Credentials
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success(errorOutputData)
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId)
|
||||
?: return Result.success(errorOutputData).also {
|
||||
// TODO, can this happen? should I update local echo?
|
||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
||||
}
|
||||
sessionComponent.inject(this)
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
val toDeviceContent = ShareRequestCancellation(
|
||||
|
@ -107,13 +100,17 @@ internal class CancelGossipRequestWorker(context: Context,
|
|||
)
|
||||
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
|
||||
return Result.success()
|
||||
} catch (exception: Throwable) {
|
||||
return if (exception.shouldBeRetried()) {
|
||||
} catch (throwable: Throwable) {
|
||||
return if (throwable.shouldBeRetried()) {
|
||||
Result.retry()
|
||||
} else {
|
||||
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL)
|
||||
Result.success(errorOutputData)
|
||||
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,16 @@ import androidx.lifecycle.LiveData
|
|||
import com.squareup.moshi.Types
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
|
@ -102,12 +108,6 @@ import org.matrix.android.sdk.internal.task.launchToCallback
|
|||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.internal.util.fetchCopied
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.olm.OlmManager
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
@ -345,13 +345,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// Open the store
|
||||
cryptoStore.open()
|
||||
// this can throw if no network
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
uploadDeviceKeys()
|
||||
}
|
||||
|
||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||
// this can throw if no backup
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
keysBackupService.checkAndStartKeysBackup()
|
||||
}
|
||||
}
|
||||
|
@ -1072,7 +1072,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
throw Exception("Error")
|
||||
}
|
||||
|
||||
megolmSessionDataImporter.handle(importedSessions, true, progressListener)
|
||||
megolmSessionDataImporter.handle(
|
||||
megolmSessionsData = importedSessions,
|
||||
fromBackup = false,
|
||||
progressListener = progressListener
|
||||
)
|
||||
}
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -34,40 +33,34 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SendGossipRequestWorker(context: Context,
|
||||
params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
val sessionId: String,
|
||||
override val sessionId: String,
|
||||
val keyShareRequest: OutgoingRoomKeyRequest? = null,
|
||||
val secretShareRequest: OutgoingSecretRequest? = null
|
||||
)
|
||||
val secretShareRequest: OutgoingSecretRequest? = null,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||
@Inject lateinit var cryptoStore: IMXCryptoStore
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var credentials: Credentials
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success(errorOutputData)
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId)
|
||||
?: return Result.success(errorOutputData).also {
|
||||
// TODO, can this happen? should I update local echo?
|
||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
||||
}
|
||||
sessionComponent.inject(this)
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
val contentMap = MXUsersDevicesMap<Any>()
|
||||
val eventType: String
|
||||
|
@ -121,7 +114,7 @@ internal class SendGossipRequestWorker(context: Context,
|
|||
}
|
||||
}
|
||||
else -> {
|
||||
return Result.success(errorOutputData).also {
|
||||
return buildErrorResult(params, "Unknown empty gossiping request").also {
|
||||
Timber.e("Unknown empty gossiping request: $params")
|
||||
}
|
||||
}
|
||||
|
@ -137,13 +130,17 @@ internal class SendGossipRequestWorker(context: Context,
|
|||
)
|
||||
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)
|
||||
return Result.success()
|
||||
} catch (exception: Throwable) {
|
||||
return if (exception.shouldBeRetried()) {
|
||||
} catch (throwable: Throwable) {
|
||||
return if (throwable.shouldBeRetried()) {
|
||||
Result.retry()
|
||||
} else {
|
||||
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.FAILED_TO_SEND)
|
||||
Result.success(errorOutputData)
|
||||
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -34,22 +33,23 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
|||
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SendGossipWorker(context: Context,
|
||||
params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
val sessionId: String,
|
||||
override val sessionId: String,
|
||||
val secretValue: String,
|
||||
val request: IncomingSecretShareRequest
|
||||
)
|
||||
val request: IncomingSecretShareRequest,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||
@Inject lateinit var cryptoStore: IMXCryptoStore
|
||||
|
@ -58,18 +58,11 @@ internal class SendGossipWorker(context: Context,
|
|||
@Inject lateinit var messageEncrypter: MessageEncrypter
|
||||
@Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success(errorOutputData)
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId)
|
||||
?: return Result.success(errorOutputData).also {
|
||||
// TODO, can this happen? should I update local echo?
|
||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
||||
}
|
||||
sessionComponent.inject(this)
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val localId = LocalEcho.createLocalEchoId()
|
||||
val eventType: String = EventType.SEND_SECRET
|
||||
|
||||
|
@ -81,7 +74,7 @@ internal class SendGossipWorker(context: Context,
|
|||
val requestingUserId = params.request.userId ?: ""
|
||||
val requestingDeviceId = params.request.deviceId ?: ""
|
||||
val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
|
||||
?: return Result.success(errorOutputData).also {
|
||||
?: return buildErrorResult(params, "Unknown deviceInfo, cannot send message").also {
|
||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
||||
Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}")
|
||||
}
|
||||
|
@ -94,7 +87,7 @@ internal class SendGossipWorker(context: Context,
|
|||
if (olmSessionResult?.sessionId == null) {
|
||||
// no session with this device, probably because there
|
||||
// were no one-time keys.
|
||||
return Result.success(errorOutputData).also {
|
||||
return buildErrorResult(params, "no session with this device").also {
|
||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
||||
Timber.e("no session with this device, probably because there were no one-time keys.")
|
||||
}
|
||||
|
@ -130,13 +123,17 @@ internal class SendGossipWorker(context: Context,
|
|||
)
|
||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED)
|
||||
return Result.success()
|
||||
} catch (exception: Throwable) {
|
||||
return if (exception.shouldBeRetried()) {
|
||||
} catch (throwable: Throwable) {
|
||||
return if (throwable.shouldBeRetried()) {
|
||||
Result.retry()
|
||||
} else {
|
||||
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
|
||||
Result.success(errorOutputData)
|
||||
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
|
|||
* Must be call on the crypto coroutine thread
|
||||
*
|
||||
* @param megolmSessionsData megolm sessions.
|
||||
* @param backUpKeys true to back up them to the homeserver.
|
||||
* @param fromBackup true if the imported keys are already backed up on the server.
|
||||
* @param progressListener the progress listener
|
||||
* @return import room keys result
|
||||
*/
|
||||
|
|
|
@ -20,6 +20,10 @@ package org.matrix.android.sdk.internal.crypto.store.db
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -85,10 +89,6 @@ import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
|||
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmException
|
||||
import timber.log.Timber
|
||||
|
@ -541,7 +541,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
deviceId = it.deviceId
|
||||
)
|
||||
}
|
||||
monarchy.writeAsync { realm ->
|
||||
doRealmTransactionAsync(realmConfiguration) { realm ->
|
||||
realm.where<MyDeviceLastSeenInfoEntity>().findAll().deleteAllFromRealm()
|
||||
entities.forEach {
|
||||
realm.insertOrUpdate(it)
|
||||
|
@ -1191,7 +1191,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
.findAll()
|
||||
.mapNotNull { entity ->
|
||||
when (entity.type) {
|
||||
GossipRequestType.KEY -> {
|
||||
GossipRequestType.KEY -> {
|
||||
IncomingRoomKeyRequest(
|
||||
userId = entity.otherUserId,
|
||||
deviceId = entity.otherDeviceId,
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db
|
|||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
|
@ -398,7 +398,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
|||
?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
|
||||
?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
|
||||
?.transform { deviceInfoEntity ->
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.model
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.crypto.GossipRequestType
|
||||
import org.matrix.android.sdk.internal.crypto.GossipingRequestState
|
||||
import org.matrix.android.sdk.internal.crypto.IncomingRoomKeyRequest
|
||||
|
@ -45,7 +45,7 @@ internal open class IncomingGossipingRequestEntity(@Index var requestId: String?
|
|||
|
||||
var type: GossipRequestType
|
||||
get() {
|
||||
return tryThis { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
||||
return tryOrNull { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
||||
}
|
||||
set(value) {
|
||||
typeStr = value.name
|
||||
|
@ -55,7 +55,7 @@ internal open class IncomingGossipingRequestEntity(@Index var requestId: String?
|
|||
|
||||
var requestState: GossipingRequestState
|
||||
get() {
|
||||
return tryThis { GossipingRequestState.valueOf(requestStateStr) }
|
||||
return tryOrNull { GossipingRequestState.valueOf(requestStateStr) }
|
||||
?: GossipingRequestState.NONE
|
||||
}
|
||||
set(value) {
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
|
|||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Types
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.crypto.GossipRequestType
|
||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequest
|
||||
import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestState
|
||||
|
@ -47,7 +47,7 @@ internal open class OutgoingGossipingRequestEntity(
|
|||
|
||||
var type: GossipRequestType
|
||||
get() {
|
||||
return tryThis { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
||||
return tryOrNull { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY
|
||||
}
|
||||
set(value) {
|
||||
typeStr = value.name
|
||||
|
@ -57,7 +57,7 @@ internal open class OutgoingGossipingRequestEntity(
|
|||
|
||||
var requestState: OutgoingGossipingRequestState
|
||||
get() {
|
||||
return tryThis { OutgoingGossipingRequestState.valueOf(requestStateStr) }
|
||||
return tryOrNull { OutgoingGossipingRequestState.valueOf(requestStateStr) }
|
||||
?: OutgoingGossipingRequestState.UNSENT
|
||||
}
|
||||
set(value) {
|
||||
|
|
|
@ -17,17 +17,17 @@
|
|||
package org.matrix.android.sdk.internal.crypto.verification
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -37,56 +37,56 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class SendVerificationMessageWorker(context: Context,
|
||||
params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val event: Event,
|
||||
val eventId: String,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject
|
||||
lateinit var sendVerificationMessageTask: SendVerificationMessageTask
|
||||
@Inject lateinit var sendVerificationMessageTask: SendVerificationMessageTask
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
@Inject lateinit var cryptoService: CryptoService
|
||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
|
||||
@Inject
|
||||
lateinit var cryptoService: CryptoService
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val errorOutputData = Data.Builder().putBoolean(OUTPUT_KEY_FAILED, true).build()
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success(errorOutputData)
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId) ?: return buildErrorResult(params, "Event not found")
|
||||
val localEventId = localEvent.eventId ?: ""
|
||||
val roomId = localEvent.roomId ?: ""
|
||||
|
||||
if (cancelSendTracker.isCancelRequestedFor(localEventId, roomId)) {
|
||||
return Result.success()
|
||||
.also {
|
||||
cancelSendTracker.markCancelled(localEventId, roomId)
|
||||
Timber.e("## SendEvent: Event sending has been cancelled $localEventId")
|
||||
}
|
||||
}
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId)
|
||||
?: return Result.success(errorOutputData).also {
|
||||
// TODO, can this happen? should I update local echo?
|
||||
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
|
||||
}
|
||||
sessionComponent.inject(this)
|
||||
val localId = params.event.eventId ?: ""
|
||||
return try {
|
||||
val eventId = sendVerificationMessageTask.execute(
|
||||
val resultEventId = sendVerificationMessageTask.execute(
|
||||
SendVerificationMessageTask.Params(
|
||||
event = params.event,
|
||||
event = localEvent,
|
||||
cryptoService = cryptoService
|
||||
)
|
||||
)
|
||||
|
||||
Result.success(Data.Builder().putString(localId, eventId).build())
|
||||
} catch (exception: Throwable) {
|
||||
if (exception.shouldBeRetried()) {
|
||||
Result.success(Data.Builder().putString(localEventId, resultEventId).build())
|
||||
} catch (throwable: Throwable) {
|
||||
if (throwable.shouldBeRetried()) {
|
||||
Result.retry()
|
||||
} else {
|
||||
Result.success(errorOutputData)
|
||||
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val OUTPUT_KEY_FAILED = "failed"
|
||||
|
||||
fun hasFailed(outputData: Data): Boolean {
|
||||
return outputData.getBoolean(OUTPUT_KEY_FAILED, false)
|
||||
}
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ import androidx.work.Data
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.Operation
|
||||
import androidx.work.WorkInfo
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.R
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
|
||||
|
@ -51,10 +54,8 @@ import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
|||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.util.StringProvider
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -87,7 +88,7 @@ internal class VerificationTransportRoomMessage(
|
|||
|
||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||
sessionId = sessionId,
|
||||
event = event
|
||||
eventId = event.eventId ?: ""
|
||||
))
|
||||
val enqueueInfo = enqueueSendWork(workerParams)
|
||||
|
||||
|
@ -115,20 +116,30 @@ internal class VerificationTransportRoomMessage(
|
|||
val observer = object : Observer<List<WorkInfo>> {
|
||||
override fun onChanged(workInfoList: List<WorkInfo>?) {
|
||||
workInfoList
|
||||
?.filter { it.state == WorkInfo.State.SUCCEEDED }
|
||||
?.firstOrNull { it.id == enqueueInfo.second }
|
||||
?.let { wInfo ->
|
||||
if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
|
||||
Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
|
||||
tx?.cancel(onErrorReason)
|
||||
} else {
|
||||
if (onDone != null) {
|
||||
onDone()
|
||||
} else {
|
||||
tx?.state = nextState
|
||||
when (wInfo.state) {
|
||||
WorkInfo.State.FAILED -> {
|
||||
tx?.cancel(onErrorReason)
|
||||
workLiveData.removeObserver(this)
|
||||
}
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
|
||||
Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
|
||||
tx?.cancel(onErrorReason)
|
||||
} else {
|
||||
if (onDone != null) {
|
||||
onDone()
|
||||
} else {
|
||||
tx?.state = nextState
|
||||
}
|
||||
}
|
||||
workLiveData.removeObserver(this)
|
||||
}
|
||||
else -> {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
workLiveData.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +185,7 @@ internal class VerificationTransportRoomMessage(
|
|||
|
||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||
sessionId = sessionId,
|
||||
event = event
|
||||
eventId = event.eventId ?: ""
|
||||
))
|
||||
|
||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
|
||||
|
@ -184,7 +195,7 @@ internal class VerificationTransportRoomMessage(
|
|||
.build()
|
||||
|
||||
workManagerProvider.workManager
|
||||
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest)
|
||||
.beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||
.enqueue()
|
||||
|
||||
// I cannot just listen to the given work request, because when used in a uniqueWork,
|
||||
|
@ -199,7 +210,7 @@ internal class VerificationTransportRoomMessage(
|
|||
?.filter { it.state == WorkInfo.State.SUCCEEDED }
|
||||
?.firstOrNull { it.id == workRequest.id }
|
||||
?.let { wInfo ->
|
||||
if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
|
||||
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
|
||||
callback(null, null)
|
||||
} else {
|
||||
val eventId = wInfo.outputData.getString(localId)
|
||||
|
@ -229,7 +240,7 @@ internal class VerificationTransportRoomMessage(
|
|||
)
|
||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||
sessionId = sessionId,
|
||||
event = event
|
||||
eventId = event.eventId ?: ""
|
||||
))
|
||||
enqueueSendWork(workerParams)
|
||||
}
|
||||
|
@ -249,7 +260,7 @@ internal class VerificationTransportRoomMessage(
|
|||
)
|
||||
val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
|
||||
sessionId = sessionId,
|
||||
event = event
|
||||
eventId = event.eventId ?: ""
|
||||
))
|
||||
val enqueueInfo = enqueueSendWork(workerParams)
|
||||
|
||||
|
@ -280,7 +291,7 @@ internal class VerificationTransportRoomMessage(
|
|||
.setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
return workManagerProvider.workManager
|
||||
.beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND, workRequest)
|
||||
.beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||
.enqueue() to workRequest.id
|
||||
}
|
||||
|
||||
|
|
|
@ -16,31 +16,52 @@
|
|||
*/
|
||||
package org.matrix.android.sdk.internal.database
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T) = withContext(Dispatchers.Default) {
|
||||
Realm.getInstance(config).use { bgRealm ->
|
||||
bgRealm.beginTransaction()
|
||||
val result: T
|
||||
try {
|
||||
val start = System.currentTimeMillis()
|
||||
result = transaction(bgRealm)
|
||||
if (isActive) {
|
||||
bgRealm.commitTransaction()
|
||||
val end = System.currentTimeMillis()
|
||||
val time = end - start
|
||||
Timber.v("Execute transaction in $time millis")
|
||||
}
|
||||
} finally {
|
||||
if (bgRealm.isInTransaction) {
|
||||
bgRealm.cancelTransaction()
|
||||
}
|
||||
}
|
||||
result
|
||||
internal fun <T> CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: suspend (realm: Realm) -> T) {
|
||||
asyncTransaction(monarchy.realmConfiguration, transaction)
|
||||
}
|
||||
|
||||
internal fun <T> CoroutineScope.asyncTransaction(realmConfiguration: RealmConfiguration, transaction: suspend (realm: Realm) -> T) {
|
||||
launch {
|
||||
awaitTransaction(realmConfiguration, transaction)
|
||||
}
|
||||
}
|
||||
|
||||
private val realmSemaphore = Semaphore(1)
|
||||
|
||||
suspend fun <T> awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T): T {
|
||||
return realmSemaphore.withPermit {
|
||||
withContext(Dispatchers.IO) {
|
||||
Realm.getInstance(config).use { bgRealm ->
|
||||
bgRealm.beginTransaction()
|
||||
val result: T
|
||||
try {
|
||||
val start = System.currentTimeMillis()
|
||||
result = transaction(bgRealm)
|
||||
if (isActive) {
|
||||
bgRealm.commitTransaction()
|
||||
val end = System.currentTimeMillis()
|
||||
val time = end - start
|
||||
Timber.v("Execute transaction in $time millis")
|
||||
}
|
||||
} finally {
|
||||
if (bgRealm.isInTransaction) {
|
||||
bgRealm.cancelTransaction()
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,21 +20,34 @@ package org.matrix.android.sdk.internal.database.mapper
|
|||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||
|
||||
internal object ContentMapper {
|
||||
|
||||
private val moshi = MoshiProvider.providesMoshi()
|
||||
private val adapter = moshi.adapter<Content>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
private val castJsonNumberMoshi by lazy {
|
||||
// We are adding the CheckNumberType as we are serializing/deserializing multiple time in a row
|
||||
// and we lost typing information doing so.
|
||||
// We don't want this check to be done on all adapters, so we create a new moshi just for that.
|
||||
MoshiProvider.providesMoshi()
|
||||
.newBuilder()
|
||||
.add(CheckNumberType.JSON_ADAPTER_FACTORY)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun map(content: String?): Content? {
|
||||
fun map(content: String?, castJsonNumbers: Boolean = false): Content? {
|
||||
return content?.let {
|
||||
adapter.fromJson(it)
|
||||
if (castJsonNumbers) {
|
||||
castJsonNumberMoshi
|
||||
} else {
|
||||
moshi
|
||||
}.adapter<Content>(JSON_DICT_PARAMETERIZED_TYPE).fromJson(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun map(content: Content?): String? {
|
||||
return content?.let {
|
||||
adapter.toJson(it)
|
||||
moshi.adapter<Content>(JSON_DICT_PARAMETERIZED_TYPE).toJson(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ internal object EventMapper {
|
|||
return eventEntity
|
||||
}
|
||||
|
||||
fun map(eventEntity: EventEntity): Event {
|
||||
fun map(eventEntity: EventEntity, castJsonNumbers: Boolean = false): Event {
|
||||
val ud = eventEntity.unsignedData
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.let {
|
||||
|
@ -69,8 +69,8 @@ internal object EventMapper {
|
|||
return Event(
|
||||
type = eventEntity.type,
|
||||
eventId = eventEntity.eventId,
|
||||
content = ContentMapper.map(eventEntity.content),
|
||||
prevContent = ContentMapper.map(eventEntity.prevContent),
|
||||
content = ContentMapper.map(eventEntity.content, castJsonNumbers),
|
||||
prevContent = ContentMapper.map(eventEntity.prevContent, castJsonNumbers),
|
||||
originServerTs = eventEntity.originServerTs,
|
||||
senderId = eventEntity.sender,
|
||||
stateKey = eventEntity.stateKey,
|
||||
|
@ -96,8 +96,8 @@ internal object EventMapper {
|
|||
}
|
||||
}
|
||||
|
||||
internal fun EventEntity.asDomain(): Event {
|
||||
return EventMapper.map(this)
|
||||
internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
|
||||
return EventMapper.map(this, castJsonNumbers)
|
||||
}
|
||||
|
||||
internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long?): EventEntity {
|
||||
|
|
|
@ -24,7 +24,7 @@ import okio.BufferedSink
|
|||
import okio.ForwardingSink
|
||||
import okio.Sink
|
||||
import okio.buffer
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import java.io.IOException
|
||||
|
||||
internal class ProgressRequestBody(private val delegate: RequestBody,
|
||||
|
@ -40,7 +40,7 @@ internal class ProgressRequestBody(private val delegate: RequestBody,
|
|||
|
||||
override fun isDuplex() = delegate.isDuplex()
|
||||
|
||||
val length = tryThis { delegate.contentLength() } ?: -1
|
||||
val length = tryOrNull { delegate.contentLength() } ?: -1
|
||||
|
||||
override fun contentLength() = length
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ interface CheckNumberType {
|
|||
val numberAsString = reader.nextString()
|
||||
val decimal = BigDecimal(numberAsString)
|
||||
if (decimal.scale() <= 0) {
|
||||
decimal.intValueExact()
|
||||
decimal.longValueExact()
|
||||
} else {
|
||||
decimal.toDouble()
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import android.webkit.MimeTypeMap
|
|||
import androidx.core.content.FileProvider
|
||||
import arrow.core.Try
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.api.session.file.FileService
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
@ -174,7 +174,7 @@ internal class DefaultFileService @Inject constructor(
|
|||
}
|
||||
}
|
||||
toNotify?.forEach { otherCallbacks ->
|
||||
tryThis { otherCallbacks.onFailure(it) }
|
||||
tryOrNull { otherCallbacks.onFailure(it) }
|
||||
}
|
||||
}, { file ->
|
||||
callback.onSuccess(file)
|
||||
|
@ -186,7 +186,7 @@ internal class DefaultFileService @Inject constructor(
|
|||
}
|
||||
Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ")
|
||||
toNotify?.forEach { otherCallbacks ->
|
||||
tryThis { otherCallbacks.onSuccess(file) }
|
||||
tryOrNull { otherCallbacks.onSuccess(file) }
|
||||
}
|
||||
})
|
||||
}.toCancelable()
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.call
|
|||
|
||||
import android.os.SystemClock
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.CallsListener
|
||||
|
@ -210,7 +210,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||
|
||||
private fun onCallHangup(hangup: CallHangupContent) {
|
||||
callListeners.toList().forEach {
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
it.onCallHangupReceived(hangup)
|
||||
}
|
||||
}
|
||||
|
@ -218,7 +218,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||
|
||||
private fun onCallAnswer(answer: CallAnswerContent) {
|
||||
callListeners.toList().forEach {
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
it.onCallAnswerReceived(answer)
|
||||
}
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||
|
||||
private fun onCallManageByOtherSession(callId: String) {
|
||||
callListeners.toList().forEach {
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
it.onCallManagedByOtherSession(callId)
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||
if (incomingCall.otherUserId == userId) return
|
||||
|
||||
callListeners.toList().forEach {
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
it.onCallInviteReceived(incomingCall, invite)
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +245,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||
|
||||
private fun onCallIceCandidate(incomingCall: MxCall, candidates: CallCandidatesContent) {
|
||||
callListeners.toList().forEach {
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
it.onCallIceCandidateReceived(incomingCall, candidates)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
|||
import okio.BufferedSink
|
||||
import okio.source
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.internal.di.Authenticated
|
||||
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
||||
|
@ -96,7 +96,7 @@ internal class FileUploader @Inject constructor(@Authenticated
|
|||
inputStream.copyTo(it)
|
||||
}
|
||||
return uploadFile(workingFile, filename, mimeType, progressListener).also {
|
||||
tryThis { workingFile.delete() }
|
||||
tryOrNull { workingFile.delete() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,10 @@ package org.matrix.android.sdk.internal.session.content
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||
|
@ -34,13 +32,18 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
||||
import org.matrix.android.sdk.internal.session.DefaultFileService
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoIdentifiers
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
@ -56,12 +59,13 @@ private data class NewImageAttributes(
|
|||
* Possible previous worker: None
|
||||
* Possible next worker : Always [MultipleEventSendingDispatcherWorker]
|
||||
*/
|
||||
internal class UploadContentWorker(val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||
internal class UploadContentWorker(val context: Context, params: WorkerParameters)
|
||||
: SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val events: List<Event>,
|
||||
val localEchoIds: List<LocalEchoIdentifiers>,
|
||||
val attachment: ContentAttachmentData,
|
||||
val isEncrypted: Boolean,
|
||||
val compressBeforeSending: Boolean,
|
||||
|
@ -73,20 +77,14 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
@Inject lateinit var fileService: DefaultFileService
|
||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
@Inject lateinit var imageCompressor: ImageCompressor
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
Timber.v("Starting upload media work with params $params")
|
||||
|
||||
if (params.lastFailureMessage != null) {
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
|
||||
// Just defensive code to ensure that we never have an uncaught exception that could break the queue
|
||||
return try {
|
||||
internalDoWork(params)
|
||||
|
@ -96,11 +94,12 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun internalDoWork(params: Params): Result {
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
|
||||
val allCancelled = params.events.all { cancelSendTracker.isCancelRequestedFor(it.eventId, it.roomId) }
|
||||
private suspend fun internalDoWork(params: Params): Result {
|
||||
val allCancelled = params.localEchoIds.all { cancelSendTracker.isCancelRequestedFor(it.eventId, it.roomId) }
|
||||
if (allCancelled) {
|
||||
// there is no point in uploading the image!
|
||||
return Result.success(inputData)
|
||||
|
@ -214,18 +213,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## FileService: ERROR")
|
||||
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
|
||||
return Result.success(
|
||||
WorkerParamsFactory.toData(
|
||||
params.copy(
|
||||
lastFailureMessage = e.localizedMessage
|
||||
)
|
||||
)
|
||||
)
|
||||
return handleFailure(params, e)
|
||||
} finally {
|
||||
// Delete all temporary files
|
||||
filesToDelete.forEach {
|
||||
tryThis { it.delete() }
|
||||
tryOrNull { it.delete() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -289,46 +281,48 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleSuccess(params: Params,
|
||||
attachmentUrl: String,
|
||||
encryptedFileInfo: EncryptedFileInfo?,
|
||||
thumbnailUrl: String?,
|
||||
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
||||
newImageAttributes: NewImageAttributes?): Result {
|
||||
private suspend fun handleSuccess(params: Params,
|
||||
attachmentUrl: String,
|
||||
encryptedFileInfo: EncryptedFileInfo?,
|
||||
thumbnailUrl: String?,
|
||||
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
||||
newImageAttributes: NewImageAttributes?): Result {
|
||||
notifyTracker(params) { contentUploadStateTracker.setSuccess(it) }
|
||||
params.localEchoIds.forEach {
|
||||
updateEvent(it.eventId, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes)
|
||||
}
|
||||
|
||||
val updatedEvents = params.events
|
||||
.map {
|
||||
updateEvent(it, attachmentUrl, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo, newImageAttributes)
|
||||
}
|
||||
|
||||
val sendParams = MultipleEventSendingDispatcherWorker.Params(params.sessionId, updatedEvents, params.isEncrypted)
|
||||
val sendParams = MultipleEventSendingDispatcherWorker.Params(
|
||||
sessionId = params.sessionId,
|
||||
localEchoIds = params.localEchoIds,
|
||||
isEncrypted = params.isEncrypted
|
||||
)
|
||||
return Result.success(WorkerParamsFactory.toData(sendParams)).also {
|
||||
Timber.v("## handleSuccess $attachmentUrl, work is stopped $isStopped")
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEvent(event: Event,
|
||||
url: String,
|
||||
encryptedFileInfo: EncryptedFileInfo?,
|
||||
thumbnailUrl: String? = null,
|
||||
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
||||
newImageAttributes: NewImageAttributes?): Event {
|
||||
val messageContent: MessageContent = event.content.toModel() ?: return event
|
||||
val updatedContent = when (messageContent) {
|
||||
is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newImageAttributes)
|
||||
is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
|
||||
is MessageFileContent -> messageContent.update(url, encryptedFileInfo)
|
||||
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo)
|
||||
else -> messageContent
|
||||
private suspend fun updateEvent(eventId: String,
|
||||
url: String,
|
||||
encryptedFileInfo: EncryptedFileInfo?,
|
||||
thumbnailUrl: String? = null,
|
||||
thumbnailEncryptedFileInfo: EncryptedFileInfo?,
|
||||
newImageAttributes: NewImageAttributes?) {
|
||||
localEchoRepository.updateEcho(eventId) { _, event ->
|
||||
val messageContent: MessageContent? = event.asDomain().content.toModel()
|
||||
val updatedContent = when (messageContent) {
|
||||
is MessageImageContent -> messageContent.update(url, encryptedFileInfo, newImageAttributes)
|
||||
is MessageVideoContent -> messageContent.update(url, encryptedFileInfo, thumbnailUrl, thumbnailEncryptedFileInfo)
|
||||
is MessageFileContent -> messageContent.update(url, encryptedFileInfo)
|
||||
is MessageAudioContent -> messageContent.update(url, encryptedFileInfo)
|
||||
else -> messageContent
|
||||
}
|
||||
event.content = ContentMapper.map(updatedContent.toContent())
|
||||
}
|
||||
return event.copy(content = updatedContent.toContent())
|
||||
}
|
||||
|
||||
private fun notifyTracker(params: Params, function: (String) -> Unit) {
|
||||
params.events
|
||||
.mapNotNull { it.eventId }
|
||||
.forEach { eventId -> function.invoke(eventId) }
|
||||
params.localEchoIds.forEach { function.invoke(it.eventId) }
|
||||
}
|
||||
|
||||
private fun MessageImageContent.update(url: String,
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.download
|
|||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
|
@ -76,7 +76,7 @@ internal class DefaultContentDownloadStateTracker @Inject constructor() : Progre
|
|||
Timber.v("## DL Progress Error code:$errorCode")
|
||||
updateState(url, ContentDownloadStateTracker.State.Failure(errorCode))
|
||||
listeners[url]?.forEach {
|
||||
tryThis { it.onDownloadStateUpdate(ContentDownloadStateTracker.State.Failure(errorCode)) }
|
||||
tryOrNull { it.onDownloadStateUpdate(ContentDownloadStateTracker.State.Failure(errorCode)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ internal class DefaultContentDownloadStateTracker @Inject constructor() : Progre
|
|||
private fun updateState(url: String, state: ContentDownloadStateTracker.State) {
|
||||
states[url] = state
|
||||
listeners[url]?.forEach {
|
||||
tryThis { it.onDownloadStateUpdate(state) }
|
||||
tryOrNull { it.onDownloadStateUpdate(state) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,20 +18,19 @@
|
|||
package org.matrix.android.sdk.internal.session.group
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Possible previous worker: None
|
||||
* Possible next worker : None
|
||||
*/
|
||||
internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||
internal class GetGroupDataWorker(context: Context, params: WorkerParameters)
|
||||
: SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
|
@ -41,13 +40,11 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
|
|||
|
||||
@Inject lateinit var getGroupDataTask: GetGroupDataTask
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.failure()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
return runCatching {
|
||||
getGroupDataTask.execute(GetGroupDataTask.Params.FetchAllActive)
|
||||
}.fold(
|
||||
|
@ -55,4 +52,8 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
|
|||
{ Result.retry() }
|
||||
)
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import androidx.lifecycle.LifecycleRegistry
|
|||
import dagger.Lazy
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -113,7 +113,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
// Url has changed, we have to reset our store, update internal configuration and notify listeners
|
||||
identityStore.setUrl(baseUrl)
|
||||
updateIdentityAPI(baseUrl)
|
||||
listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
|
||||
listeners.toList().forEach { tryOrNull { it.onIdentityServerChange() } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,7 +236,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
private suspend fun updateAccountData(url: String?) {
|
||||
// Also notify the listener
|
||||
withContext(coroutineDispatchers.main) {
|
||||
listeners.toList().forEach { tryThis { it.onIdentityServerChange() } }
|
||||
listeners.toList().forEach { tryOrNull { it.onIdentityServerChange() } }
|
||||
}
|
||||
|
||||
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.IdentityParams(
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
|||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -39,7 +40,7 @@ internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val p
|
|||
|
||||
Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids")
|
||||
// Store the list in DB
|
||||
monarchy.writeAsync { realm ->
|
||||
monarchy.awaitTransaction { realm ->
|
||||
realm.where(UserThreePidEntity::class.java).findAll().deleteAllFromRealm()
|
||||
accountThreePidsResponse.threePids?.forEach {
|
||||
val entity = UserThreePidEntity(
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
package org.matrix.android.sdk.internal.session.pushers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.pushers.PusherState
|
||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||
|
@ -28,16 +28,14 @@ import org.matrix.android.sdk.internal.database.model.PusherEntity
|
|||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
|
@ -50,14 +48,11 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
|||
@Inject @SessionDatabase lateinit var monarchy: Monarchy
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.failure()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val pusher = params.pusher
|
||||
|
||||
if (pusher.pushKey.isBlank()) {
|
||||
|
@ -82,6 +77,10 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
|
|||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
|
||||
private suspend fun setPusher(pusher: JsonPusher) {
|
||||
executeRequest<Unit>(eventBus) {
|
||||
apiCall = pushersAPI.setPusher(pusher)
|
||||
|
|
|
@ -202,13 +202,13 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||
|
||||
private fun createEncryptEventWork(event: Event, keepKeys: List<String>?): OneTimeWorkRequest {
|
||||
// Same parameter
|
||||
val params = EncryptEventWorker.Params(sessionId, event, keepKeys)
|
||||
val params = EncryptEventWorker.Params(sessionId, event.eventId!!, keepKeys)
|
||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||
return timeLineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
||||
}
|
||||
|
||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = event.eventId!!)
|
||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package org.matrix.android.sdk.internal.session.room.relation
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
@ -27,45 +26,38 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
|||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO This is not used. Delete?
|
||||
internal class SendRelationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||
internal class SendRelationWorker(context: Context, params: WorkerParameters)
|
||||
: SessionSafeCoroutineWorker<SendRelationWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val roomId: String,
|
||||
val event: Event,
|
||||
val eventId: String,
|
||||
val relationType: String? = null,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var roomAPI: RoomAPI
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.failure()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
if (params.lastFailureMessage != null) {
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
|
||||
val localEvent = params.event
|
||||
if (localEvent.eventId == null) {
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
|
||||
if (localEvent?.eventId == null) {
|
||||
return Result.failure()
|
||||
}
|
||||
val relationContent = localEvent.content.toModel<ReactionContent>()
|
||||
|
@ -88,6 +80,10 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
|
|||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
|
||||
private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) {
|
||||
executeRequest<SendResponse>(eventBus) {
|
||||
apiCall = roomAPI.sendRelation(
|
||||
|
|
|
@ -336,7 +336,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
|
||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
// Same parameter
|
||||
return EncryptEventWorker.Params(sessionId, event)
|
||||
return EncryptEventWorker.Params(sessionId, event.eventId ?: "")
|
||||
.let { WorkerParamsFactory.toData(it) }
|
||||
.let {
|
||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||
|
@ -360,7 +360,10 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||
attachment: ContentAttachmentData,
|
||||
isRoomEncrypted: Boolean,
|
||||
compressBeforeSending: Boolean): OneTimeWorkRequest {
|
||||
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, allLocalEchos, attachment, isRoomEncrypted, compressBeforeSending)
|
||||
val localEchoIds = allLocalEchos.map {
|
||||
LocalEchoIdentifiers(it.roomId!!, it.eventId!!)
|
||||
}
|
||||
val uploadMediaWorkerParams = UploadContentWorker.Params(sessionId, localEchoIds, attachment, isRoomEncrypted, compressBeforeSending)
|
||||
val uploadWorkData = WorkerParamsFactory.toData(uploadMediaWorkerParams)
|
||||
|
||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<UploadContentWorker>()
|
||||
|
|
|
@ -18,21 +18,23 @@
|
|||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -41,12 +43,12 @@ import javax.inject.Inject
|
|||
* Possible next worker : Always [SendEventWorker]
|
||||
*/
|
||||
internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<EncryptEventWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val event: Event,
|
||||
val eventId: String,
|
||||
/** Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to) */
|
||||
val keepKeys: List<String>? = null,
|
||||
override val lastFailureMessage: String? = null
|
||||
|
@ -56,24 +58,15 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.v("Start Encrypt work")
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
Timber.v("## SendEvent: Start Encrypt work for event ${params.event.eventId}")
|
||||
if (params.lastFailureMessage != null) {
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
Timber.v("## SendEvent: Start Encrypt work for event ${params.eventId}")
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
|
||||
val localEvent = params.event
|
||||
if (localEvent.eventId == null) {
|
||||
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
|
||||
if (localEvent?.eventId == null) {
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
@ -106,15 +99,10 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
modifiedContent[toKeep] = it
|
||||
}
|
||||
}
|
||||
val safeResult = result.copy(eventContent = modifiedContent)
|
||||
val encryptedEvent = localEvent.copy(
|
||||
type = safeResult.eventType,
|
||||
content = safeResult.eventContent
|
||||
)
|
||||
// Better handling of local echo, to avoid decrypting transition on remote echo
|
||||
// Should I only do it for text messages?
|
||||
if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
val decryptionLocalEcho = MXEventDecryptionResult(
|
||||
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
MXEventDecryptionResult(
|
||||
clearEvent = Event(
|
||||
type = localEvent.type,
|
||||
content = localEvent.content,
|
||||
|
@ -124,10 +112,18 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||
claimedEd25519Key = crypto.getMyDevice().fingerprint()
|
||||
)
|
||||
localEchoRepository.updateEncryptedEcho(localEvent.eventId, safeResult.eventContent, decryptionLocalEcho)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
||||
localEcho.type = EventType.ENCRYPTED
|
||||
localEcho.content = ContentMapper.map(modifiedContent)
|
||||
decryptionLocalEcho?.also {
|
||||
localEcho.setDecryptionResult(it)
|
||||
}
|
||||
}
|
||||
|
||||
val nextWorkerParams = SendEventWorker.Params(sessionId = params.sessionId, event = encryptedEvent)
|
||||
val nextWorkerParams = SendEventWorker.Params(sessionId = params.sessionId, eventId = params.eventId)
|
||||
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
||||
} else {
|
||||
val sendState = when (error) {
|
||||
|
@ -138,10 +134,14 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|||
// always return success, or the chain will be stuck for ever!
|
||||
val nextWorkerParams = SendEventWorker.Params(
|
||||
sessionId = params.sessionId,
|
||||
event = localEvent,
|
||||
eventId = localEvent.eventId,
|
||||
lastFailureMessage = error?.localizedMessage ?: "Error"
|
||||
)
|
||||
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* This is used as a holder to pass necessary data to some workers params.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class LocalEchoIdentifiers(val roomId: String, val eventId: String)
|
|
@ -18,8 +18,8 @@
|
|||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -27,11 +27,11 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.asyncTransaction
|
||||
import org.matrix.android.sdk.internal.database.helper.nextId
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.mapper.toEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
||||
|
@ -44,11 +44,13 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
|||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||
private val eventBus: EventBus,
|
||||
|
@ -76,12 +78,12 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
}
|
||||
val timelineEvent = timelineEventMapper.map(timelineEventEntity)
|
||||
eventBus.post(DefaultTimeline.OnLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent))
|
||||
monarchy.writeAsync { realm ->
|
||||
taskExecutor.executorScope.asyncTransaction(monarchy) { realm ->
|
||||
val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply {
|
||||
this.insertType = EventInsertType.LOCAL_ECHO
|
||||
}
|
||||
realm.insert(eventInsertEntity)
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@writeAsync
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() ?: return@asyncTransaction
|
||||
roomEntity.sendingTimelineEvents.add(0, timelineEventEntity)
|
||||
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||
}
|
||||
|
@ -89,30 +91,41 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
|
||||
fun updateSendState(eventId: String, sendState: SendState) {
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
|
||||
monarchy.writeAsync { realm ->
|
||||
updateEchoAsync(eventId) { realm, sendingEventEntity ->
|
||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||
// If already synced, do not put as sent
|
||||
} else {
|
||||
sendingEventEntity.sendState = sendState
|
||||
}
|
||||
roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateEcho(eventId: String, block: (realm: Realm, eventEntity: EventEntity) -> Unit) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||
// If already synced, do not put as sent
|
||||
} else {
|
||||
sendingEventEntity.sendState = sendState
|
||||
}
|
||||
roomSummaryUpdater.updateSendingInformation(realm, sendingEventEntity.roomId)
|
||||
block(realm, sendingEventEntity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateEncryptedEcho(eventId: String, encryptedContent: Content, mxEventDecryptionResult: MXEventDecryptionResult) {
|
||||
monarchy.writeAsync { realm ->
|
||||
fun updateEchoAsync(eventId: String, block: (realm: Realm, eventEntity: EventEntity) -> Unit) {
|
||||
taskExecutor.executorScope.asyncTransaction(monarchy) { realm ->
|
||||
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
|
||||
if (sendingEventEntity != null) {
|
||||
sendingEventEntity.type = EventType.ENCRYPTED
|
||||
sendingEventEntity.content = ContentMapper.map(encryptedContent)
|
||||
sendingEventEntity.setDecryptionResult(mxEventDecryptionResult)
|
||||
block(realm, sendingEventEntity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getUpToDateEcho(eventId: String): Event? {
|
||||
// We are using awaitTransaction here to make sure this executes after other transactions
|
||||
return monarchy.awaitTransaction { realm ->
|
||||
EventEntity.where(realm, eventId).findFirst()?.asDomain(castJsonNumbers = true)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteFailedEcho(roomId: String, localEcho: TimelineEvent) {
|
||||
deleteFailedEcho(roomId, localEcho.eventId)
|
||||
}
|
||||
|
@ -150,7 +163,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
return getAllEventsWithStates(roomId, SendState.HAS_FAILED_STATES)
|
||||
}
|
||||
|
||||
fun getAllEventsWithStates(roomId: String, states : List<SendState>): List<TimelineEvent> {
|
||||
fun getAllEventsWithStates(roomId: String, states: List<SendState>): List<TimelineEvent> {
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
TimelineEventEntity
|
||||
.findAllInRoomWithSendStates(realm, roomId, states)
|
||||
|
|
|
@ -19,18 +19,17 @@ package org.matrix.android.sdk.internal.session.room.send
|
|||
|
||||
import android.content.Context
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineSendEventWorkCommon
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import org.matrix.android.sdk.internal.worker.startChain
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -43,12 +42,12 @@ import javax.inject.Inject
|
|||
* Possible next worker : None, but it will post new work to send events, encrypted or not
|
||||
*/
|
||||
internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val events: List<Event>,
|
||||
val localEchoIds: List<LocalEchoIdentifiers>,
|
||||
val isEncrypted: Boolean,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
@ -57,46 +56,48 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.v("## SendEvent: Start dispatch sending multiple event work")
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
|
||||
if (params.lastFailureMessage != null) {
|
||||
params.events.forEach { event ->
|
||||
event.eventId?.let { localEchoRepository.updateSendState(it, SendState.UNDELIVERED) }
|
||||
}
|
||||
// Transmit the error if needed?
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("## SendEvent: Work cancelled due to input error from parent ${params.lastFailureMessage}") }
|
||||
override fun doOnError(params: Params): Result {
|
||||
params.localEchoIds.forEach { localEchoIds ->
|
||||
localEchoRepository.updateSendState(localEchoIds.eventId, SendState.UNDELIVERED)
|
||||
}
|
||||
|
||||
return super.doOnError(params)
|
||||
}
|
||||
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
Timber.v("## SendEvent: Start dispatch sending multiple event work")
|
||||
// Create a work for every event
|
||||
params.events.forEach { event ->
|
||||
params.localEchoIds.forEach { localEchoIds ->
|
||||
val roomId = localEchoIds.roomId
|
||||
val eventId = localEchoIds.eventId
|
||||
if (params.isEncrypted) {
|
||||
localEchoRepository.updateSendState(event.eventId ?: "", SendState.ENCRYPTING)
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event ${event.eventId}")
|
||||
val encryptWork = createEncryptEventWork(params.sessionId, event, true)
|
||||
localEchoRepository.updateSendState(eventId, SendState.ENCRYPTING)
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event $eventId")
|
||||
val encryptWork = createEncryptEventWork(params.sessionId, eventId, true)
|
||||
// Note that event will be replaced by the result of the previous work
|
||||
val sendWork = createSendEventWork(params.sessionId, event, false)
|
||||
timelineSendEventWorkCommon.postSequentialWorks(event.roomId!!, encryptWork, sendWork)
|
||||
val sendWork = createSendEventWork(params.sessionId, eventId, false)
|
||||
timelineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, sendWork)
|
||||
} else {
|
||||
localEchoRepository.updateSendState(event.eventId ?: "", SendState.SENDING)
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event ${event.eventId}")
|
||||
val sendWork = createSendEventWork(params.sessionId, event, true)
|
||||
timelineSendEventWorkCommon.postWork(event.roomId!!, sendWork)
|
||||
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
|
||||
val sendWork = createSendEventWork(params.sessionId, eventId, true)
|
||||
timelineSendEventWorkCommon.postWork(roomId, sendWork)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun createEncryptEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
val params = EncryptEventWorker.Params(sessionId, event)
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
|
||||
private fun createEncryptEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
|
||||
val params = EncryptEventWorker.Params(sessionId, eventId)
|
||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||
|
||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||
|
@ -107,8 +108,8 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||
.build()
|
||||
}
|
||||
|
||||
private fun createSendEventWork(sessionId: String, event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
|
||||
private fun createSendEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = eventId)
|
||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
|
||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||
|
|
|
@ -17,24 +17,24 @@
|
|||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Possible previous worker: None
|
||||
* Possible next worker : None
|
||||
*/
|
||||
internal class RedactEventWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||
internal class RedactEventWorker(context: Context, params: WorkerParameters)
|
||||
: SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
|
@ -49,20 +49,11 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
|
|||
@Inject lateinit var roomAPI: RoomAPI
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.failure()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
||||
if (params.lastFailureMessage != null) {
|
||||
// Transmit the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val eventId = params.eventId
|
||||
return runCatching {
|
||||
executeRequest<SendResponse>(eventBus) {
|
||||
|
@ -91,4 +82,8 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ internal class RoomEventSender @Inject constructor(
|
|||
|
||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
// Same parameter
|
||||
val params = EncryptEventWorker.Params(sessionId, event)
|
||||
val params = EncryptEventWorker.Params(sessionId, event.eventId!!)
|
||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||
|
||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||
|
@ -68,7 +68,7 @@ internal class RoomEventSender @Inject constructor(
|
|||
}
|
||||
|
||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, event = event)
|
||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = event.eventId!!)
|
||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||
|
||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||
|
|
|
@ -18,19 +18,19 @@
|
|||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import io.realm.RealmConfiguration
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -42,35 +42,29 @@ import javax.inject.Inject
|
|||
*/
|
||||
internal class SendEventWorker(context: Context,
|
||||
params: WorkerParameters)
|
||||
: CoroutineWorker(context, params) {
|
||||
: SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
override val lastFailureMessage: String? = null,
|
||||
val event: Event? = null,
|
||||
// Keep for compat at the moment, will be removed later
|
||||
val eventId: String? = null
|
||||
val eventId: String
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||
@Inject lateinit var roomAPI: RoomAPI
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("## SendEvent: Unable to parse work parameters") }
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
val event = params.event
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
val event = localEchoRepository.getUpToDateEcho(params.eventId)
|
||||
if (event?.eventId == null || event.roomId == null) {
|
||||
// Old way of sending
|
||||
if (params.eventId != null) {
|
||||
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
||||
}
|
||||
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
||||
return Result.success()
|
||||
.also { Timber.e("Work cancelled due to bad input data") }
|
||||
}
|
||||
|
@ -106,6 +100,10 @@ internal class SendEventWorker(context: Context,
|
|||
}
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
|
||||
private suspend fun sendEvent(eventId: String, roomId: String, type: String, content: Content?) {
|
||||
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
||||
executeRequest<SendResponse>(eventBus) {
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.greenrobot.eventbus.Subscribe
|
|||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -333,12 +334,22 @@ internal class DefaultTimeline(
|
|||
|
||||
// Private methods *****************************************************************************
|
||||
|
||||
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent): Boolean {
|
||||
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||
// Update the relation of existing event
|
||||
builtEvents[builtIndex]?.let { te ->
|
||||
builtEvents[builtIndex] = builder(te)
|
||||
true
|
||||
private fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean {
|
||||
return tryOrNull {
|
||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||
// Update the relation of existing event
|
||||
builtEvents[builtIndex]?.let { te ->
|
||||
val rebuiltEvent = builder(te)
|
||||
// If rebuilt event is filtered its returned as null and should be removed.
|
||||
if (rebuiltEvent == null) {
|
||||
builtEventsIdMap.remove(eventId)
|
||||
builtEventsIdMap.entries.filter { it.value > builtIndex }.forEach { it.setValue(it.value - 1) }
|
||||
builtEvents.removeAt(builtIndex)
|
||||
} else {
|
||||
builtEvents[builtIndex] = rebuiltEvent
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
} ?: false
|
||||
}
|
||||
|
@ -413,14 +424,14 @@ internal class DefaultTimeline(
|
|||
|
||||
private fun getState(direction: Timeline.Direction): State {
|
||||
return when (direction) {
|
||||
Timeline.Direction.FORWARDS -> forwardsState.get()
|
||||
Timeline.Direction.FORWARDS -> forwardsState.get()
|
||||
Timeline.Direction.BACKWARDS -> backwardsState.get()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateState(direction: Timeline.Direction, update: (State) -> State) {
|
||||
val stateReference = when (direction) {
|
||||
Timeline.Direction.FORWARDS -> forwardsState
|
||||
Timeline.Direction.FORWARDS -> forwardsState
|
||||
Timeline.Direction.BACKWARDS -> backwardsState
|
||||
}
|
||||
val currentValue = stateReference.get()
|
||||
|
@ -489,7 +500,8 @@ internal class DefaultTimeline(
|
|||
val eventEntity = results[index]
|
||||
eventEntity?.eventId?.let { eventId ->
|
||||
postSnapshot = rebuildEvent(eventId) {
|
||||
buildTimelineEvent(eventEntity)
|
||||
val builtEvent = buildTimelineEvent(eventEntity)
|
||||
listOf(builtEvent).filterEventsWithSettings().firstOrNull()
|
||||
} || postSnapshot
|
||||
}
|
||||
}
|
||||
|
@ -730,10 +742,10 @@ internal class DefaultTimeline(
|
|||
return object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
||||
when (data) {
|
||||
TokenChunkEventPersistor.Result.SUCCESS -> {
|
||||
TokenChunkEventPersistor.Result.SUCCESS -> {
|
||||
Timber.v("Success fetching $limit items $direction from pagination request")
|
||||
}
|
||||
TokenChunkEventPersistor.Result.REACHED_END -> {
|
||||
TokenChunkEventPersistor.Result.REACHED_END -> {
|
||||
postSnapshot()
|
||||
}
|
||||
TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE ->
|
||||
|
@ -775,9 +787,8 @@ internal class DefaultTimeline(
|
|||
}
|
||||
if (!filterEdits) return@filter false
|
||||
|
||||
val filterRedacted = !settings.filters.filterRedacted || it.root.isRedacted()
|
||||
|
||||
filterRedacted
|
||||
val filterRedacted = settings.filters.filterRedacted && it.root.isRedacted()
|
||||
!filterRedacted
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private
|
|||
|
||||
/**
|
||||
* At the moment we don't get any group data through the sync, so we poll where every hour.
|
||||
You can also force to refetch group data using [Group] API.
|
||||
* You can also force to refetch group data using [Group] API.
|
||||
*/
|
||||
private fun scheduleGroupDataFetchingIfNeeded(groupsSyncResponse: GroupsSyncResponse) {
|
||||
val groupIds = ArrayList<String>()
|
||||
|
|
|
@ -18,18 +18,18 @@ package org.matrix.android.sdk.internal.session.sync.job
|
|||
|
||||
import android.content.Context
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.failure.isTokenError
|
||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import org.matrix.android.sdk.internal.session.sync.SyncTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||
import org.matrix.android.sdk.internal.worker.getSessionComponent
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
@ -43,7 +43,7 @@ private const val DEFAULT_DELAY_TIMEOUT = 30_000L
|
|||
*/
|
||||
internal class SyncWorker(context: Context,
|
||||
workerParameters: WorkerParameters
|
||||
) : CoroutineWorker(context, workerParameters) {
|
||||
) : SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, Params::class.java) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class Params(
|
||||
|
@ -59,14 +59,13 @@ internal class SyncWorker(context: Context,
|
|||
@Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
|
||||
@Inject lateinit var workManagerProvider: WorkManagerProvider
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.i("Sync work starting")
|
||||
val params = WorkerParamsFactory.fromData<Params>(inputData)
|
||||
?: return Result.success()
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
injector.inject(this)
|
||||
}
|
||||
|
||||
override suspend fun doSafeWork(params: Params): Result {
|
||||
Timber.i("Sync work starting")
|
||||
|
||||
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
|
||||
sessionComponent.inject(this)
|
||||
return runCatching {
|
||||
doSync(params.timeout)
|
||||
}.fold(
|
||||
|
@ -91,6 +90,10 @@ internal class SyncWorker(context: Context,
|
|||
)
|
||||
}
|
||||
|
||||
override fun buildErrorParams(params: Params, message: String): Params {
|
||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||
}
|
||||
|
||||
private suspend fun doSync(timeout: Long) {
|
||||
val taskParams = SyncTask.Params(timeout * 1000)
|
||||
syncTask.execute(taskParams)
|
||||
|
|
|
@ -202,6 +202,6 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
|
|||
stateKey = QueryStringValue.NoCondition
|
||||
)
|
||||
val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: return false
|
||||
return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, null)
|
||||
return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
package org.matrix.android.sdk.internal.util
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmModel
|
||||
import org.matrix.android.sdk.internal.database.awaitTransaction
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
internal suspend fun <T> Monarchy.awaitTransaction(transaction: suspend (realm: Realm) -> T): T {
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* This worker should only sends Result.Success when added to a unique queue to avoid breaking the unique queue.
|
||||
* This abstract class handle the cases of problem when parsing parameter, and forward the error if any to
|
||||
* the next workers.
|
||||
*/
|
||||
internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>(
|
||||
context: Context,
|
||||
workerParameters: WorkerParameters,
|
||||
private val paramClass: Class<PARAM>
|
||||
) : CoroutineWorker(context, workerParameters) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class ErrorData(
|
||||
override val sessionId: String,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
final override suspend fun doWork(): Result {
|
||||
val params = WorkerParamsFactory.fromData(paramClass, inputData)
|
||||
?: return buildErrorResult(null, "Unable to parse work parameters")
|
||||
.also { Timber.e("Unable to parse work parameters") }
|
||||
|
||||
return try {
|
||||
val sessionComponent = getSessionComponent(params.sessionId)
|
||||
?: return buildErrorResult(params, "No session")
|
||||
|
||||
// Make sure to inject before handling error as you may need some dependencies to process them.
|
||||
injectWith(sessionComponent)
|
||||
if (params.lastFailureMessage != null) {
|
||||
// Forward error to the next workers
|
||||
doOnError(params)
|
||||
} else {
|
||||
doSafeWork(params)
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
buildErrorResult(params, throwable.localizedMessage ?: "error")
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun injectWith(injector: SessionComponent)
|
||||
|
||||
/**
|
||||
* Should only return Result.Success for workers added to a unique queue
|
||||
*/
|
||||
abstract suspend fun doSafeWork(params: PARAM): Result
|
||||
|
||||
protected fun buildErrorResult(params: PARAM?, message: String): Result {
|
||||
return Result.success(
|
||||
if (params != null) {
|
||||
WorkerParamsFactory.toData(paramClass, buildErrorParams(params, message))
|
||||
} else {
|
||||
WorkerParamsFactory.toData(ErrorData::class.java, ErrorData(sessionId = "", lastFailureMessage = message))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
abstract fun buildErrorParams(params: PARAM, message: String): PARAM
|
||||
|
||||
/**
|
||||
* This is called when the input parameters are correct, but contain an error from the previous worker.
|
||||
*/
|
||||
@CallSuper
|
||||
open fun doOnError(params: PARAM): Result {
|
||||
// Forward the error
|
||||
return Result.success(inputData)
|
||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun hasFailed(outputData: Data): Boolean {
|
||||
return WorkerParamsFactory.fromData(ErrorData::class.java, outputData)
|
||||
.let { it?.lastFailureMessage != null }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,12 +18,14 @@
|
|||
package org.matrix.android.sdk.internal.worker
|
||||
|
||||
import androidx.work.Data
|
||||
import com.squareup.moshi.Moshi
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
|
||||
|
||||
internal object WorkerParamsFactory {
|
||||
|
||||
val moshi by lazy {
|
||||
private val moshi: Moshi by lazy {
|
||||
// We are adding the CheckNumberType as we are serializing/deserializing multiple time in a row
|
||||
// and we lost typing information doing so.
|
||||
// We don't want this check to be done on all adapters, so we just add it here.
|
||||
|
@ -33,20 +35,24 @@ internal object WorkerParamsFactory {
|
|||
.build()
|
||||
}
|
||||
|
||||
const val KEY = "WORKER_PARAMS_JSON"
|
||||
private const val KEY = "WORKER_PARAMS_JSON"
|
||||
|
||||
inline fun <reified T> toData(params: T): Data {
|
||||
val adapter = moshi.adapter(T::class.java)
|
||||
inline fun <reified T> toData(params: T) = toData(T::class.java, params)
|
||||
|
||||
fun <T> toData(clazz: Class<T>, params: T): Data {
|
||||
val adapter = moshi.adapter(clazz)
|
||||
val json = adapter.toJson(params)
|
||||
return Data.Builder().putString(KEY, json).build()
|
||||
}
|
||||
|
||||
inline fun <reified T> fromData(data: Data): T? {
|
||||
inline fun <reified T> fromData(data: Data) = fromData(T::class.java, data)
|
||||
|
||||
fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull("Unable to parse work parameters") {
|
||||
val json = data.getString(KEY)
|
||||
return if (json == null) {
|
||||
null
|
||||
} else {
|
||||
val adapter = moshi.adapter(T::class.java)
|
||||
val adapter = moshi.adapter(clazz)
|
||||
adapter.fromJson(json)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,21 +151,21 @@
|
|||
<string name="notice_power_level_changed">%1$s alterou o nível de permissão de %2$s.</string>
|
||||
<string name="notice_power_level_diff">%1$s de %2$s para %3$s</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account">Primeira sincronização:
|
||||
\nImportando a conta…</string>
|
||||
<string name="initial_sync_start_importing_account_crypto">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account_crypto">Primeira sincronização:
|
||||
\nImportando as chaves de criptografia</string>
|
||||
<string name="initial_sync_start_importing_account_rooms">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account_rooms">Primeira sincronização:
|
||||
\nImportando as salas</string>
|
||||
<string name="initial_sync_start_importing_account_joined_rooms">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account_joined_rooms">Primeira sincronização:
|
||||
\nImportando as salas em que você entrou</string>
|
||||
<string name="initial_sync_start_importing_account_invited_rooms">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account_invited_rooms">Primeira sincronização:
|
||||
\nImportando as salas em que você foi convidado</string>
|
||||
<string name="initial_sync_start_importing_account_left_rooms">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account_left_rooms">Primeira sincronização:
|
||||
\nImportando as salas em que você saiu</string>
|
||||
<string name="initial_sync_start_importing_account_groups">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account_groups">Primeira sincronização:
|
||||
\nImportando as comunidades</string>
|
||||
<string name="initial_sync_start_importing_account_data">Primeira sincronização:↵
|
||||
<string name="initial_sync_start_importing_account_data">Primeira sincronização:
|
||||
\nImportando os dados da conta</string>
|
||||
|
||||
<string name="event_status_sending_message">Enviando mensagem…</string>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Generated file, do not edit -->
|
||||
<string name="verification_emoji_dog">Cachorro</string>
|
||||
<string name="verification_emoji_cat">Gato</string>
|
||||
<string name="verification_emoji_lion">Leão</string>
|
||||
<string name="verification_emoji_horse">Cavalo</string>
|
||||
<string name="verification_emoji_unicorn">Unicórnio</string>
|
||||
<string name="verification_emoji_pig">Porco</string>
|
||||
<string name="verification_emoji_elephant">Elefante</string>
|
||||
<string name="verification_emoji_rabbit">Coelho</string>
|
||||
<string name="verification_emoji_panda">Panda</string>
|
||||
<string name="verification_emoji_rooster">Galo</string>
|
||||
<string name="verification_emoji_penguin">Pinguim</string>
|
||||
<string name="verification_emoji_turtle">Tartaruga</string>
|
||||
<string name="verification_emoji_fish">Peixe</string>
|
||||
<string name="verification_emoji_octopus">Polvo</string>
|
||||
<string name="verification_emoji_butterfly">Borboleta</string>
|
||||
<string name="verification_emoji_flower">Flor</string>
|
||||
<string name="verification_emoji_tree">Árvore</string>
|
||||
<string name="verification_emoji_cactus">Cacto</string>
|
||||
<string name="verification_emoji_mushroom">Cogumelo</string>
|
||||
<string name="verification_emoji_globe">Globo</string>
|
||||
<string name="verification_emoji_moon">Lua</string>
|
||||
<string name="verification_emoji_cloud">Nuvem</string>
|
||||
<string name="verification_emoji_fire">Fogo</string>
|
||||
<string name="verification_emoji_banana">Banana</string>
|
||||
<string name="verification_emoji_apple">Maçã</string>
|
||||
<string name="verification_emoji_strawberry">Morango</string>
|
||||
<string name="verification_emoji_corn">Milho</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Bolo</string>
|
||||
<string name="verification_emoji_heart">Coração</string>
|
||||
<string name="verification_emoji_smiley">Sorriso</string>
|
||||
<string name="verification_emoji_robot">Robô</string>
|
||||
<string name="verification_emoji_hat">Chapéu</string>
|
||||
<string name="verification_emoji_glasses">Óculos</string>
|
||||
<string name="verification_emoji_spanner">Chave inglesa</string>
|
||||
<string name="verification_emoji_santa">Papai-noel</string>
|
||||
<string name="verification_emoji_thumbs_up">Joinha</string>
|
||||
<string name="verification_emoji_umbrella">Guarda-chuva</string>
|
||||
<string name="verification_emoji_hourglass">Ampulheta</string>
|
||||
<string name="verification_emoji_clock">Relógio</string>
|
||||
<string name="verification_emoji_gift">Presente</string>
|
||||
<string name="verification_emoji_light_bulb">Lâmpada</string>
|
||||
<string name="verification_emoji_book">Livro</string>
|
||||
<string name="verification_emoji_pencil">Lápis</string>
|
||||
<string name="verification_emoji_paperclip">Clipe de papel</string>
|
||||
<string name="verification_emoji_scissors">Tesoura</string>
|
||||
<string name="verification_emoji_lock">Cadeado</string>
|
||||
<string name="verification_emoji_key">Chave</string>
|
||||
<string name="verification_emoji_hammer">Martelo</string>
|
||||
<string name="verification_emoji_telephone">Telefone</string>
|
||||
<string name="verification_emoji_flag">Bandeira</string>
|
||||
<string name="verification_emoji_train">Trem</string>
|
||||
<string name="verification_emoji_bicycle">Bicicleta</string>
|
||||
<string name="verification_emoji_aeroplane">Avião</string>
|
||||
<string name="verification_emoji_rocket">Foguete</string>
|
||||
<string name="verification_emoji_trophy">Troféu</string>
|
||||
<string name="verification_emoji_ball">Bola</string>
|
||||
<string name="verification_emoji_guitar">Guitarra</string>
|
||||
<string name="verification_emoji_trumpet">Trombeta</string>
|
||||
<string name="verification_emoji_bell">Sino</string>
|
||||
<string name="verification_emoji_anchor">Âncora</string>
|
||||
<string name="verification_emoji_headphones">Fones de ouvido</string>
|
||||
<string name="verification_emoji_folder">Pasta</string>
|
||||
<string name="verification_emoji_pin">Alfinete</string>
|
||||
</resources>
|
|
@ -27,8 +27,8 @@
|
|||
<string name="notice_room_ban_by_you">Du bannade %1$s</string>
|
||||
<string name="notice_room_withdraw">%1$s drog tillbaka inbjudan för %2$s</string>
|
||||
<string name="notice_room_withdraw_by_you">Du drog tillbaka inbjudan för %1$s</string>
|
||||
<string name="notice_avatar_url_changed">%1$s ändrade sin avatar</string>
|
||||
<string name="notice_avatar_url_changed_by_you">Du ändrade din avatar</string>
|
||||
<string name="notice_avatar_url_changed">%1$s bytte sin avatar</string>
|
||||
<string name="notice_avatar_url_changed_by_you">Du bytte din avatar</string>
|
||||
<string name="notice_display_name_set">%1$s satte sitt visningsnamn till %2$s</string>
|
||||
<string name="notice_display_name_set_by_you">Du satte ditt visningsnamn till %1$s</string>
|
||||
<string name="notice_display_name_changed_from">%1$s bytte sitt visningsnamn från %2$s till %3$s</string>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<string name="verification_emoji_banana">Banan</string>
|
||||
<string name="verification_emoji_apple">Äpple</string>
|
||||
<string name="verification_emoji_strawberry">Jordgubbe</string>
|
||||
<string name="verification_emoji_corn">Majskolv</string>
|
||||
<string name="verification_emoji_corn">Majs</string>
|
||||
<string name="verification_emoji_pizza">Pizza</string>
|
||||
<string name="verification_emoji_cake">Tårta</string>
|
||||
<string name="verification_emoji_heart">Hjärta</string>
|
||||
|
@ -41,7 +41,7 @@
|
|||
<string name="verification_emoji_umbrella">Paraply</string>
|
||||
<string name="verification_emoji_hourglass">Timglas</string>
|
||||
<string name="verification_emoji_clock">Klocka</string>
|
||||
<string name="verification_emoji_gift">Paket</string>
|
||||
<string name="verification_emoji_gift">Present</string>
|
||||
<string name="verification_emoji_light_bulb">Lampa</string>
|
||||
<string name="verification_emoji_book">Bok</string>
|
||||
<string name="verification_emoji_pencil">Penna</string>
|
||||
|
@ -52,7 +52,7 @@
|
|||
<string name="verification_emoji_hammer">Hammare</string>
|
||||
<string name="verification_emoji_telephone">Telefon</string>
|
||||
<string name="verification_emoji_flag">Flagga</string>
|
||||
<string name="verification_emoji_train">Ånglok</string>
|
||||
<string name="verification_emoji_train">Tåg</string>
|
||||
<string name="verification_emoji_bicycle">Cykel</string>
|
||||
<string name="verification_emoji_aeroplane">Flygplan</string>
|
||||
<string name="verification_emoji_rocket">Raket</string>
|
||||
|
|
|
@ -7,4 +7,62 @@
|
|||
<string name="verification_emoji_horse">马</string>
|
||||
<string name="verification_emoji_unicorn">独角兽</string>
|
||||
<string name="verification_emoji_pig">猪</string>
|
||||
<string name="verification_emoji_elephant">大象</string>
|
||||
<string name="verification_emoji_rabbit">兔子</string>
|
||||
<string name="verification_emoji_panda">熊猫</string>
|
||||
<string name="verification_emoji_rooster">公鸡</string>
|
||||
<string name="verification_emoji_penguin">企鹅</string>
|
||||
<string name="verification_emoji_turtle">乌龟</string>
|
||||
<string name="verification_emoji_fish">鱼</string>
|
||||
<string name="verification_emoji_octopus">章鱼</string>
|
||||
<string name="verification_emoji_butterfly">蝴蝶</string>
|
||||
<string name="verification_emoji_flower">花</string>
|
||||
<string name="verification_emoji_tree">树</string>
|
||||
<string name="verification_emoji_cactus">仙人掌</string>
|
||||
<string name="verification_emoji_mushroom">蘑菇</string>
|
||||
<string name="verification_emoji_globe">地球</string>
|
||||
<string name="verification_emoji_moon">月亮</string>
|
||||
<string name="verification_emoji_cloud">云</string>
|
||||
<string name="verification_emoji_fire">火</string>
|
||||
<string name="verification_emoji_banana">香蕉</string>
|
||||
<string name="verification_emoji_apple">苹果</string>
|
||||
<string name="verification_emoji_strawberry">草莓</string>
|
||||
<string name="verification_emoji_corn">玉米</string>
|
||||
<string name="verification_emoji_pizza">披萨</string>
|
||||
<string name="verification_emoji_cake">蛋糕</string>
|
||||
<string name="verification_emoji_heart">心</string>
|
||||
<string name="verification_emoji_smiley">笑脸</string>
|
||||
<string name="verification_emoji_robot">机器人</string>
|
||||
<string name="verification_emoji_hat">帽子</string>
|
||||
<string name="verification_emoji_glasses">眼镜</string>
|
||||
<string name="verification_emoji_spanner">扳手</string>
|
||||
<string name="verification_emoji_santa">圣诞老人</string>
|
||||
<string name="verification_emoji_thumbs_up">赞</string>
|
||||
<string name="verification_emoji_umbrella">伞</string>
|
||||
<string name="verification_emoji_hourglass">沙漏</string>
|
||||
<string name="verification_emoji_clock">时钟</string>
|
||||
<string name="verification_emoji_gift">礼物</string>
|
||||
<string name="verification_emoji_light_bulb">灯泡</string>
|
||||
<string name="verification_emoji_book">书</string>
|
||||
<string name="verification_emoji_pencil">铅笔</string>
|
||||
<string name="verification_emoji_paperclip">回形针</string>
|
||||
<string name="verification_emoji_scissors">剪刀</string>
|
||||
<string name="verification_emoji_lock">锁</string>
|
||||
<string name="verification_emoji_key">钥匙</string>
|
||||
<string name="verification_emoji_hammer">锤子</string>
|
||||
<string name="verification_emoji_telephone">电话</string>
|
||||
<string name="verification_emoji_flag">旗帜</string>
|
||||
<string name="verification_emoji_train">火车</string>
|
||||
<string name="verification_emoji_bicycle">自行车</string>
|
||||
<string name="verification_emoji_aeroplane">飞机</string>
|
||||
<string name="verification_emoji_rocket">火箭</string>
|
||||
<string name="verification_emoji_trophy">奖杯</string>
|
||||
<string name="verification_emoji_ball">球</string>
|
||||
<string name="verification_emoji_guitar">吉他</string>
|
||||
<string name="verification_emoji_trumpet">喇叭</string>
|
||||
<string name="verification_emoji_bell">铃铛</string>
|
||||
<string name="verification_emoji_anchor">锚</string>
|
||||
<string name="verification_emoji_headphones">耳机</string>
|
||||
<string name="verification_emoji_folder">文件夹</string>
|
||||
<string name="verification_emoji_pin">图钉</string>
|
||||
</resources>
|
||||
|
|
|
@ -17,7 +17,7 @@ androidExtensions {
|
|||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 1
|
||||
ext.versionMinor = 0
|
||||
ext.versionPatch = 7
|
||||
ext.versionPatch = 8
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@ -352,7 +352,7 @@ dependencies {
|
|||
implementation 'me.saket:better-link-movement-method:2.2.0'
|
||||
implementation 'com.google.android:flexbox:1.1.1'
|
||||
implementation "androidx.autofill:autofill:$autofill_version"
|
||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta9'
|
||||
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta10'
|
||||
|
||||
// Custom Tab
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
|
|
|
@ -89,6 +89,7 @@ import im.vector.app.features.settings.VectorSettingsHelpAboutFragment
|
|||
import im.vector.app.features.settings.VectorSettingsLabsFragment
|
||||
import im.vector.app.features.settings.VectorSettingsNotificationPreferenceFragment
|
||||
import im.vector.app.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
||||
import im.vector.app.features.settings.VectorSettingsPinFragment
|
||||
import im.vector.app.features.settings.VectorSettingsPreferencesFragment
|
||||
import im.vector.app.features.settings.VectorSettingsSecurityPrivacyFragment
|
||||
import im.vector.app.features.settings.account.deactivation.DeactivateAccountFragment
|
||||
|
@ -284,6 +285,11 @@ interface FragmentModule {
|
|||
@FragmentKey(VectorSettingsLabsFragment::class)
|
||||
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(VectorSettingsPinFragment::class)
|
||||
fun bindVectorSettingsPinFragment(fragment: VectorSettingsPinFragment): Fragment
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FragmentKey(PushRulesFragment::class)
|
||||
|
|
|
@ -36,6 +36,7 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan
|
|||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.HomeRoomListDataSource
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
import im.vector.app.features.html.EventHtmlRenderer
|
||||
import im.vector.app.features.html.VectorHtmlCompressor
|
||||
import im.vector.app.features.login.ReAuthHelper
|
||||
|
@ -71,6 +72,8 @@ interface VectorComponent {
|
|||
|
||||
fun matrix(): Matrix
|
||||
|
||||
fun matrixItemColorProvider(): MatrixItemColorProvider
|
||||
|
||||
fun sessionListener(): SessionListener
|
||||
|
||||
fun currentSession(): Session
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
package im.vector.app.core.extensions
|
||||
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
|
||||
inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||
// Could throw and make the app crash
|
||||
// e.g sharedActionViewModel.observe()
|
||||
tryThis("Failed to commitTransactionNow") {
|
||||
tryOrNull("Failed to commitTransactionNow") {
|
||||
beginTransaction().func().commitNow()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,14 +18,14 @@ package im.vector.app.core.extensions
|
|||
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import org.matrix.android.sdk.api.extensions.ensurePrefix
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
|
||||
fun ThreePid.getFormattedValue(): String {
|
||||
return when (this) {
|
||||
is ThreePid.Email -> email
|
||||
is ThreePid.Msisdn -> {
|
||||
tryThis(message = "Unable to parse the phone number") {
|
||||
tryOrNull(message = "Unable to parse the phone number") {
|
||||
PhoneNumberUtil.getInstance().parse(msisdn.ensurePrefix("+"), null)
|
||||
}
|
||||
?.let {
|
||||
|
|
|
@ -84,7 +84,7 @@ import im.vector.app.receivers.DebugReceiver
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.GlobalError
|
||||
import timber.log.Timber
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
@ -318,11 +318,17 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
|
||||
when (resultCode) {
|
||||
Activity.RESULT_OK -> {
|
||||
Timber.v("Pin ok, unlock app")
|
||||
pinLocker.unlock()
|
||||
|
||||
// Cancel any new started PinActivity, after a screen rotation for instance
|
||||
finishActivity(PinActivity.PIN_REQUEST_CODE)
|
||||
}
|
||||
else -> {
|
||||
pinLocker.block()
|
||||
moveTaskToBack(true)
|
||||
if (pinLocker.getLiveState().value != PinLocker.State.UNLOCKED) {
|
||||
// Remove the task, to be sure that PIN code will be requested when resumed
|
||||
finishAndRemoveTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -362,7 +368,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
super.onPostResume()
|
||||
synchronized(postResumeScheduledActions) {
|
||||
postResumeScheduledActions.forEach {
|
||||
tryThis { it.invoke() }
|
||||
tryOrNull { it.invoke() }
|
||||
}
|
||||
postResumeScheduledActions.clear()
|
||||
}
|
||||
|
|
|
@ -56,6 +56,9 @@ abstract class GenericItemWithValue : VectorEpoxyModel<GenericItemWithValue.Hold
|
|||
@EpoxyAttribute
|
||||
var itemClickAction: View.OnClickListener? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var itemLongClickAction: View.OnLongClickListener? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.titleText.setTextOrHide(title)
|
||||
|
@ -76,6 +79,7 @@ abstract class GenericItemWithValue : VectorEpoxyModel<GenericItemWithValue.Hold
|
|||
}
|
||||
|
||||
holder.view.setOnClickListener(itemClickAction?.let { DebouncedClickListener(it) })
|
||||
holder.view.setOnLongClickListener(itemLongClickAction)
|
||||
}
|
||||
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
|
|
|
@ -40,7 +40,7 @@ import androidx.fragment.app.Fragment
|
|||
import im.vector.app.BuildConfig
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.notifications.NotificationUtils
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -530,7 +530,7 @@ fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: Strin
|
|||
return null
|
||||
} finally {
|
||||
// Close resources
|
||||
tryThis { inputStream?.close() }
|
||||
tryThis { outputStream?.close() }
|
||||
tryOrNull { inputStream?.close() }
|
||||
tryOrNull { outputStream?.close() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ import androidx.fragment.app.Fragment
|
|||
import im.vector.app.R
|
||||
import timber.log.Timber
|
||||
|
||||
private const val LOG_TAG = "PermissionUtils"
|
||||
|
||||
// Android M permission request code management
|
||||
private const val PERMISSIONS_GRANTED = true
|
||||
private const val PERMISSIONS_DENIED = !PERMISSIONS_GRANTED
|
||||
|
@ -42,16 +40,18 @@ const val PERMISSION_CAMERA = 0x1
|
|||
private const val PERMISSION_WRITE_EXTERNAL_STORAGE = 0x1 shl 1
|
||||
private const val PERMISSION_RECORD_AUDIO = 0x1 shl 2
|
||||
private const val PERMISSION_READ_CONTACTS = 0x1 shl 3
|
||||
private const val PERMISSION_READ_EXTERNAL_STORAGE = 0x1 shl 4
|
||||
|
||||
// Permissions sets
|
||||
const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA or PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA
|
||||
const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
|
||||
const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
|
||||
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
|
||||
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
|
||||
const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
|
||||
const val PERMISSIONS_FOR_READING_FILES = PERMISSION_READ_EXTERNAL_STORAGE
|
||||
const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
|
||||
|
||||
const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
|
||||
|
@ -67,7 +67,6 @@ const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
|
|||
const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
|
||||
const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
|
||||
const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
|
||||
const val PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT = 578
|
||||
const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579
|
||||
|
||||
/**
|
||||
|
@ -79,6 +78,7 @@ fun logPermissionStatuses(context: Context) {
|
|||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_CONTACTS)
|
||||
|
||||
Timber.v("## logPermissionStatuses() : log the permissions status used by the app")
|
||||
|
@ -145,7 +145,7 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
|||
fragment: Fragment?,
|
||||
requestCode: Int,
|
||||
@StringRes rationaleMessage: Int
|
||||
): Boolean {
|
||||
): Boolean {
|
||||
var isPermissionGranted = false
|
||||
|
||||
// sanity check
|
||||
|
@ -161,7 +161,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
|||
&& PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap) {
|
||||
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap
|
||||
&& PERMISSIONS_FOR_READING_FILES != permissionsToBeGrantedBitMap) {
|
||||
Timber.w("## checkPermissions(): permissions to be granted are not supported")
|
||||
isPermissionGranted = false
|
||||
} else {
|
||||
|
@ -188,6 +189,12 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
|||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
if (PERMISSION_READ_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_READ_EXTERNAL_STORAGE) {
|
||||
val permissionType = Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
isRequestPermissionRequired = isRequestPermissionRequired or
|
||||
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
|
||||
}
|
||||
|
||||
// the contact book access is requested for any android platforms
|
||||
// for android M, we use the system preferences
|
||||
// for android < M, we use a dedicated settings
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.core.utils
|
||||
|
||||
import androidx.annotation.ColorRes
|
||||
import im.vector.app.R
|
||||
import kotlin.math.abs
|
||||
|
||||
@ColorRes
|
||||
fun getColorFromUserId(userId: String?): Int {
|
||||
var hash = 0
|
||||
|
||||
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
|
||||
|
||||
return when (abs(hash) % 8) {
|
||||
1 -> R.color.riotx_username_2
|
||||
2 -> R.color.riotx_username_3
|
||||
3 -> R.color.riotx_username_4
|
||||
4 -> R.color.riotx_username_5
|
||||
5 -> R.color.riotx_username_6
|
||||
6 -> R.color.riotx_username_7
|
||||
7 -> R.color.riotx_username_8
|
||||
else -> R.color.riotx_username_1
|
||||
}
|
||||
}
|
|
@ -42,7 +42,6 @@ import im.vector.app.core.extensions.getMeasurements
|
|||
import im.vector.app.core.utils.PERMISSIONS_EMPTY
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
|
||||
import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
|
||||
import kotlin.math.max
|
||||
|
||||
|
@ -215,10 +214,10 @@ class AttachmentTypeSelectorView(context: Context,
|
|||
*/
|
||||
enum class Type(val permissionsBit: Int) {
|
||||
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
|
||||
GALLERY(PERMISSIONS_FOR_WRITING_FILES),
|
||||
FILE(PERMISSIONS_FOR_WRITING_FILES),
|
||||
GALLERY(PERMISSIONS_EMPTY),
|
||||
FILE(PERMISSIONS_EMPTY),
|
||||
STICKER(PERMISSIONS_EMPTY),
|
||||
AUDIO(PERMISSIONS_FOR_WRITING_FILES),
|
||||
AUDIO(PERMISSIONS_EMPTY),
|
||||
CONTACT(PERMISSIONS_FOR_PICKING_CONTACT)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,17 +42,13 @@ import im.vector.app.core.extensions.cleanup
|
|||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.utils.OnSnapPositionChangeListener
|
||||
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
|
||||
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT
|
||||
import im.vector.app.core.utils.SnapOnScrollListener
|
||||
import im.vector.app.core.utils.allGranted
|
||||
import im.vector.app.core.utils.attachSnapHelperWithListener
|
||||
import im.vector.app.core.utils.checkPermissions
|
||||
import im.vector.app.features.media.createUCropWithDefaultSettings
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
@ -102,7 +98,7 @@ class AttachmentsPreviewFragment @Inject constructor(
|
|||
handleRemoveAction()
|
||||
true
|
||||
}
|
||||
R.id.attachmentsPreviewEditAction -> {
|
||||
R.id.attachmentsPreviewEditAction -> {
|
||||
handleEditAction()
|
||||
true
|
||||
}
|
||||
|
@ -183,22 +179,7 @@ class AttachmentsPreviewFragment @Inject constructor(
|
|||
viewModel.handle(AttachmentsPreviewAction.RemoveCurrentAttachment)
|
||||
}
|
||||
|
||||
private fun handleEditAction() {
|
||||
// check permissions
|
||||
if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT)) {
|
||||
doHandleEditAction()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
if (requestCode == PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT && allGranted(grantResults)) {
|
||||
doHandleEditAction()
|
||||
}
|
||||
}
|
||||
|
||||
private fun doHandleEditAction() = withState(viewModel) {
|
||||
private fun handleEditAction() = withState(viewModel) {
|
||||
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
|
||||
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
|
||||
val uri = currentAttachment.queryUri
|
||||
|
|
|
@ -27,7 +27,7 @@ import io.reactivex.disposables.Disposable
|
|||
import io.reactivex.subjects.PublishSubject
|
||||
import io.reactivex.subjects.ReplaySubject
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.CallsListener
|
||||
|
@ -95,7 +95,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
|
||||
val callAudioManager = CallAudioManager(context.applicationContext) {
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onAudioDevicesChange() }
|
||||
tryOrNull { it.onAudioDevicesChange() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onCaptureStateChanged() }
|
||||
tryOrNull { it.onCaptureStateChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,7 +205,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onCurrentCallChange(value?.mxCall) }
|
||||
tryOrNull { it.onCurrentCallChange(value?.mxCall) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -745,7 +745,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
}
|
||||
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onCameraChange() }
|
||||
tryOrNull { it.onCameraChange() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -771,7 +771,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
// videoCapturer?.stopCapture()
|
||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||
currentCaptureMode = format
|
||||
currentCallsListeners.forEach { tryThis { it.onCaptureStateChanged() } }
|
||||
currentCallsListeners.forEach { tryOrNull { it.onCaptureStateChanged() } }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.jitsi.meet.sdk.JitsiMeetActivityInterface
|
|||
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions
|
||||
import org.jitsi.meet.sdk.JitsiMeetView
|
||||
import org.jitsi.meet.sdk.JitsiMeetViewListener
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import java.net.URL
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -100,7 +100,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji
|
|||
.setVideoMuted(!viewState.enableVideo)
|
||||
.setUserInfo(viewState.userInfo)
|
||||
.apply {
|
||||
tryThis { URL(viewState.jitsiUrl) }?.let {
|
||||
tryOrNull { URL(viewState.jitsiUrl) }?.let {
|
||||
setServerURL(it)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import com.jakewharton.rxbinding3.widget.textChanges
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.startImportTextFromFileIntent
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_ssss_access_from_key.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -84,7 +84,7 @@ class SharedSecuredStorageKeyFragment @Inject constructor() : VectorBaseFragment
|
|||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
|
||||
data?.data?.let { dataURI ->
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
activity?.contentResolver?.openInputStream(dataURI)
|
||||
?.bufferedReader()
|
||||
?.use { it.readText() }
|
||||
|
|
|
@ -37,7 +37,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
|||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.utils.colorizeMatchingText
|
||||
import im.vector.app.core.utils.startImportTextFromFileIntent
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.isValidRecoveryKey
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_bootstrap_enter_passphrase.bootstrapDescriptionText
|
||||
|
@ -150,7 +150,7 @@ class BootstrapMigrateBackupFragment @Inject constructor(
|
|||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == IMPORT_FILE_REQ && resultCode == Activity.RESULT_OK) {
|
||||
data?.data?.let { dataURI ->
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
activity?.contentResolver?.openInputStream(dataURI)
|
||||
?.bufferedReader()
|
||||
?.use { it.readText() }
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorBaseActivity
|
|||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.ensureProtocol
|
||||
import im.vector.app.features.discovery.change.SetIdentityServerFragment
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
import im.vector.app.features.terms.ReviewTermsActivity
|
||||
import org.matrix.android.sdk.api.session.identity.SharedState
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
|
@ -178,10 +179,6 @@ class DiscoverySettingsFragment @Inject constructor(
|
|||
}
|
||||
|
||||
private fun navigateToChangeIdentityServerFragment() {
|
||||
parentFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom, R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom)
|
||||
.replace(R.id.vector_settings_page, SetIdentityServerFragment::class.java, null)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
(vectorBaseActivity as? VectorSettingsActivity)?.navigateTo(SetIdentityServerFragment::class.java)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,11 @@
|
|||
|
||||
package im.vector.app.features.home
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
@ -33,8 +31,8 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.core.glide.GlideRequest
|
||||
import im.vector.app.core.glide.GlideRequests
|
||||
import im.vector.app.core.utils.getColorFromUserId
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
|
@ -43,7 +41,8 @@ import javax.inject.Inject
|
|||
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
|
||||
*/
|
||||
|
||||
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
|
||||
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val matrixItemColorProvider: MatrixItemColorProvider) {
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 250
|
||||
|
@ -51,21 +50,19 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
|
||||
@UiThread
|
||||
fun render(matrixItem: MatrixItem, imageView: ImageView) {
|
||||
render(imageView.context,
|
||||
GlideApp.with(imageView),
|
||||
render(GlideApp.with(imageView),
|
||||
matrixItem,
|
||||
DrawableImageViewTarget(imageView))
|
||||
}
|
||||
|
||||
fun clear(imageView: ImageView) {
|
||||
// It can be called after recycler view is destroyed, just silently catch
|
||||
tryThis { GlideApp.with(imageView).clear(imageView) }
|
||||
tryOrNull { GlideApp.with(imageView).clear(imageView) }
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
|
||||
render(imageView.context,
|
||||
glideRequests,
|
||||
render(glideRequests,
|
||||
matrixItem,
|
||||
DrawableImageViewTarget(imageView))
|
||||
}
|
||||
|
@ -79,7 +76,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
displayName = mappedContact.displayName
|
||||
)
|
||||
|
||||
val placeholder = getPlaceholderDrawable(imageView.context, matrixItem)
|
||||
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||
GlideApp.with(imageView)
|
||||
.load(mappedContact.photoURI)
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
|
@ -88,11 +85,10 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
}
|
||||
|
||||
@UiThread
|
||||
fun render(context: Context,
|
||||
glideRequests: GlideRequests,
|
||||
fun render(glideRequests: GlideRequests,
|
||||
matrixItem: MatrixItem,
|
||||
target: Target<Drawable>) {
|
||||
val placeholder = getPlaceholderDrawable(context, matrixItem)
|
||||
val placeholder = getPlaceholderDrawable(matrixItem)
|
||||
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
|
||||
.placeholder(placeholder)
|
||||
.into(target)
|
||||
|
@ -100,7 +96,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
|
||||
@AnyThread
|
||||
@Throws
|
||||
fun shortcutDrawable(context: Context, glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
|
||||
fun shortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
|
||||
return glideRequests
|
||||
.asBitmap()
|
||||
.apply {
|
||||
|
@ -108,7 +104,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
if (resolvedUrl != null) {
|
||||
load(resolvedUrl)
|
||||
} else {
|
||||
val avatarColor = avatarColor(matrixItem, context)
|
||||
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
|
||||
load(TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.bold()
|
||||
|
@ -130,8 +126,8 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
}
|
||||
|
||||
@AnyThread
|
||||
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
|
||||
val avatarColor = avatarColor(matrixItem, context)
|
||||
fun getPlaceholderDrawable(matrixItem: MatrixItem): Drawable {
|
||||
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
|
||||
return TextDrawable.builder()
|
||||
.beginConfig()
|
||||
.bold()
|
||||
|
@ -152,11 +148,4 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
|||
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
|
||||
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
}
|
||||
|
||||
private fun avatarColor(matrixItem: MatrixItem, context: Context): Int {
|
||||
return when (matrixItem) {
|
||||
is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id))
|
||||
else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class ShortcutsHandler @Inject constructor(
|
|||
.map { room ->
|
||||
val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)
|
||||
val bitmap = try {
|
||||
avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem(), iconSize)
|
||||
avatarRenderer.shortcutDrawable(GlideApp.with(context), room.toMatrixItem(), iconSize)
|
||||
} catch (failure: Throwable) {
|
||||
null
|
||||
}
|
||||
|
|
|
@ -97,7 +97,6 @@ import im.vector.app.core.utils.colorizeMatchingText
|
|||
import im.vector.app.core.utils.copyToClipboard
|
||||
import im.vector.app.core.utils.createJSonViewerStyleProvider
|
||||
import im.vector.app.core.utils.createUIHandler
|
||||
import im.vector.app.core.utils.getColorFromUserId
|
||||
import im.vector.app.core.utils.isValidUrl
|
||||
import im.vector.app.core.utils.onPermissionResultAudioIpCall
|
||||
import im.vector.app.core.utils.onPermissionResultVideoIpCall
|
||||
|
@ -127,6 +126,7 @@ import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction
|
|||
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
|
||||
|
@ -217,7 +217,9 @@ class RoomDetailFragment @Inject constructor(
|
|||
private val vectorPreferences: VectorPreferences,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val notificationUtils: NotificationUtils,
|
||||
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager) :
|
||||
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
|
||||
private val matrixItemColorProvider: MatrixItemColorProvider
|
||||
) :
|
||||
VectorBaseFragment(),
|
||||
TimelineEventController.Callback,
|
||||
VectorInviteView.Callback,
|
||||
|
@ -610,6 +612,16 @@ class RoomDetailFragment @Inject constructor(
|
|||
it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
|
||||
}
|
||||
withState(roomDetailViewModel) { state ->
|
||||
// Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions
|
||||
val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) {
|
||||
1 -> false
|
||||
2 -> state.isAllowedToStartWebRTCCall
|
||||
else -> state.isAllowedToManageWidgets
|
||||
}
|
||||
setOf(R.id.voice_call, R.id.video_call).forEach {
|
||||
menu.findItem(it).icon?.alpha = if (callButtonsEnabled) 0xFF else 0x40
|
||||
}
|
||||
|
||||
val matrixAppsMenuItem = menu.findItem(R.id.open_matrix_apps)
|
||||
val widgetsCount = state.activeRoomWidgets.invoke()?.size ?: 0
|
||||
if (widgetsCount > 0) {
|
||||
|
@ -687,6 +699,8 @@ class RoomDetailFragment @Inject constructor(
|
|||
// webRtcPeerConnectionManager.endCall()
|
||||
// safeStartCall(it, isVideoCall)
|
||||
// }
|
||||
} else if (!state.isAllowedToStartWebRTCCall) {
|
||||
showDialogWithMessage(getString(R.string.no_permissions_to_start_webrtc_call))
|
||||
} else {
|
||||
safeStartCall(isVideoCall)
|
||||
}
|
||||
|
@ -778,7 +792,7 @@ class RoomDetailFragment @Inject constructor(
|
|||
// switch to expanded bar
|
||||
composerLayout.composerRelatedMessageTitle.apply {
|
||||
text = event.senderInfo.disambiguatedDisplayName
|
||||
setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId)))
|
||||
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
|
||||
}
|
||||
|
||||
val messageContent: MessageContent? = event.getLastMessageContent()
|
||||
|
|
|
@ -57,7 +57,7 @@ import org.commonmark.renderer.html.HtmlRenderer
|
|||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -181,10 +181,12 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
.subscribe {
|
||||
val canSendMessage = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.MESSAGE)
|
||||
val isAllowedToManageWidgets = session.widgetService().hasPermissionsToHandleWidgets(room.roomId)
|
||||
val isAllowedToStartWebRTCCall = PowerLevelsHelper(it).isUserAllowedToSend(session.myUserId, false, EventType.CALL_INVITE)
|
||||
setState {
|
||||
copy(
|
||||
canSendMessage = canSendMessage,
|
||||
isAllowedToManageWidgets = isAllowedToManageWidgets
|
||||
isAllowedToManageWidgets = isAllowedToManageWidgets,
|
||||
isAllowedToStartWebRTCCall = isAllowedToStartWebRTCCall
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +336,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
val roomId: String = room.roomId
|
||||
val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.toLowerCase(VectorLocale.applicationLocale)
|
||||
|
||||
val preferredJitsiDomain = tryThis {
|
||||
val preferredJitsiDomain = tryOrNull {
|
||||
rawService.getElementWellknown(session.myUserId)
|
||||
?.jitsiServer
|
||||
?.preferredDomain
|
||||
|
@ -988,7 +990,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
&& mxcUrl?.startsWith("content://") ?: false
|
||||
val isDownloaded = mxcUrl?.let { session.fileService().isFileInCache(it, action.messageFileContent.mimeType) } ?: false
|
||||
if (isLocalSendingFile) {
|
||||
tryThis { Uri.parse(mxcUrl) }?.let {
|
||||
tryOrNull { Uri.parse(mxcUrl) }?.let {
|
||||
_viewEvents.post(RoomDetailViewEvents.OpenFile(
|
||||
action.messageFileContent.mimeType,
|
||||
it,
|
||||
|
|
|
@ -67,7 +67,8 @@ data class RoomDetailViewState(
|
|||
val canShowJumpToReadMarker: Boolean = true,
|
||||
val changeMembershipState: ChangeMembershipState = ChangeMembershipState.Unknown,
|
||||
val canSendMessage: Boolean = true,
|
||||
val isAllowedToManageWidgets: Boolean = false
|
||||
val isAllowedToManageWidgets: Boolean = false,
|
||||
val isAllowedToStartWebRTCCall: Boolean = true
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomDetailArgs) : this(
|
||||
|
|
|
@ -19,18 +19,20 @@ package im.vector.app.features.home.room.detail.timeline
|
|||
import androidx.annotation.ColorInt
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.core.utils.getColorFromUserId
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessageColorProvider @Inject constructor(
|
||||
private val colorProvider: ColorProvider,
|
||||
private val matrixItemColorProvider: MatrixItemColorProvider,
|
||||
private val vectorPreferences: VectorPreferences) {
|
||||
|
||||
@ColorInt
|
||||
fun getMemberNameTextColor(userId: String): Int {
|
||||
return colorProvider.getColor(getColorFromUserId(userId))
|
||||
fun getMemberNameTextColor(matrixItem: MatrixItem): Int {
|
||||
return matrixItemColorProvider.getColor(matrixItem)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.detail.timeline.helper
|
||||
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.abs
|
||||
|
||||
@Singleton
|
||||
class MatrixItemColorProvider @Inject constructor(
|
||||
private val colorProvider: ColorProvider
|
||||
) {
|
||||
private val cache = mutableMapOf<String, Int>()
|
||||
|
||||
@ColorInt
|
||||
fun getColor(matrixItem: MatrixItem): Int {
|
||||
return cache.getOrPut(matrixItem.id) {
|
||||
colorProvider.getColor(
|
||||
when (matrixItem) {
|
||||
is MatrixItem.UserItem -> getColorFromUserId(matrixItem.id)
|
||||
else -> getColorFromRoomId(matrixItem.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ColorRes
|
||||
@VisibleForTesting
|
||||
fun getColorFromUserId(userId: String?): Int {
|
||||
var hash = 0
|
||||
|
||||
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
|
||||
|
||||
return when (abs(hash) % 8) {
|
||||
1 -> R.color.riotx_username_2
|
||||
2 -> R.color.riotx_username_3
|
||||
3 -> R.color.riotx_username_4
|
||||
4 -> R.color.riotx_username_5
|
||||
5 -> R.color.riotx_username_6
|
||||
6 -> R.color.riotx_username_7
|
||||
7 -> R.color.riotx_username_8
|
||||
else -> R.color.riotx_username_1
|
||||
}
|
||||
}
|
||||
|
||||
@ColorRes
|
||||
private fun getColorFromRoomId(roomId: String?): Int {
|
||||
return when ((roomId?.toList()?.sumBy { it.toInt() } ?: 0) % 3) {
|
||||
1 -> R.color.riotx_avatar_fill_2
|
||||
2 -> R.color.riotx_avatar_fill_3
|
||||
else -> R.color.riotx_avatar_fill_1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,13 +21,13 @@ package im.vector.app.features.home.room.detail.timeline.helper
|
|||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.extensions.localDateTime
|
||||
import im.vector.app.core.resources.ColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
|
@ -49,7 +49,7 @@ import javax.inject.Inject
|
|||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||
private val roomSummaryHolder: RoomSummaryHolder,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val colorProvider: ColorProvider) {
|
||||
private val vectorPreferences: VectorPreferences) {
|
||||
|
||||
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
||||
// Non nullability has been tested before
|
||||
|
@ -81,6 +81,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
avatarUrl = event.senderInfo.avatarUrl,
|
||||
memberName = event.senderInfo.disambiguatedDisplayName,
|
||||
showInformation = showInformation,
|
||||
forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps(),
|
||||
orderedReactionList = event.annotations?.reactionsSummary
|
||||
// ?.filter { isSingleEmoji(it.key) }
|
||||
?.map {
|
||||
|
|
|
@ -21,6 +21,8 @@ import android.view.View
|
|||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.DebouncedClickListener
|
||||
|
@ -69,8 +71,14 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
holder.avatarImageView.setOnClickListener(null)
|
||||
holder.memberNameView.setOnClickListener(null)
|
||||
holder.avatarImageView.visibility = View.GONE
|
||||
holder.memberNameView.visibility = View.GONE
|
||||
holder.timeView.visibility = View.GONE
|
||||
if (attributes.informationData.forceShowTimestamp) {
|
||||
holder.memberNameView.isInvisible = true
|
||||
holder.timeView.isVisible = true
|
||||
holder.timeView.text = attributes.informationData.time
|
||||
} else {
|
||||
holder.memberNameView.isVisible = false
|
||||
holder.timeView.isVisible = false
|
||||
}
|
||||
holder.avatarImageView.setOnLongClickListener(null)
|
||||
holder.memberNameView.setOnLongClickListener(null)
|
||||
}
|
||||
|
@ -85,7 +93,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
super.unbind(holder)
|
||||
}
|
||||
|
||||
private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.senderId)
|
||||
private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.matrixItem)
|
||||
|
||||
abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
|
||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
|
|
|
@ -32,6 +32,7 @@ data class MessageInformationData(
|
|||
val avatarUrl: String?,
|
||||
val memberName: CharSequence? = null,
|
||||
val showInformation: Boolean = true,
|
||||
val forceShowTimestamp: Boolean = false,
|
||||
/*List of reactions (emoji,count,isSelected)*/
|
||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||
val pollResponseAggregatedSummary: PollResponseData? = null,
|
||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.app.R
|
|||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyHolder
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import java.net.URL
|
||||
|
||||
|
@ -42,7 +42,7 @@ abstract class RoomWidgetItem : EpoxyModelWithHolder<RoomWidgetItem.Holder>() {
|
|||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
holder.widgetName.text = widget.name
|
||||
holder.widgetUrl.text = tryThis { URL(widget.computedUrl) }?.host ?: widget.computedUrl
|
||||
holder.widgetUrl.text = tryOrNull { URL(widget.computedUrl) }?.host ?: widget.computedUrl
|
||||
if (iconRes != null) {
|
||||
holder.iconImage.isVisible = true
|
||||
holder.iconImage.setImageResource(iconRes!!)
|
||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.features.raw.wellknown.isE2EByDefault
|
|||
import im.vector.app.features.userdirectory.KnownUsersFragment
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
|
@ -68,7 +68,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor(
|
|||
|
||||
private fun initAdminE2eByDefault() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val adminE2EByDefault = tryThis {
|
||||
val adminE2EByDefault = tryOrNull {
|
||||
rawService.getElementWellknown(session.myUserId)
|
||||
?.isE2EByDefault()
|
||||
?: true
|
||||
|
|
|
@ -53,7 +53,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
|
|||
@UiThread
|
||||
fun bind(textView: TextView) {
|
||||
tv = WeakReference(textView)
|
||||
avatarRenderer.render(context, glideRequests, matrixItem, target)
|
||||
avatarRenderer.render(glideRequests, matrixItem, target)
|
||||
}
|
||||
|
||||
// ReplacementSpan *****************************************************************************
|
||||
|
@ -99,7 +99,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
|
|||
val icon = try {
|
||||
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
|
||||
} catch (exception: Exception) {
|
||||
avatarRenderer.getPlaceholderDrawable(context, matrixItem)
|
||||
avatarRenderer.getPlaceholderDrawable(matrixItem)
|
||||
}
|
||||
|
||||
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
||||
|
|
|
@ -19,10 +19,12 @@ package im.vector.app.features.login
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
|
@ -42,10 +44,10 @@ import im.vector.app.features.login.terms.LoginTermsFragment
|
|||
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
||||
import im.vector.app.features.login.terms.toLocalizedLoginTerms
|
||||
import im.vector.app.features.pin.UnlockedActivity
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -72,6 +74,13 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
|||
get() = supportFragmentManager.findFragmentById(R.id.loginFragmentContainer)
|
||||
|
||||
private val commonOption: (FragmentTransaction) -> Unit = { ft ->
|
||||
// Find the loginLogo on the current Fragment, this should not return null
|
||||
(topFragment?.view as? ViewGroup)
|
||||
// Find findViewById does not work, I do not know why
|
||||
// findViewById<View?>(R.id.loginLogo)
|
||||
?.children
|
||||
?.firstOrNull { it.id == R.id.loginLogo }
|
||||
?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||
}
|
||||
|
||||
|
@ -127,7 +136,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
|||
is LoginViewEvents.OutdatedHomeserver -> {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.login_error_outdated_homeserver_title)
|
||||
.setMessage(R.string.login_error_outdated_homeserver_content)
|
||||
.setMessage(R.string.login_error_outdated_homeserver_warning_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
Unit
|
||||
|
@ -136,6 +145,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
|||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginServerSelectionFragment::class.java,
|
||||
option = { ft ->
|
||||
findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
|
||||
// TODO Disabled because it provokes a flickering
|
||||
|
@ -262,7 +272,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
|
|||
super.onNewIntent(intent)
|
||||
|
||||
intent?.data
|
||||
?.let { tryThis { it.getQueryParameter("loginToken") } }
|
||||
?.let { tryOrNull { it.getQueryParameter("loginToken") } }
|
||||
?.let { loginViewModel.handle(LoginAction.LoginWithToken(it)) }
|
||||
}
|
||||
|
||||
|
|
|
@ -748,34 +748,21 @@ class LoginViewModel @AssistedInject constructor(
|
|||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
|
||||
notSupported()
|
||||
} else {
|
||||
// FIXME We should post a view event here normally?
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
// FIXME We should post a view event here normally?
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|
||||
|| data.isOutdatedHomeserver) {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
}
|
||||
}
|
||||
is LoginFlowResult.OutdatedHomeserver -> {
|
||||
notSupported()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notSupported() {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -39,7 +39,7 @@ import im.vector.app.core.utils.isLocalFile
|
|||
import kotlinx.android.parcel.Parcelize
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
@ -109,7 +109,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||
fun clear(imageView: ImageView) {
|
||||
// It can be called after recycler view is destroyed, just silently catch
|
||||
// We'd better keep ref to requestManager, but we don't have it
|
||||
tryThis {
|
||||
tryOrNull {
|
||||
GlideApp
|
||||
.with(imageView).clear(imageView)
|
||||
}
|
||||
|
|
|
@ -27,9 +27,9 @@ import im.vector.app.BuildConfig
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import me.gujun.android.span.span
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import me.gujun.android.span.span
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
@ -72,6 +72,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
private val currentSession: Session?
|
||||
get() = activeSessionDataSource.currentValue?.orNull()
|
||||
|
||||
private var useCompleteNotificationFormat = vectorPreferences.useCompleteNotificationFormat()
|
||||
|
||||
/**
|
||||
Should be called as soon as a new event is ready to be displayed.
|
||||
The notification corresponding to this event will not be displayed until
|
||||
|
@ -243,8 +245,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
roomEvents.add(event)
|
||||
}
|
||||
}
|
||||
is InviteNotifiableEvent -> invitationEvents.add(event)
|
||||
is SimpleNotifiableEvent -> simpleEvents.add(event)
|
||||
is InviteNotifiableEvent -> invitationEvents.add(event)
|
||||
is SimpleNotifiableEvent -> simpleEvents.add(event)
|
||||
else -> Timber.w("Type not handled")
|
||||
}
|
||||
}
|
||||
|
@ -253,6 +255,16 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
|
||||
var globalLastMessageTimestamp = 0L
|
||||
|
||||
val newSettings = vectorPreferences.useCompleteNotificationFormat()
|
||||
if (newSettings != useCompleteNotificationFormat) {
|
||||
// Settings has changed, remove all current notifications
|
||||
notificationUtils.cancelAllNotifications()
|
||||
useCompleteNotificationFormat = newSettings
|
||||
}
|
||||
|
||||
var simpleNotificationRoomCounter = 0
|
||||
var simpleNotificationMessageCounter = 0
|
||||
|
||||
// events have been grouped by roomId
|
||||
for ((roomId, events) in roomIdToEventMap) {
|
||||
// Build the notification for the room
|
||||
|
@ -263,6 +275,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
continue
|
||||
}
|
||||
|
||||
simpleNotificationRoomCounter++
|
||||
val roomName = events[0].roomName ?: events[0].senderName ?: ""
|
||||
|
||||
val roomEventGroupInfo = RoomEventGroupInfo(
|
||||
|
@ -303,6 +316,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
roomEventGroupInfo.hasSmartReplyError = true
|
||||
} else {
|
||||
if (!event.isRedacted) {
|
||||
simpleNotificationMessageCounter++
|
||||
style.addMessage(event.body, event.timestamp, senderPerson)
|
||||
}
|
||||
}
|
||||
|
@ -361,16 +375,18 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description)
|
||||
}
|
||||
|
||||
val notification = notificationUtils.buildMessagesListNotification(
|
||||
style,
|
||||
roomEventGroupInfo,
|
||||
largeBitmap,
|
||||
lastMessageTimestamp,
|
||||
myUserDisplayName,
|
||||
tickerText)
|
||||
if (useCompleteNotificationFormat) {
|
||||
val notification = notificationUtils.buildMessagesListNotification(
|
||||
style,
|
||||
roomEventGroupInfo,
|
||||
largeBitmap,
|
||||
lastMessageTimestamp,
|
||||
myUserDisplayName,
|
||||
tickerText)
|
||||
|
||||
// is there an id for this room?
|
||||
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
|
||||
// is there an id for this room?
|
||||
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
hasNewEvent = true
|
||||
summaryIsNoisy = summaryIsNoisy || roomEventGroupInfo.shouldBing
|
||||
|
@ -383,8 +399,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
for (event in invitationEvents) {
|
||||
// We build a invitation notification
|
||||
if (firstTime || !event.hasBeenDisplayed) {
|
||||
val notification = notificationUtils.buildRoomInvitationNotification(event, session.myUserId)
|
||||
notificationUtils.showNotificationMessage(event.roomId, ROOM_INVITATION_NOTIFICATION_ID, notification)
|
||||
if (useCompleteNotificationFormat) {
|
||||
val notification = notificationUtils.buildRoomInvitationNotification(event, session.myUserId)
|
||||
notificationUtils.showNotificationMessage(event.roomId, ROOM_INVITATION_NOTIFICATION_ID, notification)
|
||||
}
|
||||
event.hasBeenDisplayed = true // we can consider it as displayed
|
||||
hasNewEvent = true
|
||||
summaryIsNoisy = summaryIsNoisy || event.noisy
|
||||
|
@ -396,8 +414,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
for (event in simpleEvents) {
|
||||
// We build a simple notification
|
||||
if (firstTime || !event.hasBeenDisplayed) {
|
||||
val notification = notificationUtils.buildSimpleEventNotification(event, session.myUserId)
|
||||
notificationUtils.showNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID, notification)
|
||||
if (useCompleteNotificationFormat) {
|
||||
val notification = notificationUtils.buildSimpleEventNotification(event, session.myUserId)
|
||||
notificationUtils.showNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID, notification)
|
||||
}
|
||||
event.hasBeenDisplayed = true // we can consider it as displayed
|
||||
hasNewEvent = true
|
||||
summaryIsNoisy = summaryIsNoisy || event.noisy
|
||||
|
@ -421,19 +441,76 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
|||
if (eventList.isEmpty() || eventList.all { it.isRedacted }) {
|
||||
notificationUtils.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID)
|
||||
} else {
|
||||
// FIXME roomIdToEventMap.size is not correct, this is the number of rooms
|
||||
val nbEvents = roomIdToEventMap.size + simpleEvents.size
|
||||
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
|
||||
summaryInboxStyle.setBigContentTitle(sumTitle)
|
||||
// TODO get latest event?
|
||||
.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
|
||||
|
||||
val notification = notificationUtils.buildSummaryListNotification(
|
||||
summaryInboxStyle,
|
||||
sumTitle,
|
||||
noisy = hasNewEvent && summaryIsNoisy,
|
||||
lastMessageTimestamp = globalLastMessageTimestamp)
|
||||
if (useCompleteNotificationFormat) {
|
||||
val notification = notificationUtils.buildSummaryListNotification(
|
||||
summaryInboxStyle,
|
||||
sumTitle,
|
||||
noisy = hasNewEvent && summaryIsNoisy,
|
||||
lastMessageTimestamp = globalLastMessageTimestamp)
|
||||
|
||||
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
||||
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
||||
} else {
|
||||
// Add the simple events as message (?)
|
||||
simpleNotificationMessageCounter += simpleEvents.size
|
||||
val numberOfInvitations = invitationEvents.size
|
||||
|
||||
val privacyTitle = if (numberOfInvitations > 0) {
|
||||
val invitationsStr = stringProvider.getQuantityString(R.plurals.notification_invitations, numberOfInvitations, numberOfInvitations)
|
||||
if (simpleNotificationMessageCounter > 0) {
|
||||
// Invitation and message
|
||||
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
||||
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
|
||||
if (simpleNotificationRoomCounter > 1) {
|
||||
// In several rooms
|
||||
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
|
||||
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
|
||||
stringProvider.getString(
|
||||
R.string.notification_unread_notified_messages_in_room_and_invitation,
|
||||
messageStr,
|
||||
roomStr,
|
||||
invitationsStr
|
||||
)
|
||||
} else {
|
||||
// In one room
|
||||
stringProvider.getString(
|
||||
R.string.notification_unread_notified_messages_and_invitation,
|
||||
messageStr,
|
||||
invitationsStr
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Only invitation
|
||||
invitationsStr
|
||||
}
|
||||
} else {
|
||||
// No invitation, only messages
|
||||
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
|
||||
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
|
||||
if (simpleNotificationRoomCounter > 1) {
|
||||
// In several rooms
|
||||
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
|
||||
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
|
||||
stringProvider.getString(R.string.notification_unread_notified_messages_in_room, messageStr, roomStr)
|
||||
} else {
|
||||
// In one room
|
||||
messageStr
|
||||
}
|
||||
}
|
||||
val notification = notificationUtils.buildSummaryListNotification(
|
||||
style = null,
|
||||
compatSummary = privacyTitle,
|
||||
noisy = hasNewEvent && summaryIsNoisy,
|
||||
lastMessageTimestamp = globalLastMessageTimestamp)
|
||||
|
||||
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
if (hasNewEvent && summaryIsNoisy) {
|
||||
try {
|
||||
|
|
|
@ -772,7 +772,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
|||
/**
|
||||
* Build the summary notification
|
||||
*/
|
||||
fun buildSummaryListNotification(style: NotificationCompat.InboxStyle,
|
||||
fun buildSummaryListNotification(style: NotificationCompat.InboxStyle?,
|
||||
compatSummary: String,
|
||||
noisy: Boolean,
|
||||
lastMessageTimestamp: Long): Notification {
|
||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.app.core.platform.VectorBaseActivity
|
|||
class PinActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
|
||||
|
||||
companion object {
|
||||
|
||||
const val PIN_REQUEST_CODE = 17890
|
||||
|
||||
fun newIntent(context: Context, args: PinArgs): Intent {
|
||||
|
|
|
@ -32,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment
|
|||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.MainActivity
|
||||
import im.vector.app.features.MainActivityArgs
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
@ -42,7 +43,8 @@ data class PinArgs(
|
|||
) : Parcelable
|
||||
|
||||
class PinFragment @Inject constructor(
|
||||
private val pinCodeStore: PinCodeStore
|
||||
private val pinCodeStore: PinCodeStore,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : VectorBaseFragment() {
|
||||
|
||||
private val fragmentArgs: PinArgs by args()
|
||||
|
@ -53,54 +55,10 @@ class PinFragment @Inject constructor(
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
when (fragmentArgs.pinMode) {
|
||||
PinMode.CREATE -> showCreateFragment()
|
||||
PinMode.DELETE -> showDeleteFragment()
|
||||
PinMode.AUTH -> showAuthFragment()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDeleteFragment() {
|
||||
val encodedPin = pinCodeStore.getEncodedPin() ?: return
|
||||
val authFragment = PFLockScreenFragment()
|
||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
||||
.setUseBiometric(pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0)
|
||||
.setTitle(getString(R.string.auth_pin_confirm_to_disable_title))
|
||||
.setClearCodeOnError(true)
|
||||
.setMode(PFFLockScreenConfiguration.MODE_AUTH)
|
||||
authFragment.setConfiguration(builder.build())
|
||||
authFragment.setEncodedPinCode(encodedPin)
|
||||
authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener {
|
||||
override fun onPinLoginFailed() {
|
||||
onWrongPin()
|
||||
}
|
||||
|
||||
override fun onBiometricAuthSuccessful() {
|
||||
lifecycleScope.launch {
|
||||
pinCodeStore.deleteEncodedPin()
|
||||
vectorBaseActivity.setResult(Activity.RESULT_OK)
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBiometricAuthLoginFailed() {
|
||||
val remainingAttempts = pinCodeStore.onWrongBiometrics()
|
||||
if (remainingAttempts <= 0) {
|
||||
// Disable Biometrics
|
||||
builder.setUseBiometric(false)
|
||||
authFragment.setConfiguration(builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCodeInputSuccessful() {
|
||||
lifecycleScope.launch {
|
||||
pinCodeStore.deleteEncodedPin()
|
||||
vectorBaseActivity.setResult(Activity.RESULT_OK)
|
||||
vectorBaseActivity.finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
replaceFragment(R.id.pinFragmentContainer, authFragment)
|
||||
}
|
||||
|
||||
private fun showCreateFragment() {
|
||||
val createFragment = PFLockScreenFragment()
|
||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
||||
|
@ -131,9 +89,8 @@ class PinFragment @Inject constructor(
|
|||
val authFragment = PFLockScreenFragment()
|
||||
val canUseBiometrics = pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0
|
||||
val builder = PFFLockScreenConfiguration.Builder(requireContext())
|
||||
.setUseBiometric(true)
|
||||
.setAutoShowBiometric(true)
|
||||
.setUseBiometric(canUseBiometrics)
|
||||
.setUseBiometric(vectorPreferences.useBiometricsToUnlock() && canUseBiometrics)
|
||||
.setAutoShowBiometric(canUseBiometrics)
|
||||
.setTitle(getString(R.string.auth_pin_title))
|
||||
.setLeftButton(getString(R.string.auth_pin_forgot))
|
||||
|
|
|
@ -22,12 +22,14 @@ import androidx.lifecycle.LifecycleObserver
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
// 2 minutes, when enabled
|
||||
private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
|
||||
|
||||
/**
|
||||
|
@ -35,24 +37,22 @@ private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
|
|||
* It automatically locks when entering background/foreground with a grace period.
|
||||
* You can force to unlock with unlock method, use it whenever the pin code has been validated.
|
||||
*/
|
||||
|
||||
@Singleton
|
||||
class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : LifecycleObserver {
|
||||
class PinLocker @Inject constructor(
|
||||
private val pinCodeStore: PinCodeStore,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : LifecycleObserver {
|
||||
|
||||
enum class State {
|
||||
// App is locked, can be unlock
|
||||
LOCKED,
|
||||
|
||||
// App is blocked and can't be unlocked as long as the app is in foreground
|
||||
BLOCKED,
|
||||
|
||||
// is unlocked, the app can be used
|
||||
// App is unlocked, the app can be used
|
||||
UNLOCKED
|
||||
}
|
||||
|
||||
private val liveState = MutableLiveData<State>()
|
||||
|
||||
private var isBlocked = false
|
||||
private var shouldBeLocked = true
|
||||
private var entersBackgroundTs = 0L
|
||||
|
||||
|
@ -62,13 +62,13 @@ class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : Li
|
|||
|
||||
private fun computeState() {
|
||||
GlobalScope.launch {
|
||||
val state = if (isBlocked) {
|
||||
State.BLOCKED
|
||||
} else if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {
|
||||
val state = if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {
|
||||
State.LOCKED
|
||||
} else {
|
||||
State.UNLOCKED
|
||||
}
|
||||
.also { Timber.v("New state: $it") }
|
||||
|
||||
if (liveState.value != state) {
|
||||
liveState.postValue(state)
|
||||
}
|
||||
|
@ -81,23 +81,25 @@ class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : Li
|
|||
computeState()
|
||||
}
|
||||
|
||||
fun block() {
|
||||
Timber.v("Block app")
|
||||
isBlocked = true
|
||||
computeState()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun entersForeground() {
|
||||
val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs
|
||||
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= PERIOD_OF_GRACE_IN_MS
|
||||
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background")
|
||||
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= getGracePeriod()
|
||||
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background shouldBeLocked: $shouldBeLocked")
|
||||
computeState()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
fun entersBackground() {
|
||||
isBlocked = false
|
||||
Timber.v("App enters background")
|
||||
entersBackgroundTs = SystemClock.elapsedRealtime()
|
||||
}
|
||||
|
||||
private fun getGracePeriod(): Long {
|
||||
return if (vectorPreferences.useGracePeriod()) {
|
||||
PERIOD_OF_GRACE_IN_MS
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,5 @@ package im.vector.app.features.pin
|
|||
|
||||
enum class PinMode {
|
||||
CREATE,
|
||||
DELETE,
|
||||
AUTH
|
||||
}
|
||||
|
|
|
@ -16,12 +16,13 @@
|
|||
|
||||
package im.vector.app.features.raw.wellknown
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
|
||||
suspend fun RawService.getElementWellknown(userId: String): ElementWellKnown? {
|
||||
return awaitCallback<String> { getWellknown(userId, it) }
|
||||
.let { ElementWellKnownMapper.from(it) }
|
||||
return tryOrNull { awaitCallback<String> { getWellknown(userId, it) } }
|
||||
?.let { ElementWellKnownMapper.from(it) }
|
||||
}
|
||||
|
||||
fun ElementWellKnown.isE2EByDefault() = elementE2E?.e2eDefault ?: riotE2E?.e2eDefault ?: true
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.raw.wellknown
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
|
||||
object ElementWellKnownMapper {
|
||||
|
@ -24,6 +25,6 @@ object ElementWellKnownMapper {
|
|||
val adapter: JsonAdapter<ElementWellKnown> = MoshiProvider.providesMoshi().adapter(ElementWellKnown::class.java)
|
||||
|
||||
fun from(value: String): ElementWellKnown? {
|
||||
return adapter.fromJson(value)
|
||||
return tryOrNull("Unable to parse well-known data") { adapter.fromJson(value) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ import im.vector.app.features.roomdirectory.RoomDirectoryActivity
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
|
||||
|
@ -59,7 +59,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
|
|||
|
||||
private fun initAdminE2eByDefault() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
adminE2EByDefault = tryThis {
|
||||
adminE2EByDefault = tryOrNull {
|
||||
rawService.getElementWellknown(session.myUserId)
|
||||
?.isE2EByDefault()
|
||||
?: true
|
||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.app.R
|
|||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.features.homeserver.ServerUrlsRepository
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -166,6 +166,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
// Security
|
||||
const val SETTINGS_SECURITY_USE_FLAG_SECURE = "SETTINGS_SECURITY_USE_FLAG_SECURE"
|
||||
const val SETTINGS_SECURITY_USE_PIN_CODE_FLAG = "SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
|
||||
private const val SETTINGS_SECURITY_USE_BIOMETRICS_FLAG = "SETTINGS_SECURITY_USE_BIOMETRICS_FLAG"
|
||||
private const val SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG = "SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG"
|
||||
const val SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG = "SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG"
|
||||
|
||||
// other
|
||||
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
|
||||
|
@ -424,7 +427,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
}
|
||||
|
||||
fun getUnknownDeviceDismissedList(): List<String> {
|
||||
return tryThis {
|
||||
return tryOrNull {
|
||||
defaultPrefs.getStringSet(SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST, null)?.toList()
|
||||
}.orEmpty()
|
||||
}
|
||||
|
@ -839,14 +842,31 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
}
|
||||
|
||||
/**
|
||||
* The user enable protecting app access with pin code
|
||||
* The user enable protecting app access with pin code.
|
||||
* Currently we use the pin code store to know if the pin is enabled, so this is not used
|
||||
*/
|
||||
fun useFlagPinCode(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_PIN_CODE_FLAG, false)
|
||||
}
|
||||
|
||||
fun useBiometricsToUnlock(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_BIOMETRICS_FLAG, true)
|
||||
}
|
||||
|
||||
fun useGracePeriod(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Pin code is disabled, or if user set the settings to see full notification content
|
||||
*/
|
||||
fun useCompleteNotificationFormat(): Boolean {
|
||||
return !useFlagPinCode()
|
||||
|| defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG, true)
|
||||
}
|
||||
|
||||
fun backgroundSyncTimeOut(): Int {
|
||||
return tryThis {
|
||||
return tryOrNull {
|
||||
// The xml pref is saved as a string so use getString and parse
|
||||
defaultPrefs.getString(SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, null)?.toInt()
|
||||
} ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
||||
|
@ -860,7 +880,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||
}
|
||||
|
||||
fun backgroundSyncDelay(): Int {
|
||||
return tryThis {
|
||||
return tryOrNull {
|
||||
// The xml pref is saved as a string so use getString and parse
|
||||
defaultPrefs.getString(SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, null)?.toInt()
|
||||
} ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
||||
|
|
|
@ -17,6 +17,7 @@ package im.vector.app.features.settings
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
|
@ -134,6 +135,14 @@ class VectorSettingsActivity : VectorBaseActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
fun <T: Fragment> navigateTo(fragmentClass: Class<T>) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.right_in, R.anim.fade_out, R.anim.fade_in, R.anim.right_out)
|
||||
.replace(R.id.vector_settings_page, fragmentClass, null)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getIntent(context: Context, directAccess: Int) = Intent(context, VectorSettingsActivity::class.java)
|
||||
.apply { putExtra(EXTRA_DIRECT_ACCESS, directAccess) }
|
||||
|
|
|
@ -37,7 +37,7 @@ import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
|||
import im.vector.app.features.notifications.NotificationUtils
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.pushrules.RuleIds
|
||||
import org.matrix.android.sdk.api.pushrules.RuleKind
|
||||
import javax.inject.Inject
|
||||
|
@ -88,7 +88,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
|||
it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut())
|
||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue is String) {
|
||||
val syncTimeout = tryThis { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
||||
val syncTimeout = tryOrNull { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
||||
vectorPreferences.setBackgroundSyncTimeout(maxOf(0, syncTimeout))
|
||||
refreshBackgroundSyncPrefs()
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
|||
it.summary = secondsToText(vectorPreferences.backgroundSyncDelay())
|
||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue is String) {
|
||||
val syncDelay = tryThis { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
||||
val syncDelay = tryOrNull { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
||||
vectorPreferences.setBackgroundSyncDelay(maxOf(0, syncDelay))
|
||||
refreshBackgroundSyncPrefs()
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue