Merge branch 'develop' into feature/bma/fix_redirection

This commit is contained in:
Benoit Marty 2021-02-19 14:43:01 +01:00 committed by GitHub
commit 1294d211d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 724 additions and 584 deletions

View file

@ -26,6 +26,7 @@
<w>pkcs</w> <w>pkcs</w>
<w>previewable</w> <w>previewable</w>
<w>previewables</w> <w>previewables</w>
<w>pstn</w>
<w>riotx</w> <w>riotx</w>
<w>signin</w> <w>signin</w>
<w>signout</w> <w>signout</w>

View file

@ -15,6 +15,7 @@ Bugfix 🐛:
- Fix crash after initial sync on Dendrite - Fix crash after initial sync on Dendrite
- Fix crash reported by PlayStore (#2707) - Fix crash reported by PlayStore (#2707)
- Ignore url override from credential if it is not valid (#2822) - Ignore url override from credential if it is not valid (#2822)
- Fix crash when deactivating an account
Translations 🗣: Translations 🗣:
- -
@ -31,6 +32,7 @@ Test:
Other changes: Other changes:
- New Dev Tools panel for developers - New Dev Tools panel for developers
- Fix typos in CHANGES.md (#2811) - Fix typos in CHANGES.md (#2811)
- Colors rework: first step: merge file `colors_riot.xml` to file `colors_riotx.xml` and rename the file to `colors.xml`
Changes in Element 1.0.17 (2021-02-09) Changes in Element 1.0.17 (2021-02-09)
=================================================== ===================================================

View file

@ -43,12 +43,13 @@ class DeactivateAccountTest : InstrumentedTest {
@Test @Test
fun deactivateAccountTest() { fun deactivateAccountTest() {
val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = false)) val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true))
// Deactivate the account // Deactivate the account
commonTestHelper.runBlockingTest { commonTestHelper.runBlockingTest {
session.deactivateAccount( session.deactivateAccount(
object : UserInteractiveAuthInterceptor { eraseAllData = false,
userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume( promise.resume(
UserPasswordAuth( UserPasswordAuth(
@ -58,7 +59,8 @@ class DeactivateAccountTest : InstrumentedTest {
) )
) )
} }
}, false) }
)
} }
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED) // Try to login on the previous account, it will fail (M_USER_DEACTIVATED)

View file

@ -53,22 +53,24 @@ fun Throwable.isInvalidUIAAuth(): Boolean {
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
*/ */
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
return if (this is Failure.OtherServerError && this.httpCode == 401) { return if (this is Failure.OtherServerError && httpCode == 401) {
tryOrNull { tryOrNull {
MoshiProvider.providesMoshi() MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java) .adapter(RegistrationFlowResponse::class.java)
.fromJson(this.errorBody) .fromJson(errorBody)
} }
} else if (this is Failure.ServerError && this.httpCode == 401 && this.error.code == MatrixError.M_FORBIDDEN) { } else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) {
// This happens when the submission for this stage was bad (like bad password) // This happens when the submission for this stage was bad (like bad password)
if (this.error.session != null && this.error.flows != null) { if (error.session != null && error.flows != null) {
RegistrationFlowResponse( RegistrationFlowResponse(
flows = this.error.flows, flows = error.flows,
session = this.error.session, session = error.session,
completedStages = this.error.completedStages, completedStages = error.completedStages,
params = this.error.params params = error.params
) )
} else null } else {
null
}
} else { } else {
null null
} }

View file

@ -27,7 +27,8 @@ interface AccountService {
* @param password Current password. * @param password Current password.
* @param newPassword New password * @param newPassword New password
*/ */
suspend fun changePassword(password: String, newPassword: String) suspend fun changePassword(password: String,
newPassword: String)
/** /**
* Deactivate the account. * Deactivate the account.
@ -41,9 +42,10 @@ interface AccountService {
* be shared with any new or unregistered users, but registered users who already have access to these messages will still * be shared with any new or unregistered users, but registered users who already have access to these messages will still
* have access to their copy. * have access to their copy.
* *
* @param password the account password
* @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see * @param eraseAllData set to true to forget all messages that have been sent. Warning: this will cause future users to see
* an incomplete view of conversations * an incomplete view of conversations
* @param userInteractiveAuthInterceptor see [UserInteractiveAuthInterceptor]
*/ */
suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) suspend fun deactivateAccount(eraseAllData: Boolean,
userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
} }

View file

@ -16,12 +16,11 @@
package org.matrix.android.sdk.api.session.call package org.matrix.android.sdk.api.session.call
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
interface CallSignalingService { interface CallSignalingService {
fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable suspend fun getTurnServer(): TurnServerResponse
fun getPSTNProtocolChecker(): PSTNProtocolChecker
/** /**
* Create an outgoing call * Create an outgoing call

View file

@ -0,0 +1,98 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* 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.api.session.call
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.thirdparty.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
private const val PSTN_MATRIX_KEY = "m.protocol.pstn"
/**
* This class is responsible for checking if the HS support the PSTN protocol.
* As long as the request succeed, it'll check only once by session.
*/
@SessionScope
class PSTNProtocolChecker @Inject internal constructor(private val taskExecutor: TaskExecutor,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask) {
interface Listener {
fun onPSTNSupportUpdated()
}
private var alreadyChecked = AtomicBoolean(false)
private val pstnSupportListeners = mutableListOf<Listener>()
fun addListener(listener: Listener) {
pstnSupportListeners.add(listener)
}
fun removeListener(listener: Listener) {
pstnSupportListeners.remove(listener)
}
var supportedPSTNProtocol: String? = null
private set
fun checkForPSTNSupportIfNeeded() {
if (alreadyChecked.get()) return
taskExecutor.executorScope.checkForPSTNSupport()
}
private fun CoroutineScope.checkForPSTNSupport() = launch {
try {
supportedPSTNProtocol = getSupportedPSTN(3)
alreadyChecked.set(true)
if (supportedPSTNProtocol != null) {
pstnSupportListeners.forEach {
tryOrNull { it.onPSTNSupportUpdated() }
}
}
} catch (failure: Throwable) {
Timber.v("Fail to get supported PSTN, will check again next time.")
}
}
private suspend fun getSupportedPSTN(maxTries: Int): String? {
val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
getThirdPartyProtocolsTask.execute(Unit)
} catch (failure: Throwable) {
if (maxTries == 1) {
throw failure
} else {
// Wait for 10s before trying again
delay(10_000L)
return getSupportedPSTN(maxTries - 1)
}
}
return when {
thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY
thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY
else -> null
}
}
}

View file

@ -56,8 +56,6 @@ interface CryptoService {
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>)
fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>)
fun getCryptoVersion(context: Context, longFormat: Boolean): String fun getCryptoVersion(context: Context, longFormat: Boolean): String
fun isCryptoEnabled(): Boolean fun isCryptoEnabled(): Boolean

View file

@ -16,14 +16,25 @@
package org.matrix.android.sdk.internal.auth.registration package org.matrix.android.sdk.internal.auth.registration
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
import org.matrix.android.sdk.api.auth.UIABaseAuth
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveAuthInterceptor, retryBlock: suspend (UIABaseAuth) -> Unit): Boolean { /**
* Handle a UIA challenge
*
* @param failure the failure to handle
* @param interceptor see doc in [UserInteractiveAuthInterceptor]
* @param retryBlock called at the end of the process, in this block generally retry executing the task, with
* provided authUpdate
* @return true if UIA is handled without error
*/
internal suspend fun handleUIA(failure: Throwable,
interceptor: UserInteractiveAuthInterceptor,
retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
Timber.d("## UIA: check error ${failure.message}") Timber.d("## UIA: check error ${failure.message}")
val flowResponse = failure.toRegistrationFlowResponse() val flowResponse = failure.toRegistrationFlowResponse()
?: return false.also { ?: return false.also {
@ -38,16 +49,16 @@ internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveA
suspendCoroutine<UIABaseAuth> { continuation -> suspendCoroutine<UIABaseAuth> { continuation ->
interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation) interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
} }
} catch (failure: Throwable) { } catch (failure2: Throwable) {
Timber.w(failure, "## UIA: failed to participate") Timber.w(failure2, "## UIA: failed to participate")
return false return false
} }
Timber.d("## UIA: updated auth $authUpdate") Timber.d("## UIA: updated auth")
return try { return try {
retryBlock(authUpdate) retryBlock(authUpdate)
true true
} catch (failure: Throwable) { } catch (failure3: Throwable) {
handleUIA(failure, interceptor, retryBlock) handleUIA(failure3, interceptor, retryBlock)
} }
} }

View file

@ -61,7 +61,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask
@ -75,7 +74,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
@ -240,9 +238,6 @@ internal abstract class CryptoModule {
@Binds @Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
@Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
@Binds @Binds
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService

View file

@ -75,7 +75,6 @@ import org.matrix.android.sdk.internal.crypto.model.toRest
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
@ -153,9 +152,8 @@ internal class DefaultCryptoService @Inject constructor(
// Repository // Repository
private val megolmEncryptionFactory: MXMegolmEncryptionFactory, private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
private val olmEncryptionFactory: MXOlmEncryptionFactory, private val olmEncryptionFactory: MXOlmEncryptionFactory,
private val deleteDeviceTask: DeleteDeviceTask,
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
// Tasks // Tasks
private val deleteDeviceTask: DeleteDeviceTask,
private val getDevicesTask: GetDevicesTask, private val getDevicesTask: GetDevicesTask,
private val getDeviceInfoTask: GetDeviceInfoTask, private val getDeviceInfoTask: GetDeviceInfoTask,
private val setDeviceNameTask: SetDeviceNameTask, private val setDeviceNameTask: SetDeviceNameTask,
@ -217,15 +215,6 @@ internal class DefaultCryptoService @Inject constructor(
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback<Unit>) {
deleteDeviceWithUserPasswordTask
.configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) {
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun getCryptoVersion(context: Context, longFormat: Boolean): String { override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
} }

View file

@ -47,12 +47,16 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
} }
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (params.userInteractiveAuthInterceptor == null if (params.userInteractiveAuthInterceptor == null
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> || !handleUIA(
execute(params.copy(userAuthParam = auth)) failure = throwable,
} interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) { ) {
Timber.d("## UIA: propagate failure") Timber.d("## UIA: propagate failure")
throw throwable throw throwable
} }
} }
} }

View file

@ -1,57 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.crypto.tasks
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserPasswordTask.Params, Unit> {
data class Params(
val deviceId: String,
val authSession: String?,
val password: String
)
}
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
private val cryptoApi: CryptoApi,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver
) : DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
return executeRequest(globalErrorReceiver) {
apiCall = cryptoApi.deleteDevice(params.deviceId,
DeleteDeviceParams(
auth = UserPasswordAuth(
type = LoginFlowTypes.PASSWORD,
session = params.authSession,
user = userId,
password = params.password
).asMap()
)
)
}
}
}

View file

@ -126,11 +126,16 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
uploadSigningKeysTask.execute(uploadSigningKeysParams) uploadSigningKeysTask.execute(uploadSigningKeysParams)
} catch (failure: Throwable) { } catch (failure: Throwable) {
if (params.interactiveAuthInterceptor == null if (params.interactiveAuthInterceptor == null
|| !handleUIA(failure, params.interactiveAuthInterceptor) { authUpdate -> || !handleUIA(
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate)) failure = failure,
}) { interceptor = params.interactiveAuthInterceptor,
retryBlock = { authUpdate ->
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure") Timber.d("## UIA: propagate failure")
throw failure throw failure
} }
} }

View file

@ -16,10 +16,9 @@
package org.matrix.android.sdk.internal.session.account package org.matrix.android.sdk.internal.session.account
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.internal.auth.registration.handleUIA import org.matrix.android.sdk.internal.auth.registration.handleUIA
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.cleanup.CleanupSession import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
@ -30,8 +29,8 @@ import javax.inject.Inject
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> { internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
data class Params( data class Params(
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val eraseAllData: Boolean, val eraseAllData: Boolean,
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
val userAuthParam: UIABaseAuth? = null val userAuthParam: UIABaseAuth? = null
) )
} }
@ -39,7 +38,6 @@ internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Un
internal class DefaultDeactivateAccountTask @Inject constructor( internal class DefaultDeactivateAccountTask @Inject constructor(
private val accountAPI: AccountAPI, private val accountAPI: AccountAPI,
private val globalErrorReceiver: GlobalErrorReceiver, private val globalErrorReceiver: GlobalErrorReceiver,
@UserId private val userId: String,
private val identityDisconnectTask: IdentityDisconnectTask, private val identityDisconnectTask: IdentityDisconnectTask,
private val cleanupSession: CleanupSession private val cleanupSession: CleanupSession
) : DeactivateAccountTask { ) : DeactivateAccountTask {
@ -47,23 +45,33 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
override suspend fun execute(params: DeactivateAccountTask.Params) { override suspend fun execute(params: DeactivateAccountTask.Params) {
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData) val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
try { val canCleanup = try {
executeRequest<Unit>(globalErrorReceiver) { executeRequest<Unit>(globalErrorReceiver) {
apiCall = accountAPI.deactivate(deactivateAccountParams) apiCall = accountAPI.deactivate(deactivateAccountParams)
} }
true
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> if (!handleUIA(
execute(params.copy(userAuthParam = auth)) failure = throwable,
} interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) { ) {
Timber.d("## UIA: propagate failure") Timber.d("## UIA: propagate failure")
throw throwable throw throwable
} else {
false
} }
} }
// Logout from identity server if any, ignoring errors
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
cleanupSession.handle() if (canCleanup) {
// Logout from identity server if any, ignoring errors
runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
cleanupSession.handle()
}
} }
} }

View file

@ -27,7 +27,7 @@ internal class DefaultAccountService @Inject constructor(private val changePassw
changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword)) changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
} }
override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) { override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData)) deactivateAccountTask.execute(DeactivateAccountTask.Params(eraseAllData, userInteractiveAuthInterceptor))
} }
} }

View file

@ -16,16 +16,12 @@
package org.matrix.android.sdk.internal.session.call package org.matrix.android.sdk.internal.session.call
import kotlinx.coroutines.Dispatchers
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.launchToCallback
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -34,14 +30,16 @@ internal class DefaultCallSignalingService @Inject constructor(
private val callSignalingHandler: CallSignalingHandler, private val callSignalingHandler: CallSignalingHandler,
private val mxCallFactory: MxCallFactory, private val mxCallFactory: MxCallFactory,
private val activeCallHandler: ActiveCallHandler, private val activeCallHandler: ActiveCallHandler,
private val taskExecutor: TaskExecutor, private val turnServerDataSource: TurnServerDataSource,
private val turnServerDataSource: TurnServerDataSource private val pstnProtocolChecker: PSTNProtocolChecker
) : CallSignalingService { ) : CallSignalingService {
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable { override suspend fun getTurnServer(): TurnServerResponse {
return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) { return turnServerDataSource.getTurnServer()
turnServerDataSource.getTurnServer() }
}
override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
return pstnProtocolChecker
} }
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {

View file

@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
@ -47,11 +46,12 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
private val profileAPI: ProfileAPI, private val profileAPI: ProfileAPI,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val pendingThreePidMapper: PendingThreePidMapper, private val pendingThreePidMapper: PendingThreePidMapper,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() { private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() {
override suspend fun execute(params: Params) { override suspend fun execute(params: Params) {
if (params.userWantsToCancel.not()) { val canCleanup = if (params.userWantsToCancel) {
true
} else {
// Get the required pending data // Get the required pending data
val pendingThreePids = monarchy.fetchAllMappedSync( val pendingThreePids = monarchy.fetchAllMappedSync(
{ it.where(PendingThreePidEntity::class.java) }, { it.where(PendingThreePidEntity::class.java) },
@ -69,21 +69,30 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
) )
apiCall = profileAPI.finalizeAddThreePid(body) apiCall = profileAPI.finalizeAddThreePid(body)
} }
true
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (params.userInteractiveAuthInterceptor == null if (params.userInteractiveAuthInterceptor == null
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth -> || !handleUIA(
execute(params.copy(userAuthParam = auth)) failure = throwable,
} interceptor = params.userInteractiveAuthInterceptor,
retryBlock = { authUpdate ->
execute(params.copy(userAuthParam = authUpdate))
}
)
) { ) {
Timber.d("## UIA: propagate failure") Timber.d("## UIA: propagate failure")
throw throwable.toRegistrationFlowResponse() throw throwable.toRegistrationFlowResponse()
?.let { Failure.RegistrationFlowError(it) } ?.let { Failure.RegistrationFlowError(it) }
?: throwable ?: throwable
} else {
false
} }
} }
} }
cleanupDatabase(params) if (canCleanup) {
cleanupDatabase(params)
}
} }
private suspend fun cleanupDatabase(params: Params) { private suspend fun cleanupDatabase(params: Params) {

View file

@ -41,6 +41,9 @@ parser.add_argument('-b',
type=int, type=int,
required=True, required=True,
help='the buildkite build number.') help='the buildkite build number.')
parser.add_argument('-f',
'--filename',
help='the filename, to download only one artifact.')
parser.add_argument('-e', parser.add_argument('-e',
'--expecting', '--expecting',
type=int, type=int,
@ -148,6 +151,8 @@ for elt in data:
print(" %s: %s" % (key, str(value))) print(" %s: %s" % (key, str(value)))
url = elt.get("download_url") url = elt.get("download_url")
filename = elt.get("filename") filename = elt.get("filename")
if args.filename is not None and args.filename != filename:
continue
target = targetDir + "/" + filename target = targetDir + "/" + filename
print("Downloading %s to '%s'..." % (filename, targetDir)) print("Downloading %s to '%s'..." % (filename, targetDir))
if not args.simulate: if not args.simulate:

View file

@ -0,0 +1,34 @@
/*
* 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.epoxy
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
@EpoxyModelClass(layout = R.layout.item_timeline_empty)
abstract class TimelineEmptyItem : VectorEpoxyModel<TimelineEmptyItem.Holder>(), ItemWithEvents {
@EpoxyAttribute lateinit var eventId: String
override fun getEventIds(): List<String> {
return listOf(eventId)
}
class Holder : VectorEpoxyHolder()
}

View file

@ -113,7 +113,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// It's the only way we have to know if sso falback flow was successful // It's the only way we have to know if sso fallback flow was successful
withState(sharedViewModel) { withState(sharedViewModel) {
if (it.ssoFallbackPageWasShown) { if (it.ssoFallbackPageWasShown) {
Timber.d("## UIA ssoFallbackPageWasShown tentative success") Timber.d("## UIA ssoFallbackPageWasShown tentative success")

View file

@ -16,6 +16,7 @@
package im.vector.app.features.call package im.vector.app.features.call
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
@ -29,17 +30,16 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session 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.CallState
import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import java.util.Timer
import java.util.TimerTask
class VectorCallViewModel @AssistedInject constructor( class VectorCallViewModel @AssistedInject constructor(
@Assisted initialState: VectorCallViewState, @Assisted initialState: VectorCallViewState,
@ -50,7 +50,7 @@ class VectorCallViewModel @AssistedInject constructor(
private var call: WebRtcCall? = null private var call: WebRtcCall? = null
private var connectionTimeoutTimer: Timer? = null private var connectionTimeoutJob: Job? = null
private var hasBeenConnectedOnce = false private var hasBeenConnectedOnce = false
private val callListener = object : WebRtcCall.Listener { private val callListener = object : WebRtcCall.Listener {
@ -92,26 +92,20 @@ class VectorCallViewModel @AssistedInject constructor(
val callState = call.state val callState = call.state
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
hasBeenConnectedOnce = true hasBeenConnectedOnce = true
connectionTimeoutTimer?.cancel() connectionTimeoutJob?.cancel()
connectionTimeoutTimer = null connectionTimeoutJob = null
} else { } else {
// do we reset as long as it's moving? // do we reset as long as it's moving?
connectionTimeoutTimer?.cancel() connectionTimeoutJob?.cancel()
if (hasBeenConnectedOnce) { if (hasBeenConnectedOnce) {
connectionTimeoutTimer = Timer().apply { connectionTimeoutJob = viewModelScope.launch {
schedule(object : TimerTask() { delay(30_000)
override fun run() { try {
session.callSignalingService().getTurnServer(object : MatrixCallback<TurnServerResponse> { val turn = session.callSignalingService().getTurnServer()
override fun onFailure(failure: Throwable) { _viewEvents.post(VectorCallViewEvents.ConnectionTimeout(turn))
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null)) } catch (failure: Throwable) {
} _viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
}
override fun onSuccess(data: TurnServerResponse) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(data))
}
})
}
}, 30_000)
} }
} }
} }

View file

@ -83,12 +83,12 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
fun setAudioDevice(device: Device) { fun setAudioDevice(device: Device) {
runInAudioThread(Runnable { runInAudioThread(Runnable {
if (!_availableDevices.contains(device)) { if (!_availableDevices.contains(device)) {
Timber.w(" Audio device not available: $device") Timber.w("Audio device not available: $device")
userSelectedDevice = null userSelectedDevice = null
return@Runnable return@Runnable
} }
if (mode != Mode.DEFAULT) { if (mode != Mode.DEFAULT) {
Timber.i(" User selected device set to: $device") Timber.i("User selected device set to: $device")
userSelectedDevice = device userSelectedDevice = device
updateAudioRoute(mode, false) updateAudioRoute(mode, false)
} }
@ -108,7 +108,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
success = updateAudioRoute(mode, false) success = updateAudioRoute(mode, false)
} catch (e: Throwable) { } catch (e: Throwable) {
success = false success = false
Timber.e(e, " Failed to update audio route for mode: " + mode) Timber.e(e, "Failed to update audio route for mode: $mode")
} }
if (success) { if (success) {
this@CallAudioManager.mode = mode this@CallAudioManager.mode = mode
@ -124,7 +124,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
* `false`, otherwise. * `false`, otherwise.
*/ */
private fun updateAudioRoute(mode: Mode, force: Boolean): Boolean { private fun updateAudioRoute(mode: Mode, force: Boolean): Boolean {
Timber.i(" Update audio route for mode: " + mode) Timber.i("Update audio route for mode: $mode")
if (!audioDeviceRouter?.setMode(mode).orFalse()) { if (!audioDeviceRouter?.setMode(mode).orFalse()) {
return false return false
} }
@ -158,7 +158,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
return true return true
} }
selectedDevice = audioDevice selectedDevice = audioDevice
Timber.i(" Selected audio device: " + audioDevice) Timber.i("Selected audio device: $audioDevice")
audioDeviceRouter?.setAudioRoute(audioDevice) audioDeviceRouter?.setAudioRoute(audioDevice)
configChange?.invoke() configChange?.invoke()
return true return true

View file

@ -22,20 +22,22 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject import javax.inject.Inject
class DialPadLookup @Inject constructor(val session: Session, class DialPadLookup @Inject constructor(
val directRoomHelper: DirectRoomHelper, private val session: Session,
val callManager: WebRtcCallManager private val directRoomHelper: DirectRoomHelper,
private val callManager: WebRtcCallManager
) { ) {
class Failure : Throwable() class Failure : Throwable()
data class Result(val userId: String, val roomId: String) data class Result(val userId: String, val roomId: String)
suspend fun lookupPhoneNumber(phoneNumber: String): Result { suspend fun lookupPhoneNumber(phoneNumber: String): Result {
val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure() val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure()
val thirdPartyUser = tryOrNull { val thirdPartyUser = tryOrNull {
session.thirdPartyService().getThirdPartyUser(supportedProtocolKey, fields = mapOf( session.thirdPartyService().getThirdPartyUser(
"m.id.phone" to phoneNumber protocol = supportedProtocolKey,
)).firstOrNull() fields = mapOf("m.id.phone" to phoneNumber)
).firstOrNull()
} ?: throw Failure() } ?: throw Failure()
val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId) val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2021 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.call.webrtc
import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
private const val PSTN_MATRIX_KEY = "m.protocol.pstn"
suspend fun Session.getSupportedPSTN(maxTries: Int): String? {
val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
thirdPartyService().getThirdPartyProtocols()
} catch (failure: Throwable) {
if (maxTries == 1) {
return null
} else {
// Wait for 10s before trying again
delay(10_000L)
return getSupportedPSTN(maxTries - 1)
}
}
return when {
thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY
thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY
else -> null
}
}

View file

@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.internal.util.awaitCallback
import org.threeten.bp.Duration import org.threeten.bp.Duration
import org.webrtc.AudioSource import org.webrtc.AudioSource
import org.webrtc.AudioTrack import org.webrtc.AudioTrack
@ -420,9 +419,7 @@ class WebRtcCall(val mxCall: MxCall,
private suspend fun getTurnServer(): TurnServerResponse? { private suspend fun getTurnServer(): TurnServerResponse? {
return tryOrNull { return tryOrNull {
awaitCallback { sessionProvider.get()?.callSignalingService()?.getTurnServer()
sessionProvider.get()?.callSignalingService()?.getTurnServer(it)
}
} }
} }

View file

@ -26,14 +26,13 @@ import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.utils.EglUtils import im.vector.app.features.call.utils.EglUtils
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
@ -65,22 +64,26 @@ class WebRtcCallManager @Inject constructor(
private val currentSession: Session? private val currentSession: Session?
get() = activeSessionDataSource.currentValue?.orNull() get() = activeSessionDataSource.currentValue?.orNull()
private val pstnProtocolChecker: PSTNProtocolChecker?
get() = currentSession?.callSignalingService()?.getPSTNProtocolChecker()
interface CurrentCallListener { interface CurrentCallListener {
fun onCurrentCallChange(call: WebRtcCall?) {} fun onCurrentCallChange(call: WebRtcCall?) {}
fun onAudioDevicesChange() {} fun onAudioDevicesChange() {}
} }
interface PSTNSupportListener { val supportedPSTNProtocol: String?
fun onPSTNSupportUpdated() get() = pstnProtocolChecker?.supportedPSTNProtocol
val supportsPSTNProtocol: Boolean
get() = supportedPSTNProtocol != null
fun addPstnSupportListener(listener: PSTNProtocolChecker.Listener) {
pstnProtocolChecker?.addListener(listener)
} }
private val pstnSupportListeners = emptyList<PSTNSupportListener>().toMutableList() fun removePstnSupportListener(listener: PSTNProtocolChecker.Listener) {
fun addPstnSupportListener(listener: PSTNSupportListener) { pstnProtocolChecker?.removeListener(listener)
pstnSupportListeners.add(listener)
}
fun removePstnSupportListener(listener: PSTNSupportListener) {
pstnSupportListeners.remove(listener)
} }
private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>() private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
@ -104,27 +107,11 @@ class WebRtcCallManager @Inject constructor(
private var peerConnectionFactory: PeerConnectionFactory? = null private var peerConnectionFactory: PeerConnectionFactory? = null
private val executor = Executors.newSingleThreadExecutor() private val executor = Executors.newSingleThreadExecutor()
private val dispatcher = executor.asCoroutineDispatcher() private val dispatcher = executor.asCoroutineDispatcher()
var supportedPSTNProtocol: String? = null
private set
val supportsPSTNProtocol: Boolean
get() = supportedPSTNProtocol != null
private val rootEglBase by lazy { EglUtils.rootEglBase } private val rootEglBase by lazy { EglUtils.rootEglBase }
private var isInBackground: Boolean = true private var isInBackground: Boolean = true
init {
GlobalScope.launch {
supportedPSTNProtocol = currentSession?.getSupportedPSTN(3)
if (supportedPSTNProtocol != null) {
pstnSupportListeners.forEach {
tryOrNull { it.onPSTNSupportUpdated() }
}
}
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() { fun entersForeground() {
isInBackground = false isInBackground = false
@ -167,6 +154,10 @@ class WebRtcCallManager @Inject constructor(
return callsByCallId.values.toList() return callsByCallId.values.toList()
} }
fun checkForPSTNSupportIfNeeded() {
pstnProtocolChecker?.checkForPSTNSupportIfNeeded()
}
/** /**
* @return a set of all advertised call during the lifetime of the app. * @return a set of all advertised call during the lifetime of the app.
*/ */
@ -176,7 +167,6 @@ class WebRtcCallManager @Inject constructor(
Timber.v("## VOIP headSetButtonTapped") Timber.v("## VOIP headSetButtonTapped")
val call = getCurrentCall() ?: return val call = getCurrentCall() ?: return
if (call.mxCall.state is CallState.LocalRinging) { if (call.mxCall.state is CallState.LocalRinging) {
// accept call
call.acceptIncomingCall() call.acceptIncomingCall()
} }
if (call.mxCall.state is CallState.Connected) { if (call.mxCall.state is CallState.Connected) {

View file

@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback
import java.io.OutputStream import java.io.OutputStream
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class BootstrapSharedViewModel @AssistedInject constructor( class BootstrapSharedViewModel @AssistedInject constructor(
@Assisted initialState: BootstrapViewState, @Assisted initialState: BootstrapViewState,
@ -421,7 +422,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
_viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode)) _viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode))
} }
else -> { else -> {
promise.resumeWith(Result.failure(UnsupportedOperationException())) promise.resumeWithException(UnsupportedOperationException())
} }
} }
} }

View file

@ -52,6 +52,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class HomeActivityViewModel @AssistedInject constructor( class HomeActivityViewModel @AssistedInject constructor(
@Assisted initialState: HomeActivityViewState, @Assisted initialState: HomeActivityViewState,
@ -228,7 +229,7 @@ class HomeActivityViewModel @AssistedInject constructor(
) )
) )
} else { } else {
promise.resumeWith(Result.failure(Exception("Cannot silently initialize cross signing, UIA missing"))) promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
} }
} }
}, },

View file

@ -26,8 +26,8 @@ import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.BehaviorRelay
import com.jakewharton.rxrelay2.PublishRelay import com.jakewharton.rxrelay2.PublishRelay
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
@ -64,6 +64,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
@ -120,7 +121,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
timelineSettingsFactory: TimelineSettingsFactory timelineSettingsFactory: TimelineSettingsFactory
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
Timeline.Listener, ChatEffectManager.Delegate, WebRtcCallManager.PSTNSupportListener { Timeline.Listener, ChatEffectManager.Delegate, PSTNProtocolChecker.Listener {
private val room = session.getRoom(initialState.roomId)!! private val room = session.getRoom(initialState.roomId)!!
private val eventId = initialState.eventId private val eventId = initialState.eventId
@ -176,6 +177,7 @@ class RoomDetailViewModel @AssistedInject constructor(
// Inform the SDK that the room is displayed // Inform the SDK that the room is displayed
session.onRoomDisplayed(initialState.roomId) session.onRoomDisplayed(initialState.roomId)
callManager.addPstnSupportListener(this) callManager.addPstnSupportListener(this)
callManager.checkForPSTNSupportIfNeeded()
chatEffectManager.delegate = this chatEffectManager.delegate = this
} }
@ -231,65 +233,65 @@ class RoomDetailViewModel @AssistedInject constructor(
override fun handle(action: RoomDetailAction) { override fun handle(action: RoomDetailAction) {
when (action) { when (action) {
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
is RoomDetailAction.SaveDraft -> handleSaveDraft(action) is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
is RoomDetailAction.SendMessage -> handleSendMessage(action) is RoomDetailAction.SendMessage -> handleSendMessage(action)
is RoomDetailAction.SendMedia -> handleSendMedia(action) is RoomDetailAction.SendMedia -> handleSendMedia(action)
is RoomDetailAction.SendSticker -> handleSendSticker(action) is RoomDetailAction.SendSticker -> handleSendSticker(action)
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
is RoomDetailAction.SendReaction -> handleSendReaction(action) is RoomDetailAction.SendReaction -> handleSendReaction(action)
is RoomDetailAction.AcceptInvite -> handleAcceptInvite() is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
is RoomDetailAction.RejectInvite -> handleRejectInvite() is RoomDetailAction.RejectInvite -> handleRejectInvite()
is RoomDetailAction.RedactAction -> handleRedactEvent(action) is RoomDetailAction.RedactAction -> handleRedactEvent(action)
is RoomDetailAction.UndoReaction -> handleUndoReact(action) is RoomDetailAction.UndoReaction -> handleUndoReact(action)
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
is RoomDetailAction.EnterEditMode -> handleEditAction(action) is RoomDetailAction.EnterEditMode -> handleEditAction(action)
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
is RoomDetailAction.ResendMessage -> handleResendEvent(action) is RoomDetailAction.ResendMessage -> handleResendEvent(action)
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
is RoomDetailAction.ResendAll -> handleResendAll() is RoomDetailAction.ResendAll -> handleResendAll()
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
is RoomDetailAction.ReportContent -> handleReportContent(action) is RoomDetailAction.ReportContent -> handleReportContent(action)
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
is RoomDetailAction.RequestVerification -> handleRequestVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action) is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
is RoomDetailAction.StartCall -> handleStartCall(action) is RoomDetailAction.StartCall -> handleStartCall(action)
is RoomDetailAction.AcceptCall -> handleAcceptCall(action) is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
is RoomDetailAction.EndCall -> handleEndCall() is RoomDetailAction.EndCall -> handleEndCall()
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
is RoomDetailAction.CancelSend -> handleCancel(action) is RoomDetailAction.CancelSend -> handleCancel(action)
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
is RoomDetailAction.ShowRoomAvatarFullScreen -> { is RoomDetailAction.ShowRoomAvatarFullScreen -> {
_viewEvents.post( _viewEvents.post(
RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView) RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView)
) )
} }
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
}.exhaustive }.exhaustive
} }
@ -618,10 +620,10 @@ class RoomDetailViewModel @AssistedInject constructor(
return@withState false return@withState false
} }
when (itemId) { when (itemId) {
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.timeline_setting -> true R.id.timeline_setting -> true
R.id.invite -> state.canInvite R.id.invite -> state.canInvite
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
R.id.open_matrix_apps -> true R.id.open_matrix_apps -> true
R.id.voice_call, R.id.voice_call,
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
@ -741,7 +743,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
popDraft() popDraft()
} }
is ParsedCommand.SendChatEffect -> { is ParsedCommand.SendChatEffect -> {
sendChatEffect(slashCommandResult) sendChatEffect(slashCommandResult)
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
popDraft() popDraft()
@ -774,7 +776,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
}.exhaustive }.exhaustive
} }
is SendMode.EDIT -> { is SendMode.EDIT -> {
// is original event a reply? // is original event a reply?
val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
if (inReplyTo != null) { if (inReplyTo != null) {
@ -799,7 +801,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.MessageSent) _viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft() popDraft()
} }
is SendMode.QUOTE -> { is SendMode.QUOTE -> {
val messageContent: MessageContent? = val messageContent: MessageContent? =
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel() ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
@ -822,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.MessageSent) _viewEvents.post(RoomDetailViewEvents.MessageSent)
popDraft() popDraft()
} }
is SendMode.REPLY -> { is SendMode.REPLY -> {
state.sendMode.timelineEvent.let { state.sendMode.timelineEvent.let {
room.replyToMessage(it, action.text.toString(), action.autoMarkdown) room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
_viewEvents.post(RoomDetailViewEvents.MessageSent) _viewEvents.post(RoomDetailViewEvents.MessageSent)
@ -1441,7 +1443,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
override fun onPSTNSupportUpdated() { override fun onPSTNSupportUpdated() {
updateShowDialerOptionState() updateShowDialerOptionState()
} }
private fun updateShowDialerOptionState() { private fun updateShowDialerOptionState() {

View file

@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.core.platform.DefaultListUpdateCallback
import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
@ -47,8 +47,8 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
if (layoutManager.findFirstVisibleItemPosition() != position) { if (layoutManager.findFirstVisibleItemPosition() != position) {
return return
} }
val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? BaseEventItem ?: return val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? ItemWithEvents ?: return
val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() ?: return
val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds) val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds)
if (indexOfFirstNewItem != -1) { if (indexOfFirstNewItem != -1) {
Timber.v("Should scroll to position: $position") Timber.v("Should scroll to position: $position")

View file

@ -38,18 +38,15 @@ import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItem
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ReadMarkerVisibilityStateChangedListener import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.app.features.home.room.detail.timeline.item.BaseEventItem
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.media.VideoContentRenderer
@ -194,75 +191,20 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
} }
} }
private val interceptorHelper = TimelineControllerInterceptorHelper(
::positionOfReadMarker,
adapterPositionMapping,
vectorPreferences,
callManager
)
init { init {
addInterceptor(this) addInterceptor(this)
requestModelBuild() requestModelBuild()
} }
// Update position when we are building new items
override fun intercept(models: MutableList<EpoxyModel<*>>) = synchronized(modelCache) { override fun intercept(models: MutableList<EpoxyModel<*>>) = synchronized(modelCache) {
positionOfReadMarker = null interceptorHelper.intercept(models, unreadState, timeline, callback)
adapterPositionMapping.clear()
val callIds = mutableSetOf<String>()
val modelsIterator = models.listIterator()
val showHiddenEvents = vectorPreferences.shouldShowHiddenEvents()
modelsIterator.withIndex().forEach {
val index = it.index
val epoxyModel = it.value
if (epoxyModel is CallTileTimelineItem) {
val callId = epoxyModel.attributes.callId
// We should remove the call tile if we already have one for this call or
// if this is an active call tile without an actual call (which can happen with permalink)
val shouldRemoveCallItem = callIds.contains(callId)
|| (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive())
if (shouldRemoveCallItem && !showHiddenEvents) {
modelsIterator.remove()
return@forEach
}
callIds.add(callId)
}
if (epoxyModel is BaseEventItem) {
epoxyModel.getEventIds().forEach { eventId ->
adapterPositionMapping[eventId] = index
}
}
}
val currentUnreadState = this.unreadState
if (currentUnreadState is UnreadState.HasUnread) {
val position = adapterPositionMapping[currentUnreadState.firstUnreadEventId]?.plus(1)
positionOfReadMarker = position
if (position != null) {
val readMarker = TimelineReadMarkerItem_()
.also {
it.id("read_marker")
it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback))
}
models.add(position, readMarker)
}
}
val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false
if (shouldAddBackwardPrefetch) {
val indexOfPrefetchBackward = (previousModelsSize - 1)
.coerceAtMost(models.size - DEFAULT_PREFETCH_THRESHOLD)
.coerceAtLeast(0)
val loadingItem = LoadingItem_()
.id("prefetch_backward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS)
models.add(indexOfPrefetchBackward, loadingItem)
}
val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false
if (shouldAddForwardPrefetch) {
val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(models.size - 1)
val loadingItem = LoadingItem_()
.id("prefetch_forward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS)
models.add(indexOfPrefetchForward, loadingItem)
}
previousModelsSize = models.size
} }
fun update(viewState: RoomDetailViewState) { fun update(viewState: RoomDetailViewState) {
@ -431,6 +373,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
} }
} }
private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onLoadMore(direction)
}
}
}
private fun updateUTDStates(event: TimelineEvent, nextEvent: TimelineEvent?) { private fun updateUTDStates(event: TimelineEvent, nextEvent: TimelineEvent?) {
if (vectorPreferences.labShowCompleteHistoryInEncryptedRoom()) { if (vectorPreferences.labShowCompleteHistoryInEncryptedRoom()) {
return return
@ -461,14 +411,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
return shouldAdd return shouldAdd
} }
private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onLoadMore(direction)
}
}
}
fun searchPositionOfEvent(eventId: String?): Int? = synchronized(modelCache) { fun searchPositionOfEvent(eventId: String?): Int? = synchronized(modelCache) {
return adapterPositionMapping[eventId] return adapterPositionMapping[eventId]
} }

View file

@ -16,7 +16,8 @@
package im.vector.app.features.home.room.detail.timeline.factory package im.vector.app.features.home.room.detail.timeline.factory
import im.vector.app.core.epoxy.EmptyItem_ import im.vector.app.core.epoxy.TimelineEmptyItem
import im.vector.app.core.epoxy.TimelineEmptyItem_
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.TimelineEventController
@ -114,6 +115,12 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
Timber.e(throwable, "failed to create message item") Timber.e(throwable, "failed to create message item")
defaultItemFactory.create(event, highlight, callback, throwable) defaultItemFactory.create(event, highlight, callback, throwable)
} }
return (computedModel ?: EmptyItem_()) return computedModel ?: buildEmptyItem(event)
}
private fun buildEmptyItem(timelineEvent: TimelineEvent): TimelineEmptyItem {
return TimelineEmptyItem_()
.id(timelineEvent.localId)
.eventId(timelineEvent.eventId)
} }
} }

View file

@ -0,0 +1,151 @@
/*
* Copyright (c) 2021 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 com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.VisibilityState
import im.vector.app.core.epoxy.LoadingItem_
import im.vector.app.core.epoxy.TimelineEmptyItem_
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.room.detail.UnreadState
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import kotlin.reflect.KMutableProperty0
private const val DEFAULT_PREFETCH_THRESHOLD = 30
class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMutableProperty0<Int?>,
private val adapterPositionMapping: MutableMap<String, Int>,
private val vectorPreferences: VectorPreferences,
private val callManager: WebRtcCallManager
) {
private var previousModelsSize = 0
// Update position when we are building new items
fun intercept(
models: MutableList<EpoxyModel<*>>,
unreadState: UnreadState,
timeline: Timeline?,
callback: TimelineEventController.Callback?
) {
positionOfReadMarker.set(null)
adapterPositionMapping.clear()
val callIds = mutableSetOf<String>()
// Add some prefetch loader if needed
models.addBackwardPrefetchIfNeeded(timeline, callback)
models.addForwardPrefetchIfNeeded(timeline, callback)
val modelsIterator = models.listIterator()
val showHiddenEvents = vectorPreferences.shouldShowHiddenEvents()
var index = 0
val firstUnreadEventId = (unreadState as? UnreadState.HasUnread)?.firstUnreadEventId
// Then iterate on models so we have the exact positions in the adapter
modelsIterator.forEach { epoxyModel ->
if (epoxyModel is ItemWithEvents) {
epoxyModel.getEventIds().forEach { eventId ->
adapterPositionMapping[eventId] = index
if (eventId == firstUnreadEventId) {
modelsIterator.addReadMarkerItem(callback)
index++
positionOfReadMarker.set(index)
}
}
}
if (epoxyModel is CallTileTimelineItem) {
modelsIterator.removeCallItemIfNeeded(epoxyModel, callIds, showHiddenEvents)
}
index++
}
previousModelsSize = models.size
}
private fun MutableListIterator<EpoxyModel<*>>.addReadMarkerItem(callback: TimelineEventController.Callback?) {
val readMarker = TimelineReadMarkerItem_()
.also {
it.id("read_marker")
it.setOnVisibilityStateChanged(ReadMarkerVisibilityStateChangedListener(callback))
}
add(readMarker)
// Use next as we still have some process to do before the next iterator loop
next()
}
private fun MutableListIterator<EpoxyModel<*>>.removeCallItemIfNeeded(
epoxyModel: CallTileTimelineItem,
callIds: MutableSet<String>,
showHiddenEvents: Boolean
) {
val callId = epoxyModel.attributes.callId
// We should remove the call tile if we already have one for this call or
// if this is an active call tile without an actual call (which can happen with permalink)
val shouldRemoveCallItem = callIds.contains(callId)
|| (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive())
if (shouldRemoveCallItem && !showHiddenEvents) {
remove()
val emptyItem = TimelineEmptyItem_()
.id(epoxyModel.id())
.eventId(epoxyModel.attributes.informationData.eventId)
add(emptyItem)
}
callIds.add(callId)
}
private fun MutableList<EpoxyModel<*>>.addBackwardPrefetchIfNeeded(timeline: Timeline?, callback: TimelineEventController.Callback?) {
val shouldAddBackwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) ?: false
if (shouldAddBackwardPrefetch) {
val indexOfPrefetchBackward = (previousModelsSize - 1)
.coerceAtMost(size - DEFAULT_PREFETCH_THRESHOLD)
.coerceAtLeast(0)
val loadingItem = LoadingItem_()
.id("prefetch_backward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS, callback)
add(indexOfPrefetchBackward, loadingItem)
}
}
private fun MutableList<EpoxyModel<*>>.addForwardPrefetchIfNeeded(timeline: Timeline?, callback: TimelineEventController.Callback?) {
val shouldAddForwardPrefetch = timeline?.hasMoreToLoad(Timeline.Direction.FORWARDS) ?: false
if (shouldAddForwardPrefetch) {
val indexOfPrefetchForward = DEFAULT_PREFETCH_THRESHOLD.coerceAtMost(size - 1)
val loadingItem = LoadingItem_()
.id("prefetch_forward_loading${System.currentTimeMillis()}")
.showLoader(false)
.setVisibilityStateChangedListener(Timeline.Direction.FORWARDS, callback)
add(indexOfPrefetchForward, loadingItem)
}
}
private fun LoadingItem_.setVisibilityStateChangedListener(
direction: Timeline.Direction,
callback: TimelineEventController.Callback?
): LoadingItem_ {
return onVisibilityStateChanged { _, _, visibilityState ->
if (visibilityState == VisibilityState.VISIBLE) {
callback?.onLoadMore(direction)
}
}
}
}

View file

@ -32,7 +32,7 @@ import im.vector.app.core.utils.DimensionConverter
/** /**
* Children must override getViewType() * Children must override getViewType()
*/ */
abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>() { abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>(), ItemWithEvents {
// To use for instance when opening a permalink with an eventId // To use for instance when opening a permalink with an eventId
@EpoxyAttribute @EpoxyAttribute
@ -53,12 +53,6 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
holder.checkableBackground.isChecked = highlighted holder.checkableBackground.isChecked = highlighted
} }
/**
* Returns the eventIds associated with the EventItem.
* Will generally get only one, but it handles the merging items.
*/
abstract fun getEventIds(): List<String>
abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() { abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() {
val leftGuideline by bind<View>(R.id.messageStartGuideline) val leftGuideline by bind<View>(R.id.messageStartGuideline)
val checkableBackground by bind<CheckableView>(R.id.messageSelectedBackground) val checkableBackground by bind<CheckableView>(R.id.messageSelectedBackground)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright 2019 New Vector Ltd * Copyright (c) 2021 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,12 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.core.epoxy package im.vector.app.features.home.room.detail.timeline.item
import com.airbnb.epoxy.EpoxyModelClass interface ItemWithEvents {
import im.vector.app.R /**
* Returns the eventIds associated with the EventItem.
@EpoxyModelClass(layout = R.layout.item_empty) * Will generally get only one, but it handles the merged items.
abstract class EmptyItem : VectorEpoxyModel<EmptyItem.Holder>() { */
class Holder : VectorEpoxyHolder() fun getEventIds(): List<String>
} }

View file

@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
data class DeactivateAccountViewState( data class DeactivateAccountViewState(
val passwordShown: Boolean = false val passwordShown: Boolean = false
@ -64,7 +65,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
if (pendingAuth != null) { if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!) uiaContinuation?.resume(pendingAuth!!)
} else { } else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) uiaContinuation?.resumeWithException(IllegalArgumentException())
} }
} }
is DeactivateAccountAction.PasswordAuthDone -> { is DeactivateAccountAction.PasswordAuthDone -> {
@ -79,7 +80,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
} }
DeactivateAccountAction.ReAuthCancelled -> { DeactivateAccountAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled") Timber.d("## UIA - Reauth cancelled")
uiaContinuation?.resumeWith(Result.failure((Exception()))) uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null uiaContinuation = null
pendingAuth = null pendingAuth = null
} }
@ -98,13 +99,15 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
viewModelScope.launch { viewModelScope.launch {
val event = try { val event = try {
session.deactivateAccount( session.deactivateAccount(
action.eraseAllData,
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
_viewEvents.post(DeactivateAccountViewEvents.RequestReAuth(flowResponse, errCode)) _viewEvents.post(DeactivateAccountViewEvents.RequestReAuth(flowResponse, errCode))
pendingAuth = DefaultBaseAuth(session = flowResponse.session) pendingAuth = DefaultBaseAuth(session = flowResponse.session)
uiaContinuation = promise uiaContinuation = promise
} }
}, action.eraseAllData) }
)
DeactivateAccountViewEvents.Done DeactivateAccountViewEvents.Done
} catch (failure: Exception) { } catch (failure: Exception) {
if (failure.isInvalidUIAAuth()) { if (failure.isInvalidUIAAuth()) {

View file

@ -49,6 +49,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class CrossSigningSettingsViewModel @AssistedInject constructor( class CrossSigningSettingsViewModel @AssistedInject constructor(
@Assisted private val initialState: CrossSigningSettingsViewState, @Assisted private val initialState: CrossSigningSettingsViewState,
@ -130,7 +131,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
if (pendingAuth != null) { if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!) uiaContinuation?.resume(pendingAuth!!)
} else { } else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) uiaContinuation?.resumeWithException(IllegalArgumentException())
} }
} }
is CrossSigningSettingsAction.PasswordAuthDone -> { is CrossSigningSettingsAction.PasswordAuthDone -> {
@ -146,7 +147,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
CrossSigningSettingsAction.ReAuthCancelled -> { CrossSigningSettingsAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled") Timber.d("## UIA - Reauth cancelled")
_viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView) _viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView)
uiaContinuation?.resumeWith(Result.failure((Exception()))) uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null uiaContinuation = null
pendingAuth = null pendingAuth = null
} }

View file

@ -64,6 +64,7 @@ import java.util.concurrent.TimeUnit
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
data class DevicesViewState( data class DevicesViewState(
val myDeviceId: String = "", val myDeviceId: String = "",
@ -217,7 +218,7 @@ class DevicesViewModel @AssistedInject constructor(
if (pendingAuth != null) { if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!) uiaContinuation?.resume(pendingAuth!!)
} else { } else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) uiaContinuation?.resumeWithException(IllegalArgumentException())
} }
Unit Unit
} }
@ -235,7 +236,7 @@ class DevicesViewModel @AssistedInject constructor(
DevicesAction.ReAuthCancelled -> { DevicesAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled") Timber.d("## UIA - Reauth cancelled")
// _viewEvents.post(DevicesViewEvents.Loading) // _viewEvents.post(DevicesViewEvents.Loading)
uiaContinuation?.resumeWith(Result.failure((Exception()))) uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null uiaContinuation = null
pendingAuth = null pendingAuth = null
} }

View file

@ -46,6 +46,7 @@ import org.matrix.android.sdk.rx.rx
import timber.log.Timber import timber.log.Timber
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
class ThreePidsSettingsViewModel @AssistedInject constructor( class ThreePidsSettingsViewModel @AssistedInject constructor(
@Assisted initialState: ThreePidsSettingsViewState, @Assisted initialState: ThreePidsSettingsViewState,
@ -140,7 +141,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
if (pendingAuth != null) { if (pendingAuth != null) {
uiaContinuation?.resume(pendingAuth!!) uiaContinuation?.resume(pendingAuth!!)
} else { } else {
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException()))) uiaContinuation?.resumeWithException(IllegalArgumentException())
} }
} }
is ThreePidsSettingsAction.PasswordAuthDone -> { is ThreePidsSettingsAction.PasswordAuthDone -> {
@ -155,7 +156,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
} }
ThreePidsSettingsAction.ReAuthCancelled -> { ThreePidsSettingsAction.ReAuthCancelled -> {
Timber.d("## UIA - Reauth cancelled") Timber.d("## UIA - Reauth cancelled")
uiaContinuation?.resumeWith(Result.failure((Exception()))) uiaContinuation?.resumeWithException(Exception())
uiaContinuation = null uiaContinuation = null
pendingAuth = null pendingAuth = null
} }

View file

@ -1,55 +1,52 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<declare-styleable name="VectorStyles"> <!-- application bar text color -->
<attr name="vctr_toolbar_primary_text_color" format="color" />
<attr name="vctr_toolbar_secondary_text_color" format="color" />
<attr name="vctr_toolbar_link_text_color" format="color" />
<!-- application bar text color --> <!-- default text colors -->
<attr name="vctr_toolbar_primary_text_color" format="color" /> <attr name="vctr_default_text_hint_color" format="color" />
<attr name="vctr_toolbar_secondary_text_color" format="color" />
<attr name="vctr_toolbar_link_text_color" format="color" />
<!-- default text colors --> <!-- room message colors -->
<attr name="vctr_default_text_hint_color" format="color" /> <attr name="vctr_unsent_message_text_color" format="color" />
<attr name="vctr_message_text_color" format="color" />
<attr name="vctr_notice_text_color" format="color" />
<attr name="vctr_notice_secondary" format="color" />
<attr name="vctr_encrypting_message_text_color" format="color" />
<attr name="vctr_sending_message_text_color" format="color" />
<attr name="vctr_markdown_block_background_color" format="color" />
<attr name="vctr_spoiler_background_color" format="color" />
<!-- room message colors --> <!-- tab bar colors -->
<attr name="vctr_unsent_message_text_color" format="color" /> <attr name="vctr_tab_bar_inverted_background_color" format="color" />
<attr name="vctr_message_text_color" format="color" />
<attr name="vctr_notice_text_color" format="color" />
<attr name="vctr_notice_secondary" format="color" />
<attr name="vctr_encrypting_message_text_color" format="color" />
<attr name="vctr_sending_message_text_color" format="color" />
<attr name="vctr_markdown_block_background_color" format="color" />
<attr name="vctr_spoiler_background_color" format="color" />
<!-- tab bar colors --> <!-- list colors -->
<attr name="vctr_tab_bar_inverted_background_color" format="color" /> <attr name="vctr_list_header_background_color" format="color" />
<attr name="vctr_list_header_primary_text_color" format="color" />
<attr name="vctr_list_header_secondary_text_color" format="color" />
<!-- list colors --> <attr name="vctr_list_divider_color" format="color" />
<attr name="vctr_list_header_background_color" format="color" />
<attr name="vctr_list_header_primary_text_color" format="color" />
<attr name="vctr_list_header_secondary_text_color" format="color" />
<attr name="vctr_list_divider_color" format="color" /> <!-- outgoing call background color -->
<attr name="vctr_pending_outgoing_view_background_color" format="color" />
<!-- outgoing call background color --> <!-- room notification text color (typing, unsent...) -->
<attr name="vctr_pending_outgoing_view_background_color" format="color" /> <attr name="vctr_room_notification_text_color" format="color" />
<!-- room notification text color (typing, unsent...) --> <!-- icon colors -->
<attr name="vctr_room_notification_text_color" format="color" /> <attr name="vctr_icon_tint_on_light_action_bar_color" format="color" />
<attr name="vctr_settings_icon_tint_color" format="color" />
<!-- icon colors --> <attr name="vctr_social_login_button_google_style" format="reference" />
<attr name="vctr_icon_tint_on_light_action_bar_color" format="color" /> <attr name="vctr_social_login_button_github_style" format="reference" />
<attr name="vctr_settings_icon_tint_color" format="color" /> <attr name="vctr_social_login_button_facebook_style" format="reference" />
<attr name="vctr_social_login_button_twitter_style" format="reference" />
<attr name="vctr_social_login_button_apple_style" format="reference" />
<attr name="vctr_social_login_button_gitlab_style" format="reference" />
<attr name="vctr_social_login_button_google_style" format="reference" /> <attr name="vctr_chat_effect_snow_background" format="color" />
<attr name="vctr_social_login_button_github_style" format="reference" />
<attr name="vctr_social_login_button_facebook_style" format="reference" />
<attr name="vctr_social_login_button_twitter_style" format="reference" />
<attr name="vctr_social_login_button_apple_style" format="reference" />
<attr name="vctr_social_login_button_gitlab_style" format="reference" />
<attr name="vctr_chat_effect_snow_background" format="color" />
</declare-styleable>
<declare-styleable name="PollResultLineView"> <declare-styleable name="PollResultLineView">
<attr name="optionName" format="string" localization="suggested" /> <attr name="optionName" format="string" localization="suggested" />
@ -70,16 +67,16 @@
<declare-styleable name="SignOutBottomSheetActionButton"> <declare-styleable name="SignOutBottomSheetActionButton">
<attr name="iconTint" format="color" /> <attr name="iconTint" format="color" />
<attr name="actionTitle"/> <attr name="actionTitle" />
<attr name="leftIcon" /> <attr name="leftIcon" />
<attr name="textColor" format="color" /> <attr name="textColor" format="color" />
</declare-styleable> </declare-styleable>
<declare-styleable name="SocialLoginButtonsView"> <declare-styleable name="SocialLoginButtonsView">
<attr name="signMode" format="enum"> <attr name="signMode" format="enum">
<enum name="signin" value="0"/> <enum name="signin" value="0" />
<enum name="signup" value="1"/> <enum name="signup" value="1" />
<enum name="continue_with" value="2"/> <enum name="continue_with" value="2" />
</attr> </attr>
</declare-styleable> </declare-styleable>
</resources> </resources>

View file

@ -1,7 +1,115 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Source: https://zpl.io/aBKw9Mk --> <!-- Error colors -->
<color name="vector_success_color">#70BF56</color>
<color name="vector_warning_color">#ff4b55</color>
<color name="vector_error_color">#ff4b55</color>
<color name="vector_info_color">#2f9edb</color>
<!-- main app colors -->
<color name="vector_fuchsia_color">#ff4b55</color>
<color name="vector_silver_color">#FFC7C7C7</color>
<color name="vector_dark_grey_color">#FF999999</color>
<!-- theses colours are requested a background cannot be set by an ?attr on android < 5 -->
<!-- dedicated drawables are created for each theme -->
<!-- Default/Background-->
<color name="riot_primary_background_color_light">#FFFFFFFF</color>
<!-- Dark/Background-->
<color name="riot_primary_background_color_dark">#FF181B21</color>
<!-- Black/Background-->
<color name="riot_primary_background_color_black">#F000</color>
<!--Default/Android Status Bar-->
<color name="primary_color_dark_light">#FF1A2027</color>
<!--Default/Base-->
<color name="primary_color_light">#03b381</color>
<!--Default/Accent-->
<color name="accent_color_light">#03b381</color>
<!--Dark/Android Status Bar-->
<color name="primary_color_dark_dark">#FF0D0E10</color>
<!--Dark/Base-->
<color name="primary_color_dark">#FF15171B</color>
<!--Dark/Accent-->
<color name="accent_color_dark">#03b381</color>
<!--Black/Android Status Bar-->
<color name="primary_color_dark_black">#000</color>
<!--Black/Base-->
<color name="primary_color_black">#FF060708</color>
<!--Default/Line break mobile-->
<attr name="list_divider_color" format="color" />
<color name="list_divider_color_light">#EEEFEF</color>
<!--Dark/Line break mobile-->
<color name="list_divider_color_dark">#FF61708B</color>
<!--Black/Line break mobile-->
<color name="list_divider_color_black">#FF22262E</color>
<attr name="tab_bar_selected_background_color" format="color" />
<color name="tab_bar_selected_background_color_light">@color/riotx_android_secondary_light</color>
<color name="tab_bar_selected_background_color_dark">@color/riotx_android_secondary_dark</color>
<attr name="tab_bar_unselected_background_color" format="color" />
<color name="tab_bar_unselected_background_color_light">@color/riotx_background_light</color>
<color name="tab_bar_unselected_background_color_dark">@color/riotx_background_dark</color>
<!-- Hint Colors -->
<color name="primary_hint_text_color_light">#FFFFFF</color>
<color name="primary_hint_text_color_dark">#FFFFFF</color>
<color name="default_text_hint_color_light">#903C3C3C</color>
<color name="default_text_hint_color_dark">#CCDDDDDD</color>
<!-- Text Colors -->
<attr name="riot_primary_text_color" format="color" />
<attr name="riot_primary_text_color_disabled" format="color" />
<!--Default/Text Primary-->
<color name="riot_primary_text_color_light">#FF2E2F32</color>
<color name="riot_primary_text_color_disabled_light">#FF9E9E9E</color>
<!--Default/Text Secondary-->
<color name="riot_secondary_text_color_light">#FF9E9E9E</color>
<color name="riot_tertiary_text_color_light">@color/riot_primary_text_color_light</color>
<!--Dark /Text Primary-->
<color name="riot_primary_text_color_dark">#FFEDF3FF</color>
<color name="riot_primary_text_color_disabled_dark">#FFA1B2D1</color>
<!--Dark /Text Secondary-->
<color name="riot_secondary_text_color_dark">#FFA1B2D1</color>
<color name="riot_tertiary_text_color_dark">@color/riot_primary_text_color_dark</color>
<!-- Notification view colors -->
<color name="soft_resource_limit_exceeded">#2f9edb</color>
<color name="hard_resource_limit_exceeded">@color/vector_fuchsia_color</color>
<!-- Password Strength bar colors -->
<color name="password_strength_bar_weak">#FFF56679</color>
<color name="password_strength_bar_low">#FFFFC666</color>
<color name="password_strength_bar_ok">#FFF8E71C</color>
<color name="password_strength_bar_strong">#FF7AC9A1</color>
<color name="password_strength_bar_undefined">#FF9E9E9E</color>
<!-- Button color -->
<color name="button_enabled_text_color">#FFFFFFFF</color>
<color name="button_disabled_text_color">#FFFFFFFF</color>
<color name="button_destructive_enabled_text_color">#FF4B55</color>
<color name="button_destructive_disabled_text_color">#FF4B55</color>
<color name="button_bot_enabled_text_color">#FF368BD6</color>
<color name="button_bot_disabled_text_color">#61708B</color>
<!-- Link color -->
<color name="link_color_light">#368BD6</color>
<color name="link_color_dark">#368BD6</color>
<!-- Notification (do not depends on theme) -->
<color name="notification_accent_color">#368BD6</color>
<color name="key_share_req_accent_color">#ff812d</color>
<!-- Source: https://zpl.io/aBKw9Mk -->
<!-- Accents --> <!-- Accents -->
<color name="riotx_accent">#FF0DBD8B</color> <color name="riotx_accent">#FF0DBD8B</color>
@ -38,7 +146,7 @@
<color name="riotx_username_7">#5c56f5</color> <color name="riotx_username_7">#5c56f5</color>
<color name="riotx_username_8">#74d12c</color> <color name="riotx_username_8">#74d12c</color>
<!-- Other usefull color --> <!-- Other useful color -->
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="black_alpha">#55000000</color> <color name="black_alpha">#55000000</color>
@ -256,6 +364,4 @@
<color name="riotx_keys_backup_banner_accent_color_light">#FFF8E3</color> <color name="riotx_keys_backup_banner_accent_color_light">#FFF8E3</color>
<color name="riotx_keys_backup_banner_accent_color_dark">#22262E</color> <color name="riotx_keys_backup_banner_accent_color_dark">#22262E</color>
</resources>
</resources>

View file

@ -1,112 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Error colors -->
<color name="vector_success_color">#70BF56</color>
<color name="vector_warning_color">#ff4b55</color>
<color name="vector_error_color">#ff4b55</color>
<color name="vector_info_color">#2f9edb</color>
<!-- main app colors -->
<color name="vector_fuchsia_color">#ff4b55</color>
<color name="vector_silver_color">#FFC7C7C7</color>
<color name="vector_dark_grey_color">#FF999999</color>
<!-- theses colours are requested a background cannot be set by an ?attr on android < 5 -->
<!-- dedicated drawables are created for each theme -->
<!-- Default/Background-->
<color name="riot_primary_background_color_light">#FFFFFFFF</color>
<!-- Dark/Background-->
<color name="riot_primary_background_color_dark">#FF181B21</color>
<!-- Black/Background-->
<color name="riot_primary_background_color_black">#F000</color>
<!--Default/Android Status Bar-->
<color name="primary_color_dark_light">#FF1A2027</color>
<!--Default/Base-->
<color name="primary_color_light">#03b381</color>
<!--Default/Accent-->
<color name="accent_color_light">#03b381</color>
<!--Dark/Android Status Bar-->
<color name="primary_color_dark_dark">#FF0D0E10</color>
<!--Dark/Base-->
<color name="primary_color_dark">#FF15171B</color>
<!--Dark/Accent-->
<color name="accent_color_dark">#03b381</color>
<!--Black/Android Status Bar-->
<color name="primary_color_dark_black">#000</color>
<!--Black/Base-->
<color name="primary_color_black">#FF060708</color>
<!--Default/Line break mobile-->
<attr name="list_divider_color" format="color" />
<color name="list_divider_color_light">#EEEFEF</color>
<!--Dark/Line break mobile-->
<color name="list_divider_color_dark">#FF61708B</color>
<!--Black/Line break mobile-->
<color name="list_divider_color_black">#FF22262E</color>
<attr name="tab_bar_selected_background_color" format="color" />
<color name="tab_bar_selected_background_color_light">@color/riotx_android_secondary_light</color>
<color name="tab_bar_selected_background_color_dark">@color/riotx_android_secondary_dark</color>
<attr name="tab_bar_unselected_background_color" format="color" />
<color name="tab_bar_unselected_background_color_light">@color/riotx_background_light</color>
<color name="tab_bar_unselected_background_color_dark">@color/riotx_background_dark</color>
<!-- Hint Colors -->
<color name="primary_hint_text_color_light">#FFFFFF</color>
<color name="primary_hint_text_color_dark">#FFFFFF</color>
<color name="default_text_hint_color_light">#903C3C3C</color>
<color name="default_text_hint_color_dark">#CCDDDDDD</color>
<!-- Text Colors -->
<attr name="riot_primary_text_color" format="color" />
<attr name="riot_primary_text_color_disabled" format="color" />
<!--Default/Text Primary-->
<color name="riot_primary_text_color_light">#FF2E2F32</color>
<color name="riot_primary_text_color_disabled_light">#FF9E9E9E</color>
<!--Default/Text Secondary-->
<color name="riot_secondary_text_color_light">#FF9E9E9E</color>
<color name="riot_tertiary_text_color_light">@color/riot_primary_text_color_light</color>
<!--Dark /Text Primary-->
<color name="riot_primary_text_color_dark">#FFEDF3FF</color>
<color name="riot_primary_text_color_disabled_dark">#FFA1B2D1</color>
<!--Dark /Text Secondary-->
<color name="riot_secondary_text_color_dark">#FFA1B2D1</color>
<color name="riot_tertiary_text_color_dark">@color/riot_primary_text_color_dark</color>
<!-- Notification view colors -->
<color name="soft_resource_limit_exceeded">#2f9edb</color>
<color name="hard_resource_limit_exceeded">@color/vector_fuchsia_color</color>
<!-- Password Strength bar colors -->
<color name="password_strength_bar_weak">#FFF56679</color>
<color name="password_strength_bar_low">#FFFFC666</color>
<color name="password_strength_bar_ok">#FFF8E71C</color>
<color name="password_strength_bar_strong">#FF7AC9A1</color>
<color name="password_strength_bar_undefined">#FF9E9E9E</color>
<!-- Button color -->
<color name="button_enabled_text_color">#FFFFFFFF</color>
<color name="button_disabled_text_color">#FFFFFFFF</color>
<color name="button_destructive_enabled_text_color">#FF4B55</color>
<color name="button_destructive_disabled_text_color">#FF4B55</color>
<color name="button_bot_enabled_text_color">#FF368BD6</color>
<color name="button_bot_disabled_text_color">#61708B</color>
<!-- Link color -->
<color name="link_color_light">#368BD6</color>
<color name="link_color_dark">#368BD6</color>
<!-- Notification (do not depends on theme) -->
<color name="notification_accent_color">#368BD6</color>
<color name="key_share_req_accent_color">#ff812d</color>
</resources>