mirror of
https://github.com/vector-im/riotX-android
synced 2025-10-06 00:02:48 +02:00
Compare commits
50 Commits
feature/ex
...
v1.1.0
Author | SHA1 | Date | |
---|---|---|---|
|
bb33a92d27 | ||
|
809c0e720e | ||
|
371251c994 | ||
|
616b02cea8 | ||
|
e12cbf92c3 | ||
|
1294d211d6 | ||
|
e8026c6d3f | ||
|
9614d55612 | ||
|
8c1b99586b | ||
|
544c1e4a6a | ||
|
2271ddacf8 | ||
|
9443b80811 | ||
|
5afdc81ce0 | ||
|
2df9b43abc | ||
|
73e93e7d3d | ||
|
c76ced68e0 | ||
|
b9f5863b53 | ||
|
dea76fd81b | ||
|
fd94536118 | ||
|
61ea78a9d9 | ||
|
a4b5f79e8f | ||
|
fc1c2bc2c0 | ||
|
51fd45d317 | ||
|
fcee1f1150 | ||
|
0e322630f1 | ||
|
d19cedef88 | ||
|
6aa5dc992d | ||
|
c787de75f5 | ||
|
a34c072c48 | ||
|
373586c23e | ||
|
754dec949b | ||
|
776ebce497 | ||
|
79acf1cc42 | ||
|
b0ea7cecb5 | ||
|
d351c2cabb | ||
|
aab70e14fc | ||
|
3170d4428c | ||
|
5e3e5d2648 | ||
|
96b02d3154 | ||
|
109a9e816b | ||
|
61373b8b51 | ||
|
fa710ff601 | ||
|
391ddf1925 | ||
|
ce6d4c4a64 | ||
|
7c013de7b9 | ||
|
0412b87ad1 | ||
|
1946058c8e | ||
|
dffdcfe1e4 | ||
|
bba167d4ea | ||
|
9a5e71f391 |
1
.idea/dictionaries/bmarty.xml
generated
1
.idea/dictionaries/bmarty.xml
generated
@@ -26,6 +26,7 @@
|
||||
<w>pkcs</w>
|
||||
<w>previewable</w>
|
||||
<w>previewables</w>
|
||||
<w>pstn</w>
|
||||
<w>riotx</w>
|
||||
<w>signin</w>
|
||||
<w>signout</w>
|
||||
|
16
CHANGES.md
16
CHANGES.md
@@ -1,4 +1,4 @@
|
||||
Changes in Element 1.0.18 (2021-XX-XX)
|
||||
Changes in Element 1.1.0 (2021-02-19)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
@@ -14,22 +14,16 @@ Bugfix 🐛:
|
||||
- VoIP : fix audio devices output
|
||||
- Fix crash after initial sync on Dendrite
|
||||
- Fix crash reported by PlayStore (#2707)
|
||||
|
||||
Translations 🗣:
|
||||
-
|
||||
- Ignore url override from credential if it is not valid (#2822)
|
||||
- Fix crash when deactivating an account
|
||||
|
||||
SDK API changes ⚠️:
|
||||
-
|
||||
|
||||
Build 🧱:
|
||||
-
|
||||
|
||||
Test:
|
||||
-
|
||||
- Migrate AuthenticationService API to coroutines (#2449)
|
||||
|
||||
Other changes:
|
||||
- New Dev Tools panel for developers
|
||||
- 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)
|
||||
===================================================
|
||||
|
2
fastlane/metadata/android/en-US/changelogs/40101000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40101000.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Main changes in this version: VoIP (audio and video calls in DM) improvement and bug fixes!
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.0
|
@@ -26,15 +26,12 @@ import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
import org.matrix.android.sdk.common.TestConstants
|
||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@@ -46,12 +43,13 @@ class DeactivateAccountTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
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
|
||||
commonTestHelper.runBlockingTest {
|
||||
session.deactivateAccount(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
eraseAllData = false,
|
||||
userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(
|
||||
UserPasswordAuth(
|
||||
@@ -61,7 +59,8 @@ class DeactivateAccountTest : InstrumentedTest {
|
||||
)
|
||||
)
|
||||
}
|
||||
}, false)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Try to login on the previous account, it will fail (M_USER_DEACTIVATED)
|
||||
@@ -75,23 +74,23 @@ class DeactivateAccountTest : InstrumentedTest {
|
||||
// Try to create an account with the deactivate account user id, it will fail (M_USER_IN_USE)
|
||||
val hs = commonTestHelper.createHomeServerConfig()
|
||||
|
||||
commonTestHelper.doSync<LoginFlowResult> {
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs, it)
|
||||
commonTestHelper.runBlockingTest {
|
||||
commonTestHelper.matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
var accountCreationError: Throwable? = null
|
||||
commonTestHelper.waitWithLatch {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null,
|
||||
object : TestMatrixCallback<RegistrationResult>(it, false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
commonTestHelper.runBlockingTest {
|
||||
try {
|
||||
commonTestHelper.matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(
|
||||
session.myUserId.substringAfter("@").substringBefore(":"),
|
||||
TestConstants.PASSWORD,
|
||||
null
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
accountCreationError = failure
|
||||
}
|
||||
}
|
||||
|
||||
// Test the error
|
||||
|
@@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
@@ -210,22 +209,21 @@ class CommonTestHelper(context: Context) {
|
||||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult>(timeout = 60_000) {
|
||||
runBlockingTest(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
.createAccount(userName, password, null)
|
||||
}
|
||||
|
||||
// Perform dummy step
|
||||
val registrationResult = doSync<RegistrationResult>(timeout = 60_000) {
|
||||
val registrationResult = runBlockingTest(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
.dummy()
|
||||
}
|
||||
|
||||
assertTrue(registrationResult is RegistrationResult.Success)
|
||||
@@ -249,15 +247,14 @@ class CommonTestHelper(context: Context) {
|
||||
sessionTestParams: SessionTestParams): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
val session = doSync<Session> {
|
||||
val session = runBlockingTest {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", it)
|
||||
.login(userName, password, "myDevice")
|
||||
}
|
||||
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
@@ -277,21 +274,19 @@ class CommonTestHelper(context: Context) {
|
||||
password: String): Throwable {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService.getLoginFlow(hs)
|
||||
}
|
||||
|
||||
var requestFailure: Throwable? = null
|
||||
waitWithLatch { latch ->
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice", object : TestMatrixCallback<Session>(latch, onlySuccessful = false) {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
requestFailure = failure
|
||||
super.onFailure(failure)
|
||||
}
|
||||
})
|
||||
runBlockingTest {
|
||||
try {
|
||||
matrix.authenticationService
|
||||
.getLoginWizard()
|
||||
.login(userName, password, "myDevice")
|
||||
} catch (failure: Throwable) {
|
||||
requestFailure = failure
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull(requestFailure)
|
||||
|
@@ -61,7 +61,7 @@ class SearchMessagesTest : InstrumentedTest {
|
||||
2)
|
||||
|
||||
run {
|
||||
var lock = CountDownLatch(1)
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val eventListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2
|
||||
@@ -70,7 +70,6 @@ class SearchMessagesTest : InstrumentedTest {
|
||||
aliceTimeline.addListener(eventListener)
|
||||
commonTestHelper.await(lock)
|
||||
|
||||
lock = CountDownLatch(1)
|
||||
val data = commonTestHelper.runBlockingTest {
|
||||
aliceSession
|
||||
.searchService()
|
||||
|
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.matrix.android.sdk.api.auth
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
@@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||
@@ -32,14 +30,14 @@ import org.matrix.android.sdk.api.util.Cancelable
|
||||
interface AuthenticationService {
|
||||
/**
|
||||
* Request the supported login flows for this homeserver.
|
||||
* This is the first method to call to be able to get a wizard to login or the create an account
|
||||
* This is the first method to call to be able to get a wizard to login or to create an account
|
||||
*/
|
||||
fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult
|
||||
|
||||
/**
|
||||
* Request the supported login flows for the corresponding sessionId.
|
||||
*/
|
||||
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult
|
||||
|
||||
/**
|
||||
* Get a SSO url
|
||||
@@ -69,12 +67,12 @@ interface AuthenticationService {
|
||||
/**
|
||||
* Cancel pending login or pending registration
|
||||
*/
|
||||
fun cancelPendingLoginOrRegistration()
|
||||
suspend fun cancelPendingLoginOrRegistration()
|
||||
|
||||
/**
|
||||
* Reset all pending settings, including current HomeServerConnectionConfig
|
||||
*/
|
||||
fun reset()
|
||||
suspend fun reset()
|
||||
|
||||
/**
|
||||
* Check if there is an authenticated [Session].
|
||||
@@ -91,24 +89,21 @@ interface AuthenticationService {
|
||||
/**
|
||||
* Create a session after a SSO successful login
|
||||
*/
|
||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials): Session
|
||||
|
||||
/**
|
||||
* Perform a wellknown request, using the domain from the matrixId
|
||||
*/
|
||||
fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?,
|
||||
callback: MatrixCallback<WellknownResult>): Cancelable
|
||||
suspend fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult
|
||||
|
||||
/**
|
||||
* Authenticate with a matrixId and a password
|
||||
* Usually call this after a successful call to getWellKnownData()
|
||||
*/
|
||||
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String): Session
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.matrix.android.sdk.api.auth.login
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
@@ -29,26 +28,23 @@ interface LoginWizard {
|
||||
* @param callback the matrix callback on which you'll receive the result of authentication.
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String): Session
|
||||
|
||||
/**
|
||||
* Exchange a login token to an access token
|
||||
*/
|
||||
fun loginWithToken(loginToken: String,
|
||||
callback: MatrixCallback<Session>): Cancelable
|
||||
suspend fun loginWithToken(loginToken: String): Session
|
||||
|
||||
/**
|
||||
* Reset user password
|
||||
*/
|
||||
fun resetPassword(email: String,
|
||||
newPassword: String,
|
||||
callback: MatrixCallback<Unit>): Cancelable
|
||||
suspend fun resetPassword(email: String,
|
||||
newPassword: String)
|
||||
|
||||
/**
|
||||
* Confirm the new password, once the user has checked his email
|
||||
* Confirm the new password, once the user has checked their email
|
||||
*/
|
||||
fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable
|
||||
suspend fun resetPasswordMailConfirmed()
|
||||
}
|
||||
|
@@ -16,28 +16,25 @@
|
||||
|
||||
package org.matrix.android.sdk.api.auth.registration
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
interface RegistrationWizard {
|
||||
|
||||
fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun getRegistrationFlow(): RegistrationResult
|
||||
|
||||
fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult
|
||||
|
||||
fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun performReCaptcha(response: String): RegistrationResult
|
||||
|
||||
fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun acceptTerms(): RegistrationResult
|
||||
|
||||
fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun dummy(): RegistrationResult
|
||||
|
||||
fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult
|
||||
|
||||
fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun sendAgainThreePid(): RegistrationResult
|
||||
|
||||
fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun handleValidateThreePid(code: String): RegistrationResult
|
||||
|
||||
fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable
|
||||
suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult
|
||||
|
||||
val currentThreePid: String?
|
||||
|
||||
|
@@ -53,22 +53,24 @@ fun Throwable.isInvalidUIAAuth(): Boolean {
|
||||
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
|
||||
*/
|
||||
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
|
||||
return if (this is Failure.OtherServerError && this.httpCode == 401) {
|
||||
return if (this is Failure.OtherServerError && httpCode == 401) {
|
||||
tryOrNull {
|
||||
MoshiProvider.providesMoshi()
|
||||
.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)
|
||||
if (this.error.session != null && this.error.flows != null) {
|
||||
if (error.session != null && error.flows != null) {
|
||||
RegistrationFlowResponse(
|
||||
flows = this.error.flows,
|
||||
session = this.error.session,
|
||||
completedStages = this.error.completedStages,
|
||||
params = this.error.params
|
||||
flows = error.flows,
|
||||
session = error.session,
|
||||
completedStages = error.completedStages,
|
||||
params = error.params
|
||||
)
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
@@ -27,7 +27,8 @@ interface AccountService {
|
||||
* @param password Current password.
|
||||
* @param newPassword New password
|
||||
*/
|
||||
suspend fun changePassword(password: String, newPassword: String)
|
||||
suspend fun changePassword(password: String,
|
||||
newPassword: String)
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
* an incomplete view of conversations
|
||||
* @param userInteractiveAuthInterceptor see [UserInteractiveAuthInterceptor]
|
||||
*/
|
||||
suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean)
|
||||
suspend fun deactivateAccount(eraseAllData: Boolean,
|
||||
userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
|
||||
}
|
||||
|
@@ -16,12 +16,11 @@
|
||||
|
||||
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 {
|
||||
|
||||
fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable
|
||||
suspend fun getTurnServer(): TurnServerResponse
|
||||
|
||||
fun getPSTNProtocolChecker(): PSTNProtocolChecker
|
||||
|
||||
/**
|
||||
* Create an outgoing call
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
@@ -56,8 +56,6 @@ interface CryptoService {
|
||||
|
||||
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 isCryptoEnabled(): Boolean
|
||||
|
@@ -20,7 +20,9 @@ import android.content.Context
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.internal.auth.db.AuthRealmMigration
|
||||
import org.matrix.android.sdk.internal.auth.db.AuthRealmModule
|
||||
@@ -32,8 +34,6 @@ import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||
import org.matrix.android.sdk.internal.di.AuthDatabase
|
||||
import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter
|
||||
import org.matrix.android.sdk.internal.wellknown.WellknownModule
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import java.io.File
|
||||
|
||||
@Module(includes = [WellknownModule::class])
|
||||
@@ -82,6 +82,9 @@ internal abstract class AuthModule {
|
||||
@Binds
|
||||
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindIsValidClientServerApiTask(task: DefaultIsValidClientServerApiTask): IsValidClientServerApiTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
|
||||
}
|
||||
|
@@ -18,10 +18,7 @@ package org.matrix.android.sdk.internal.auth
|
||||
|
||||
import android.net.Uri
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
@@ -32,8 +29,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.api.util.appendParamToUrl
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
|
||||
@@ -50,11 +45,6 @@ import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
|
||||
import org.matrix.android.sdk.internal.network.ssl.UnrecognizedCertificateException
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.internal.util.toCancelable
|
||||
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
@@ -63,14 +53,12 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val sessionManager: SessionManager,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val getWellknownTask: GetWellknownTask,
|
||||
private val directLoginTask: DirectLoginTask,
|
||||
private val taskExecutor: TaskExecutor
|
||||
private val directLoginTask: DirectLoginTask
|
||||
) : AuthenticationService {
|
||||
|
||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||
@@ -89,15 +77,11 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult {
|
||||
val homeServerConnectionConfig = sessionParamsStore.get(sessionId)?.homeServerConnectionConfig
|
||||
?: throw IllegalStateException("Session not found")
|
||||
|
||||
return if (homeServerConnectionConfig == null) {
|
||||
callback.onFailure(IllegalStateException("Session not found"))
|
||||
NoOpCancellable
|
||||
} else {
|
||||
getLoginFlow(homeServerConnectionConfig, callback)
|
||||
}
|
||||
return getLoginFlow(homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
||||
@@ -146,70 +130,70 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
?.trim { it == '/' }
|
||||
}
|
||||
|
||||
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
/**
|
||||
* This is the entry point of the authentication service.
|
||||
* homeServerConnectionConfig contains a homeserver URL probably entered by the user, which can be a
|
||||
* valid homeserver API url, the url of Element Web, or anything else.
|
||||
*/
|
||||
override suspend fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
pendingSessionData = null
|
||||
|
||||
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
pendingSessionStore.delete()
|
||||
|
||||
val result = runCatching {
|
||||
getLoginFlowInternal(homeServerConnectionConfig)
|
||||
}
|
||||
result.fold(
|
||||
{
|
||||
if (it is LoginFlowResult.Success) {
|
||||
// The homeserver exists and up to date, keep the config
|
||||
// Homeserver url may have been changed, if it was a Riot url
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse(it.homeServerUrl)
|
||||
)
|
||||
|
||||
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||
}
|
||||
callback.onSuccess(it)
|
||||
},
|
||||
{
|
||||
if (it is UnrecognizedCertificateException) {
|
||||
callback.onFailure(Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint))
|
||||
} else {
|
||||
callback.onFailure(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
val result = runCatching {
|
||||
getLoginFlowInternal(homeServerConnectionConfig)
|
||||
}
|
||||
.toCancelable()
|
||||
return result.fold(
|
||||
{
|
||||
if (it is LoginFlowResult.Success) {
|
||||
// The homeserver exists and up to date, keep the config
|
||||
// Homeserver url may have been changed, if it was a Riot url
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = Uri.parse(it.homeServerUrl)
|
||||
)
|
||||
|
||||
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
|
||||
.also { data -> pendingSessionStore.savePendingSessionData(data) }
|
||||
}
|
||||
it
|
||||
},
|
||||
{
|
||||
if (it is UnrecognizedCertificateException) {
|
||||
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||
val authAPI = buildAuthAPI(homeServerConnectionConfig)
|
||||
|
||||
// First check the homeserver version
|
||||
runCatching {
|
||||
executeRequest<Versions>(null) {
|
||||
apiCall = authAPI.versions()
|
||||
}
|
||||
// First check the homeserver version
|
||||
return runCatching {
|
||||
executeRequest<Versions>(null) {
|
||||
apiCall = authAPI.versions()
|
||||
}
|
||||
.map { versions ->
|
||||
// Ok, it seems that the homeserver url is valid
|
||||
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
|
||||
}
|
||||
.fold(
|
||||
{
|
||||
it
|
||||
},
|
||||
{
|
||||
if (it is Failure.OtherServerError
|
||||
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||
// It's maybe a Riot url?
|
||||
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.map { versions ->
|
||||
// Ok, it seems that the homeserver url is valid
|
||||
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
|
||||
}
|
||||
.fold(
|
||||
{
|
||||
it
|
||||
},
|
||||
{
|
||||
if (it is Failure.OtherServerError
|
||||
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||
// It's maybe a Riot url?
|
||||
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
|
||||
@@ -338,12 +322,9 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultRegistrationWizard(
|
||||
buildClient(it),
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
buildAuthAPI(it),
|
||||
sessionCreator,
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
pendingSessionStore
|
||||
).also {
|
||||
currentRegistrationWizard = it
|
||||
}
|
||||
@@ -359,12 +340,9 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultLoginWizard(
|
||||
buildClient(it),
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
buildAuthAPI(it),
|
||||
sessionCreator,
|
||||
pendingSessionStore,
|
||||
taskExecutor.executorScope
|
||||
pendingSessionStore
|
||||
).also {
|
||||
currentLoginWizard = it
|
||||
}
|
||||
@@ -372,7 +350,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelPendingLoginOrRegistration() {
|
||||
override suspend fun cancelPendingLoginOrRegistration() {
|
||||
currentLoginWizard = null
|
||||
currentRegistrationWizard = null
|
||||
|
||||
@@ -381,61 +359,39 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
pendingSessionData = pendingSessionData?.homeServerConnectionConfig
|
||||
?.let { PendingSessionData(it) }
|
||||
.also {
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
if (it == null) {
|
||||
// Should not happen
|
||||
pendingSessionStore.delete()
|
||||
} else {
|
||||
pendingSessionStore.savePendingSessionData(it)
|
||||
}
|
||||
if (it == null) {
|
||||
// Should not happen
|
||||
pendingSessionStore.delete()
|
||||
} else {
|
||||
pendingSessionStore.savePendingSessionData(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
override suspend fun reset() {
|
||||
currentLoginWizard = null
|
||||
currentRegistrationWizard = null
|
||||
|
||||
pendingSessionData = null
|
||||
|
||||
taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||
pendingSessionStore.delete()
|
||||
}
|
||||
pendingSessionStore.delete()
|
||||
}
|
||||
|
||||
override fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
createSessionFromSso(credentials, homeServerConnectionConfig)
|
||||
}
|
||||
override suspend fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
credentials: Credentials): Session {
|
||||
return sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?,
|
||||
callback: MatrixCallback<WellknownResult>): Cancelable {
|
||||
return getWellknownTask
|
||||
.configureWith(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun getWellKnownData(matrixId: String,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
|
||||
return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig))
|
||||
}
|
||||
|
||||
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return directLoginTask
|
||||
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
private suspend fun createSessionFromSso(credentials: Credentials,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
||||
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String): Session {
|
||||
return directLoginTask.execute(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName))
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.internal.auth
|
||||
|
||||
import dagger.Lazy
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
|
||||
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
internal interface IsValidClientServerApiTask : Task<IsValidClientServerApiTask.Params, Boolean> {
|
||||
data class Params(
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultIsValidClientServerApiTask @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory
|
||||
) : IsValidClientServerApiTask {
|
||||
|
||||
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
|
||||
val client = buildClient(params.homeServerConnectionConfig)
|
||||
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
|
||||
|
||||
val authAPI = retrofitFactory.create(client, homeServerUrl)
|
||||
.create(AuthAPI::class.java)
|
||||
|
||||
return try {
|
||||
executeRequest<LoginFlowResponse>(null) {
|
||||
apiCall = authAPI.getLoginFlows()
|
||||
}
|
||||
// We get a response, so the API is valid
|
||||
true
|
||||
} catch (failure: Throwable) {
|
||||
if (failure is Failure.OtherServerError
|
||||
&& failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
|
||||
// Probably not valid
|
||||
false
|
||||
} else {
|
||||
// Other error
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.build()
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ import android.net.Uri
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import timber.log.Timber
|
||||
@@ -32,7 +33,8 @@ internal interface SessionCreator {
|
||||
internal class DefaultSessionCreator @Inject constructor(
|
||||
private val sessionParamsStore: SessionParamsStore,
|
||||
private val sessionManager: SessionManager,
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val isValidClientServerApiTask: IsValidClientServerApiTask
|
||||
) : SessionCreator {
|
||||
|
||||
/**
|
||||
@@ -43,16 +45,28 @@ internal class DefaultSessionCreator @Inject constructor(
|
||||
// We can cleanup the pending session params
|
||||
pendingSessionStore.delete()
|
||||
|
||||
val overriddenUrl = credentials.discoveryInformation?.homeServer?.baseURL
|
||||
// remove trailing "/"
|
||||
?.trim { it == '/' }
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") }
|
||||
?.let { Uri.parse(it) }
|
||||
?.takeIf {
|
||||
// Validate the URL, if the configuration is wrong server side, do not override
|
||||
tryOrNull {
|
||||
isValidClientServerApiTask.execute(
|
||||
IsValidClientServerApiTask.Params(
|
||||
homeServerConnectionConfig.copy(homeServerUri = it)
|
||||
)
|
||||
)
|
||||
.also { Timber.d("Overriding homeserver url: $it") }
|
||||
} ?: true // In case of other error (no network, etc.), consider it is valid...
|
||||
}
|
||||
|
||||
val sessionParams = SessionParams(
|
||||
credentials = credentials,
|
||||
homeServerConnectionConfig = homeServerConnectionConfig.copy(
|
||||
homeServerUri = credentials.discoveryInformation?.homeServer?.baseURL
|
||||
// remove trailing "/"
|
||||
?.trim { it == '/' }
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?.also { Timber.d("Overriding homeserver url to $it") }
|
||||
?.let { Uri.parse(it) }
|
||||
?: homeServerConnectionConfig.homeServerUri,
|
||||
homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri,
|
||||
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
|
||||
// remove trailing "/"
|
||||
?.trim { it == '/' }
|
||||
|
@@ -17,13 +17,10 @@
|
||||
package org.matrix.android.sdk.internal.auth.login
|
||||
|
||||
import android.util.Patterns
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.internal.auth.AuthAPI
|
||||
import org.matrix.android.sdk.internal.auth.PendingSessionStore
|
||||
import org.matrix.android.sdk.internal.auth.SessionCreator
|
||||
@@ -34,56 +31,19 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
|
||||
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
|
||||
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
|
||||
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
internal class DefaultLoginWizard(
|
||||
okHttpClient: OkHttpClient,
|
||||
retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val authAPI: AuthAPI,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
) : LoginWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
|
||||
private val authAPI = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
|
||||
.create(AuthAPI::class.java)
|
||||
|
||||
override fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String,
|
||||
callback: MatrixCallback<Session>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
loginInternal(login, password, deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
|
||||
*/
|
||||
override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val loginParams = TokenLoginParams(
|
||||
token = loginToken
|
||||
)
|
||||
val credentials = executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loginInternal(login: String,
|
||||
password: String,
|
||||
deviceName: String) = withContext(coroutineDispatchers.computation) {
|
||||
override suspend fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String): Session {
|
||||
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
|
||||
PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName)
|
||||
} else {
|
||||
@@ -93,16 +53,24 @@ internal class DefaultLoginWizard(
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override fun resetPassword(email: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
resetPasswordInternal(email, newPassword)
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
|
||||
*/
|
||||
override suspend fun loginWithToken(loginToken: String): Session {
|
||||
val loginParams = TokenLoginParams(
|
||||
token = loginToken
|
||||
)
|
||||
val credentials = executeRequest<Credentials>(null) {
|
||||
apiCall = authAPI.login(loginParams)
|
||||
}
|
||||
|
||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
private suspend fun resetPasswordInternal(email: String, newPassword: String) {
|
||||
override suspend fun resetPassword(email: String, newPassword: String) {
|
||||
val param = RegisterAddThreePidTask.Params(
|
||||
RegisterThreePid.Email(email),
|
||||
pendingSessionData.clientSecret,
|
||||
@@ -120,21 +88,14 @@ internal class DefaultLoginWizard(
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
}
|
||||
|
||||
override fun resetPasswordMailConfirmed(callback: MatrixCallback<Unit>): Cancelable {
|
||||
val safeResetPasswordData = pendingSessionData.resetPasswordData ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, no reset password in progress"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
resetPasswordMailConfirmedInternal(safeResetPasswordData)
|
||||
}
|
||||
}
|
||||
override suspend fun resetPasswordMailConfirmed() {
|
||||
val safeResetPasswordData = pendingSessionData.resetPasswordData
|
||||
?: throw IllegalStateException("developer error, no reset password in progress")
|
||||
|
||||
private suspend fun resetPasswordMailConfirmedInternal(resetPasswordData: ResetPasswordData) {
|
||||
val param = ResetPasswordMailConfirmed.create(
|
||||
pendingSessionData.clientSecret,
|
||||
resetPasswordData.addThreePidRegistrationResponse.sid,
|
||||
resetPasswordData.newPassword
|
||||
safeResetPasswordData.addThreePidRegistrationResponse.sid,
|
||||
safeResetPasswordData.newPassword
|
||||
)
|
||||
|
||||
executeRequest<Unit>(null) {
|
||||
|
@@ -16,10 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.auth.registration
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
@@ -27,31 +24,22 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.toFlowResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.internal.auth.AuthAPI
|
||||
import org.matrix.android.sdk.internal.auth.PendingSessionStore
|
||||
import org.matrix.android.sdk.internal.auth.SessionCreator
|
||||
import org.matrix.android.sdk.internal.auth.db.PendingSessionData
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
|
||||
/**
|
||||
* This class execute the registration request and is responsible to keep the session of interactive authentication
|
||||
*/
|
||||
internal class DefaultRegistrationWizard(
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
authAPI: AuthAPI,
|
||||
private val sessionCreator: SessionCreator,
|
||||
private val pendingSessionStore: PendingSessionStore,
|
||||
private val coroutineScope: CoroutineScope
|
||||
private val pendingSessionStore: PendingSessionStore
|
||||
) : RegistrationWizard {
|
||||
|
||||
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
|
||||
|
||||
private val authAPI = buildAuthAPI()
|
||||
private val registerTask = DefaultRegisterTask(authAPI)
|
||||
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
|
||||
private val validateCodeTask = DefaultValidateCodeTask(authAPI)
|
||||
@@ -71,70 +59,54 @@ internal class DefaultRegistrationWizard(
|
||||
override val isRegistrationStarted: Boolean
|
||||
get() = pendingSessionData.isRegistrationStarted
|
||||
|
||||
override fun getRegistrationFlow(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
override suspend fun getRegistrationFlow(): RegistrationResult {
|
||||
val params = RegistrationParams()
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun createAccount(userName: String,
|
||||
password: String,
|
||||
initialDeviceDisplayName: String?,
|
||||
callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
override suspend fun createAccount(userName: String,
|
||||
password: String,
|
||||
initialDeviceDisplayName: String?): RegistrationResult {
|
||||
val params = RegistrationParams(
|
||||
username = userName,
|
||||
password = password,
|
||||
initialDeviceDisplayName = initialDeviceDisplayName
|
||||
)
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
.also {
|
||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
}
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
.also {
|
||||
pendingSessionData = pendingSessionData.copy(isRegistrationStarted = true)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun performReCaptcha(response: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeSession = pendingSessionData.currentSession ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
override suspend fun performReCaptcha(response: String): RegistrationResult {
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams.createForCaptcha(safeSession, response))
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun acceptTerms(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeSession = pendingSessionData.currentSession ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
override suspend fun acceptTerms(): RegistrationResult {
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.TERMS, session = safeSession))
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun addThreePid(threePid: RegisterThreePid, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
override suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult {
|
||||
pendingSessionData = pendingSessionData.copy(currentThreePidData = null)
|
||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||
|
||||
sendThreePid(threePid)
|
||||
}
|
||||
return sendThreePid(threePid)
|
||||
}
|
||||
|
||||
override fun sendAgainThreePid(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
sendThreePid(safeCurrentThreePid)
|
||||
}
|
||||
override suspend fun sendAgainThreePid(): RegistrationResult {
|
||||
val safeCurrentThreePid = pendingSessionData.currentThreePidData?.threePid
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
return sendThreePid(safeCurrentThreePid)
|
||||
}
|
||||
|
||||
private suspend fun sendThreePid(threePid: RegisterThreePid): RegistrationResult {
|
||||
@@ -173,20 +145,15 @@ internal class DefaultRegistrationWizard(
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
override fun checkIfEmailHasBeenValidated(delayMillis: Long, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeParam = pendingSessionData.currentThreePidData?.registrationParams ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, no pending three pid"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
performRegistrationRequest(safeParam, delayMillis)
|
||||
}
|
||||
override suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult {
|
||||
val safeParam = pendingSessionData.currentThreePidData?.registrationParams
|
||||
?: throw IllegalStateException("developer error, no pending three pid")
|
||||
|
||||
return performRegistrationRequest(safeParam, delayMillis)
|
||||
}
|
||||
|
||||
override fun handleValidateThreePid(code: String, callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
validateThreePid(code)
|
||||
}
|
||||
override suspend fun handleValidateThreePid(code: String): RegistrationResult {
|
||||
return validateThreePid(code)
|
||||
}
|
||||
|
||||
private suspend fun validateThreePid(code: String): RegistrationResult {
|
||||
@@ -210,15 +177,12 @@ internal class DefaultRegistrationWizard(
|
||||
}
|
||||
}
|
||||
|
||||
override fun dummy(callback: MatrixCallback<RegistrationResult>): Cancelable {
|
||||
val safeSession = pendingSessionData.currentSession ?: run {
|
||||
callback.onFailure(IllegalStateException("developer error, call createAccount() method first"))
|
||||
return NoOpCancellable
|
||||
}
|
||||
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||
performRegistrationRequest(params)
|
||||
}
|
||||
override suspend fun dummy(): RegistrationResult {
|
||||
val safeSession = pendingSessionData.currentSession
|
||||
?: throw IllegalStateException("developer error, call createAccount() method first")
|
||||
|
||||
val params = RegistrationParams(auth = AuthParams(type = LoginFlowTypes.DUMMY, session = safeSession))
|
||||
return performRegistrationRequest(params)
|
||||
}
|
||||
|
||||
private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,
|
||||
@@ -239,9 +203,4 @@ internal class DefaultRegistrationWizard(
|
||||
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
return RegistrationResult.Success(session)
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(): AuthAPI {
|
||||
val retrofit = retrofitFactory.create(okHttpClient, pendingSessionData.homeServerConnectionConfig.homeServerUri.toString())
|
||||
return retrofit.create(AuthAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
@@ -16,14 +16,25 @@
|
||||
|
||||
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.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||
import timber.log.Timber
|
||||
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}")
|
||||
val flowResponse = failure.toRegistrationFlowResponse()
|
||||
?: return false.also {
|
||||
@@ -38,16 +49,16 @@ internal suspend fun handleUIA(failure: Throwable, interceptor: UserInteractiveA
|
||||
suspendCoroutine<UIABaseAuth> { continuation ->
|
||||
interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.w(failure, "## UIA: failed to participate")
|
||||
} catch (failure2: Throwable) {
|
||||
Timber.w(failure2, "## UIA: failed to participate")
|
||||
return false
|
||||
}
|
||||
|
||||
Timber.d("## UIA: updated auth $authUpdate")
|
||||
Timber.d("## UIA: updated auth")
|
||||
return try {
|
||||
retryBlock(authUpdate)
|
||||
true
|
||||
} catch (failure: Throwable) {
|
||||
handleUIA(failure, interceptor, retryBlock)
|
||||
} catch (failure3: Throwable) {
|
||||
handleUIA(failure3, interceptor, retryBlock)
|
||||
}
|
||||
}
|
||||
|
@@ -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.DefaultClaimOneTimeKeysForUsersDevice
|
||||
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.DefaultEncryptEventTask
|
||||
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.DefaultUploadSigningKeysTask
|
||||
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.EncryptEventTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
|
||||
@@ -240,9 +238,6 @@ internal abstract class CryptoModule {
|
||||
@Binds
|
||||
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindDeleteDeviceWithUserPasswordTask(task: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
|
||||
|
||||
|
@@ -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.store.IMXCryptoStore
|
||||
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.GetDevicesTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
||||
@@ -153,9 +152,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
// Repository
|
||||
private val megolmEncryptionFactory: MXMegolmEncryptionFactory,
|
||||
private val olmEncryptionFactory: MXOlmEncryptionFactory,
|
||||
private val deleteDeviceTask: DeleteDeviceTask,
|
||||
private val deleteDeviceWithUserPasswordTask: DeleteDeviceWithUserPasswordTask,
|
||||
// Tasks
|
||||
private val deleteDeviceTask: DeleteDeviceTask,
|
||||
private val getDevicesTask: GetDevicesTask,
|
||||
private val getDeviceInfoTask: GetDeviceInfoTask,
|
||||
private val setDeviceNameTask: SetDeviceNameTask,
|
||||
@@ -217,15 +215,6 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
.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 {
|
||||
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
|
||||
}
|
||||
|
@@ -47,12 +47,16 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
if (params.userInteractiveAuthInterceptor == null
|
||||
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
|
||||
execute(params.copy(userAuthParam = auth))
|
||||
}
|
||||
|| !handleUIA(
|
||||
failure = throwable,
|
||||
interceptor = params.userInteractiveAuthInterceptor,
|
||||
retryBlock = { authUpdate ->
|
||||
execute(params.copy(userAuthParam = authUpdate))
|
||||
}
|
||||
)
|
||||
) {
|
||||
Timber.d("## UIA: propagate failure")
|
||||
throw throwable
|
||||
throw throwable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@@ -126,11 +126,16 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
|
||||
uploadSigningKeysTask.execute(uploadSigningKeysParams)
|
||||
} catch (failure: Throwable) {
|
||||
if (params.interactiveAuthInterceptor == null
|
||||
|| !handleUIA(failure, params.interactiveAuthInterceptor) { authUpdate ->
|
||||
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
|
||||
}) {
|
||||
|| !handleUIA(
|
||||
failure = failure,
|
||||
interceptor = params.interactiveAuthInterceptor,
|
||||
retryBlock = { authUpdate ->
|
||||
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
|
||||
}
|
||||
)
|
||||
) {
|
||||
Timber.d("## UIA: propagate failure")
|
||||
throw failure
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -16,10 +16,9 @@
|
||||
|
||||
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.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.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.cleanup.CleanupSession
|
||||
@@ -30,8 +29,8 @@ import javax.inject.Inject
|
||||
|
||||
internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Unit> {
|
||||
data class Params(
|
||||
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
|
||||
val eraseAllData: Boolean,
|
||||
val userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor,
|
||||
val userAuthParam: UIABaseAuth? = null
|
||||
)
|
||||
}
|
||||
@@ -39,7 +38,6 @@ internal interface DeactivateAccountTask : Task<DeactivateAccountTask.Params, Un
|
||||
internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||
private val accountAPI: AccountAPI,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||
@UserId private val userId: String,
|
||||
private val identityDisconnectTask: IdentityDisconnectTask,
|
||||
private val cleanupSession: CleanupSession
|
||||
) : DeactivateAccountTask {
|
||||
@@ -47,23 +45,33 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||
override suspend fun execute(params: DeactivateAccountTask.Params) {
|
||||
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
|
||||
|
||||
try {
|
||||
val canCleanup = try {
|
||||
executeRequest<Unit>(globalErrorReceiver) {
|
||||
apiCall = accountAPI.deactivate(deactivateAccountParams)
|
||||
}
|
||||
true
|
||||
} catch (throwable: Throwable) {
|
||||
if (!handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
|
||||
execute(params.copy(userAuthParam = auth))
|
||||
}
|
||||
if (!handleUIA(
|
||||
failure = throwable,
|
||||
interceptor = params.userInteractiveAuthInterceptor,
|
||||
retryBlock = { authUpdate ->
|
||||
execute(params.copy(userAuthParam = authUpdate))
|
||||
}
|
||||
)
|
||||
) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ internal class DefaultAccountService @Inject constructor(private val changePassw
|
||||
changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword))
|
||||
}
|
||||
|
||||
override suspend fun deactivateAccount(userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, eraseAllData: Boolean) {
|
||||
deactivateAccountTask.execute(DeactivateAccountTask.Params(userInteractiveAuthInterceptor, eraseAllData))
|
||||
override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
|
||||
deactivateAccountTask.execute(DeactivateAccountTask.Params(eraseAllData, userInteractiveAuthInterceptor))
|
||||
}
|
||||
}
|
||||
|
@@ -16,16 +16,12 @@
|
||||
|
||||
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.CallSignalingService
|
||||
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.util.Cancelable
|
||||
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 javax.inject.Inject
|
||||
|
||||
@@ -34,14 +30,16 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||
private val callSignalingHandler: CallSignalingHandler,
|
||||
private val mxCallFactory: MxCallFactory,
|
||||
private val activeCallHandler: ActiveCallHandler,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val turnServerDataSource: TurnServerDataSource
|
||||
private val turnServerDataSource: TurnServerDataSource,
|
||||
private val pstnProtocolChecker: PSTNProtocolChecker
|
||||
) : CallSignalingService {
|
||||
|
||||
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
|
||||
return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) {
|
||||
turnServerDataSource.getTurnServer()
|
||||
}
|
||||
override suspend fun getTurnServer(): TurnServerResponse {
|
||||
return turnServerDataSource.getTurnServer()
|
||||
}
|
||||
|
||||
override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
|
||||
return pstnProtocolChecker
|
||||
}
|
||||
|
||||
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
||||
|
@@ -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.PendingThreePidEntityFields
|
||||
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.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
@@ -47,11 +46,12 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
|
||||
private val profileAPI: ProfileAPI,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val pendingThreePidMapper: PendingThreePidMapper,
|
||||
@UserId private val userId: String,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver) : FinalizeAddingThreePidTask() {
|
||||
|
||||
override suspend fun execute(params: Params) {
|
||||
if (params.userWantsToCancel.not()) {
|
||||
val canCleanup = if (params.userWantsToCancel) {
|
||||
true
|
||||
} else {
|
||||
// Get the required pending data
|
||||
val pendingThreePids = monarchy.fetchAllMappedSync(
|
||||
{ it.where(PendingThreePidEntity::class.java) },
|
||||
@@ -69,21 +69,30 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
|
||||
)
|
||||
apiCall = profileAPI.finalizeAddThreePid(body)
|
||||
}
|
||||
true
|
||||
} catch (throwable: Throwable) {
|
||||
if (params.userInteractiveAuthInterceptor == null
|
||||
|| !handleUIA(throwable, params.userInteractiveAuthInterceptor) { auth ->
|
||||
execute(params.copy(userAuthParam = auth))
|
||||
}
|
||||
|| !handleUIA(
|
||||
failure = throwable,
|
||||
interceptor = params.userInteractiveAuthInterceptor,
|
||||
retryBlock = { authUpdate ->
|
||||
execute(params.copy(userAuthParam = authUpdate))
|
||||
}
|
||||
)
|
||||
) {
|
||||
Timber.d("## UIA: propagate failure")
|
||||
throw throwable.toRegistrationFlowResponse()
|
||||
throw throwable.toRegistrationFlowResponse()
|
||||
?.let { Failure.RegistrationFlowError(it) }
|
||||
?: throwable
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanupDatabase(params)
|
||||
if (canCleanup) {
|
||||
cleanupDatabase(params)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun cleanupDatabase(params: Params) {
|
||||
|
@@ -41,6 +41,9 @@ parser.add_argument('-b',
|
||||
type=int,
|
||||
required=True,
|
||||
help='the buildkite build number.')
|
||||
parser.add_argument('-f',
|
||||
'--filename',
|
||||
help='the filename, to download only one artifact.')
|
||||
parser.add_argument('-e',
|
||||
'--expecting',
|
||||
type=int,
|
||||
@@ -148,6 +151,8 @@ for elt in data:
|
||||
print(" %s: %s" % (key, str(value)))
|
||||
url = elt.get("download_url")
|
||||
filename = elt.get("filename")
|
||||
if args.filename is not None and args.filename != filename:
|
||||
continue
|
||||
target = targetDir + "/" + filename
|
||||
print("Downloading %s to '%s'..." % (filename, targetDir))
|
||||
if not args.simulate:
|
||||
|
@@ -12,8 +12,8 @@ kapt {
|
||||
|
||||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 1
|
||||
ext.versionMinor = 0
|
||||
ext.versionPatch = 18
|
||||
ext.versionMinor = 1
|
||||
ext.versionPatch = 0
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@@ -23,11 +23,11 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
@@ -47,22 +47,21 @@ abstract class VerificationTestBase {
|
||||
withInitialSync: Boolean): Session {
|
||||
val hs = createHomeServerConfig()
|
||||
|
||||
doSync<LoginFlowResult> {
|
||||
matrix.authenticationService()
|
||||
.getLoginFlow(hs, it)
|
||||
runBlockingTest {
|
||||
matrix.authenticationService().getLoginFlow(hs)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult> {
|
||||
runBlockingTest {
|
||||
matrix.authenticationService()
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
.createAccount(userName, password, null)
|
||||
}
|
||||
|
||||
// Perform dummy step
|
||||
val registrationResult = doSync<RegistrationResult> {
|
||||
val registrationResult = runBlockingTest {
|
||||
matrix.authenticationService()
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
.dummy()
|
||||
}
|
||||
|
||||
Assert.assertTrue(registrationResult is RegistrationResult.Success)
|
||||
@@ -80,6 +79,14 @@ abstract class VerificationTestBase {
|
||||
.build()
|
||||
}
|
||||
|
||||
protected fun <T> runBlockingTest(timeout: Long = 20_000, block: suspend () -> T): T {
|
||||
return runBlocking {
|
||||
withTimeout(timeout) {
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform a method with a MatrixCallback to a synchronous method
|
||||
inline fun <reified T> doSync(block: (MatrixCallback<T>) -> Unit): T {
|
||||
val lock = CountDownLatch(1)
|
||||
|
@@ -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()
|
||||
}
|
@@ -113,7 +113,7 @@ class ReAuthActivity : SimpleFragmentActivity(), ReAuthViewModel.Factory {
|
||||
|
||||
override fun 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) {
|
||||
if (it.ssoFallbackPageWasShown) {
|
||||
Timber.d("## UIA ssoFallbackPageWasShown tentative success")
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package im.vector.app.features.call
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
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.webrtc.WebRtcCall
|
||||
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.call.CallState
|
||||
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.TurnServerResponse
|
||||
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.toMatrixItem
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
class VectorCallViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: VectorCallViewState,
|
||||
@@ -50,7 +50,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
|
||||
private var call: WebRtcCall? = null
|
||||
|
||||
private var connectionTimeoutTimer: Timer? = null
|
||||
private var connectionTimeoutJob: Job? = null
|
||||
private var hasBeenConnectedOnce = false
|
||||
|
||||
private val callListener = object : WebRtcCall.Listener {
|
||||
@@ -92,26 +92,20 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
val callState = call.state
|
||||
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||
hasBeenConnectedOnce = true
|
||||
connectionTimeoutTimer?.cancel()
|
||||
connectionTimeoutTimer = null
|
||||
connectionTimeoutJob?.cancel()
|
||||
connectionTimeoutJob = null
|
||||
} else {
|
||||
// do we reset as long as it's moving?
|
||||
connectionTimeoutTimer?.cancel()
|
||||
connectionTimeoutJob?.cancel()
|
||||
if (hasBeenConnectedOnce) {
|
||||
connectionTimeoutTimer = Timer().apply {
|
||||
schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
session.callSignalingService().getTurnServer(object : MatrixCallback<TurnServerResponse> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
|
||||
}
|
||||
|
||||
override fun onSuccess(data: TurnServerResponse) {
|
||||
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(data))
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 30_000)
|
||||
connectionTimeoutJob = viewModelScope.launch {
|
||||
delay(30_000)
|
||||
try {
|
||||
val turn = session.callSignalingService().getTurnServer()
|
||||
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(turn))
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -83,12 +83,12 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
|
||||
fun setAudioDevice(device: Device) {
|
||||
runInAudioThread(Runnable {
|
||||
if (!_availableDevices.contains(device)) {
|
||||
Timber.w(" Audio device not available: $device")
|
||||
Timber.w("Audio device not available: $device")
|
||||
userSelectedDevice = null
|
||||
return@Runnable
|
||||
}
|
||||
if (mode != Mode.DEFAULT) {
|
||||
Timber.i(" User selected device set to: $device")
|
||||
Timber.i("User selected device set to: $device")
|
||||
userSelectedDevice = device
|
||||
updateAudioRoute(mode, false)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
|
||||
success = updateAudioRoute(mode, false)
|
||||
} catch (e: Throwable) {
|
||||
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) {
|
||||
this@CallAudioManager.mode = mode
|
||||
@@ -124,7 +124,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
|
||||
* `false`, otherwise.
|
||||
*/
|
||||
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()) {
|
||||
return false
|
||||
}
|
||||
@@ -158,7 +158,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
|
||||
return true
|
||||
}
|
||||
selectedDevice = audioDevice
|
||||
Timber.i(" Selected audio device: " + audioDevice)
|
||||
Timber.i("Selected audio device: $audioDevice")
|
||||
audioDeviceRouter?.setAudioRoute(audioDevice)
|
||||
configChange?.invoke()
|
||||
return true
|
||||
|
@@ -22,20 +22,22 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
|
||||
class DialPadLookup @Inject constructor(val session: Session,
|
||||
val directRoomHelper: DirectRoomHelper,
|
||||
val callManager: WebRtcCallManager
|
||||
class DialPadLookup @Inject constructor(
|
||||
private val session: Session,
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
private val callManager: WebRtcCallManager
|
||||
) {
|
||||
|
||||
class Failure : Throwable()
|
||||
|
||||
data class Result(val userId: String, val roomId: String)
|
||||
|
||||
suspend fun lookupPhoneNumber(phoneNumber: String): Result {
|
||||
val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure()
|
||||
val thirdPartyUser = tryOrNull {
|
||||
session.thirdPartyService().getThirdPartyUser(supportedProtocolKey, fields = mapOf(
|
||||
"m.id.phone" to phoneNumber
|
||||
)).firstOrNull()
|
||||
session.thirdPartyService().getThirdPartyUser(
|
||||
protocol = supportedProtocolKey,
|
||||
fields = mapOf("m.id.phone" to phoneNumber)
|
||||
).firstOrNull()
|
||||
} ?: throw Failure()
|
||||
|
||||
val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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.CallNegotiateContent
|
||||
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.webrtc.AudioSource
|
||||
import org.webrtc.AudioTrack
|
||||
@@ -420,9 +419,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
|
||||
private suspend fun getTurnServer(): TurnServerResponse? {
|
||||
return tryOrNull {
|
||||
awaitCallback {
|
||||
sessionProvider.get()?.callSignalingService()?.getTurnServer(it)
|
||||
}
|
||||
sessionProvider.get()?.callSignalingService()?.getTurnServer()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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.utils.EglUtils
|
||||
import im.vector.app.push.fcm.FcmHelper
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
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.CallState
|
||||
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.CallCandidatesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||
@@ -65,22 +64,26 @@ class WebRtcCallManager @Inject constructor(
|
||||
private val currentSession: Session?
|
||||
get() = activeSessionDataSource.currentValue?.orNull()
|
||||
|
||||
private val pstnProtocolChecker: PSTNProtocolChecker?
|
||||
get() = currentSession?.callSignalingService()?.getPSTNProtocolChecker()
|
||||
|
||||
interface CurrentCallListener {
|
||||
fun onCurrentCallChange(call: WebRtcCall?) {}
|
||||
fun onAudioDevicesChange() {}
|
||||
}
|
||||
|
||||
interface PSTNSupportListener {
|
||||
fun onPSTNSupportUpdated()
|
||||
val supportedPSTNProtocol: String?
|
||||
get() = pstnProtocolChecker?.supportedPSTNProtocol
|
||||
|
||||
val supportsPSTNProtocol: Boolean
|
||||
get() = supportedPSTNProtocol != null
|
||||
|
||||
fun addPstnSupportListener(listener: PSTNProtocolChecker.Listener) {
|
||||
pstnProtocolChecker?.addListener(listener)
|
||||
}
|
||||
|
||||
private val pstnSupportListeners = emptyList<PSTNSupportListener>().toMutableList()
|
||||
fun addPstnSupportListener(listener: PSTNSupportListener) {
|
||||
pstnSupportListeners.add(listener)
|
||||
}
|
||||
|
||||
fun removePstnSupportListener(listener: PSTNSupportListener) {
|
||||
pstnSupportListeners.remove(listener)
|
||||
fun removePstnSupportListener(listener: PSTNProtocolChecker.Listener) {
|
||||
pstnProtocolChecker?.removeListener(listener)
|
||||
}
|
||||
|
||||
private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
|
||||
@@ -104,27 +107,11 @@ class WebRtcCallManager @Inject constructor(
|
||||
private var peerConnectionFactory: PeerConnectionFactory? = null
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
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 var isInBackground: Boolean = true
|
||||
|
||||
init {
|
||||
GlobalScope.launch {
|
||||
supportedPSTNProtocol = currentSession?.getSupportedPSTN(3)
|
||||
if (supportedPSTNProtocol != null) {
|
||||
pstnSupportListeners.forEach {
|
||||
tryOrNull { it.onPSTNSupportUpdated() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun entersForeground() {
|
||||
isInBackground = false
|
||||
@@ -167,6 +154,10 @@ class WebRtcCallManager @Inject constructor(
|
||||
return callsByCallId.values.toList()
|
||||
}
|
||||
|
||||
fun checkForPSTNSupportIfNeeded() {
|
||||
pstnProtocolChecker?.checkForPSTNSupportIfNeeded()
|
||||
}
|
||||
|
||||
/**
|
||||
* @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")
|
||||
val call = getCurrentCall() ?: return
|
||||
if (call.mxCall.state is CallState.LocalRinging) {
|
||||
// accept call
|
||||
call.acceptIncomingCall()
|
||||
}
|
||||
if (call.mxCall.state is CallState.Connected) {
|
||||
|
@@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import java.io.OutputStream
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: BootstrapViewState,
|
||||
@@ -421,7 +422,7 @@ class BootstrapSharedViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(BootstrapViewEvents.RequestReAuth(flowResponse, errCode))
|
||||
}
|
||||
else -> {
|
||||
promise.resumeWith(Result.failure(UnsupportedOperationException()))
|
||||
promise.resumeWithException(UnsupportedOperationException())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -52,6 +52,7 @@ import org.matrix.android.sdk.rx.rx
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
class HomeActivityViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: HomeActivityViewState,
|
||||
@@ -228,7 +229,7 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||
)
|
||||
)
|
||||
} else {
|
||||
promise.resumeWith(Result.failure(Exception("Cannot silently initialize cross signing, UIA missing")))
|
||||
promise.resumeWithException(Exception("Cannot silently initialize cross signing, UIA missing"))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -26,8 +26,8 @@ import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import com.jakewharton.rxrelay2.PublishRelay
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
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.raw.RawService
|
||||
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.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||
@@ -120,7 +121,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
private val directRoomHelper: DirectRoomHelper,
|
||||
timelineSettingsFactory: TimelineSettingsFactory
|
||||
) : 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 eventId = initialState.eventId
|
||||
@@ -176,6 +177,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
// Inform the SDK that the room is displayed
|
||||
session.onRoomDisplayed(initialState.roomId)
|
||||
callManager.addPstnSupportListener(this)
|
||||
callManager.checkForPSTNSupportIfNeeded()
|
||||
chatEffectManager.delegate = this
|
||||
}
|
||||
|
||||
@@ -231,65 +233,65 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
|
||||
override fun handle(action: RoomDetailAction) {
|
||||
when (action) {
|
||||
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
|
||||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||
is RoomDetailAction.SendReaction -> handleSendReaction(action)
|
||||
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
|
||||
is RoomDetailAction.RejectInvite -> handleRejectInvite()
|
||||
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
|
||||
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
|
||||
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
||||
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
|
||||
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
|
||||
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
|
||||
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
|
||||
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
|
||||
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
|
||||
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
||||
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
||||
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
||||
is RoomDetailAction.ResendAll -> handleResendAll()
|
||||
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
||||
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
|
||||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||
is RoomDetailAction.SendReaction -> handleSendReaction(action)
|
||||
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
|
||||
is RoomDetailAction.RejectInvite -> handleRejectInvite()
|
||||
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
|
||||
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
|
||||
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
||||
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
|
||||
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
|
||||
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
|
||||
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
|
||||
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
|
||||
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
|
||||
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
||||
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
||||
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
||||
is RoomDetailAction.ResendAll -> handleResendAll()
|
||||
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
||||
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
|
||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
|
||||
is RoomDetailAction.EndCall -> handleEndCall()
|
||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
||||
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
||||
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
||||
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
|
||||
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
|
||||
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
|
||||
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
|
||||
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
|
||||
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
|
||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
|
||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
|
||||
is RoomDetailAction.EndCall -> handleEndCall()
|
||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
||||
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
||||
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
||||
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
|
||||
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
|
||||
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
|
||||
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
|
||||
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
|
||||
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
|
||||
_viewEvents.post(
|
||||
RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView)
|
||||
)
|
||||
}
|
||||
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
|
||||
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -618,10 +620,10 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
return@withState false
|
||||
}
|
||||
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.invite -> state.canInvite
|
||||
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||
R.id.invite -> state.canInvite
|
||||
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||
R.id.open_matrix_apps -> true
|
||||
R.id.voice_call,
|
||||
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
|
||||
@@ -741,7 +743,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
}
|
||||
is ParsedCommand.SendChatEffect -> {
|
||||
is ParsedCommand.SendChatEffect -> {
|
||||
sendChatEffect(slashCommandResult)
|
||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||
popDraft()
|
||||
@@ -774,7 +776,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
is SendMode.EDIT -> {
|
||||
is SendMode.EDIT -> {
|
||||
// is original event a reply?
|
||||
val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
|
||||
if (inReplyTo != null) {
|
||||
@@ -799,7 +801,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is SendMode.QUOTE -> {
|
||||
is SendMode.QUOTE -> {
|
||||
val messageContent: MessageContent? =
|
||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
@@ -822,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
popDraft()
|
||||
}
|
||||
is SendMode.REPLY -> {
|
||||
is SendMode.REPLY -> {
|
||||
state.sendMode.timelineEvent.let {
|
||||
room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
|
||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||
@@ -1441,7 +1443,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onPSTNSupportUpdated() {
|
||||
updateShowDialerOptionState()
|
||||
updateShowDialerOptionState()
|
||||
}
|
||||
|
||||
private fun updateShowDialerOptionState() {
|
||||
|
@@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
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.item.BaseEventItem
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@@ -47,8 +47,8 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager,
|
||||
if (layoutManager.findFirstVisibleItemPosition() != position) {
|
||||
return
|
||||
}
|
||||
val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? BaseEventItem ?: return
|
||||
val firstNewItemIds = firstNewItem.getEventIds().firstOrNull()
|
||||
val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? ItemWithEvents ?: return
|
||||
val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() ?: return
|
||||
val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds)
|
||||
if (indexOfFirstNewItem != -1) {
|
||||
Timber.v("Should scroll to position: $position")
|
||||
|
@@ -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.helper.ContentDownloadStateTrackerBinder
|
||||
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.TimelineEventVisibilityStateChangedListener
|
||||
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.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.MessageInformationData
|
||||
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.media.ImageContentRenderer
|
||||
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 {
|
||||
addInterceptor(this)
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
// Update position when we are building new items
|
||||
override fun intercept(models: MutableList<EpoxyModel<*>>) = synchronized(modelCache) {
|
||||
positionOfReadMarker = null
|
||||
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
|
||||
interceptorHelper.intercept(models, unreadState, timeline, callback)
|
||||
}
|
||||
|
||||
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?) {
|
||||
if (vectorPreferences.labShowCompleteHistoryInEncryptedRoom()) {
|
||||
return
|
||||
@@ -461,14 +411,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||
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) {
|
||||
return adapterPositionMapping[eventId]
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@
|
||||
|
||||
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.resources.UserPreferencesProvider
|
||||
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")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -32,7 +32,7 @@ import im.vector.app.core.utils.DimensionConverter
|
||||
/**
|
||||
* 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
|
||||
@EpoxyAttribute
|
||||
@@ -53,12 +53,6 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
|
||||
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() {
|
||||
val leftGuideline by bind<View>(R.id.messageStartGuideline)
|
||||
val checkableBackground by bind<CheckableView>(R.id.messageSelectedBackground)
|
||||
|
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,12 +14,12 @@
|
||||
* 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
|
||||
import im.vector.app.R
|
||||
|
||||
@EpoxyModelClass(layout = R.layout.item_empty)
|
||||
abstract class EmptyItem : VectorEpoxyModel<EmptyItem.Holder>() {
|
||||
class Holder : VectorEpoxyHolder()
|
||||
interface ItemWithEvents {
|
||||
/**
|
||||
* Returns the eventIds associated with the EventItem.
|
||||
* Will generally get only one, but it handles the merged items.
|
||||
*/
|
||||
fun getEventIds(): List<String>
|
||||
}
|
@@ -19,6 +19,7 @@ package im.vector.app.features.login
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
@@ -27,8 +28,8 @@ import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.configureAndStart
|
||||
@@ -37,7 +38,8 @@ import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.ensureTrailingSlash
|
||||
import im.vector.app.features.signout.soft.SoftLogoutActivity
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
@@ -51,7 +53,6 @@ import org.matrix.android.sdk.api.auth.registration.Stage
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
@@ -117,7 +118,12 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
private var loginConfig: LoginConfig? = null
|
||||
|
||||
private var currentTask: Cancelable? = null
|
||||
private var currentJob: Job? = null
|
||||
set(value) {
|
||||
// Cancel any previous Job
|
||||
field?.cancel()
|
||||
field = value
|
||||
}
|
||||
|
||||
override fun handle(action: LoginAction) {
|
||||
when (action) {
|
||||
@@ -140,7 +146,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) {
|
||||
// It happen when we get the login flow, or during direct authentication.
|
||||
// It happens when we get the login flow, or during direct authentication.
|
||||
// So alter the homeserver config and retrieve again the login flow
|
||||
when (val finalLastAction = lastAction) {
|
||||
is LoginAction.UpdateHomeServer -> {
|
||||
@@ -186,22 +192,20 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.loginWithToken(
|
||||
action.loginToken,
|
||||
object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
onSessionCreated(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.loginWithToken(action.loginToken)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
?.let { onSessionCreated(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,46 +235,49 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) {
|
||||
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
|
||||
currentTask?.cancel()
|
||||
currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback)
|
||||
currentJob = executeRegistrationStep(withLoading = false) {
|
||||
it.checkIfEmailHasBeenValidated(action.delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStopEmailValidationCheck() {
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
currentJob = null
|
||||
}
|
||||
|
||||
private fun handleValidateThreePid(action: LoginAction.ValidateThreePid) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.handleValidateThreePid(action.code, registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.handleValidateThreePid(action.code)
|
||||
}
|
||||
}
|
||||
|
||||
private val registrationCallback = object : MatrixCallback<RegistrationResult> {
|
||||
override fun onSuccess(data: RegistrationResult) {
|
||||
/*
|
||||
// Simulate registration disabled
|
||||
onFailure(Failure.ServerError(MatrixError(
|
||||
code = MatrixError.FORBIDDEN,
|
||||
message = "Registration is disabled"
|
||||
), 403))
|
||||
*/
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
|
||||
when (data) {
|
||||
is RegistrationResult.Success -> onSessionCreated(data.session)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
|
||||
}
|
||||
private fun executeRegistrationStep(withLoading: Boolean = true,
|
||||
block: suspend (RegistrationWizard) -> RegistrationResult): Job {
|
||||
if (withLoading) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
if (failure !is CancellationException) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
return viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.let { block(it) }
|
||||
/*
|
||||
// Simulate registration disabled
|
||||
throw Failure.ServerError(MatrixError(
|
||||
code = MatrixError.FORBIDDEN,
|
||||
message = "Registration is disabled"
|
||||
), 403))
|
||||
*/
|
||||
} catch (failure: Throwable) {
|
||||
if (failure !is CancellationException) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
}
|
||||
null
|
||||
}
|
||||
?.let { data ->
|
||||
when (data) {
|
||||
is RegistrationResult.Success -> onSessionCreated(data.session)
|
||||
is RegistrationResult.FlowResponse -> onFlowResponse(data.flowResult)
|
||||
}
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
@@ -281,78 +288,68 @@ class LoginViewModel @AssistedInject constructor(
|
||||
|
||||
private fun handleAddThreePid(action: LoginAction.AddThreePid) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.addThreePid(action.threePid, object : MatrixCallback<RegistrationResult> {
|
||||
override fun onSuccess(data: RegistrationResult) {
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.addThreePid(action.threePid)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendAgainThreePid() {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.sendAgainThreePid(object : MatrixCallback<RegistrationResult> {
|
||||
override fun onSuccess(data: RegistrationResult) {
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
registrationWizard?.sendAgainThreePid()
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAcceptTerms() {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.acceptTerms(registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.acceptTerms()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterDummy() {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.dummy(registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.dummy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRegisterWith(action: LoginAction.LoginOrRegister) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
reAuthHelper.data = action.password
|
||||
currentTask = registrationWizard?.createAccount(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
registrationCallback
|
||||
)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.createAccount(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCaptchaDone(action: LoginAction.CaptchaDone) {
|
||||
setState { copy(asyncRegistration = Loading()) }
|
||||
currentTask = registrationWizard?.performReCaptcha(action.captchaResponse, registrationCallback)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.performReCaptcha(action.captchaResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResetAction(action: LoginAction.ResetAction) {
|
||||
// Cancel any request
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
currentJob = null
|
||||
|
||||
when (action) {
|
||||
LoginAction.ResetHomeServerType -> {
|
||||
@@ -363,16 +360,17 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
LoginAction.ResetHomeServerUrl -> {
|
||||
authenticationService.reset()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = null,
|
||||
loginMode = LoginMode.Unknown,
|
||||
serverType = ServerType.Unknown,
|
||||
loginModeSupportedTypes = emptyList()
|
||||
)
|
||||
viewModelScope.launch {
|
||||
authenticationService.reset()
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = null,
|
||||
loginMode = LoginMode.Unknown,
|
||||
serverType = ServerType.Unknown,
|
||||
loginModeSupportedTypes = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
LoginAction.ResetSignMode -> {
|
||||
@@ -386,13 +384,14 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
LoginAction.ResetLogin -> {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized,
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized,
|
||||
asyncRegistration = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
LoginAction.ResetResetPassword -> {
|
||||
@@ -473,26 +472,27 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.resetPassword(action.email, action.newPassword, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Success(data),
|
||||
resetPasswordEmail = action.email
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.resetPassword(action.email, action.newPassword)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Fail(failure)
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
})
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Success(Unit),
|
||||
resetPasswordEmail = action.email
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordSendThreePidDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,26 +514,26 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.resetPasswordMailConfirmed(object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Success(data),
|
||||
resetPasswordEmail = null
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.resetPasswordMailConfirmed()
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Fail(failure)
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
})
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Success(Unit),
|
||||
resetPasswordEmail = null
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,36 +553,36 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback<WellknownResult> {
|
||||
override fun onSuccess(data: WellknownResult) {
|
||||
when (data) {
|
||||
is WellknownResult.Prompt ->
|
||||
onWellknownSuccess(action, data, homeServerConnectionConfig)
|
||||
is WellknownResult.FailPrompt ->
|
||||
// Relax on IS discovery if home server is valid
|
||||
if (data.homeServerUrl != null && data.wellKnown != null) {
|
||||
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
|
||||
} else {
|
||||
onWellKnownError()
|
||||
}
|
||||
is WellknownResult.InvalidMatrixId -> {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized
|
||||
)
|
||||
}
|
||||
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
|
||||
}
|
||||
else -> {
|
||||
currentJob = viewModelScope.launch {
|
||||
val data = try {
|
||||
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig)
|
||||
} catch (failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
return@launch
|
||||
}
|
||||
when (data) {
|
||||
is WellknownResult.Prompt ->
|
||||
onWellknownSuccess(action, data, homeServerConnectionConfig)
|
||||
is WellknownResult.FailPrompt ->
|
||||
// Relax on IS discovery if home server is valid
|
||||
if (data.homeServerUrl != null && data.wellKnown != null) {
|
||||
onWellknownSuccess(action, WellknownResult.Prompt(data.homeServerUrl!!, null, data.wellKnown!!), homeServerConnectionConfig)
|
||||
} else {
|
||||
onWellKnownError()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
}
|
||||
})
|
||||
is WellknownResult.InvalidMatrixId -> {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Uninitialized
|
||||
)
|
||||
}
|
||||
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
|
||||
}
|
||||
else -> {
|
||||
onWellKnownError()
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
private fun onWellKnownError() {
|
||||
@@ -594,9 +594,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
|
||||
}
|
||||
|
||||
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
|
||||
wellKnownPrompt: WellknownResult.Prompt,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||
private suspend fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
|
||||
wellKnownPrompt: WellknownResult.Prompt,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
|
||||
?.copy(
|
||||
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||
@@ -607,20 +607,17 @@ class LoginViewModel @AssistedInject constructor(
|
||||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||
)
|
||||
|
||||
authenticationService.directAuthentication(
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
onSessionCreated(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
}
|
||||
})
|
||||
val data = try {
|
||||
authenticationService.directAuthentication(
|
||||
alteredHomeServerConnectionConfig,
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName)
|
||||
} catch (failure: Throwable) {
|
||||
onDirectLoginError(failure)
|
||||
return
|
||||
}
|
||||
onSessionCreated(data)
|
||||
}
|
||||
|
||||
private fun onDirectLoginError(failure: Throwable) {
|
||||
@@ -657,35 +654,33 @@ class LoginViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = safeLoginWizard.login(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName,
|
||||
object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.login(
|
||||
action.username,
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
?.let {
|
||||
reAuthHelper.data = action.password
|
||||
onSessionCreated(data)
|
||||
onSessionCreated(it)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncLoginAction = Fail(failure)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startRegistrationFlow() {
|
||||
setState {
|
||||
copy(
|
||||
asyncRegistration = Loading()
|
||||
)
|
||||
currentJob = executeRegistrationStep {
|
||||
it.getRegistrationFlow()
|
||||
}
|
||||
|
||||
currentTask = registrationWizard?.getRegistrationFlow(registrationCallback)
|
||||
}
|
||||
|
||||
private fun startAuthenticationFlow() {
|
||||
@@ -706,8 +701,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSessionCreated(session: Session) {
|
||||
private suspend fun onSessionCreated(session: Session) {
|
||||
activeSessionHolder.setActiveSession(session)
|
||||
|
||||
authenticationService.reset()
|
||||
session.configureAndStart(applicationContext)
|
||||
setState {
|
||||
@@ -724,15 +720,17 @@ class LoginViewModel @AssistedInject constructor(
|
||||
// Should not happen
|
||||
Timber.w("homeServerConnectionConfig is null")
|
||||
} else {
|
||||
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials, object : MatrixCallback<Session> {
|
||||
override fun onSuccess(data: Session) {
|
||||
onSessionCreated(data)
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(asyncLoginAction = Fail(failure))
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) = setState {
|
||||
copy(asyncLoginAction = Fail(failure))
|
||||
}
|
||||
})
|
||||
?.let { onSessionCreated(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,21 +747,21 @@ class LoginViewModel @AssistedInject constructor(
|
||||
private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) {
|
||||
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
||||
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
currentJob = viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading(),
|
||||
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
|
||||
// It is also useful to set the value again in the case of a certificate error on matrix.org
|
||||
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
|
||||
)
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading(),
|
||||
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
|
||||
// It is also useful to set the value again in the case of a certificate error on matrix.org
|
||||
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
val data = try {
|
||||
authenticationService.getLoginFlow(homeServerConnectionConfig)
|
||||
} catch (failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
@@ -772,47 +770,39 @@ class LoginViewModel @AssistedInject constructor(
|
||||
serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
if (data is LoginFlowResult.Success) {
|
||||
// Valid Homeserver, add it to the history.
|
||||
// Note: we add what the user has input, data.homeServerUrl can be different
|
||||
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
|
||||
|
||||
when (data) {
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
// FIXME We should post a view event here normally?
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|
||||
|| data.isOutdatedHomeserver) {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
}
|
||||
}
|
||||
// FIXME We should post a view event here normally?
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|
||||
|| data.isOutdatedHomeserver) {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
currentTask?.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
||||
fun getInitialHomeServerUrl(): String? {
|
||||
|
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.rageshake
|
||||
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.logging.Formatter
|
||||
import java.util.logging.LogRecord
|
||||
|
||||
class LogFormatter : Formatter() {
|
||||
|
||||
override fun format(r: LogRecord): String {
|
||||
if (!mIsTimeZoneSet) {
|
||||
DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")
|
||||
mIsTimeZoneSet = true
|
||||
}
|
||||
|
||||
val thrown = r.thrown
|
||||
if (thrown != null) {
|
||||
val sw = StringWriter()
|
||||
val pw = PrintWriter(sw)
|
||||
sw.write(r.message)
|
||||
sw.write(LINE_SEPARATOR)
|
||||
thrown.printStackTrace(pw)
|
||||
pw.flush()
|
||||
return sw.toString()
|
||||
} else {
|
||||
val b = StringBuilder()
|
||||
val date = DATE_FORMAT.format(Date(r.millis))
|
||||
b.append(date)
|
||||
b.append("Z ")
|
||||
b.append(r.message)
|
||||
b.append(LINE_SEPARATOR)
|
||||
return b.toString()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
|
||||
|
||||
// private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US)
|
||||
|
||||
private var mIsTimeZoneSet = false
|
||||
}
|
||||
}
|
@@ -22,45 +22,41 @@ import im.vector.app.features.settings.VectorPreferences
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.logging.FileHandler
|
||||
import java.util.logging.Formatter
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.LogRecord
|
||||
import java.util.logging.Logger
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val SIZE_20MB = 20 * 1024 * 1024
|
||||
private const val SIZE_50MB = 50 * 1024 * 1024
|
||||
|
||||
@Singleton
|
||||
class VectorFileLogger @Inject constructor(val context: Context, private val vectorPreferences: VectorPreferences) : Timber.Tree() {
|
||||
class VectorFileLogger @Inject constructor(
|
||||
context: Context,
|
||||
private val vectorPreferences: VectorPreferences
|
||||
) : Timber.Tree() {
|
||||
|
||||
private val maxLogSizeByte: Int
|
||||
private val logRotationCount: Int
|
||||
companion object {
|
||||
private const val SIZE_20MB = 20 * 1024 * 1024
|
||||
private const val SIZE_50MB = 50 * 1024 * 1024
|
||||
}
|
||||
|
||||
init {
|
||||
if (vectorPreferences.labAllowedExtendedLogging()) {
|
||||
maxLogSizeByte = SIZE_50MB
|
||||
logRotationCount = 15
|
||||
} else {
|
||||
maxLogSizeByte = SIZE_20MB
|
||||
logRotationCount = 7
|
||||
private val maxLogSizeByte = if (vectorPreferences.labAllowedExtendedLogging()) SIZE_50MB else SIZE_20MB
|
||||
private val logRotationCount = if (vectorPreferences.labAllowedExtendedLogging()) 15 else 7
|
||||
|
||||
private val logger = Logger.getLogger(context.packageName).apply {
|
||||
tryOrNull {
|
||||
useParentHandlers = false
|
||||
level = Level.ALL
|
||||
}
|
||||
}
|
||||
|
||||
private val sLogger = Logger.getLogger("im.vector.app")
|
||||
private var sFileHandler: FileHandler? = null
|
||||
private var sCacheDirectory: File? = null
|
||||
private var sFileName = "elementLogs"
|
||||
private val fileHandler: FileHandler?
|
||||
private val cacheDirectory = File(context.cacheDir, "logs")
|
||||
private var fileNamePrefix = "logs"
|
||||
|
||||
private val prioPrefixes = mapOf(
|
||||
Log.VERBOSE to "V/ ",
|
||||
@@ -72,24 +68,29 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
|
||||
)
|
||||
|
||||
init {
|
||||
val logsDirectoryFile = context.cacheDir.absolutePath + "/logs"
|
||||
setLogDirectory(File(logsDirectoryFile))
|
||||
try {
|
||||
if (sCacheDirectory != null) {
|
||||
sFileHandler = FileHandler(sCacheDirectory!!.absolutePath + "/" + sFileName + ".%g.txt", maxLogSizeByte, logRotationCount)
|
||||
sFileHandler?.formatter = LogFormatter()
|
||||
sLogger.useParentHandlers = false
|
||||
sLogger.level = Level.ALL
|
||||
sFileHandler?.let { sLogger.addHandler(it) }
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Failed to initialize FileLogger")
|
||||
if (!cacheDirectory.exists()) {
|
||||
cacheDirectory.mkdirs()
|
||||
}
|
||||
|
||||
for (i in 0..15) {
|
||||
val file = File(cacheDirectory, "elementLogs.$i.txt")
|
||||
tryOrNull { file.delete() }
|
||||
}
|
||||
|
||||
fileHandler = tryOrNull("Failed to initialize FileLogger") {
|
||||
FileHandler(
|
||||
cacheDirectory.absolutePath + "/" + fileNamePrefix + ".%g.txt",
|
||||
maxLogSizeByte,
|
||||
logRotationCount
|
||||
)
|
||||
.also { it.formatter = LogFormatter() }
|
||||
.also { logger.addHandler(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
fileHandler ?: return
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
if (sFileHandler == null) return@launch
|
||||
if (skipLog(priority)) return@launch
|
||||
if (t != null) {
|
||||
logToFile(t)
|
||||
@@ -107,84 +108,22 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the directory to put log files.
|
||||
*
|
||||
* @param cacheDir The directory, usually [android.content.ContextWrapper.getCacheDir]
|
||||
*/
|
||||
private fun setLogDirectory(cacheDir: File) {
|
||||
if (!cacheDir.exists()) {
|
||||
cacheDir.mkdirs()
|
||||
}
|
||||
sCacheDirectory = cacheDir
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds our own log files to the provided list of files.
|
||||
*
|
||||
* @param files The list of files to add to.
|
||||
* @return The same list with more files added.
|
||||
* @return The list of files with logs.
|
||||
*/
|
||||
fun getLogFiles(): List<File> {
|
||||
val files = ArrayList<File>()
|
||||
|
||||
try {
|
||||
// reported by GA
|
||||
if (null != sFileHandler) {
|
||||
sFileHandler!!.flush()
|
||||
val absPath = sCacheDirectory?.absolutePath ?: return emptyList()
|
||||
|
||||
for (i in 0..logRotationCount) {
|
||||
val filepath = "$absPath/$sFileName.$i.txt"
|
||||
val file = File(filepath)
|
||||
if (file.exists()) {
|
||||
files.add(file)
|
||||
return tryOrNull("## getLogFiles() failed") {
|
||||
fileHandler
|
||||
?.flush()
|
||||
?.let { 0 until logRotationCount }
|
||||
?.mapNotNull { index ->
|
||||
File(cacheDirectory, "$fileNamePrefix.$index.txt")
|
||||
.takeIf { it.exists() }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## addLogFiles() failed")
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
class LogFormatter : Formatter() {
|
||||
|
||||
override fun format(r: LogRecord): String {
|
||||
if (!mIsTimeZoneSet) {
|
||||
DATE_FORMAT.timeZone = TimeZone.getTimeZone("UTC")
|
||||
mIsTimeZoneSet = true
|
||||
}
|
||||
|
||||
val thrown = r.thrown
|
||||
if (thrown != null) {
|
||||
val sw = StringWriter()
|
||||
val pw = PrintWriter(sw)
|
||||
sw.write(r.message)
|
||||
sw.write(LINE_SEPARATOR)
|
||||
thrown.printStackTrace(pw)
|
||||
pw.flush()
|
||||
return sw.toString()
|
||||
} else {
|
||||
val b = StringBuilder()
|
||||
val date = DATE_FORMAT.format(Date(r.millis))
|
||||
b.append(date)
|
||||
b.append("Z ")
|
||||
b.append(r.message)
|
||||
b.append(LINE_SEPARATOR)
|
||||
return b.toString()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LINE_SEPARATOR = System.getProperty("line.separator") ?: "\n"
|
||||
|
||||
// private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
|
||||
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss*SSSZZZZ", Locale.US)
|
||||
|
||||
private var mIsTimeZoneSet = false
|
||||
}
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,20 +132,15 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
|
||||
* @param throwable the throwable to log
|
||||
*/
|
||||
private fun logToFile(throwable: Throwable?) {
|
||||
if (null == sCacheDirectory || throwable == null) {
|
||||
return
|
||||
}
|
||||
throwable ?: return
|
||||
|
||||
val errors = StringWriter()
|
||||
throwable.printStackTrace(PrintWriter(errors))
|
||||
|
||||
sLogger.info(errors.toString())
|
||||
logger.info(errors.toString())
|
||||
}
|
||||
|
||||
private fun logToFile(level: String, tag: String, content: String) {
|
||||
if (null == sCacheDirectory) {
|
||||
return
|
||||
}
|
||||
val b = StringBuilder()
|
||||
b.append(Thread.currentThread().id)
|
||||
b.append(" ")
|
||||
@@ -215,6 +149,6 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
|
||||
b.append(tag)
|
||||
b.append(": ")
|
||||
b.append(content)
|
||||
sLogger.info(b.toString())
|
||||
logger.info(b.toString())
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
data class DeactivateAccountViewState(
|
||||
val passwordShown: Boolean = false
|
||||
@@ -64,7 +65,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
|
||||
if (pendingAuth != null) {
|
||||
uiaContinuation?.resume(pendingAuth!!)
|
||||
} else {
|
||||
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
|
||||
uiaContinuation?.resumeWithException(IllegalArgumentException())
|
||||
}
|
||||
}
|
||||
is DeactivateAccountAction.PasswordAuthDone -> {
|
||||
@@ -79,7 +80,7 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
|
||||
}
|
||||
DeactivateAccountAction.ReAuthCancelled -> {
|
||||
Timber.d("## UIA - Reauth cancelled")
|
||||
uiaContinuation?.resumeWith(Result.failure((Exception())))
|
||||
uiaContinuation?.resumeWithException(Exception())
|
||||
uiaContinuation = null
|
||||
pendingAuth = null
|
||||
}
|
||||
@@ -98,13 +99,15 @@ class DeactivateAccountViewModel @AssistedInject constructor(@Assisted private v
|
||||
viewModelScope.launch {
|
||||
val event = try {
|
||||
session.deactivateAccount(
|
||||
action.eraseAllData,
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
_viewEvents.post(DeactivateAccountViewEvents.RequestReAuth(flowResponse, errCode))
|
||||
pendingAuth = DefaultBaseAuth(session = flowResponse.session)
|
||||
uiaContinuation = promise
|
||||
}
|
||||
}, action.eraseAllData)
|
||||
}
|
||||
)
|
||||
DeactivateAccountViewEvents.Done
|
||||
} catch (failure: Exception) {
|
||||
if (failure.isInvalidUIAAuth()) {
|
||||
|
@@ -49,6 +49,7 @@ import org.matrix.android.sdk.rx.rx
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
class CrossSigningSettingsViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: CrossSigningSettingsViewState,
|
||||
@@ -130,7 +131,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
||||
if (pendingAuth != null) {
|
||||
uiaContinuation?.resume(pendingAuth!!)
|
||||
} else {
|
||||
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
|
||||
uiaContinuation?.resumeWithException(IllegalArgumentException())
|
||||
}
|
||||
}
|
||||
is CrossSigningSettingsAction.PasswordAuthDone -> {
|
||||
@@ -146,7 +147,7 @@ class CrossSigningSettingsViewModel @AssistedInject constructor(
|
||||
CrossSigningSettingsAction.ReAuthCancelled -> {
|
||||
Timber.d("## UIA - Reauth cancelled")
|
||||
_viewEvents.post(CrossSigningSettingsViewEvents.HideModalWaitingView)
|
||||
uiaContinuation?.resumeWith(Result.failure((Exception())))
|
||||
uiaContinuation?.resumeWithException(Exception())
|
||||
uiaContinuation = null
|
||||
pendingAuth = null
|
||||
}
|
||||
|
@@ -64,6 +64,7 @@ import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
data class DevicesViewState(
|
||||
val myDeviceId: String = "",
|
||||
@@ -217,7 +218,7 @@ class DevicesViewModel @AssistedInject constructor(
|
||||
if (pendingAuth != null) {
|
||||
uiaContinuation?.resume(pendingAuth!!)
|
||||
} else {
|
||||
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
|
||||
uiaContinuation?.resumeWithException(IllegalArgumentException())
|
||||
}
|
||||
Unit
|
||||
}
|
||||
@@ -235,7 +236,7 @@ class DevicesViewModel @AssistedInject constructor(
|
||||
DevicesAction.ReAuthCancelled -> {
|
||||
Timber.d("## UIA - Reauth cancelled")
|
||||
// _viewEvents.post(DevicesViewEvents.Loading)
|
||||
uiaContinuation?.resumeWith(Result.failure((Exception())))
|
||||
uiaContinuation?.resumeWithException(Exception())
|
||||
uiaContinuation = null
|
||||
pendingAuth = null
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.rx.rx
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
class ThreePidsSettingsViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: ThreePidsSettingsViewState,
|
||||
@@ -140,7 +141,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
|
||||
if (pendingAuth != null) {
|
||||
uiaContinuation?.resume(pendingAuth!!)
|
||||
} else {
|
||||
uiaContinuation?.resumeWith(Result.failure((IllegalArgumentException())))
|
||||
uiaContinuation?.resumeWithException(IllegalArgumentException())
|
||||
}
|
||||
}
|
||||
is ThreePidsSettingsAction.PasswordAuthDone -> {
|
||||
@@ -155,7 +156,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor(
|
||||
}
|
||||
ThreePidsSettingsAction.ReAuthCancelled -> {
|
||||
Timber.d("## UIA - Reauth cancelled")
|
||||
uiaContinuation?.resumeWith(Result.failure((Exception())))
|
||||
uiaContinuation?.resumeWithException(Exception())
|
||||
uiaContinuation = null
|
||||
pendingAuth = null
|
||||
}
|
||||
|
@@ -25,19 +25,17 @@ import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.extensions.hasUnsavedKeys
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.login.LoginMode
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@@ -76,54 +74,49 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private var currentTask: Cancelable? = null
|
||||
|
||||
init {
|
||||
// Get the supported login flow
|
||||
getSupportedLoginFlow()
|
||||
}
|
||||
|
||||
private fun getSupportedLoginFlow() {
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
viewModelScope.launch {
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
)
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = authenticationService.getLoginFlowOfSession(session.sessionId, object : MatrixCallback<LoginFlowResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
val data = try {
|
||||
authenticationService.getLoginFlowOfSession(session.sessionId)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Fail(failure)
|
||||
)
|
||||
}
|
||||
null
|
||||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
when (data) {
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
if (data is LoginFlowResult.Success) {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
|
||||
&& data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Success(loginMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Success(loginMode)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun handle(action: SoftLogoutAction) {
|
||||
@@ -227,9 +220,4 @@ class SoftLogoutViewModel @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
currentTask?.cancel()
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
@@ -81,17 +81,19 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/loginSignupSigninSubmit"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<!-- Social Logins buttons -->
|
||||
<!-- Social Login buttons -->
|
||||
<LinearLayout
|
||||
android:id="@+id/loginSignupSigninSignInSocialLoginContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn">
|
||||
app:layout_constraintTop_toBottomOf="@id/loginSignupSigninSignIn"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loginSignupSigninSocialLoginHeader"
|
||||
|
@@ -1,55 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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 -->
|
||||
<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" />
|
||||
<!-- default text colors -->
|
||||
<attr name="vctr_default_text_hint_color" format="color" />
|
||||
|
||||
<!-- default text colors -->
|
||||
<attr name="vctr_default_text_hint_color" format="color" />
|
||||
<!-- room message colors -->
|
||||
<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 -->
|
||||
<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" />
|
||||
<!-- tab bar colors -->
|
||||
<attr name="vctr_tab_bar_inverted_background_color" format="color" />
|
||||
|
||||
<!-- tab bar colors -->
|
||||
<attr name="vctr_tab_bar_inverted_background_color" format="color" />
|
||||
<!-- list colors -->
|
||||
<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_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" />
|
||||
|
||||
<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 -->
|
||||
<attr name="vctr_pending_outgoing_view_background_color" format="color" />
|
||||
<!-- room notification text color (typing, unsent...) -->
|
||||
<attr name="vctr_room_notification_text_color" format="color" />
|
||||
|
||||
<!-- room notification text color (typing, unsent...) -->
|
||||
<attr name="vctr_room_notification_text_color" format="color" />
|
||||
<!-- icon colors -->
|
||||
<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_icon_tint_on_light_action_bar_color" format="color" />
|
||||
<attr name="vctr_settings_icon_tint_color" format="color" />
|
||||
<attr name="vctr_social_login_button_google_style" format="reference" />
|
||||
<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_social_login_button_google_style" format="reference" />
|
||||
<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>
|
||||
<attr name="vctr_chat_effect_snow_background" format="color" />
|
||||
|
||||
<declare-styleable name="PollResultLineView">
|
||||
<attr name="optionName" format="string" localization="suggested" />
|
||||
@@ -70,16 +67,16 @@
|
||||
|
||||
<declare-styleable name="SignOutBottomSheetActionButton">
|
||||
<attr name="iconTint" format="color" />
|
||||
<attr name="actionTitle"/>
|
||||
<attr name="actionTitle" />
|
||||
<attr name="leftIcon" />
|
||||
<attr name="textColor" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SocialLoginButtonsView">
|
||||
<attr name="signMode" format="enum">
|
||||
<enum name="signin" value="0"/>
|
||||
<enum name="signup" value="1"/>
|
||||
<enum name="continue_with" value="2"/>
|
||||
<enum name="signin" value="0" />
|
||||
<enum name="signup" value="1" />
|
||||
<enum name="continue_with" value="2" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
@@ -1,7 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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 -->
|
||||
<color name="riotx_accent">#FF0DBD8B</color>
|
||||
@@ -38,7 +146,7 @@
|
||||
<color name="riotx_username_7">#5c56f5</color>
|
||||
<color name="riotx_username_8">#74d12c</color>
|
||||
|
||||
<!-- Other usefull color -->
|
||||
<!-- Other useful color -->
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</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_dark">#22262E</color>
|
||||
|
||||
|
||||
|
||||
</resources>
|
||||
</resources>
|
@@ -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>
|
Reference in New Issue
Block a user