Merge remote-tracking branch 'upstream/develop' into rust

This commit is contained in:
Damir Jelić 2021-04-20 14:44:02 +02:00
commit 324cdc4db1
63 changed files with 947 additions and 397 deletions

View file

@ -1,9 +1,47 @@
Changes in Element 1.1.4 (2021-XX-XX) Changes in Element 1.1.7 (2021-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
- -
Improvements 🙌:
-
Bugfix 🐛:
- Message states cosmetic changes (#3007)
Translations 🗣:
-
SDK API changes ⚠️:
-
Build 🧱:
- Upgrade to gradle 7
Test:
-
Other changes:
- New store descriptions
Changes in Element 1.1.6 (2021-04-16)
===================================================
Bugfix 🐛:
- Fix crash on the timeline
- App crashes on "troubleshoot notifications" button (#3187)
Changes in Element 1.1.5 (2021-04-15)
===================================================
Bugfix 🐛:
- Fix crash during Realm migration
- Fix crash when playing video (#3179)
Changes in Element 1.1.4 (2021-04-09)
===================================================
Improvements 🙌: Improvements 🙌:
- Split network request `/keys/query` into smaller requests (250 users max) (#2925) - Split network request `/keys/query` into smaller requests (250 users max) (#2925)
- Crypto improvement | Bulk send NO_OLM withheld code - Crypto improvement | Bulk send NO_OLM withheld code
@ -18,6 +56,8 @@ Improvements 🙌:
- Room list improvements (paging) - Room list improvements (paging)
- Fix quick click action (#3127) - Fix quick click action (#3127)
- Get Event after a Push for a faster notification display in some conditions - Get Event after a Push for a faster notification display in some conditions
- Always try to retry Http requests in case of 429 (#1300)
- registration availability endpoint added to matrix-sdk
Bugfix 🐛: Bugfix 🐛:
- Fix bad theme change for the MainActivity - Fix bad theme change for the MainActivity
@ -26,9 +66,7 @@ Bugfix 🐛:
- Fix avatar rendering for DMs, after initial sync (#2693) - Fix avatar rendering for DMs, after initial sync (#2693)
- Fix mandatory parameter in API (#3065) - Fix mandatory parameter in API (#3065)
- If signout request fails, do not start LoginActivity, but restart the app (#3099) - If signout request fails, do not start LoginActivity, but restart the app (#3099)
- Retain keyword order in emoji import script, and update the generated file (#3147)
Translations 🗣:
-
SDK API changes ⚠️: SDK API changes ⚠️:
- Several Services have been migrated to coroutines (#2449) - Several Services have been migrated to coroutines (#2449)
@ -37,9 +75,6 @@ SDK API changes ⚠️:
Build 🧱: Build 🧱:
- Properly exclude gms dependencies in fdroid build flavour which were pulled in through the jitsi SDK (#3125) - Properly exclude gms dependencies in fdroid build flavour which were pulled in through the jitsi SDK (#3125)
Test:
-
Other changes: Other changes:
- Add version details on the login screen, in debug or developer mode - Add version details on the login screen, in debug or developer mode
- Migrate Retrofit interface to coroutine calls - Migrate Retrofit interface to coroutine calls

View file

@ -69,7 +69,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01" implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
} }

View file

@ -17,7 +17,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3'
classpath "com.likethesalad.android:string-reference:1.2.1" classpath "com.likethesalad.android:string-reference:1.2.2"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -51,7 +51,7 @@ allprojects {
} }
} }
maven { maven {
url "http://dl.bintray.com/piasy/maven" url "https://dl.bintray.com/piasy/maven"
content { content {
includeGroupByRegex "com\\.github\\.piasy" includeGroupByRegex "com\\.github\\.piasy"
} }

View file

@ -0,0 +1,2 @@
Main changes in this version: performance improvement and bug fixes!
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.4

View file

@ -0,0 +1,2 @@
Main changes in this version: hot fixes for 1.1.4
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.5

View file

@ -0,0 +1,2 @@
Main changes in this version: hot fixes for 1.1.5
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.6

View file

@ -1,30 +1,39 @@
Element is a new type of messenger and collaboration app that: Element is both a secure messenger and a productivity team collaboration app that is ideal for group chats while remote working. This chat app uses end-to-end encryption to provide powerful video conferencing, file sharing and voice calls.
1. Puts you in control to preserve your privacy <b>Elements features include:</b>
2. Lets you communicate with anyone in the Matrix network, and even beyond by integrating with apps such as Slack - Advanced online communication tools
3. Protects you from advertising, datamining and walled gardens - Fully encrypted messages to allow safer corporate communication, even for remote workers
4. Secures you through end-to-end encryption, with cross-signing to verify others - Decentralized chat based on the Matrix open source framework
- File sharing securely with encrypted data while managing projects
- Video chats with Voice over IP and screen sharing
- Easy integration with your favourite online collaboration tools, project management tools, VoIP services and other team messaging apps
Element is completely different from other messaging and collaboration apps because it is decentralised and open source. Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages.
Element lets you self-host - or choose a host - so that you have privacy, ownership and control of your data and conversations. It gives you access to an open network; so youre not just stuck speaking to other Element users only. And it is very secure. <b>Privacy and encrypted messaging</b>
Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification.
Element is able to do all this because it operates on Matrix - the standard for open, decentralised communication. Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack.
Element puts you in control by letting you choose who hosts your conversations. From the Element app, you can choose to host in different ways: <b>Element can be self-hosted</b>
To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility.
<b>Own your data</b>
You decide where to keep your data and messages. Without the risk of data mining or access from third parties.
Element puts you in control in different ways:
1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers 1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers
2. Self-host your account by running a server on your own hardware 2. Self-host your account by running a server on your own IT infrastructure
3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform 3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform
<b>Why choose Element?</b> <b>Open messaging and collaboration</b>
You can chat with anyone on the Matrix network, whether theyre using Element, another Matrix app or even if they are using a different messaging app.
<b>OWN YOUR DATA</b>: You decide where to keep your data and messages. You own it and control it, not some MEGACORP that mines your data or gives access to third parties. <b>Super secure</b>
Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signed device verification.
<b>OPEN MESSAGING AND COLLABORATION</b>: You can chat with anyone else in the Matrix network, whether theyre using Element or another Matrix app, and even if they are using a different messaging system of the likes of Slack, IRC or XMPP. <b>Complete communication and integration</b>
Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
<b>SUPER-SECURE</b>: Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signing to verify the devices of conversation participants. <b>Pick up where you left off</b>
Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io
<b>COMPLETE COMMUNICATION</b>: Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
<b>EVERYWHERE YOU ARE</b>: Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io.

View file

@ -1 +1 @@
Secure decentralised chat & VoIP. Keep your data safe from third parties. Group messenger - encrypted messaging, group chat and video calls

View file

@ -1 +1 @@
Element (previously Riot.im) Element - Secure Messenger

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7 distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View file

@ -1,2 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest package="org.matrix.android.sdk.rx" />
package="org.matrix.android.sdk.rx" />

View file

@ -124,8 +124,8 @@ class RxSession(private val session: Session) {
.startWithCallable { session.getPendingThreePids() } .startWithCallable { session.getPendingThreePids() }
} }
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder { fun createRoom(roomParams: CreateRoomParams): Single<String> = rxSingle {
session.createRoom(roomParams, it) session.createRoom(roomParams)
} }
fun searchUsersDirectory(search: String, fun searchUsersDirectory(search: String,
@ -136,13 +136,13 @@ class RxSession(private val session: Session) {
fun joinRoom(roomIdOrAlias: String, fun joinRoom(roomIdOrAlias: String,
reason: String? = null, reason: String? = null,
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder { viaServers: List<String> = emptyList()): Single<Unit> = rxSingle {
session.joinRoom(roomIdOrAlias, reason, viaServers, it) session.joinRoom(roomIdOrAlias, reason, viaServers)
} }
fun getRoomIdByAlias(roomAlias: String, fun getRoomIdByAlias(roomAlias: String,
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = singleBuilder { searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = rxSingle {
session.getRoomIdByAlias(roomAlias, searchOnServer, it) session.getRoomIdByAlias(roomAlias, searchOnServer)
} }
fun getProfileInfo(userId: String): Single<JsonDict> = rxSingle { fun getProfileInfo(userId: String): Single<JsonDict> = rxSingle {

View file

@ -6,10 +6,13 @@ apply plugin: 'realm-android'
buildscript { buildscript {
repositories { repositories {
mavenCentral() // mavenCentral()
//noinspection GrDeprecatedAPIUsage
jcenter()
} }
dependencies { dependencies {
classpath "io.realm:realm-gradle-plugin:10.4.0" // Stick to this version until https://github.com/realm/realm-java/issues/7402 is fixed
classpath "io.realm:realm-gradle-plugin:10.3.1"
} }
} }
@ -126,7 +129,7 @@ dependencies {
def lifecycle_version = '2.2.0' def lifecycle_version = '2.2.0'
def arch_version = '2.1.0' def arch_version = '2.1.0'
def markwon_version = '3.1.0' def markwon_version = '3.1.0'
def daggerVersion = '2.33' def daggerVersion = '2.34'
def work_version = '2.5.0' def work_version = '2.5.0'
def retrofit_version = '2.9.0' def retrofit_version = '2.9.0'

View file

@ -66,8 +66,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData { fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
val roomId = mTestHelper.doSync<String> { val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it) aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
} }
if (encryptedRoom) { if (encryptedRoom) {
@ -135,7 +135,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(roomJoinedObserver) bobRoomSummariesLive.observeForever(roomJoinedObserver)
} }
mTestHelper.doSync<Unit> { bobSession.joinRoom(aliceRoomId, callback = it) } mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
mTestHelper.await(lock) mTestHelper.await(lock)
@ -176,8 +176,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
room.invite(samSession.myUserId, null) room.invite(samSession.myUserId, null)
} }
mTestHelper.doSync<Unit> { mTestHelper.runBlockingTest {
samSession.joinRoom(room.roomId, null, emptyList(), it) samSession.joinRoom(room.roomId, null, emptyList())
} }
return samSession return samSession
@ -256,8 +256,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
} }
fun createDM(alice: Session, bob: Session): String { fun createDM(alice: Session, bob: Session): String {
val roomId = mTestHelper.doSync<String> { val roomId = mTestHelper.runBlockingTest {
alice.createDirectRoom(bob.myUserId, it) alice.createDirectRoom(bob.myUserId)
} }
mTestHelper.waitWithLatch { latch -> mTestHelper.waitWithLatch { latch ->
@ -300,7 +300,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(newRoomObserver) bobRoomSummariesLive.observeForever(newRoomObserver)
} }
mTestHelper.doSync<Unit> { bob.joinRoom(roomId, callback = it) } mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
} }
return roomId return roomId
@ -398,8 +398,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
aliceSession.cryptoService().setWarnOnUnknownDevices(false) aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val roomId = mTestHelper.doSync<String> { val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it) aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
} }
val room = aliceSession.getRoom(roomId)!! val room = aliceSession.getRoom(roomId)!!
@ -412,7 +412,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val session = mTestHelper.createAccount("User_$index", defaultSessionParams) val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
println("TEST -> " + session.myUserId + " invited") println("TEST -> " + session.myUserId + " invited")
mTestHelper.doSync<Unit> { session.joinRoom(room.roomId, null, emptyList(), it) } mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
println("TEST -> " + session.myUserId + " joined") println("TEST -> " + session.myUserId + " joined")
sessions.add(session) sessions.add(session)
} }

View file

@ -66,6 +66,138 @@ class KeyShareTests : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context()) private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
@Test
fun test_DoNotSelfShareIfNotTrusted() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
// Create an encrypted room and add a message
val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
}
)
}
val room = aliceSession.getRoom(roomId)
assertNotNull(room)
Thread.sleep(4_000)
assertTrue(room?.isEncrypted() == true)
val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
// Open a new sessionx
val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
val receivedEvent = roomSecondSessionPOV?.getTimeLineEvent(sentEventId)
assertNotNull(receivedEvent)
assert(receivedEvent!!.isEncrypted())
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
fail("should fail")
} catch (failure: Throwable) {
}
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
// Try to request
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
val waitLatch = CountDownLatch(1)
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
var outGoingRequestId: String? = null
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
.filter { req ->
// filter out request that was known before
!outgoingRequestsBefore.any { req.requestId == it.requestId }
}
.let {
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
outGoingRequestId = outgoing?.requestId
outgoing != null
}
}
mTestHelper.await(waitLatch)
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
// We should have a new request
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestsBefore.size)
Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId })
// The first session should see an incoming request
// the request should be refused, because the device is not trusted
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
// DEBUG LOGS
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
Log.v("TEST", "=========================")
it.forEach { keyRequest ->
Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}")
}
Log.v("TEST", "=========================")
}
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
incoming?.state == GossipingRequestState.REJECTED
}
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
fail("should fail")
} catch (failure: Throwable) {
}
// Mark the device as trusted
aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
aliceSession2.sessionParams.deviceId ?: "")
// Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
Log.v("TEST", "Incoming request Session 1")
Log.v("TEST", "=========================")
it.forEach {
Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}")
}
Log.v("TEST", "=========================")
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED }
}
}
}
Thread.sleep(6_000)
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let {
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
}
}
}
try {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
} catch (failure: Throwable) {
fail("should have been able to decrypt")
}
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(aliceSession2)
}
@Test @Test
fun test_ShareSSSSSecret() { fun test_ShareSSSSSecret() {
val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
@ -199,13 +331,12 @@ class KeyShareTests : InstrumentedTest {
} }
// Create an encrypted room and send a couple of messages // Create an encrypted room and send a couple of messages
val roomId = mTestHelper.doSync<String> { val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom( aliceSession.createRoom(
CreateRoomParams().apply { CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption() enableEncryption()
}, }
it
) )
} }
val roomAlicePov = aliceSession.getRoom(roomId) val roomAlicePov = aliceSession.getRoom(roomId)
@ -238,8 +369,8 @@ class KeyShareTests : InstrumentedTest {
roomAlicePov.invite(bobSession.myUserId, null) roomAlicePov.invite(bobSession.myUserId, null)
} }
mTestHelper.doSync<Unit> { mTestHelper.runBlockingTest {
bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it) bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
} }
// we want to discard alice outbound session // we want to discard alice outbound session

View file

@ -16,12 +16,10 @@
package org.matrix.android.sdk.api.auth.data package org.matrix.android.sdk.api.auth.data
sealed class LoginFlowResult { data class LoginFlowResult(
data class Success( val supportedLoginTypes: List<String>,
val supportedLoginTypes: List<String>, val ssoIdentityProviders: List<SsoIdentityProvider>?,
val ssoIdentityProviders: List<SsoIdentityProvider>?, val isLoginAndRegistrationSupported: Boolean,
val isLoginAndRegistrationSupported: Boolean, val homeServerUrl: String,
val homeServerUrl: String, val isOutdatedHomeserver: Boolean
val isOutdatedHomeserver: Boolean )
) : LoginFlowResult()
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 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.auth.registration
import org.matrix.android.sdk.api.failure.Failure
sealed class RegistrationAvailability {
object Available : RegistrationAvailability()
data class NotAvailable(val failure: Failure.ServerError) : RegistrationAvailability()
}

View file

@ -36,6 +36,8 @@ interface RegistrationWizard {
suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult
suspend fun registrationAvailable(userName: String): RegistrationAvailability
val currentThreePid: String? val currentThreePid: String?
// True when login and password has been sent with success to the homeserver // True when login and password has been sent with success to the homeserver

View file

@ -65,13 +65,16 @@ fun Throwable.isInvalidUIAAuth(): Boolean {
* Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible
*/ */
fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
return if (this is Failure.OtherServerError && httpCode == 401) { return if (this is Failure.OtherServerError
&& httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */) {
tryOrNull { tryOrNull {
MoshiProvider.providesMoshi() MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java) .adapter(RegistrationFlowResponse::class.java)
.fromJson(errorBody) .fromJson(errorBody)
} }
} else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) { } else if (this is Failure.ServerError
&& httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
&& error.code == MatrixError.M_FORBIDDEN) {
// This happens when the submission for this stage was bad (like bad password) // This happens when the submission for this stage was bad (like bad password)
if (error.session != null && error.flows != null) { if (error.session != null && error.flows != null) {
RegistrationFlowResponse( RegistrationFlowResponse(
@ -87,3 +90,11 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? {
null null
} }
} }
fun Throwable.isRegistrationAvailabilityError(): Boolean {
return this is Failure.ServerError
&& httpCode == HttpsURLConnection.HTTP_BAD_REQUEST /* 400 */
&& (error.code == MatrixError.M_USER_IN_USE
|| error.code == MatrixError.M_INVALID_USERNAME
|| error.code == MatrixError.M_EXCLUSIVE)
}

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.session.room
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
@ -26,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
@ -38,22 +36,19 @@ interface RoomService {
/** /**
* Create a room asynchronously * Create a room asynchronously
*/ */
fun createRoom(createRoomParams: CreateRoomParams, suspend fun createRoom(createRoomParams: CreateRoomParams): String
callback: MatrixCallback<String>): Cancelable
/** /**
* Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters * Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
*/ */
fun createDirectRoom(otherUserId: String, suspend fun createDirectRoom(otherUserId: String): String {
callback: MatrixCallback<String>): Cancelable {
return createRoom( return createRoom(
CreateRoomParams() CreateRoomParams()
.apply { .apply {
invitedUserIds.add(otherUserId) invitedUserIds.add(otherUserId)
setDirectMessage() setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true enableEncryptionIfInvitedUsersSupportIt = true
}, }
callback
) )
} }
@ -63,10 +58,9 @@ interface RoomService {
* @param reason optional reason for joining the room * @param reason optional reason for joining the room
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room. * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
*/ */
fun joinRoom(roomIdOrAlias: String, suspend fun joinRoom(roomIdOrAlias: String,
reason: String? = null, reason: String? = null,
viaServers: List<String> = emptyList(), viaServers: List<String> = emptyList())
callback: MatrixCallback<Unit>): Cancelable
/** /**
* Get a room from a roomId * Get a room from a roomId
@ -112,20 +106,18 @@ interface RoomService {
* Inform the Matrix SDK that a room is displayed. * Inform the Matrix SDK that a room is displayed.
* The SDK will update the breadcrumbs in the user account data * The SDK will update the breadcrumbs in the user account data
*/ */
fun onRoomDisplayed(roomId: String): Cancelable suspend fun onRoomDisplayed(roomId: String)
/** /**
* Mark all rooms as read * Mark all rooms as read
*/ */
fun markAllAsRead(roomIds: List<String>, suspend fun markAllAsRead(roomIds: List<String>)
callback: MatrixCallback<Unit>): Cancelable
/** /**
* Resolve a room alias to a room ID. * Resolve a room alias to a room ID.
*/ */
fun getRoomIdByAlias(roomAlias: String, suspend fun getRoomIdByAlias(roomAlias: String,
searchOnServer: Boolean, searchOnServer: Boolean): Optional<RoomAliasDescription>
callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable
/** /**
* Delete a room alias * Delete a room alias
@ -172,14 +164,14 @@ interface RoomService {
/** /**
* Get some state events about a room * Get some state events about a room
*/ */
fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>) suspend fun getRoomState(roomId: String): List<Event>
/** /**
* Use this if you want to get information from a room that you are not yet in (or invited) * Use this if you want to get information from a room that you are not yet in (or invited)
* It might be possible to get some information on this room if it is public or if guest access is allowed * It might be possible to get some information on this room if it is public or if guest access is allowed
* This call will try to gather some information on this room, but it could fail and get nothing more * This call will try to gather some information on this room, but it could fail and get nothing more
*/ */
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>) suspend fun peekRoom(roomIdOrAlias: String): PeekResult
/** /**
* TODO Doc * TODO Doc

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth package org.matrix.android.sdk.internal.auth
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.internal.auth.data.Availability
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
import org.matrix.android.sdk.internal.auth.data.RiotConfig import org.matrix.android.sdk.internal.auth.data.RiotConfig
@ -34,6 +35,7 @@ import retrofit2.http.GET
import retrofit2.http.Headers import retrofit2.http.Headers
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Url import retrofit2.http.Url
/** /**
@ -65,6 +67,12 @@ internal interface AuthAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
suspend fun register(@Body registrationParams: RegistrationParams): Credentials suspend fun register(@Body registrationParams: RegistrationParams): Credentials
/**
* Checks to see if a username is available, and valid, for the server.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/available")
suspend fun registerAvailable(@Query("username") username: String): Availability
/** /**
* Add 3Pid during registration * Add 3Pid during registration
* Ref: https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928 * Ref: https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928

View file

@ -144,16 +144,14 @@ internal class DefaultAuthenticationService @Inject constructor(
} }
return result.fold( return result.fold(
{ {
if (it is LoginFlowResult.Success) { // The homeserver exists and up to date, keep the config
// The homeserver exists and up to date, keep the config // Homeserver url may have been changed, if it was a Riot url
// Homeserver url may have been changed, if it was a Riot url val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( homeServerUri = Uri.parse(it.homeServerUrl)
homeServerUri = Uri.parse(it.homeServerUrl) )
)
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
.also { data -> pendingSessionStore.savePendingSessionData(data) } .also { data -> pendingSessionStore.savePendingSessionData(data) }
}
it it
}, },
{ {
@ -307,12 +305,12 @@ internal class DefaultAuthenticationService @Inject constructor(
val loginFlowResponse = executeRequest(null) { val loginFlowResponse = executeRequest(null) {
authAPI.getLoginFlows() authAPI.getLoginFlows()
} }
return LoginFlowResult.Success( return LoginFlowResult(
loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
versions.isLoginAndRegistrationSupportedBySdk(), isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
homeServerUrl, homeServerUrl = homeServerUrl,
!versions.isSupportedBySdk() isOutdatedHomeserver = !versions.isSupportedBySdk()
) )
} }

View file

@ -0,0 +1,29 @@
/*
* Copyright 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.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class Availability(
/**
* A flag to indicate that the username is available. This should always be true when the server replies with 200 OK.
*/
@Json(name = "available")
val available: Boolean? = null
)

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.auth.registration
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes 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.RegisterThreePid
import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.registration.toFlowResult import org.matrix.android.sdk.api.auth.registration.toFlowResult
@ -40,9 +41,10 @@ internal class DefaultRegistrationWizard(
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private val registerTask = DefaultRegisterTask(authAPI) private val registerTask: RegisterTask = DefaultRegisterTask(authAPI)
private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI)
private val validateCodeTask = DefaultValidateCodeTask(authAPI) private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI)
override val currentThreePid: String? override val currentThreePid: String?
get() { get() {
@ -203,4 +205,8 @@ internal class DefaultRegistrationWizard(
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return RegistrationResult.Success(session) return RegistrationResult.Success(session)
} }
override suspend fun registrationAvailable(userName: String): RegistrationAvailability {
return registerAvailableTask.execute(RegisterAvailableTask.Params(userName))
}
} }

View file

@ -0,0 +1,47 @@
/*
* Copyright 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.registration
import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isRegistrationAvailabilityError
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
internal interface RegisterAvailableTask : Task<RegisterAvailableTask.Params, RegistrationAvailability> {
data class Params(
val userName: String
)
}
internal class DefaultRegisterAvailableTask(private val authAPI: AuthAPI) : RegisterAvailableTask {
override suspend fun execute(params: RegisterAvailableTask.Params): RegistrationAvailability {
return try {
executeRequest(null) {
authAPI.registerAvailable(params.userName)
}
RegistrationAvailability.Available
} catch (exception: Throwable) {
if (exception.isRegistrationAvailabilityError()) {
RegistrationAvailability.NotAvailable(exception as Failure.ServerError)
} else {
throw exception
}
}
}
}

View file

@ -44,7 +44,7 @@ data class CryptoDeviceInfo(
*/ */
fun fingerprint(): String? { fun fingerprint(): String? {
return keys return keys
?.takeIf { !deviceId.isBlank() } ?.takeIf { deviceId.isNotBlank() }
?.get("ed25519:$deviceId") ?.get("ed25519:$deviceId")
} }
@ -53,7 +53,7 @@ data class CryptoDeviceInfo(
*/ */
fun identityKey(): String? { fun identityKey(): String? {
return keys return keys
?.takeIf { !deviceId.isBlank() } ?.takeIf { deviceId.isNotBlank() }
?.get("curve25519:$deviceId") ?.get("curve25519:$deviceId")
} }

View file

@ -103,7 +103,7 @@ data class MXDeviceInfo(
*/ */
fun fingerprint(): String? { fun fingerprint(): String? {
return keys return keys
?.takeIf { !deviceId.isBlank() } ?.takeIf { deviceId.isNotBlank() }
?.get("ed25519:$deviceId") ?.get("ed25519:$deviceId")
} }
@ -112,7 +112,7 @@ data class MXDeviceInfo(
*/ */
fun identityKey(): String? { fun identityKey(): String? {
return keys return keys
?.takeIf { !deviceId.isBlank() } ?.takeIf { deviceId.isNotBlank() }
?.get("curve25519:$deviceId") ?.get("curve25519:$deviceId")
} }

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.network
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.getRetryDelay import org.matrix.android.sdk.api.failure.getRetryDelay
import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.internal.network.ssl.CertUtil import org.matrix.android.sdk.internal.network.ssl.CertUtil
@ -28,22 +29,21 @@ import java.io.IOException
/** /**
* Execute a request from the requestBlock and handle some of the Exception it could generate * Execute a request from the requestBlock and handle some of the Exception it could generate
* Ref: https://github.com/matrix-org/matrix-js-sdk/blob/develop/src/scheduler.js#L138-L175
* *
* @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError] * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError]
* @param canRetry if set to true, the request will be executed again in case of error, after a delay * @param canRetry if set to true, the request will be executed again in case of error, after a delay
* @param initialDelayBeforeRetry the first delay to wait before a request is retried. Will be doubled after each retry
* @param maxDelayBeforeRetry the max delay to wait before a retry * @param maxDelayBeforeRetry the max delay to wait before a retry
* @param maxRetriesCount the max number of retries * @param maxRetriesCount the max number of retries
* @param requestBlock a suspend lambda to perform the network request * @param requestBlock a suspend lambda to perform the network request
*/ */
internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?, internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?,
canRetry: Boolean = false, canRetry: Boolean = false,
initialDelayBeforeRetry: Long = 100L, maxDelayBeforeRetry: Long = 32_000L,
maxDelayBeforeRetry: Long = 10_000L, maxRetriesCount: Int = 4,
maxRetriesCount: Int = Int.MAX_VALUE,
noinline requestBlock: suspend () -> DATA): DATA { noinline requestBlock: suspend () -> DATA): DATA {
var currentRetryCount = 0 var currentRetryCount = 0
var currentDelay = initialDelayBeforeRetry var currentDelay = 1_000L
while (true) { while (true) {
try { try {
@ -72,9 +72,16 @@ internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErr
// } // }
?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException }
if (canRetry && currentRetryCount++ < maxRetriesCount && exception.shouldBeRetried()) { currentRetryCount++
// In case of 429, ensure we wait enough
delay(currentDelay.coerceAtLeast(exception.getRetryDelay(0))) if (exception is Failure.ServerError
&& exception.httpCode == 429
&& exception.error.code == MatrixError.M_LIMIT_EXCEEDED
&& currentRetryCount < maxRetriesCount) {
// 429, we can retry
delay(exception.getRetryDelay(1_000))
} else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) {
delay(currentDelay)
currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry) currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry)
// Try again (loop) // Try again (loop)
} else { } else {

View file

@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.paging.PagedList import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.RoomService
@ -32,7 +31,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
@ -50,8 +48,6 @@ import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.fetchCopied import org.matrix.android.sdk.internal.util.fetchCopied
import javax.inject.Inject import javax.inject.Inject
@ -67,16 +63,11 @@ internal class DefaultRoomService @Inject constructor(
private val peekRoomTask: PeekRoomTask, private val peekRoomTask: PeekRoomTask,
private val roomGetter: RoomGetter, private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource, private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
private val taskExecutor: TaskExecutor
) : RoomService { ) : RoomService {
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable { override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
return createRoomTask return createRoomTask.execute(createRoomParams)
.configureWith(createRoomParams) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
override fun getRoom(roomId: String): Room? { override fun getRoom(roomId: String): Room? {
@ -121,34 +112,20 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getBreadcrumbsLive(queryParams) return roomSummaryDataSource.getBreadcrumbsLive(queryParams)
} }
override fun onRoomDisplayed(roomId: String): Cancelable { override suspend fun onRoomDisplayed(roomId: String) {
return updateBreadcrumbsTask updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
.configureWith(UpdateBreadcrumbsTask.Params(roomId))
.executeBy(taskExecutor)
} }
override fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable { override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>) {
return joinRoomTask joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
.configureWith(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
override fun markAllAsRead(roomIds: List<String>, callback: MatrixCallback<Unit>): Cancelable { override suspend fun markAllAsRead(roomIds: List<String>) {
return markAllRoomsReadTask markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds))
.configureWith(MarkAllRoomsReadTask.Params(roomIds)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable { override suspend fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Optional<RoomAliasDescription> {
return roomIdByAliasTask return roomIdByAliasTask.execute(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer))
.configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
override suspend fun deleteRoomAlias(roomAlias: String) { override suspend fun deleteRoomAlias(roomAlias: String) {
@ -179,19 +156,11 @@ internal class DefaultRoomService @Inject constructor(
} }
} }
override fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>) { override suspend fun getRoomState(roomId: String): List<Event> {
resolveRoomStateTask return resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomId))
.configureWith(ResolveRoomStateTask.Params(roomId)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>) { override suspend fun peekRoom(roomIdOrAlias: String): PeekResult {
peekRoomTask return peekRoomTask.execute(PeekRoomTask.Params(roomIdOrAlias))
.configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
} }

View file

@ -91,7 +91,7 @@ internal class SendEventWorker(context: Context,
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) { if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}") Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED) localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED)
return Result.success() Result.success()
} else { } else {
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}") Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
Result.retry() Result.retry()

View file

@ -114,11 +114,13 @@ for emoji in emoji_picker_datasource_emojis:
# If additional keywords exist, add them to emoji_picker_datasource_emojis # If additional keywords exist, add them to emoji_picker_datasource_emojis
# Avoid duplicates and keep order. Put official unicode.com keywords first and extend up with emojilib ones. # Avoid duplicates and keep order. Put official unicode.com keywords first and extend up with emojilib ones.
new_keywords = OrderedDict.fromkeys(emoji_picker_datasource_emojis[emoji]["j"] + emoji_additional_keywords).keys() new_keywords = OrderedDict.fromkeys(emoji_picker_datasource_emojis[emoji]["j"] + emoji_additional_keywords)
# Remove the ones derived from the unicode name # Remove the ones derived from the unicode name
new_keywords = new_keywords - {emoji.replace("-", "_")} - {emoji.replace("-", " ")} - {emoji_name} for keyword in [emoji.replace("-", "_")] + [emoji.replace("-", " ")] + [emoji_name]:
if keyword in new_keywords:
new_keywords.pop(keyword)
# Write new keywords back # Write new keywords back
emoji_picker_datasource_emojis[emoji]["j"] = list(new_keywords) emoji_picker_datasource_emojis[emoji]["j"] = list(new_keywords.keys())
# Filter out components from unicode 13.1 (as they are not suitable for single-emoji reactions) # Filter out components from unicode 13.1 (as they are not suitable for single-emoji reactions)
emoji_picker_datasource['categories'] = [x for x in emoji_picker_datasource['categories'] if x['id'] != 'component'] emoji_picker_datasource['categories'] = [x for x in emoji_picker_datasource['categories'] if x['id'] != 'component']

View file

@ -14,7 +14,7 @@ kapt {
// Note: 2 digits max for each value // Note: 2 digits max for each value
ext.versionMajor = 1 ext.versionMajor = 1
ext.versionMinor = 1 ext.versionMinor = 1
ext.versionPatch = 4 ext.versionPatch = 7
static def getGitTimestamp() { static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct' def cmd = 'git show -s --format=%ct'
@ -297,7 +297,7 @@ dependencies {
def big_image_viewer_version = '1.7.1' def big_image_viewer_version = '1.7.1'
def glide_version = '4.12.0' def glide_version = '4.12.0'
def moshi_version = '1.12.0' def moshi_version = '1.12.0'
def daggerVersion = '2.33' def daggerVersion = '2.34'
def autofill_version = "1.1.0" def autofill_version = "1.1.0"
def work_version = '2.5.0' def work_version = '2.5.0'
def arch_version = '2.1.0' def arch_version = '2.1.0'
@ -320,13 +320,13 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01" implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.sharetarget:sharetarget:1.1.0" implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation "androidx.media:media:1.2.1" implementation "androidx.media:media:1.3.0"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb" implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0" implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
@ -423,7 +423,7 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion"
// gplay flavor only // gplay flavor only
gplayImplementation('com.google.firebase:firebase-messaging:21.0.1') { gplayImplementation('com.google.firebase:firebase-messaging:21.1.0') {
exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'

View file

@ -0,0 +1,128 @@
/*
* 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.roomdirectory
import im.vector.app.InstrumentedTest
import im.vector.app.core.utils.AssetReader
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class ExplicitTermFilterTest : InstrumentedTest {
private val explicitTermFilter = ExplicitTermFilter(AssetReader(context()))
@Test
fun isValidEmptyTrue() {
explicitTermFilter.isValid("") shouldBe true
}
@Test
fun isValidTrue() {
explicitTermFilter.isValid("Hello") shouldBe true
}
@Test
fun isValidFalse() {
explicitTermFilter.isValid("nsfw") shouldBe false
}
@Test
fun isValidUpCaseFalse() {
explicitTermFilter.isValid("Nsfw") shouldBe false
}
@Test
fun isValidMultilineTrue() {
explicitTermFilter.isValid("Hello\nWorld") shouldBe true
}
@Test
fun isValidMultilineFalse() {
explicitTermFilter.isValid("Hello\nnsfw") shouldBe false
}
@Test
fun isValidMultilineFalse2() {
explicitTermFilter.isValid("nsfw\nHello") shouldBe false
}
@Test
fun isValidAnalFalse() {
explicitTermFilter.isValid("anal") shouldBe false
}
@Test
fun isValidAnal2False() {
explicitTermFilter.isValid("There is some anal in this room") shouldBe false
}
@Test
fun isValidAnalysisTrue() {
explicitTermFilter.isValid("analysis") shouldBe true
}
@Test
fun isValidAnalysis2True() {
explicitTermFilter.isValid("There is some analysis in the room") shouldBe true
}
@Test
fun isValidSpecialCharFalse() {
explicitTermFilter.isValid("18+") shouldBe false
}
@Test
fun isValidSpecialChar2False() {
explicitTermFilter.isValid("This is a room with 18+ content") shouldBe false
}
@Test
fun isValidOtherSpecialCharFalse() {
explicitTermFilter.isValid("strap-on") shouldBe false
}
@Test
fun isValidOtherSpecialChar2False() {
explicitTermFilter.isValid("This is a room with strap-on content") shouldBe false
}
@Test
fun isValid18True() {
explicitTermFilter.isValid("18") shouldBe true
}
@Test
fun isValidLastFalse() {
explicitTermFilter.isValid("zoo") shouldBe false
}
@Test
fun canSearchForFalse() {
explicitTermFilter.canSearchFor("zoo") shouldBe false
}
@Test
fun canSearchForTrue() {
explicitTermFilter.canSearchFor("android") shouldBe true
}
}

View file

@ -78,6 +78,7 @@ class UiAllScreensSanityTest {
// Last passing: // Last passing:
// 2020-11-09 // 2020-11-09
// 2020-12-16 After ViewBinding huge change // 2020-12-16 After ViewBinding huge change
// 2021-04-08 Testing 429 change
@Test @Test
fun allScreensTest() { fun allScreensTest() {
// Create an account // Create an account

View file

@ -39,7 +39,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:textSize="16sp" android:textSize="16sp"
tools:text="@string/verification_emoji_wrench" /> tools:text="@string/verification_emoji_spanner" />
<TextView <TextView
android:id="@+id/sas_emoji_text_id" android:id="@+id/sas_emoji_text_id"
@ -47,7 +47,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:textSize="12sp" android:textSize="12sp"
tools:text="verification_emoji_wrench" /> tools:text="verification_emoji_spanner" />
</LinearLayout> </LinearLayout>

View file

@ -24,9 +24,11 @@ import im.vector.app.core.pushers.PushersManager
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.troubleshoot.TroubleshootTest import im.vector.app.features.settings.troubleshoot.TroubleshootTest
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure
import javax.inject.Inject import javax.inject.Inject
@ -47,22 +49,26 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat
return return
} }
action = GlobalScope.launch { action = GlobalScope.launch {
status = runCatching { pushersManager.testPush(fcmToken) } val result = runCatching { pushersManager.testPush(fcmToken) }
.fold(
{ withContext(Dispatchers.Main) {
// Wait for the push to be received status = result
description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) .fold(
TestStatus.RUNNING {
}, // Wait for the push to be received
{ description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push)
description = if (it is PushGatewayFailure.PusherRejected) { TestStatus.RUNNING
stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed) },
} else { {
errorFormatter.toHumanReadable(it) description = if (it is PushGatewayFailure.PusherRejected) {
stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed)
} else {
errorFormatter.toHumanReadable(it)
}
TestStatus.FAILED
} }
TestStatus.FAILED )
} }
)
} }
} }

View file

@ -0,0 +1,68 @@
anal
bbw
bdsm
beast
bestiality
blowjob
bondage
boobs
clit
cock
cuck
cum
cunt
daddy
dick
dildo
erotic
exhibitionism
faggot
femboy
fisting
flogging
fmf
foursome
futa
gangbang
gore
h3ntai
handjob
hentai
incest
jizz
kink
loli
m4f
masturbate
masturbation
mfm
milf
moresome
naked
neet
nsfw
nude
nudity
orgy
pedo
pegging
penis
petplay
porn
pussy
rape
rimming
sadism
sadomasochism
sexy
shota
spank
squirt
strap-on
threesome
vagina
vibrator
voyeur
watersports
xxx
zoo

View file

@ -28,8 +28,10 @@ import com.bumptech.glide.signature.ObjectKey
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.files.LocalFilesHelper
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import timber.log.Timber import timber.log.Timber
import java.io.IOException import java.io.IOException
@ -121,10 +123,12 @@ class VectorGlideDataFetcher(context: Context,
url = data.url, url = data.url,
elementToDecrypt = data.elementToDecrypt) elementToDecrypt = data.elementToDecrypt)
} }
result.fold( withContext(Dispatchers.Main) {
{ callback.onDataReady(it.inputStream()) }, result.fold(
{ callback.onLoadFailed(it as? Exception ?: IOException(it.localizedMessage)) } { callback.onDataReady(it.inputStream()) },
) { callback.onLoadFailed(it as? Exception ?: IOException(it.localizedMessage)) }
)
}
} }
// val url = contentUrlResolver.resolveFullSize(data.url) // val url = contentUrlResolver.resolveFullSize(data.url)
// ?: return // ?: return

View file

@ -23,7 +23,7 @@ import javax.inject.Inject
class AppNameProvider @Inject constructor(private val context: Context) { class AppNameProvider @Inject constructor(private val context: Context) {
fun getAppName(): String { fun getAppName(): String {
try { return try {
val appPackageName = context.applicationContext.packageName val appPackageName = context.applicationContext.packageName
val pm = context.packageManager val pm = context.packageManager
val appInfo = pm.getApplicationInfo(appPackageName, 0) val appInfo = pm.getApplicationInfo(appPackageName, 0)
@ -33,10 +33,10 @@ class AppNameProvider @Inject constructor(private val context: Context) {
if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) { if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) {
appName = appPackageName appName = appPackageName
} }
return appName appName
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "## AppNameProvider() : failed") Timber.e(e, "## AppNameProvider() : failed")
return "ElementAndroid" "ElementAndroid"
} }
} }
} }

View file

@ -146,7 +146,7 @@ class DialPadFragment : Fragment() {
} }
private fun poll() { private fun poll() {
if (!input.isEmpty()) { if (input.isNotEmpty()) {
input = input.substring(0, input.length - 1) input = input.substring(0, input.length - 1)
formatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(regionCode) formatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(regionCode)
if (formatAsYouType) { if (formatAsYouType) {

View file

@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.internal.util.awaitCallback
import javax.inject.Inject import javax.inject.Inject
class DirectRoomHelper @Inject constructor( class DirectRoomHelper @Inject constructor(
@ -45,9 +44,7 @@ class DirectRoomHelper @Inject constructor(
setDirectMessage() setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
} }
roomId = awaitCallback { roomId = session.createRoom(roomParams)
session.createRoom(roomParams, it)
}
} }
return roomId return roomId
} }

View file

@ -60,7 +60,7 @@ class IncomingVerificationRequestHandler @Inject constructor(
// TODO maybe check also if // TODO maybe check also if
val uid = "kvr_${tx.transactionId}" val uid = "kvr_${tx.transactionId}"
when (tx.state) { when (tx.state) {
is VerificationTxState.OnStarted -> { is VerificationTxState.OnStarted -> {
// Add a notification for every incoming request // Add a notification for every incoming request
val user = session?.getUser(tx.otherUserId) val user = session?.getUser(tx.otherUserId)
val name = user?.getBestName() ?: tx.otherUserId val name = user?.getBestName() ?: tx.otherUserId
@ -119,6 +119,13 @@ class IncomingVerificationRequestHandler @Inject constructor(
Timber.v("## SAS verificationRequestCreated ${pr.transactionId}") Timber.v("## SAS verificationRequestCreated ${pr.transactionId}")
// For incoming request we should prompt (if not in activity where this request apply) // For incoming request we should prompt (if not in activity where this request apply)
if (pr.isIncoming) { if (pr.isIncoming) {
// if it's a self verification for my devices, we can discard the review login alert
// if not this request will be underneath and not visible by the user...
// it will re-appear later
if (pr.otherUserId == session?.myUserId) {
// XXX this is a bit hard coded :/
popupAlertManager.cancelAlert("review_login")
}
val user = session?.getUser(pr.otherUserId)?.toMatrixItem() val user = session?.getUser(pr.otherUserId)?.toMatrixItem()
val name = user?.getBestName() ?: pr.otherUserId val name = user?.getBestName() ?: pr.otherUserId
val description = if (name == pr.otherUserId) { val description = if (name == pr.otherUserId) {

View file

@ -249,32 +249,34 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
pendingRequest = Loading() pendingRequest = Loading()
) )
} }
session.createDirectRoom(otherUserId, object : MatrixCallback<String> { viewModelScope.launch {
override fun onSuccess(data: String) { val result = runCatching { session.createDirectRoom(otherUserId) }
setState { result.fold(
copy( { data ->
roomId = data, setState {
pendingRequest = Success( copy(
session roomId = data,
.cryptoService() pendingRequest = Success(
.verificationService() session
.requestKeyVerificationInDMs( .cryptoService()
supportedVerificationMethodsProvider.provide(), .verificationService()
otherUserId, .requestKeyVerificationInDMs(
data, supportedVerificationMethodsProvider.provide(),
pendingLocalId otherUserId,
) data,
pendingLocalId
)
)
) )
) }
} },
} { failure ->
setState {
override fun onFailure(failure: Throwable) { copy(pendingRequest = Fail(failure))
setState { }
copy(pendingRequest = Fail(failure)) }
} )
} }
})
} else { } else {
setState { setState {
copy( copy(

View file

@ -203,9 +203,8 @@ class HomeActivityViewModel @AssistedInject constructor(
_viewEvents.post( _viewEvents.post(
HomeActivityViewEvents.OnNewSession( HomeActivityViewEvents.OnNewSession(
session.getUser(session.myUserId)?.toMatrixItem(), session.getUser(session.myUserId)?.toMatrixItem(),
// If it's an old unverified, we should send requests // Always send request instead of waiting for an incoming as per recent EW changes
// instead of waiting for an incoming one false
reAuthHelper.data != null
) )
) )
} }

View file

@ -35,7 +35,6 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.asObservable
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
import timber.log.Timber import timber.log.Timber
@ -109,9 +108,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
) )
.map { it.roomId } .map { it.roomId }
try { try {
awaitCallback<Unit> { session.markAllAsRead(roomIds)
session.markAllAsRead(roomIds, it)
}
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.d(failure, "Failed to mark all as read") Timber.d(failure, "Failed to mark all as read")
} }

View file

@ -177,14 +177,16 @@ class RoomDetailViewModel @AssistedInject constructor(
observePowerLevel() observePowerLevel()
updateShowDialerOptionState() updateShowDialerOptionState()
room.getRoomSummaryLive() room.getRoomSummaryLive()
viewModelScope.launch {
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
}
// Inform the SDK that the room is displayed
viewModelScope.launch { viewModelScope.launch {
try { try {
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) session.onRoomDisplayed(initialState.roomId)
} catch (_: Exception) { } catch (_: Exception) {
} }
} }
// Inform the SDK that the room is displayed
session.onRoomDisplayed(initialState.roomId)
callManager.addPstnSupportListener(this) callManager.addPstnSupportListener(this)
callManager.checkForPSTNSupportIfNeeded() callManager.checkForPSTNSupportIfNeeded()
chatEffectManager.delegate = this chatEffectManager.delegate = this
@ -546,10 +548,7 @@ class RoomDetailViewModel @AssistedInject constructor(
if (trackUnreadMessages.getAndSet(false)) { if (trackUnreadMessages.getAndSet(false)) {
mostRecentDisplayedEvent?.root?.eventId?.also { mostRecentDisplayedEvent?.root?.eventId?.also {
viewModelScope.launch { viewModelScope.launch {
try { tryOrNull { room.setReadMarker(it) }
room.setReadMarker(it)
} catch (_: Exception) {
}
} }
} }
mostRecentDisplayedEvent = null mostRecentDisplayedEvent = null
@ -902,19 +901,19 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) {
session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback<Unit> { viewModelScope.launch {
override fun onSuccess(data: Unit) { try {
session.getRoomSummary(command.roomAlias) session.joinRoom(command.roomAlias, command.reason, emptyList())
?.roomId } catch (failure: Throwable) {
?.let {
_viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSuccess(it))
}
}
override fun onFailure(failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure))
return@launch
} }
}) session.getRoomSummary(command.roomAlias)
?.roomId
?.let {
_viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSuccess(it))
}
}
} }
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
@ -1254,7 +1253,7 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId ->
viewModelScope.launch { viewModelScope.launch {
room.setReadReceipt(eventId) tryOrNull { room.setReadReceipt(eventId) }
} }
} }
}) })
@ -1263,10 +1262,7 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun handleMarkAllAsRead() { private fun handleMarkAllAsRead() {
viewModelScope.launch { viewModelScope.launch {
try { tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) }
room.markAsRead(ReadService.MarkAsReadParams.BOTH)
} catch (_: Exception) {
}
} }
} }

View file

@ -91,18 +91,25 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
SignMode.SignUp -> { SignMode.SignUp -> {
views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP
} }
SignMode.SignIn, SignMode.SignIn,
SignMode.SignInWithMatrixId -> { SignMode.SignInWithMatrixId -> {
views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN
} }
}.exhaustive }.exhaustive
} }
} }
private fun setupSocialLoginButtons(state: LoginViewState) {
views.loginSocialLoginButtons.mode = when (state.signMode) {
SignMode.Unknown -> error("developer error")
SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP
SignMode.SignIn,
SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN
}.exhaustive
}
private fun submit() { private fun submit() {
cleanupUi() cleanupUi()
@ -277,6 +284,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
setupUi(state) setupUi(state)
setupAutoFill(state) setupAutoFill(state)
setupSocialLoginButtons(state)
setupButtons(state) setupButtons(state)
when (state.asyncLoginAction) { when (state.asyncLoginAction) {

View file

@ -43,7 +43,6 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig 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.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.FlowResult import org.matrix.android.sdk.api.auth.registration.FlowResult
@ -773,34 +772,34 @@ class LoginViewModel @AssistedInject constructor(
null null
} }
if (data is LoginFlowResult.Success) { data ?: return@launch
// Valid Homeserver, add it to the history.
// Note: we add what the user has input, data.homeServerUrl can be different
rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
val loginMode = when { // Valid Homeserver, add it to the history.
// SSO login is taken first // Note: we add what the user has input, data.homeServerUrl can be different
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString())
&& 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? val loginMode = when {
setState { // SSO login is taken first
copy( data.supportedLoginTypes.contains(LoginFlowTypes.SSO)
asyncHomeServerLoginFlowRequest = Uninitialized, && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders)
homeServerUrl = data.homeServerUrl, data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders)
loginMode = loginMode, data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
loginModeSupportedTypes = data.supportedLoginTypes.toList() else -> LoginMode.Unsupported
) }
}
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) // FIXME We should post a view event here normally?
|| data.isOutdatedHomeserver) { setState {
// Notify the UI copy(
_viewEvents.post(LoginViewEvents.OutdatedHomeserver) 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)
} }
} }
} }

View file

@ -31,8 +31,10 @@ import im.vector.lib.attachmentviewer.AttachmentInfo
import im.vector.lib.attachmentviewer.AttachmentSourceProvider import im.vector.lib.attachmentviewer.AttachmentSourceProvider
import im.vector.lib.attachmentviewer.ImageLoaderTarget import im.vector.lib.attachmentviewer.ImageLoaderTarget
import im.vector.lib.attachmentviewer.VideoLoaderTarget import im.vector.lib.attachmentviewer.VideoLoaderTarget
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -162,10 +164,12 @@ abstract class BaseAttachmentProvider<Type>(
elementToDecrypt = data.elementToDecrypt elementToDecrypt = data.elementToDecrypt
) )
} }
result.fold( withContext(Dispatchers.Main) {
{ target.onVideoFileReady(info.uid, it) }, result.fold(
{ target.onVideoFileLoadFailed(info.uid) } { target.onVideoFileReady(info.uid, it) },
) { target.onVideoFileLoadFailed(info.uid) }
)
}
} }
} }
} }

View file

@ -19,8 +19,10 @@ package im.vector.app.features.media
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -87,7 +89,9 @@ class DataAttachmentRoomProvider(
elementToDecrypt = item.elementToDecrypt elementToDecrypt = item.elementToDecrypt
) )
} }
callback(result.getOrNull()) withContext(Dispatchers.Main) {
callback(result.getOrNull())
}
} }
} }
} }

View file

@ -19,8 +19,10 @@ package im.vector.app.features.media
import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@ -134,7 +136,9 @@ class RoomEventsAttachmentProvider(
url = messageContent.getFileUrl(), url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt())
} }
callback(result.getOrNull()) withContext(Dispatchers.Main) {
callback(result.getOrNull())
}
} }
} }
} }

View file

@ -25,8 +25,10 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.files.LocalFilesHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
import timber.log.Timber import timber.log.Timber
@ -83,21 +85,23 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc
url = data.url, url = data.url,
elementToDecrypt = data.elementToDecrypt) elementToDecrypt = data.elementToDecrypt)
} }
result.fold( withContext(Dispatchers.Main) {
{ data -> result.fold(
thumbnailView.isVisible = false { data ->
loadingView.isVisible = false thumbnailView.isVisible = false
videoView.isVisible = true loadingView.isVisible = false
videoView.isVisible = true
videoView.setVideoPath(data.path) videoView.setVideoPath(data.path)
videoView.start() videoView.start()
}, },
{ {
loadingView.isVisible = false loadingView.isVisible = false
errorView.isVisible = true errorView.isVisible = true
errorView.text = errorFormatter.toHumanReadable(it) errorView.text = errorFormatter.toHumanReadable(it)
} }
) )
}
} }
} }
} else { } else {
@ -124,21 +128,23 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc
url = data.url, url = data.url,
elementToDecrypt = null) elementToDecrypt = null)
} }
result.fold( withContext(Dispatchers.Main) {
{ data -> result.fold(
thumbnailView.isVisible = false { data ->
loadingView.isVisible = false thumbnailView.isVisible = false
videoView.isVisible = true loadingView.isVisible = false
videoView.isVisible = true
videoView.setVideoPath(data.path) videoView.setVideoPath(data.path)
videoView.start() videoView.start()
}, },
{ {
loadingView.isVisible = false loadingView.isVisible = false
errorView.isVisible = true errorView.isVisible = true
errorView.text = errorFormatter.toHumanReadable(it) errorView.text = errorFormatter.toHumanReadable(it)
} }
) )
}
} }
} }
} }

View file

@ -25,6 +25,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.extensions.vectorComponent
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch 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.Session
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.read.ReadService
@ -78,7 +79,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
val room = session.getRoom(roomId) val room = session.getRoom(roomId)
if (room != null) { if (room != null) {
GlobalScope.launch { GlobalScope.launch {
room.join() tryOrNull { room.join() }
} }
} }
} }
@ -89,7 +90,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
val room = session.getRoom(roomId) val room = session.getRoom(roomId)
if (room != null) { if (room != null) {
GlobalScope.launch { GlobalScope.launch {
room.leave() tryOrNull { room.leave() }
} }
} }
} }
@ -100,10 +101,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
val room = session.getRoom(roomId) val room = session.getRoom(roomId)
if (room != null) { if (room != null) {
GlobalScope.launch { GlobalScope.launch {
try { tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT)
} catch (_: Exception) {
}
} }
} }
} }

View file

@ -0,0 +1,46 @@
/*
* 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.roomdirectory
import im.vector.app.core.utils.AssetReader
import javax.inject.Inject
class ExplicitTermFilter @Inject constructor(
assetReader: AssetReader
) {
// List of forbidden terms is in file asset forbidden_terms.txt, in lower case
private val explicitTerms = assetReader.readAssetFile("forbidden_terms.txt")
.orEmpty()
.split("\n")
.map { it.trim() }
.distinct()
.filter { it.isNotEmpty() }
private val explicitContentRegex = explicitTerms
.joinToString(prefix = ".*\\b(", separator = "|", postfix = ")\\b.*")
.toRegex(RegexOption.IGNORE_CASE)
fun canSearchFor(term: String): Boolean {
return term !in explicitTerms && term != "18+"
}
fun isValid(str: String): Boolean {
return explicitContentRegex.matches(str.replace("\n", " ")).not()
// Special treatment for "18+" since word boundaries does not work here
&& str.contains("18+").not()
}
}

View file

@ -31,7 +31,6 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -42,12 +41,12 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryDat
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
import timber.log.Timber import timber.log.Timber
import java.util.Locale
class RoomDirectoryViewModel @AssistedInject constructor( class RoomDirectoryViewModel @AssistedInject constructor(
@Assisted initialState: PublicRoomsViewState, @Assisted initialState: PublicRoomsViewState,
vectorPreferences: VectorPreferences, vectorPreferences: VectorPreferences,
private val session: Session private val session: Session,
private val explicitTermFilter: ExplicitTermFilter
) : VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, RoomDirectoryViewEvents>(initialState) { ) : VectorViewModel<PublicRoomsViewState, RoomDirectoryAction, RoomDirectoryViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -58,11 +57,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(
companion object : MvRxViewModelFactory<RoomDirectoryViewModel, PublicRoomsViewState> { companion object : MvRxViewModelFactory<RoomDirectoryViewModel, PublicRoomsViewState> {
private const val PUBLIC_ROOMS_LIMIT = 20 private const val PUBLIC_ROOMS_LIMIT = 20
// List of forbidden terms, in lower case
private val explicitContentTerms = listOf(
"nsfw"
)
@JvmStatic @JvmStatic
override fun create(viewModelContext: ViewModelContext, state: PublicRoomsViewState): RoomDirectoryViewModel? { override fun create(viewModelContext: ViewModelContext, state: PublicRoomsViewState): RoomDirectoryViewModel? {
val activity: RoomDirectoryActivity = (viewModelContext as ActivityViewModelContext).activity() val activity: RoomDirectoryActivity = (viewModelContext as ActivityViewModelContext).activity()
@ -166,6 +160,17 @@ class RoomDirectoryViewModel @AssistedInject constructor(
} }
private fun load(filter: String, roomDirectoryData: RoomDirectoryData) { private fun load(filter: String, roomDirectoryData: RoomDirectoryData) {
if (!showAllRooms && !explicitTermFilter.canSearchFor(filter)) {
setState {
copy(
asyncPublicRoomsRequest = Success(Unit),
publicRooms = emptyList(),
hasMore = false
)
}
return
}
currentJob = viewModelScope.launch { currentJob = viewModelScope.launch {
val data = try { val data = try {
session.getPublicRooms(roomDirectoryData.homeServer, session.getPublicRooms(roomDirectoryData.homeServer,
@ -202,11 +207,7 @@ class RoomDirectoryViewModel @AssistedInject constructor(
// Filter // Filter
val newPublicRooms = data.chunk.orEmpty() val newPublicRooms = data.chunk.orEmpty()
.filter { .filter {
showAllRooms showAllRooms || explicitTermFilter.isValid("${it.name.orEmpty()} ${it.topic.orEmpty()}")
|| "${it.name.orEmpty()} ${it.topic.orEmpty()} ${it.canonicalAlias.orEmpty()}".toLowerCase(Locale.ROOT)
.let { str ->
explicitContentTerms.all { term -> term !in str }
}
} }
setState { setState {
@ -232,17 +233,16 @@ class RoomDirectoryViewModel @AssistedInject constructor(
val viaServers = state.roomDirectoryData.homeServer val viaServers = state.roomDirectoryData.homeServer
?.let { listOf(it) } ?.let { listOf(it) }
.orEmpty() .orEmpty()
session.joinRoom(action.roomId, viaServers = viaServers, callback = object : MatrixCallback<Unit> { viewModelScope.launch {
override fun onSuccess(data: Unit) { try {
session.joinRoom(action.roomId, viaServers = viaServers)
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
// Instead, we wait for the room to be joined // Instead, we wait for the room to be joined
} } catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
// Notify the user // Notify the user
_viewEvents.post(RoomDirectoryViewEvents.Failure(failure)) _viewEvents.post(RoomDirectoryViewEvents.Failure(failure))
} }
}) }
} }
override fun onCleared() { override fun onCleared() {

View file

@ -34,7 +34,6 @@ import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault import im.vector.app.features.raw.wellknown.isE2EByDefault
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -216,19 +215,22 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
} }
} }
session.createRoom(createRoomParams, object : MatrixCallback<String> { // TODO: Should this be non-cancellable?
override fun onSuccess(data: String) { viewModelScope.launch {
setState { val result = runCatching { session.createRoom(createRoomParams) }
copy(asyncCreateRoomRequest = Success(data)) result.fold(
} { roomId ->
} setState {
copy(asyncCreateRoomRequest = Success(roomId))
override fun onFailure(failure: Throwable) { }
setState { },
copy(asyncCreateRoomRequest = Fail(failure)) { failure ->
} setState {
_viewEvents.post(CreateRoomViewEvents.Failure(failure)) copy(asyncCreateRoomRequest = Fail(failure))
} }
}) _viewEvents.post(CreateRoomViewEvents.Failure(failure))
}
)
}
} }
} }

View file

@ -31,7 +31,6 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.roomdirectory.JoinState import im.vector.app.features.roomdirectory.JoinState
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
@ -39,7 +38,6 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.rx
import timber.log.Timber import timber.log.Timber
@ -77,9 +75,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
} }
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
val peekResult = tryOrNull { val peekResult = tryOrNull {
awaitCallback<PeekResult> { session.peekRoom(initialState.roomAlias ?: initialState.roomId)
session.peekRoom(initialState.roomAlias ?: initialState.roomId, it)
}
} }
when (peekResult) { when (peekResult) {
@ -177,15 +173,14 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
Timber.w("Try to join an already joining room. Should not happen") Timber.w("Try to join an already joining room. Should not happen")
return@withState return@withState
} }
session.joinRoom(state.roomId, viaServers = state.homeServers, callback = object : MatrixCallback<Unit> { viewModelScope.launch {
override fun onSuccess(data: Unit) { try {
session.joinRoom(state.roomId, viaServers = state.homeServers)
// We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
// Instead, we wait for the room to be joined // Instead, we wait for the room to be joined
} } catch (failure: Throwable) {
override fun onFailure(failure: Throwable) {
setState { copy(lastError = failure) } setState { copy(lastError = failure) }
} }
}) }
} }
} }

View file

@ -20,8 +20,11 @@ import androidx.activity.result.ActivityResultLauncher
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.pushrules.RuleIds import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.RuleKind
import javax.inject.Inject import javax.inject.Inject
@ -50,10 +53,12 @@ class TestAccountSettings @Inject constructor(private val stringProvider: String
if (manager?.diagStatus == TestStatus.RUNNING) return // wait before all is finished if (manager?.diagStatus == TestStatus.RUNNING) return // wait before all is finished
GlobalScope.launch { GlobalScope.launch {
runCatching { tryOrNull {
session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled) session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled)
} }
manager?.retry(activityResultLauncher) withContext(Dispatchers.Main) {
manager?.retry(activityResultLauncher)
}
} }
} }
} }

View file

@ -33,7 +33,6 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode import im.vector.app.features.login.LoginMode
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService 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.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import timber.log.Timber import timber.log.Timber
@ -100,21 +99,21 @@ class SoftLogoutViewModel @AssistedInject constructor(
null null
} }
if (data is LoginFlowResult.Success) { data ?: return@launch
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 { val loginMode = when {
copy( // SSO login is taken first
asyncHomeServerLoginFlowRequest = Success(loginMode) 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)
)
} }
} }
} }

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_root" android:id="@+id/layout_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View file

@ -145,6 +145,7 @@
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:contentDescription="@string/event_status_a11y_sending" android:contentDescription="@string/event_status_a11y_sending"
android:src="@drawable/ic_sending_message" android:src="@drawable/ic_sending_message"
android:tint="?riotx_text_tertiary"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
@ -158,6 +159,7 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp" android:layout_marginBottom="4dp"
android:tint="?riotx_text_tertiary"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />

File diff suppressed because one or more lines are too long

View file

@ -198,10 +198,8 @@
<attr name="riotx_text_tertiary" format="color" /> <attr name="riotx_text_tertiary" format="color" />
<color name="riotx_text_tertiary_light">#FF8D99A5</color> <color name="riotx_text_tertiary_light">#FF8D99A5</color>
<!-- TODO Pick color from Figma, I do not know where to find it --> <color name="riotx_text_tertiary_dark">#FF8E99A4</color>
<color name="riotx_text_tertiary_dark">#FF8D99A5</color> <color name="riotx_text_tertiary_black">#FF8E99A4</color>
<!-- TODO Pick color from Figma, I do not know where to find it -->
<color name="riotx_text_tertiary_black">#FF8D99A5</color>
<attr name="riotx_text_primary_body_contrast" format="color" /> <attr name="riotx_text_primary_body_contrast" format="color" />
<color name="riotx_text_primary_body_contrast_light">#FF61708B</color> <color name="riotx_text_primary_body_contrast_light">#FF61708B</color>