Merge branch 'develop' into feature/bma/currentTimeMillis

This commit is contained in:
Benoit Marty 2022-05-04 17:43:03 +02:00 committed by GitHub
commit 09e628f227
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
146 changed files with 1410 additions and 640 deletions
.github/ISSUE_TEMPLATE
CONTRIBUTING.mdbuild.gradle
changelog.d
matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow
matrix-sdk-android
build.gradle
docs
src
androidTest/java/org/matrix/android/sdk
main/java/org/matrix/android/sdk
test/java/org/matrix/android/sdk/api/session/pushrules
vector/src

View file

@ -106,7 +106,7 @@ body:
https://github.com/matrix-org/matrix-android-sdk2-sample https://github.com/matrix-org/matrix-android-sdk2-sample
- [ ] Update the dependency to the new version of the SDK2. It can take some time for MavenCentral to make the librarie available. You can check status on https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/ - [ ] Update the dependency to the new version of the SDK2. It can take a few minutes for MavenCentral to make the library available. You can check status on https://repo1.maven.org/maven2/org/matrix/android/matrix-android-sdk2/
- [ ] Build and run the sample, you may have to fix some API break - [ ] Build and run the sample, you may have to fix some API break
- [ ] Commit and push directly on `main` - [ ] Commit and push directly on `main`
validations: validations:

View file

@ -124,7 +124,9 @@ As a general rule, please never edit or add or remove translations to the projec
#### Adding new string #### Adding new string
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators using Weblate. When adding new string resources, please only add new entries in the file `value/strings.xml`. Translations will be added later by the community of translators using Weblate.
The file `value/strings.xml` must only contain American English (U. S. English) values, as this is the default language of the Android operating system. So for instance, please use "color" instead of "colour". Element Android will still use the language set on the system by the user, like any other Android applications which provide translations. The system language can be any other English language variants, or any other languages. Note that this is also possible to override the system language using the Element Android in-app language settings.
New strings can be added anywhere in the file `value/strings.xml`, not necessarily at the end of the file. Generally, it's even better to add the new strings in some dedicated section per feature, and not at the end of the file, to avoid merge conflict between 2 PR adding strings at the end of the same file. New strings can be added anywhere in the file `value/strings.xml`, not necessarily at the end of the file. Generally, it's even better to add the new strings in some dedicated section per feature, and not at the end of the file, to avoid merge conflict between 2 PR adding strings at the end of the same file.

View file

@ -30,7 +30,7 @@ buildscript {
// ktlint Plugin // ktlint Plugin
plugins { plugins {
id "org.jlleitschuh.gradle.ktlint" version "10.2.1" id "org.jlleitschuh.gradle.ktlint" version "10.3.0"
} }
// https://github.com/jeremylong/DependencyCheck // https://github.com/jeremylong/DependencyCheck

1
changelog.d/5421.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes crash when accepting or receiving VOIP calls

1
changelog.d/5854.doc Normal file
View file

@ -0,0 +1 @@
Improve documentation of the project and of the SDK

1
changelog.d/5855.sdk Normal file
View file

@ -0,0 +1 @@
- Add return type to RoomApi.sendStateEvent() to retrieve the created event id

1
changelog.d/5858.sdk Normal file
View file

@ -0,0 +1 @@
`Room` apis are now available by requesting the service first. For instance `Room.updateAvatar(...)` is now `Room.stateService().updateAvatar(...)`

1
changelog.d/5862.wip Normal file
View file

@ -0,0 +1 @@
[Live location sharing] Improve aggregation process of events

1
changelog.d/5874.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes sign in via other requiring homeserver registration to be enabled

1
changelog.d/5890.sdk Normal file
View file

@ -0,0 +1 @@
Remove unecessary field `eventId` from `EventAnnotationsSummary` and `ReferencesAggregatedSummary`

1
changelog.d/5924.bugfix Normal file
View file

@ -0,0 +1 @@
Fix a crash with space invitations in the space list, and do not display space invitation twice.

1
changelog.d/5925.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes crash on android api 21/22 devices when opening messages due to Konfetti library

View file

@ -21,6 +21,8 @@ import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
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.getStateEvent
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.ReadReceipt
@ -45,81 +47,81 @@ class FlowRoom(private val room: Room) {
} }
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>> { fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>> {
return room.getRoomMembersLive(queryParams).asFlow() return room.membershipService().getRoomMembersLive(queryParams).asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getRoomMembers(queryParams) room.membershipService().getRoomMembers(queryParams)
} }
} }
fun liveAnnotationSummary(eventId: String): Flow<Optional<EventAnnotationsSummary>> { fun liveAnnotationSummary(eventId: String): Flow<Optional<EventAnnotationsSummary>> {
return room.getEventAnnotationsSummaryLive(eventId).asFlow() return room.relationService().getEventAnnotationsSummaryLive(eventId).asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getEventAnnotationsSummary(eventId).toOptional() room.relationService().getEventAnnotationsSummary(eventId).toOptional()
} }
} }
fun liveTimelineEvent(eventId: String): Flow<Optional<TimelineEvent>> { fun liveTimelineEvent(eventId: String): Flow<Optional<TimelineEvent>> {
return room.getTimelineEventLive(eventId).asFlow() return room.timelineService().getTimelineEventLive(eventId).asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getTimelineEvent(eventId).toOptional() room.getTimelineEvent(eventId).toOptional()
} }
} }
fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow<Optional<Event>> { fun liveStateEvent(eventType: String, stateKey: QueryStringValue): Flow<Optional<Event>> {
return room.getStateEventLive(eventType, stateKey).asFlow() return room.stateService().getStateEventLive(eventType, stateKey).asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getStateEvent(eventType, stateKey).toOptional() room.getStateEvent(eventType, stateKey).toOptional()
} }
} }
fun liveStateEvents(eventTypes: Set<String>): Flow<List<Event>> { fun liveStateEvents(eventTypes: Set<String>): Flow<List<Event>> {
return room.getStateEventsLive(eventTypes).asFlow() return room.stateService().getStateEventsLive(eventTypes).asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getStateEvents(eventTypes) room.stateService().getStateEvents(eventTypes)
} }
} }
fun liveReadMarker(): Flow<Optional<String>> { fun liveReadMarker(): Flow<Optional<String>> {
return room.getReadMarkerLive().asFlow() return room.readService().getReadMarkerLive().asFlow()
} }
fun liveReadReceipt(): Flow<Optional<String>> { fun liveReadReceipt(): Flow<Optional<String>> {
return room.getMyReadReceiptLive().asFlow() return room.readService().getMyReadReceiptLive().asFlow()
} }
fun liveEventReadReceipts(eventId: String): Flow<List<ReadReceipt>> { fun liveEventReadReceipts(eventId: String): Flow<List<ReadReceipt>> {
return room.getEventReadReceiptsLive(eventId).asFlow() return room.readService().getEventReadReceiptsLive(eventId).asFlow()
} }
fun liveDraft(): Flow<Optional<UserDraft>> { fun liveDraft(): Flow<Optional<UserDraft>> {
return room.getDraftLive().asFlow() return room.draftService().getDraftLive().asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getDraft().toOptional() room.draftService().getDraft().toOptional()
} }
} }
fun liveNotificationState(): Flow<RoomNotificationState> { fun liveNotificationState(): Flow<RoomNotificationState> {
return room.getLiveRoomNotificationState().asFlow() return room.roomPushRuleService().getLiveRoomNotificationState().asFlow()
} }
fun liveThreadSummaries(): Flow<List<ThreadSummary>> { fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
return room.getAllThreadSummariesLive().asFlow() return room.threadsService().getAllThreadSummariesLive().asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getAllThreadSummaries() room.threadsService().getAllThreadSummaries()
} }
} }
fun liveThreadList(): Flow<List<ThreadRootEvent>> { fun liveThreadList(): Flow<List<ThreadRootEvent>> {
return room.getAllThreadsLive().asFlow() return room.threadsLocalService().getAllThreadsLive().asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getAllThreads() room.threadsLocalService().getAllThreads()
} }
} }
fun liveLocalUnreadThreadList(): Flow<List<ThreadRootEvent>> { fun liveLocalUnreadThreadList(): Flow<List<ThreadRootEvent>> {
return room.getMarkedThreadNotificationsLive().asFlow() return room.threadsLocalService().getMarkedThreadNotificationsLive().asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getMarkedThreadNotifications() room.threadsLocalService().getMarkedThreadNotifications()
} }
} }
} }

View file

@ -19,13 +19,18 @@ dokkaHtml {
configureEach { configureEach {
// Emit warnings about not documented members. // Emit warnings about not documented members.
reportUndocumented.set(true) reportUndocumented.set(true)
// Suppress package legacy riot. Sadly this does not work // Suppress legacy Riot's packages.
/*
perPackageOption { perPackageOption {
prefix.set("org.matrix.android.sdk.internal.legacy.riot") matchingRegex.set("org.matrix.android.sdk.internal.legacy.riot")
suppress.set(true) suppress.set(true)
} }
*/ perPackageOption {
matchingRegex.set("org.matrix.androidsdk.crypto.data")
suppress.set(true)
}
// List of files with module and package documentation
// https://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation
includes.from("./docs/modules.md", "./docs/packages.md")
} }
} }
} }

View file

@ -0,0 +1,18 @@
# Module matrix-sdk-android
## Welcome to the matrix-sdk-android documentation!
This pages list the complete API that this SDK is exposing to a client application.
*We are still building the documentation, so everything is not documented yet.*
A few entry points:
- **Matrix**: The app will have to create and manage a Matrix object.
- From this **Matrix** object, you will be able to get various services, including the **AuthenticationService**.
- With this **AuthenticationService** you will be able to get an existing **Session**, or create one using a **LoginWizard** or a **RegistrationWizard**, which will finally give you a **Session**.
- From the **Session**, you will be able to retrieve many Services, including the **RoomService**.
- From the **RoomService**, you will be able to list the rooms, create a **Room**, and get a specific **Room**.
- And from a **Room**, you will be able to do many things, including get a **Timeline**, send messages, etc.
Please read the whole documentation to learn more!

View file

@ -0,0 +1,3 @@
# Package org.matrix.android.sdk.api
This is the root package of the API exposed by this SDK.

View file

@ -146,7 +146,7 @@ class CommonTestHelper(context: Context) {
* @param nbOfMessages the number of time the message will be sent * @param nbOfMessages the number of time the message will be sent
*/ */
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> { fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
val timeline = room.createTimeline(null, TimelineSettings(10)) val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
timeline.start() timeline.start()
val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout) val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout)
timeline.dispose() timeline.dispose()
@ -166,12 +166,12 @@ class CommonTestHelper(context: Context) {
.forEach { batchedMessages -> .forEach { batchedMessages ->
batchedMessages.forEach { formattedMessage -> batchedMessages.forEach { formattedMessage ->
if (rootThreadEventId != null) { if (rootThreadEventId != null) {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = rootThreadEventId, rootThreadEventId = rootThreadEventId,
replyInThreadText = formattedMessage replyInThreadText = formattedMessage
) )
} else { } else {
room.sendTextMessage(formattedMessage) room.sendService().sendTextMessage(formattedMessage)
} }
} }
waitWithLatch(timeout) { latch -> waitWithLatch(timeout) { latch ->
@ -216,7 +216,7 @@ class CommonTestHelper(context: Context) {
numberOfMessages: Int, numberOfMessages: Int,
rootThreadEventId: String, rootThreadEventId: String,
timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> { timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
val timeline = room.createTimeline(null, TimelineSettings(10)) val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
timeline.start() timeline.start()
val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId) val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId)
timeline.dispose() timeline.dispose()

View file

@ -70,7 +70,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
if (encryptedRoom) { if (encryptedRoom) {
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
val room = aliceSession.getRoom(roomId)!! val room = aliceSession.getRoom(roomId)!!
room.enableEncryption() room.roomCryptoService().enableEncryption()
val roomSummaryLive = room.getRoomSummaryLive() val roomSummaryLive = room.getRoomSummaryLive()
val roomSummaryObserver = object : Observer<Optional<RoomSummary>> { val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
override fun onChanged(roomSummary: Optional<RoomSummary>) { override fun onChanged(roomSummary: Optional<RoomSummary>) {
@ -109,7 +109,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
} }
} }
bobRoomSummariesLive.observeForever(newRoomObserver) bobRoomSummariesLive.observeForever(newRoomObserver)
aliceRoom.invite(bobSession.myUserId) aliceRoom.membershipService().invite(bobSession.myUserId)
} }
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
@ -117,6 +117,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val roomJoinedObserver = object : Observer<List<RoomSummary>> { val roomJoinedObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) { override fun onChanged(t: List<RoomSummary>?) {
if (bobSession.getRoom(aliceRoomId) if (bobSession.getRoom(aliceRoomId)
?.membershipService()
?.getRoomMember(bobSession.myUserId) ?.getRoomMember(bobSession.myUserId)
?.membership == Membership.JOIN) { ?.membership == Membership.JOIN) {
bobRoomSummariesLive.removeObserver(this) bobRoomSummariesLive.removeObserver(this)
@ -161,7 +162,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
testHelper.runBlockingTest { testHelper.runBlockingTest {
room.invite(samSession.myUserId, null) room.membershipService().invite(samSession.myUserId, null)
} }
testHelper.runBlockingTest { testHelper.runBlockingTest {
@ -261,6 +262,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val newRoomObserver = object : Observer<List<RoomSummary>> { val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) { override fun onChanged(t: List<RoomSummary>?) {
if (bob.getRoom(roomId) if (bob.getRoom(roomId)
?.membershipService()
?.getRoomMember(bob.myUserId) ?.getRoomMember(bob.myUserId)
?.membership == Membership.JOIN) { ?.membership == Membership.JOIN) {
bobRoomSummariesLive.removeObserver(this) bobRoomSummariesLive.removeObserver(this)
@ -373,13 +375,13 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val room = aliceSession.getRoom(roomId)!! val room = aliceSession.getRoom(roomId)!!
testHelper.runBlockingTest { testHelper.runBlockingTest {
room.enableEncryption() room.roomCryptoService().enableEncryption()
} }
val sessions = mutableListOf(aliceSession) val sessions = mutableListOf(aliceSession)
for (index in 1 until numberOfMembers) { for (index in 1 until numberOfMembers) {
val session = testHelper.createAccount("User_$index", defaultSessionParams) val session = testHelper.createAccount("User_$index", defaultSessionParams)
testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } testHelper.runBlockingTest(timeout = 600_000) { room.membershipService().invite(session.myUserId, null) }
println("TEST -> " + session.myUserId + " invited") println("TEST -> " + session.myUserId + " invited")
testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) } testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) }
println("TEST -> " + session.myUserId + " joined") println("TEST -> " + session.myUserId + " joined")

View file

@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
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.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
import org.matrix.android.sdk.api.session.room.getTimelineEvent
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.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
@ -88,7 +89,7 @@ class E2eeSanityTests : InstrumentedTest {
otherAccounts.forEach { otherAccounts.forEach {
testHelper.runBlockingTest { testHelper.runBlockingTest {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}") Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.invite(it.myUserId) aliceRoomPOV.membershipService().invite(it.myUserId)
} }
} }
@ -130,7 +131,7 @@ class E2eeSanityTests : InstrumentedTest {
newAccount.forEach { newAccount.forEach {
testHelper.runBlockingTest { testHelper.runBlockingTest {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}") Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.invite(it.myUserId) aliceRoomPOV.membershipService().invite(it.myUserId)
} }
} }
@ -525,10 +526,10 @@ class E2eeSanityTests : InstrumentedTest {
} }
private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? { private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
aliceRoomPOV.sendTextMessage(text) aliceRoomPOV.sendService().sendTextMessage(text)
var sentEventId: String? = null var sentEventId: String? = null
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch -> testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60)) val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start() timeline.start()
testHelper.retryPeriodicallyWithLatch(latch) { testHelper.retryPeriodicallyWithLatch(latch) {

View file

@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
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.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CryptoTestHelper

View file

@ -98,7 +98,7 @@ class UnwedgingTest : InstrumentedTest {
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20)) val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(20))
bobTimeline.start() bobTimeline.start()
val bobFinalLatch = CountDownLatch(1) val bobFinalLatch = CountDownLatch(1)
@ -129,7 +129,7 @@ class UnwedgingTest : InstrumentedTest {
messagesReceivedByBob = emptyList() messagesReceivedByBob = emptyList()
// - Alice sends a 1st message with a 1st megolm session // - Alice sends a 1st message with a 1st megolm session
roomFromAlicePOV.sendTextMessage("First message") roomFromAlicePOV.sendService().sendTextMessage("First message")
// Wait for the message to be received by Bob // Wait for the message to be received by Bob
testHelper.await(latch) testHelper.await(latch)
@ -157,7 +157,7 @@ class UnwedgingTest : InstrumentedTest {
Timber.i("## CRYPTO | testUnwedging: Alice sends a 2nd message with a 2nd megolm session") Timber.i("## CRYPTO | testUnwedging: Alice sends a 2nd message with a 2nd megolm session")
// - Alice sends a 2nd message with a 2nd megolm session // - Alice sends a 2nd message with a 2nd megolm session
roomFromAlicePOV.sendTextMessage("Second message") roomFromAlicePOV.sendService().sendTextMessage("Second message")
// Wait for the message to be received by Bob // Wait for the message to be received by Bob
testHelper.await(latch) testHelper.await(latch)
@ -186,7 +186,7 @@ class UnwedgingTest : InstrumentedTest {
Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session") Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session // - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
roomFromAlicePOV.sendTextMessage("Third message") roomFromAlicePOV.sendService().sendTextMessage("Third message")
// Bob should not be able to decrypt, because the session key could not be sent // Bob should not be able to decrypt, because the session key could not be sent
} }
bobTimeline.removeListener(bobEventsListener) bobTimeline.removeListener(bobEventsListener)

View file

@ -49,7 +49,7 @@ class EncryptionTest : InstrumentedTest {
fun test_EncryptionEvent() { fun test_EncryptionEvent() {
performTest(roomShouldBeEncrypted = false) { room -> performTest(roomShouldBeEncrypted = false) { room ->
// Send an encryption Event as an Event (and not as a state event) // Send an encryption Event as an Event (and not as a state event)
room.sendEvent( room.sendService().sendEvent(
eventType = EventType.STATE_ROOM_ENCRYPTION, eventType = EventType.STATE_ROOM_ENCRYPTION,
content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
) )
@ -61,7 +61,7 @@ class EncryptionTest : InstrumentedTest {
performTest(roomShouldBeEncrypted = true) { room -> performTest(roomShouldBeEncrypted = true) { room ->
runBlocking { runBlocking {
// Send an encryption Event as a State Event // Send an encryption Event as a State Event
room.sendStateEvent( room.stateService().sendStateEvent(
eventType = EventType.STATE_ROOM_ENCRYPTION, eventType = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "", stateKey = "",
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
@ -76,9 +76,9 @@ class EncryptionTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val room = aliceSession.getRoom(cryptoTestData.roomId)!! val room = aliceSession.getRoom(cryptoTestData.roomId)!!
room.isEncrypted() shouldBe false room.roomCryptoService().isEncrypted() shouldBe false
val timeline = room.createTimeline(null, TimelineSettings(10)) val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
val timelineListener = object : Timeline.Listener { val timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) { override fun onTimelineFailure(throwable: Throwable) {
@ -106,7 +106,7 @@ class EncryptionTest : InstrumentedTest {
testHelper.await(latch) testHelper.await(latch)
timeline.dispose() timeline.dispose()
testHelper.waitWithLatch { testHelper.waitWithLatch {
room.isEncrypted() shouldBe roomShouldBeEncrypted room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
it.countDown() it.countDown()
} }
cryptoTestData.cleanUp(testHelper) cryptoTestData.cleanUp(testHelper)

View file

@ -51,6 +51,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
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.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
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.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@ -84,7 +85,7 @@ class KeyShareTests : InstrumentedTest {
val room = aliceSession.getRoom(roomId) val room = aliceSession.getRoom(roomId)
assertNotNull(room) assertNotNull(room)
Thread.sleep(4_000) Thread.sleep(4_000)
assertTrue(room?.isEncrypted() == true) assertTrue(room?.roomCryptoService()?.isEncrypted() == true)
val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
// Open a new sessionx // Open a new sessionx
@ -351,7 +352,7 @@ class KeyShareTests : InstrumentedTest {
val roomAlicePov = aliceSession.getRoom(roomId) val roomAlicePov = aliceSession.getRoom(roomId)
assertNotNull(roomAlicePov) assertNotNull(roomAlicePov)
Thread.sleep(1_000) Thread.sleep(1_000)
assertTrue(roomAlicePov?.isEncrypted() == true) assertTrue(roomAlicePov?.roomCryptoService()?.isEncrypted() == true)
val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
// Create bob session // Create bob session
@ -375,7 +376,7 @@ class KeyShareTests : InstrumentedTest {
// Let alice invite bob // Let alice invite bob
commonTestHelper.runBlockingTest { commonTestHelper.runBlockingTest {
roomAlicePov.invite(bobSession.myUserId, null) roomAlicePov.membershipService().invite(bobSession.myUserId, null)
} }
commonTestHelper.runBlockingTest { commonTestHelper.runBlockingTest {

View file

@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
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.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.MockOkHttpInterceptor import org.matrix.android.sdk.common.MockOkHttpInterceptor

View file

@ -81,7 +81,7 @@ class ThreadMessagingTest : InstrumentedTest {
replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId) replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
// The init normal message should now be a root thread event // The init normal message should now be a root thread event
val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
timeline.start() timeline.start()
aliceSession.startSync(true) aliceSession.startSync(true)
@ -142,7 +142,7 @@ class ThreadMessagingTest : InstrumentedTest {
replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId) replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
// The init normal message should now be a root thread event // The init normal message should now be a root thread event
val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
timeline.start() timeline.start()
aliceSession.startSync(true) aliceSession.startSync(true)
@ -215,7 +215,7 @@ class ThreadMessagingTest : InstrumentedTest {
} }
// The init normal message should now be a root thread event // The init normal message should now be a root thread event
val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
timeline.start() timeline.start()
aliceSession.startSync(true) aliceSession.startSync(true)
@ -310,7 +310,7 @@ class ThreadMessagingTest : InstrumentedTest {
} }
// The init normal message should now be a root thread event // The init normal message should now be a root thread event
val timeline = aliceRoom.createTimeline(null, TimelineSettings(30)) val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
timeline.start() timeline.start()
aliceSession.startSync(true) aliceSession.startSync(true)

View file

@ -58,10 +58,10 @@ class PollAggregationTest : InstrumentedTest {
val roomFromBobPOV = cryptoTestData.secondSession!!.getRoom(cryptoTestData.roomId)!! val roomFromBobPOV = cryptoTestData.secondSession!!.getRoom(cryptoTestData.roomId)!!
// Bob creates a poll // Bob creates a poll
roomFromBobPOV.sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions) roomFromBobPOV.sendService().sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions)
aliceSession.startSync(true) aliceSession.startSync(true)
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30))
aliceTimeline.start() aliceTimeline.start()
val TOTAL_TEST_COUNT = 7 val TOTAL_TEST_COUNT = 7
@ -84,37 +84,37 @@ class PollAggregationTest : InstrumentedTest {
// Poll has just been created. // Poll has just been created.
testInitialPollConditions(pollContent, pollSummary) testInitialPollConditions(pollContent, pollSummary)
lock.countDown() lock.countDown()
roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "") roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
} }
TOTAL_TEST_COUNT - 1 -> { TOTAL_TEST_COUNT - 1 -> {
// Bob: Option 1 // Bob: Option 1
testBobVotesOption1(pollContent, pollSummary) testBobVotesOption1(pollContent, pollSummary)
lock.countDown() lock.countDown()
roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
} }
TOTAL_TEST_COUNT - 2 -> { TOTAL_TEST_COUNT - 2 -> {
// Bob: Option 2 // Bob: Option 2
testBobChangesVoteToOption2(pollContent, pollSummary) testBobChangesVoteToOption2(pollContent, pollSummary)
lock.countDown() lock.countDown()
roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
} }
TOTAL_TEST_COUNT - 3 -> { TOTAL_TEST_COUNT - 3 -> {
// Alice: Option 2, Bob: Option 2 // Alice: Option 2, Bob: Option 2
testAliceAndBobVoteToOption2(pollContent, pollSummary) testAliceAndBobVoteToOption2(pollContent, pollSummary)
lock.countDown() lock.countDown()
roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "") roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
} }
TOTAL_TEST_COUNT - 4 -> { TOTAL_TEST_COUNT - 4 -> {
// Alice: Option 1, Bob: Option 2 // Alice: Option 1, Bob: Option 2
testAliceVotesOption1AndBobVotesOption2(pollContent, pollSummary) testAliceVotesOption1AndBobVotesOption2(pollContent, pollSummary)
lock.countDown() lock.countDown()
roomFromBobPOV.endPoll(pollEventId) roomFromBobPOV.sendService().endPoll(pollEventId)
} }
TOTAL_TEST_COUNT - 5 -> { TOTAL_TEST_COUNT - 5 -> {
// Alice: Option 1, Bob: Option 2 [poll is ended] // Alice: Option 1, Bob: Option 2 [poll is ended]
testEndedPoll(pollSummary) testEndedPoll(pollSummary)
lock.countDown() lock.countDown()
roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "") roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
} }
TOTAL_TEST_COUNT - 6 -> { TOTAL_TEST_COUNT - 6 -> {
// Alice: Option 1 (ignore change), Bob: Option 2 [poll is ended] // Alice: Option 1 (ignore change), Bob: Option 2 [poll is ended]

View file

@ -75,7 +75,7 @@ class TimelineForwardPaginationTest : InstrumentedTest {
// Alice clear the cache and restart the sync // Alice clear the cache and restart the sync
commonTestHelper.clearCacheAndSync(aliceSession) commonTestHelper.clearCacheAndSync(aliceSession)
val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30)) val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30))
aliceTimeline.start() aliceTimeline.start()
// Alice sees the 10 last message of the room, and can only navigate BACKWARD // Alice sees the 10 last message of the room, and can only navigate BACKWARD

View file

@ -63,7 +63,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
bobTimeline.start() bobTimeline.start()
run { run {

View file

@ -66,7 +66,7 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
message, message,
numberOfMessagesToSent) numberOfMessagesToSent)
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30)) val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
bobTimeline.start() bobTimeline.start()
commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) { commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) {

View file

@ -72,7 +72,7 @@ class TimelineWithManyMembersTest : InstrumentedTest {
for (index in 1 until cryptoTestData.sessions.size) { for (index in 1 until cryptoTestData.sessions.size) {
val session = cryptoTestData.sessions[index] val session = cryptoTestData.sessions[index]
val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!! val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!!
val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30)) val timelineForCurrentMember = roomForCurrentMember.timelineService().createTimeline(null, TimelineSettings(30))
timelineForCurrentMember.start() timelineForCurrentMember.start()
session.startSync(true) session.startSync(true)

View file

@ -29,6 +29,7 @@ import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent

View file

@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -499,7 +500,7 @@ class SpaceHierarchyTest : InstrumentedTest {
)) ))
commonTestHelper.runBlockingTest { commonTestHelper.runBlockingTest {
aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null) aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
} }
commonTestHelper.runBlockingTest { commonTestHelper.runBlockingTest {
@ -509,7 +510,7 @@ class SpaceHierarchyTest : InstrumentedTest {
var bobRoomId = "" var bobRoomId = ""
commonTestHelper.waitWithLatch { commonTestHelper.waitWithLatch {
bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" }) bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" })
bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId) bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId)
it.countDown() it.countDown()
} }
@ -554,7 +555,7 @@ class SpaceHierarchyTest : InstrumentedTest {
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
?.toContent() ?.toContent()
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!) room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
it.countDown() it.countDown()
} }

View file

@ -44,7 +44,7 @@ class RoomMemberCountCondition(
// Parse the is field into prefix and number the first time // Parse the is field into prefix and number the first time
val (prefix, count) = parseIsField() ?: return false val (prefix, count) = parseIsField() ?: return false
val numMembers = room.getNumberOfJoinedMembers() val numMembers = room.membershipService().getNumberOfJoinedMembers()
return when (prefix) { return when (prefix) {
"<" -> numMembers < count "<" -> numMembers < count

View file

@ -44,26 +44,7 @@ import org.matrix.android.sdk.api.util.Optional
/** /**
* This interface defines methods to interact within a room. * This interface defines methods to interact within a room.
*/ */
interface Room : interface Room {
TimelineService,
ThreadsService,
ThreadsLocalService,
SendService,
DraftService,
ReadService,
TypingService,
AliasService,
TagsService,
MembershipService,
StateService,
UploadsService,
ReportingService,
RoomCallService,
RelationService,
RoomCryptoService,
RoomPushRuleService,
RoomAccountDataService,
RoomVersionService {
val coroutineDispatchers: MatrixCoroutineDispatchers val coroutineDispatchers: MatrixCoroutineDispatchers
@ -87,4 +68,99 @@ interface Room :
* Use this room as a Space, if the type is correct. * Use this room as a Space, if the type is correct.
*/ */
fun asSpace(): Space? fun asSpace(): Space?
/**
* Get the TimelineService associated to this Room
*/
fun timelineService(): TimelineService
/**
* Get the ThreadsService associated to this Room
*/
fun threadsService(): ThreadsService
/**
* Get the ThreadsLocalService associated to this Room
*/
fun threadsLocalService(): ThreadsLocalService
/**
* Get the SendService associated to this Room
*/
fun sendService(): SendService
/**
* Get the DraftService associated to this Room
*/
fun draftService(): DraftService
/**
* Get the ReadService associated to this Room
*/
fun readService(): ReadService
/**
* Get the TypingService associated to this Room
*/
fun typingService(): TypingService
/**
* Get the AliasService associated to this Room
*/
fun aliasService(): AliasService
/**
* Get the TagsService associated to this Room
*/
fun tagsService(): TagsService
/**
* Get the MembershipService associated to this Room
*/
fun membershipService(): MembershipService
/**
* Get the StateService associated to this Room
*/
fun stateService(): StateService
/**
* Get the UploadsService associated to this Room
*/
fun uploadsService(): UploadsService
/**
* Get the ReportingService associated to this Room
*/
fun reportingService(): ReportingService
/**
* Get the RoomCallService associated to this Room
*/
fun roomCallService(): RoomCallService
/**
* Get the RelationService associated to this Room
*/
fun relationService(): RelationService
/**
* Get the RoomCryptoService associated to this Room
*/
fun roomCryptoService(): RoomCryptoService
/**
* Get the RoomPushRuleService associated to this Room
*/
fun roomPushRuleService(): RoomPushRuleService
/**
* Get the RoomAccountDataService associated to this Room
*/
fun roomAccountDataService(): RoomAccountDataService
/**
* Get the RoomVersionService associated to this Room
*/
fun roomVersionService(): RoomVersionService
} }

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
/**
* Get a TimelineEvent using the TimelineService of a Room
*/
fun Room.getTimelineEvent(eventId: String): TimelineEvent? =
timelineService().getTimelineEvent(eventId)
/**
* Get a StateEvent using the StateService of a Room
*/
fun Room.getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? =
stateService().getStateEvent(eventType, stateKey)

View file

@ -15,10 +15,12 @@
*/ */
package org.matrix.android.sdk.api.session.room.model package org.matrix.android.sdk.api.session.room.model
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
data class EventAnnotationsSummary( data class EventAnnotationsSummary(
val eventId: String,
val reactionsSummary: List<ReactionAggregatedSummary> = emptyList(), val reactionsSummary: List<ReactionAggregatedSummary> = emptyList(),
val editSummary: EditAggregatedSummary? = null, val editSummary: EditAggregatedSummary? = null,
val pollResponseSummary: PollResponseAggregatedSummary? = null, val pollResponseSummary: PollResponseAggregatedSummary? = null,
val referencesAggregatedSummary: ReferencesAggregatedSummary? = null val referencesAggregatedSummary: ReferencesAggregatedSummary? = null,
val liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummary? = null,
) )

View file

@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.events.model.Content
* of all events that are referencing the 'eventId' event via the RelationType.REFERENCE * of all events that are referencing the 'eventId' event via the RelationType.REFERENCE
*/ */
data class ReferencesAggregatedSummary( data class ReferencesAggregatedSummary(
val eventId: String,
val content: Content?, val content: Content?,
val sourceEvents: List<String>, val sourceEvents: List<String>,
val localEchos: List<String> val localEchos: List<String>

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.model.livelocation
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
/**
* Aggregation info concerning a live location share.
*/
data class LiveLocationShareAggregatedSummary(
val isActive: Boolean?,
val endOfLiveTimestampMillis: Long?,
val lastLocationDataContent: MessageBeaconLocationDataContent?,
)

View file

@ -14,25 +14,28 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.api.session.room.model.livelocation package org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
/**
* Content of the state event of type
* [EventType.STATE_ROOM_BEACON_INFO][org.matrix.android.sdk.api.session.events.model.EventType.STATE_ROOM_BEACON_INFO]
*
* It contains general info related to a live location share.
* Locations are sent in a different message related to the state event.
* See [MessageBeaconLocationDataContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
*/
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class LiveLocationBeaconContent( data class MessageBeaconInfoContent(
/** /**
* Local message type, not from server * Local message type, not from server
*/ */
@Transient @Transient
override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION_STATE, override val msgType: String = MessageType.MSGTYPE_BEACON_INFO,
@Json(name = "body") override val body: String = "", @Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@ -54,26 +57,16 @@ data class LiveLocationBeaconContent(
/** /**
* Beacon creation timestamp. * Beacon creation timestamp.
*/ */
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val timestampAsMilliseconds: Long? = null, @Json(name = "m.ts") val timestampMillis: Long? = null,
/** /**
* Live location asset type. * Live location asset type.
*/ */
@Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF), @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
@Json(name = "m.asset") val locationAsset: LocationAsset? = null, @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
/**
* Client side tracking of the last location
*/
var lastLocationContent: MessageLiveLocationContent? = null,
/**
* Client side tracking of whether the beacon has timed out.
*/
var hasTimedOut: Boolean = false
) : MessageContent { ) : MessageContent {
fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
} }

View file

@ -21,13 +21,21 @@ import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
/**
* Content of the state event of type
* [EventType.BEACON_LOCATION_DATA][org.matrix.android.sdk.api.session.events.model.EventType.BEACON_LOCATION_DATA]
*
* It contains location data related to a live location share.
* It is related to the state event that originally started the live.
* See [MessageBeaconInfoContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent]
*/
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageLiveLocationContent( data class MessageBeaconLocationDataContent(
/** /**
* Local message type, not from server * Local message type, not from server
*/ */
@Transient @Transient
override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION, override val msgType: String = MessageType.MSGTYPE_BEACON_LOCATION_DATA,
@Json(name = "body") override val body: String = "", @Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@ -42,11 +50,11 @@ data class MessageLiveLocationContent(
/** /**
* Exact time that the data in the event refers to (milliseconds since the UNIX epoch) * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
*/ */
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val timestampAsMilliseconds: Long? = null @Json(name = "m.ts") val timestampMillis: Long? = null
) : MessageContent { ) : MessageContent {
fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
} }

View file

@ -49,8 +49,8 @@ data class MessageLocationContent(
/** /**
* Exact time that the data in the event refers to (milliseconds since the UNIX epoch) * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
*/ */
@Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null, @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val ts: Long? = null, @Json(name = "m.ts") val timestampMillis: Long? = null,
@Json(name = "org.matrix.msc1767.text") val unstableText: String? = null, @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
@Json(name = "m.text") val text: String? = null, @Json(name = "m.text") val text: String? = null,
/** /**
@ -66,7 +66,7 @@ data class MessageLocationContent(
fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
fun getBestTs() = ts ?: unstableTs fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
fun getBestText() = text ?: unstableText fun getBestText() = text ?: unstableText

View file

@ -41,6 +41,6 @@ object MessageType {
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
// Fake message types for live location events to be able to inherit them from MessageContent // Fake message types for live location events to be able to inherit them from MessageContent
const val MSGTYPE_LIVE_LOCATION_STATE = "org.matrix.android.sdk.livelocation.state" const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
const val MSGTYPE_LIVE_LOCATION = "org.matrix.android.sdk.livelocation" const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
} }

View file

@ -84,8 +84,9 @@ interface StateService {
* @param eventType The type of event to send. * @param eventType The type of event to send.
* @param stateKey The state_key for the state to send. Can be an empty string. * @param stateKey The state_key for the state to send. Can be an empty string.
* @param body The content object of the event; the fields in this object will vary depending on the type of event * @param body The content object of the event; the fields in this object will vary depending on the type of event
* @return the id of the created state event
*/ */
suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict) suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict): String
/** /**
* Get a state event of the room * Get a state event of the room

View file

@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.isSticker
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.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.ReadReceipt
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
@ -139,7 +139,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
return when (root.getClearType()) { return when (root.getClearType()) {
EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>() EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>() in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>()
in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<LiveLocationBeaconContent>() in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
} }
} }

View file

@ -19,6 +19,9 @@ package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.RealmResults import io.realm.RealmResults
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntity
@ -38,10 +41,22 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true)
} }
private val onResultsChangedFlow = MutableSharedFlow<RealmResults<EventInsertEntity>>()
init {
onResultsChangedFlow
.onEach { handleChange(it) }
.launchIn(observerScope)
}
override fun onChange(results: RealmResults<EventInsertEntity>) { override fun onChange(results: RealmResults<EventInsertEntity>) {
if (!results.isLoaded || results.isEmpty()) { if (!results.isLoaded || results.isEmpty()) {
return return
} }
observerScope.launch { onResultsChangedFlow.emit(results) }
}
private suspend fun handleChange(results: RealmResults<EventInsertEntity>) {
val idsToDeleteAfterProcess = ArrayList<String>() val idsToDeleteAfterProcess = ArrayList<String>()
val filteredEvents = ArrayList<EventInsertEntity>(results.size) val filteredEvents = ArrayList<EventInsertEntity>(results.size)
Timber.v("EventInsertEntity updated with ${results.size} results in db") Timber.v("EventInsertEntity updated with ${results.size} results in db")
@ -58,7 +73,7 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
} }
idsToDeleteAfterProcess.add(it.eventId) idsToDeleteAfterProcess.add(it.eventId)
} }
observerScope.launch {
awaitTransaction(realmConfiguration) { realm -> awaitTransaction(realmConfiguration) { realm ->
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ") Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
filteredEvents.forEach { eventInsert -> filteredEvents.forEach { eventInsert ->
@ -82,7 +97,6 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
} }
processors.forEach { it.onPostProcess() } processors.forEach { it.onPostProcess() }
} }
}
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
return processors.any { return processors.any {

View file

@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000 override fun hashCode() = 1000
val schemaVersion = 26L val schemaVersion = 27L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Realm Session from $oldVersion to $newVersion") Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
@ -89,5 +90,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 24) MigrateSessionTo024(realm).perform() if (oldVersion < 24) MigrateSessionTo024(realm).perform()
if (oldVersion < 25) MigrateSessionTo025(realm).perform() if (oldVersion < 25) MigrateSessionTo025(realm).perform()
if (oldVersion < 26) MigrateSessionTo026(realm).perform() if (oldVersion < 26) MigrateSessionTo026(realm).perform()
if (oldVersion < 27) MigrateSessionTo027(realm).perform()
} }
} }

View file

@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
internal object EventAnnotationsSummaryMapper { internal object EventAnnotationsSummaryMapper {
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary { fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
return EventAnnotationsSummary( return EventAnnotationsSummary(
eventId = annotationsSummary.eventId,
reactionsSummary = annotationsSummary.reactionsSummary.toList().map { reactionsSummary = annotationsSummary.reactionsSummary.toList().map {
ReactionAggregatedSummary( ReactionAggregatedSummary(
it.key, it.key,
@ -50,7 +49,6 @@ internal object EventAnnotationsSummaryMapper {
}, },
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let { referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
ReferencesAggregatedSummary( ReferencesAggregatedSummary(
it.eventId,
ContentMapper.map(it.content), ContentMapper.map(it.content),
it.sourceEvents.toList(), it.sourceEvents.toList(),
it.sourceLocalEcho.toList() it.sourceLocalEcho.toList()
@ -58,8 +56,10 @@ internal object EventAnnotationsSummaryMapper {
}, },
pollResponseSummary = annotationsSummary.pollResponseSummary?.let { pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
PollResponseAggregatedSummaryEntityMapper.map(it) PollResponseAggregatedSummaryEntityMapper.map(it)
},
liveLocationShareAggregatedSummary = annotationsSummary.liveLocationShareAggregatedSummary?.let {
LiveLocationShareAggregatedSummaryMapper.map(it)
} }
) )
} }
} }

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 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.database.mapper
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
internal object LiveLocationShareAggregatedSummaryMapper {
fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
return LiveLocationShareAggregatedSummary(
isActive = entity.isActive,
endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,
lastLocationDataContent = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
)
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2022 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.database.migration
import io.realm.DynamicRealm
import io.realm.FieldAttribute
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* Migrating to:
* Live location sharing aggregated summary
*/
internal class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) {
override fun doMigrate(realm: DynamicRealm) {
val liveLocationSummaryEntity = realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
?: realm.schema.create("LiveLocationShareAggregatedSummaryEntity")
.addField(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, String::class.java, FieldAttribute.REQUIRED)
.addField(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, String::class.java, FieldAttribute.REQUIRED)
.addField(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, Boolean::class.java)
.setNullable(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
.addField(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, Long::class.java)
.setNullable(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, true)
.addField(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT, String::class.java)
?: return
realm.schema.get("EventAnnotationsSummaryEntity")
?.addRealmObjectField(EventAnnotationsSummaryEntityFields.LIVE_LOCATION_SHARE_AGGREGATED_SUMMARY.`$`, liveLocationSummaryEntity)
}
}

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import timber.log.Timber import timber.log.Timber
internal open class EventAnnotationsSummaryEntity( internal open class EventAnnotationsSummaryEntity(
@ -27,7 +28,8 @@ internal open class EventAnnotationsSummaryEntity(
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(), var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
var editSummary: EditAggregatedSummaryEntity? = null, var editSummary: EditAggregatedSummaryEntity? = null,
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null, var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null,
var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null,
) : RealmObject() { ) : RealmObject() {
/** /**

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.database.model package org.matrix.android.sdk.internal.database.model
import io.realm.annotations.RealmModule import io.realm.annotations.RealmModule
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
@ -47,6 +48,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
EditAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class,
EditionOfEvent::class, EditionOfEvent::class,
PollResponseAggregatedSummaryEntity::class, PollResponseAggregatedSummaryEntity::class,
LiveLocationShareAggregatedSummaryEntity::class,
ReferencesAggregatedSummaryEntity::class, ReferencesAggregatedSummaryEntity::class,
PushRulesEntity::class, PushRulesEntity::class,
PushRuleEntity::class, PushRuleEntity::class,

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022 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.database.model.livelocation
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
/**
* Aggregation info concerning a live location share.
*/
internal open class LiveLocationShareAggregatedSummaryEntity(
/**
* Event id of the event that started the live.
*/
@PrimaryKey
var eventId: String = "",
var roomId: String = "",
var isActive: Boolean? = null,
var endOfLiveTimestampMillis: Long? = null,
/**
* For now we persist this as a JSON for greater flexibility
* @see [org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
*/
var lastLocationContent: String? = null,
) : RealmObject() {
companion object
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2022 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.database.query
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
realm: Realm,
roomId: String,
eventId: String,
): RealmQuery<LiveLocationShareAggregatedSummaryEntity> {
return realm.where<LiveLocationShareAggregatedSummaryEntity>()
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId)
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
}
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.create(
realm: Realm,
roomId: String,
eventId: String,
): LiveLocationShareAggregatedSummaryEntity {
val obj = realm.createObject(LiveLocationShareAggregatedSummaryEntity::class.java, eventId).apply {
this.roomId = roomId
}
val annotationSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId = roomId, eventId = eventId)
annotationSummary.liveLocationShareAggregatedSummary = obj
return obj
}
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
realm: Realm,
roomId: String,
eventId: String,
): LiveLocationShareAggregatedSummaryEntity {
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
}

View file

@ -72,6 +72,7 @@ internal class ViaParameterFinder @Inject constructor(
*/ */
private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> { private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
return roomGetterProvider.get().getRoom(roomId) return roomGetterProvider.get().getRoom(roomId)
?.membershipService()
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId } ?.map { it.userId }
.orEmpty() .orEmpty()
@ -84,6 +85,7 @@ internal class ViaParameterFinder @Inject constructor(
// It may not be possible for a user to join a room if there's no overlap between these // It may not be possible for a user to join a room if there's no overlap between these
fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> { fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
val userThatCanInvite = roomGetterProvider.get().getRoom(roomId) val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
?.membershipService()
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId } ?.map { it.userId }
?.filter { userCanInvite(userId, roomId) } ?.filter { userCanInvite(userId, roomId) }

View file

@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition
import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition
import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition
import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.RoomGetter
@ -60,7 +61,7 @@ internal class DefaultConditionResolver @Inject constructor(
condition: ContainsDisplayNameCondition): Boolean { condition: ContainsDisplayNameCondition): Boolean {
val roomId = event.roomId ?: return false val roomId = event.roomId ?: return false
val room = roomGetter.getRoom(roomId) ?: return false val room = roomGetter.getRoom(roomId) ?: return false
val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false val myDisplayName = room.membershipService().getRoomMember(userId)?.displayName ?: return false
return condition.isSatisfied(event, myDisplayName) return condition.isSatisfied(event, myDisplayName)
} }
} }

View file

@ -18,13 +18,11 @@ package org.matrix.android.sdk.internal.session.room
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.EventType
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.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService
import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.MembershipService
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.RoomType
@ -44,15 +42,14 @@ import org.matrix.android.sdk.api.session.room.uploads.UploadsService
import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.api.session.room.version.RoomVersionService
import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.awaitCallback
import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
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.space.DefaultSpace import org.matrix.android.sdk.internal.session.space.DefaultSpace
import java.security.InvalidParameterException
internal class DefaultRoom(override val roomId: String, internal class DefaultRoom(
override val roomId: String,
private val roomSummaryDataSource: RoomSummaryDataSource, private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomCryptoService: RoomCryptoService,
private val timelineService: TimelineService, private val timelineService: TimelineService,
private val threadsService: ThreadsService, private val threadsService: ThreadsService,
private val threadsLocalService: ThreadsLocalService, private val threadsLocalService: ThreadsLocalService,
@ -66,35 +63,14 @@ internal class DefaultRoom(override val roomId: String,
private val typingService: TypingService, private val typingService: TypingService,
private val aliasService: AliasService, private val aliasService: AliasService,
private val tagsService: TagsService, private val tagsService: TagsService,
private val cryptoService: CryptoService,
private val relationService: RelationService, private val relationService: RelationService,
private val roomMembersService: MembershipService, private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService, private val roomPushRuleService: RoomPushRuleService,
private val roomAccountDataService: RoomAccountDataService, private val roomAccountDataService: RoomAccountDataService,
private val roomVersionService: RoomVersionService, private val roomVersionService: RoomVersionService,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder, private val viaParameterFinder: ViaParameterFinder,
override val coroutineDispatchers: MatrixCoroutineDispatchers override val coroutineDispatchers: MatrixCoroutineDispatchers
) : ) : Room {
Room,
TimelineService by timelineService,
ThreadsService by threadsService,
ThreadsLocalService by threadsLocalService,
SendService by sendService,
DraftService by draftService,
StateService by stateService,
UploadsService by uploadsService,
ReportingService by reportingService,
RoomCallService by roomCallService,
ReadService by readService,
TypingService by typingService,
AliasService by aliasService,
TagsService by tagsService,
RelationService by relationService,
MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService,
RoomAccountDataService by roomAccountDataService,
RoomVersionService by roomVersionService {
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> { override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
return roomSummaryDataSource.getRoomSummaryLive(roomId) return roomSummaryDataSource.getRoomSummaryLive(roomId)
@ -104,49 +80,28 @@ internal class DefaultRoom(override val roomId: String,
return roomSummaryDataSource.getRoomSummary(roomId) return roomSummaryDataSource.getRoomSummary(roomId)
} }
override fun isEncrypted(): Boolean {
return cryptoService.isRoomEncrypted(roomId)
}
override fun encryptionAlgorithm(): String? {
return cryptoService.getEncryptionAlgorithm(roomId)
}
override fun shouldEncryptForInvitedMembers(): Boolean {
return cryptoService.shouldEncryptForInvitedMembers(roomId)
}
override suspend fun prepareToEncrypt() {
awaitCallback<Unit> {
cryptoService.prepareToEncrypt(roomId, it)
}
}
override suspend fun enableEncryption(algorithm: String, force: Boolean) {
when {
(!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> {
throw IllegalStateException("Encryption is already enabled for this room")
}
(!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM) -> {
throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
}
else -> {
val params = SendStateTask.Params(
roomId = roomId,
stateKey = "",
eventType = EventType.STATE_ROOM_ENCRYPTION,
body = mapOf(
"algorithm" to algorithm
)
)
sendStateTask.execute(params)
}
}
}
override fun asSpace(): Space? { override fun asSpace(): Space? {
if (roomSummary()?.roomType != RoomType.SPACE) return null if (roomSummary()?.roomType != RoomType.SPACE) return null
return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder) return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
} }
override fun timelineService() = timelineService
override fun threadsService() = threadsService
override fun threadsLocalService() = threadsLocalService
override fun sendService() = sendService
override fun draftService() = draftService
override fun stateService() = stateService
override fun uploadsService() = uploadsService
override fun reportingService() = reportingService
override fun roomCallService() = roomCallService
override fun readService() = readService
override fun typingService() = typingService
override fun aliasService() = aliasService
override fun tagsService() = tagsService
override fun relationService() = relationService
override fun roomCryptoService() = roomCryptoService
override fun membershipService() = roomMembersService
override fun roomPushRuleService() = roomPushRuleService
override fun roomAccountDataService() = roomAccountDataService
override fun roomVersionService() = roomVersionService
} }

View file

@ -28,14 +28,16 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
import org.matrix.android.sdk.api.session.room.model.VoteInfo import org.matrix.android.sdk.api.session.room.model.VoteInfo
import org.matrix.android.sdk.api.session.room.model.VoteSummary import org.matrix.android.sdk.api.session.room.model.VoteSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
@ -93,7 +95,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// EventType.KEY_VERIFICATION_READY, // EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_KEY,
EventType.ENCRYPTED EventType.ENCRYPTED
) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.BEACON_LOCATION_DATA ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
return allowedTypes.contains(eventType) return allowedTypes.contains(eventType)
@ -191,8 +193,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
} }
in EventType.BEACON_LOCATION_DATA -> { in EventType.BEACON_LOCATION_DATA -> {
event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let { event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho)
} }
} }
} }
@ -253,9 +255,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
handleEndPoll(realm, event, it, roomId, isLocalEcho) handleEndPoll(realm, event, it, roomId, isLocalEcho)
} }
} }
in EventType.BEACON_LOCATION_DATA -> { in EventType.STATE_ROOM_BEACON_INFO -> {
event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let { event.content.toModel<MessageBeaconInfoContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho)
} }
} }
else -> Timber.v("UnHandled event ${event.eventId}") else -> Timber.v("UnHandled event ${event.eventId}")

View file

@ -194,7 +194,8 @@ internal interface RoomAPI {
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}") @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
suspend fun sendStateEvent(@Path("roomId") roomId: String, suspend fun sendStateEvent(@Path("roomId") roomId: String,
@Path("state_event_type") stateEventType: String, @Path("state_event_type") stateEventType: String,
@Body params: JsonDict) @Body params: JsonDict
): SendResponse
/** /**
* Send a generic state event * Send a generic state event
@ -208,7 +209,8 @@ internal interface RoomAPI {
suspend fun sendStateEvent(@Path("roomId") roomId: String, suspend fun sendStateEvent(@Path("roomId") roomId: String,
@Path("state_event_type") stateEventType: String, @Path("state_event_type") stateEventType: String,
@Path("state_key") stateKey: String, @Path("state_key") stateKey: String,
@Body params: JsonDict) @Body params: JsonDict
): SendResponse
/** /**
* Get state events of a room * Get state events of a room

View file

@ -17,13 +17,13 @@
package org.matrix.android.sdk.internal.session.room package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService
import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
import org.matrix.android.sdk.internal.session.room.crypto.DefaultRoomCryptoService
import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService
import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService
@ -32,7 +32,6 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultRelationServ
import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService
import org.matrix.android.sdk.internal.session.room.send.DefaultSendService import org.matrix.android.sdk.internal.session.room.send.DefaultSendService
import org.matrix.android.sdk.internal.session.room.state.DefaultStateService import org.matrix.android.sdk.internal.session.room.state.DefaultStateService
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
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.room.tags.DefaultTagsService import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService
@ -48,7 +47,7 @@ internal interface RoomFactory {
} }
@SessionScope @SessionScope
internal class DefaultRoomFactory @Inject constructor(private val cryptoService: CryptoService, internal class DefaultRoomFactory @Inject constructor(
private val roomSummaryDataSource: RoomSummaryDataSource, private val roomSummaryDataSource: RoomSummaryDataSource,
private val timelineServiceFactory: DefaultTimelineService.Factory, private val timelineServiceFactory: DefaultTimelineService.Factory,
private val threadsServiceFactory: DefaultThreadsService.Factory, private val threadsServiceFactory: DefaultThreadsService.Factory,
@ -64,19 +63,20 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val aliasServiceFactory: DefaultAliasService.Factory, private val aliasServiceFactory: DefaultAliasService.Factory,
private val tagsServiceFactory: DefaultTagsService.Factory, private val tagsServiceFactory: DefaultTagsService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory, private val relationServiceFactory: DefaultRelationService.Factory,
private val roomCryptoServiceFactory: DefaultRoomCryptoService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
private val roomVersionServiceFactory: DefaultRoomVersionService.Factory, private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder, private val viaParameterFinder: ViaParameterFinder,
private val coroutineDispatchers: MatrixCoroutineDispatchers) : private val coroutineDispatchers: MatrixCoroutineDispatchers
RoomFactory { ) : RoomFactory {
override fun create(roomId: String): Room { override fun create(roomId: String): Room {
return DefaultRoom( return DefaultRoom(
roomId = roomId, roomId = roomId,
roomSummaryDataSource = roomSummaryDataSource, roomSummaryDataSource = roomSummaryDataSource,
roomCryptoService = roomCryptoServiceFactory.create(roomId),
timelineService = timelineServiceFactory.create(roomId), timelineService = timelineServiceFactory.create(roomId),
threadsService = threadsServiceFactory.create(roomId), threadsService = threadsServiceFactory.create(roomId),
threadsLocalService = threadsLocalServiceFactory.create(roomId), threadsLocalService = threadsLocalServiceFactory.create(roomId),
@ -90,13 +90,11 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
typingService = typingServiceFactory.create(roomId), typingService = typingServiceFactory.create(roomId),
aliasService = aliasServiceFactory.create(roomId), aliasService = aliasServiceFactory.create(roomId),
tagsService = tagsServiceFactory.create(roomId), tagsService = tagsServiceFactory.create(roomId),
cryptoService = cryptoService,
relationService = relationServiceFactory.create(roomId), relationService = relationServiceFactory.create(roomId),
roomMembersService = membershipServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
roomAccountDataService = roomAccountDataServiceFactory.create(roomId), roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
roomVersionService = roomVersionServiceFactory.create(roomId), roomVersionService = roomVersionServiceFactory.create(roomId),
sendStateTask = sendStateTask,
viaParameterFinder = viaParameterFinder, viaParameterFinder = viaParameterFinder,
coroutineDispatchers = coroutineDispatchers coroutineDispatchers = coroutineDispatchers
) )

View file

@ -17,69 +17,78 @@
package org.matrix.android.sdk.internal.session.room.aggregation.livelocation package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import io.realm.Realm import io.realm.Realm
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orTrue
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.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.getOrCreate
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor { internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor {
override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) { override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
val locationSenderId = event.senderId ?: return if (event.senderId.isNullOrEmpty() || isLocalEcho) {
// We shouldn't process local echos
if (isLocalEcho) {
return return
} }
// A beacon info state event has to be sent before sending location val targetEventId = if (content.isLive.orTrue()) {
// TODO handle missing check of m_relatesTo field event.eventId
var beaconInfoEntity: CurrentStateEventEntity? = null
val eventTypesIterator = EventType.STATE_ROOM_BEACON_INFO.iterator()
while (beaconInfoEntity == null && eventTypesIterator.hasNext()) {
beaconInfoEntity = CurrentStateEventEntity.getOrNull(realm, roomId, locationSenderId, eventTypesIterator.next())
}
if (beaconInfoEntity == null) {
Timber.v("## LIVE LOCATION. There is not any beacon info which should be emitted before sending location updates")
return
}
val beaconInfoContent = ContentMapper.map(beaconInfoEntity.root?.content)?.toModel<LiveLocationBeaconContent>(catchError = true)
if (beaconInfoContent == null) {
Timber.v("## LIVE LOCATION. Beacon info content is invalid")
return
}
// Check if live location is ended
if (!beaconInfoContent.isLive.orFalse()) {
Timber.v("## LIVE LOCATION. Beacon info is not live anymore")
return
}
// Check if beacon info is outdated
if (isBeaconInfoOutdated(beaconInfoContent, content)) {
Timber.v("## LIVE LOCATION. Beacon info has timeout")
beaconInfoContent.hasTimedOut = true
} else { } else {
beaconInfoContent.lastLocationContent = content // when live is set to false, we use the id of the event that should have been replaced
event.unsignedData?.replacesState
} }
beaconInfoEntity.root?.content = ContentMapper.map(beaconInfoContent.toContent()) if (targetEventId.isNullOrEmpty()) {
Timber.w("no target event id found for the beacon content")
return
} }
private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent, val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
liveLocationContent: MessageLiveLocationContent): Boolean { realm = realm,
val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0 roomId = roomId,
val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0 eventId = targetEventId
val timeout = beaconInfoContent.timeout ?: 0 )
return liveLocationEventTime - beaconInfoStartTime > timeout
Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")
aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
aggregatedSummary.isActive = content.isLive
}
override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) {
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
return
}
val targetEventId = content.relatesTo?.eventId
if (targetEventId.isNullOrEmpty()) {
Timber.w("no target event id found for the live location content")
return
}
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
realm = realm,
roomId = roomId,
eventId = targetEventId
)
val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0
val currentLocationTimestamp = ContentMapper
.map(aggregatedSummary.lastLocationContent)
.toModel<MessageBeaconLocationDataContent>()
?.getBestTimestampMillis()
?: 0
if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) {
Timber.d("updating last location of the summary of id=$targetEventId")
aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent())
} }
} }
private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp
}

View file

@ -18,12 +18,23 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import io.realm.Realm import io.realm.Realm
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.model.message.MessageLiveLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
internal interface LiveLocationAggregationProcessor { internal interface LiveLocationAggregationProcessor {
fun handleLiveLocation(realm: Realm, fun handleBeaconInfo(
realm: Realm,
event: Event, event: Event,
content: MessageLiveLocationContent, content: MessageBeaconInfoContent,
roomId: String, roomId: String,
isLocalEcho: Boolean) isLocalEcho: Boolean,
)
fun handleBeaconLocationData(
realm: Realm,
event: Event,
content: MessageBeaconLocationDataContent,
roomId: String,
isLocalEcho: Boolean,
)
} }

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2022 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.session.room.crypto
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
import org.matrix.android.sdk.api.util.awaitCallback
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
import java.security.InvalidParameterException
internal class DefaultRoomCryptoService @AssistedInject constructor(
@Assisted private val roomId: String,
private val cryptoService: CryptoService,
private val sendStateTask: SendStateTask,
) : RoomCryptoService {
@AssistedFactory
interface Factory {
fun create(roomId: String): DefaultRoomCryptoService
}
override fun isEncrypted(): Boolean {
return cryptoService.isRoomEncrypted(roomId)
}
override fun encryptionAlgorithm(): String? {
return cryptoService.getEncryptionAlgorithm(roomId)
}
override fun shouldEncryptForInvitedMembers(): Boolean {
return cryptoService.shouldEncryptForInvitedMembers(roomId)
}
override suspend fun prepareToEncrypt() {
awaitCallback<Unit> {
cryptoService.prepareToEncrypt(roomId, it)
}
}
override suspend fun enableEncryption(algorithm: String, force: Boolean) {
when {
(!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> {
throw IllegalStateException("Encryption is already enabled for this room")
}
(!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM) -> {
throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
}
else -> {
val params = SendStateTask.Params(
roomId = roomId,
stateKey = "",
eventType = EventType.STATE_ROOM_ENCRYPTION,
body = mapOf(
"algorithm" to algorithm
)
)
sendStateTask.execute(params)
}
}
}
}

View file

@ -37,13 +37,13 @@ import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.LocationInfo
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
@ -72,7 +72,6 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -245,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor(
body = geoUri, body = geoUri,
unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
unstableLocationAsset = LocationAsset(type = assetType), unstableLocationAsset = LocationAsset(type = assetType),
unstableTs = TimeUnit.MILLISECONDS.toSeconds(clock.epochMillis()), unstableTimestampMillis = clock.epochMillis(),
unstableText = geoUri unstableText = geoUri
) )
return createMessageEvent(roomId, content) return createMessageEvent(roomId, content)
@ -257,14 +256,14 @@ internal class LocalEchoEventFactory @Inject constructor(
longitude: Double, longitude: Double,
uncertainty: Double?): Event { uncertainty: Double?): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty) val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val content = MessageLiveLocationContent( val content = MessageBeaconLocationDataContent(
body = geoUri, body = geoUri,
relatesTo = RelationDefaultContent( relatesTo = RelationDefaultContent(
type = RelationType.REFERENCE, type = RelationType.REFERENCE,
eventId = beaconInfoEventId eventId = beaconInfoEventId
), ),
unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
unstableTimestampAsMilliseconds = TimeUnit.MILLISECONDS.toSeconds(clock.epochMillis()), unstableTimestampMillis = clock.epochMillis(),
) )
val localId = LocalEcho.createLocalEchoId() val localId = LocalEcho.createLocalEchoId()
return Event( return Event(

View file

@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.MimeTypes
@ -73,14 +73,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
eventType: String, eventType: String,
stateKey: String, stateKey: String,
body: JsonDict body: JsonDict
) { ): String {
val params = SendStateTask.Params( val params = SendStateTask.Params(
roomId = roomId, roomId = roomId,
stateKey = stateKey, stateKey = stateKey,
eventType = eventType, eventType = eventType,
body = body.toSafeJson(eventType) body = body.toSafeJson(eventType)
) )
sendStateTask.executeRetry(params, 3) return sendStateTask.executeRetry(params, 3)
} }
private fun JsonDict.toSafeJson(eventType: String): JsonDict { private fun JsonDict.toSafeJson(eventType: String): JsonDict {
@ -192,7 +192,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
override suspend fun stopLiveLocation(userId: String) { override suspend fun stopLiveLocation(userId: String) {
getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent -> getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent ->
beaconInfoStateEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.let { content -> beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.let { content ->
val updatedContent = content.copy(isLive = false).toContent() val updatedContent = content.copy(isLive = false).toContent()
beaconInfoStateEvent.stateKey?.let { beaconInfoStateEvent.stateKey?.let {
@ -217,7 +217,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
} }
.firstOrNull { beaconInfoEvent -> .firstOrNull { beaconInfoEvent ->
!filterOnlyLive || !filterOnlyLive ||
beaconInfoEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.isLive.orFalse() beaconInfoEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.isLive.orFalse()
} }
} }
} }

View file

@ -21,9 +21,10 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal interface SendStateTask : Task<SendStateTask.Params, Unit> { internal interface SendStateTask : Task<SendStateTask.Params, String> {
data class Params( data class Params(
val roomId: String, val roomId: String,
val stateKey: String, val stateKey: String,
@ -37,9 +38,9 @@ internal class DefaultSendStateTask @Inject constructor(
private val globalErrorReceiver: GlobalErrorReceiver private val globalErrorReceiver: GlobalErrorReceiver
) : SendStateTask { ) : SendStateTask {
override suspend fun execute(params: SendStateTask.Params) { override suspend fun execute(params: SendStateTask.Params): String {
return executeRequest(globalErrorReceiver) { return executeRequest(globalErrorReceiver) {
if (params.stateKey.isEmpty()) { val response = if (params.stateKey.isEmpty()) {
roomAPI.sendStateEvent( roomAPI.sendStateEvent(
roomId = params.roomId, roomId = params.roomId,
stateEventType = params.eventType, stateEventType = params.eventType,
@ -53,6 +54,9 @@ internal class DefaultSendStateTask @Inject constructor(
params = params.body params = params.body
) )
} }
response.eventId.also {
Timber.d("State event: $it just sent in room ${params.roomId}")
}
} }
} }
} }

View file

@ -26,7 +26,6 @@ import com.zhuinden.monarchy.Monarchy
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.isNormalized import org.matrix.android.sdk.api.query.isNormalized
@ -43,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification
import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
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.RealmSessionProvider
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@ -57,10 +55,8 @@ import javax.inject.Inject
internal class RoomSummaryDataSource @Inject constructor( internal class RoomSummaryDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
private val roomSummaryMapper: RoomSummaryMapper, private val roomSummaryMapper: RoomSummaryMapper,
private val queryStringValueProcessor: QueryStringValueProcessor, private val queryStringValueProcessor: QueryStringValueProcessor,
private val coroutineDispatchers: MatrixCoroutineDispatchers
) { ) {
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {

View file

@ -102,9 +102,7 @@ internal class UIEchoManager(
val relatedEventID = timelineEvent.eventId val relatedEventID = timelineEvent.eventId
val contents = inMemoryReactions[relatedEventID] ?: return timelineEvent val contents = inMemoryReactions[relatedEventID] ?: return timelineEvent
var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary( var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary()
relatedEventID
)
val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList() val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList()
contents.forEach { uiEchoReaction -> contents.forEach { uiEchoReaction ->

View file

@ -60,7 +60,7 @@ internal class DefaultSpace(
} }
?: viaParameterFinder.computeViaParams(roomId, 3)) ?: viaParameterFinder.computeViaParams(roomId, 3))
room.sendStateEvent( room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD, eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId, stateKey = roomId,
body = SpaceChildContent( body = SpaceChildContent(
@ -79,7 +79,7 @@ internal class DefaultSpace(
// return // return
// edit state event and set via to null // edit state event and set via to null
room.sendStateEvent( room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD, eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId, stateKey = roomId,
body = SpaceChildContent( body = SpaceChildContent(
@ -91,19 +91,19 @@ internal class DefaultSpace(
} }
override fun getChildInfo(roomId: String): SpaceChildContent? { override fun getChildInfo(roomId: String): SpaceChildContent? {
return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) return room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
.firstOrNull() .firstOrNull()
?.content.toModel<SpaceChildContent>() ?.content.toModel<SpaceChildContent>()
} }
override suspend fun setChildrenOrder(roomId: String, order: String?) { override suspend fun setChildrenOrder(roomId: String, order: String?) {
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
.firstOrNull() .firstOrNull()
?.content.toModel<SpaceChildContent>() ?.content.toModel<SpaceChildContent>()
?: throw IllegalArgumentException("$roomId is not a child of this space") ?: throw IllegalArgumentException("$roomId is not a child of this space")
// edit state event and set via to null // edit state event and set via to null
room.sendStateEvent( room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD, eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId, stateKey = roomId,
body = SpaceChildContent( body = SpaceChildContent(
@ -140,7 +140,7 @@ internal class DefaultSpace(
// } // }
override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) { override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) {
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
.firstOrNull() .firstOrNull()
?.content.toModel<SpaceChildContent>() ?.content.toModel<SpaceChildContent>()
?: throw IllegalArgumentException("$roomId is not a child of this space") ?: throw IllegalArgumentException("$roomId is not a child of this space")
@ -150,7 +150,7 @@ internal class DefaultSpace(
return return
} }
// edit state event and set via to null // edit state event and set via to null
room.sendStateEvent( room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD, eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId, stateKey = roomId,
body = SpaceChildContent( body = SpaceChildContent(

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
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.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.GuestAccess
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.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
@ -258,7 +259,7 @@ internal class DefaultSpaceService @Inject constructor(
val room = roomGetter.getRoom(childRoomId) val room = roomGetter.getRoom(childRoomId)
?: throw IllegalArgumentException("Unknown Room $childRoomId") ?: throw IllegalArgumentException("Unknown Room $childRoomId")
room.sendStateEvent( room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_PARENT, eventType = EventType.STATE_SPACE_PARENT,
stateKey = parentSpaceId, stateKey = parentSpaceId,
body = SpaceParentContent( body = SpaceParentContent(
@ -276,7 +277,7 @@ internal class DefaultSpaceService @Inject constructor(
if (existingEvent != null) { if (existingEvent != null) {
// Should i check if it was sent by me? // Should i check if it was sent by me?
// we don't check power level, it will throw if you cannot do that // we don't check power level, it will throw if you cannot do that
room.sendStateEvent( room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_PARENT, eventType = EventType.STATE_SPACE_PARENT,
stateKey = parentSpaceId, stateKey = parentSpaceId,
body = SpaceParentContent( body = SpaceParentContent(

View file

@ -29,9 +29,10 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, Unit> { internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, String> {
data class Params( data class Params(
val roomId: String, val roomId: String,
@ -45,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
@UserId private val userId: String, @UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask { private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask {
override suspend fun execute(params: CreateWidgetTask.Params) { override suspend fun execute(params: CreateWidgetTask.Params): String {
executeRequest(globalErrorReceiver) { val response = executeRequest(globalErrorReceiver) {
roomAPI.sendStateEvent( roomAPI.sendStateEvent(
roomId = params.roomId, roomId = params.roomId,
stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY, stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY,
@ -60,5 +61,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
.and() .and()
.equalTo(CurrentStateEventEntityFields.ROOT.SENDER, userId) .equalTo(CurrentStateEventEntityFields.ROOT.SENDER, userId)
} }
return response.eventId.also {
Timber.d("Widget state event: $it just sent in room ${params.roomId}")
}
} }
} }

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.MatrixTest
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.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
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.members.MembershipService
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.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
@ -148,14 +149,22 @@ class PushRulesConditionTest : MatrixTest {
val room2JoinedId = "2joined" val room2JoinedId = "2joined"
val room3JoinedId = "3joined" val room3JoinedId = "3joined"
val roomStub2Joined = mockk<Room> { val roomMembershipService2 = mockk<MembershipService> {
every { getNumberOfJoinedMembers() } returns 2 every { getNumberOfJoinedMembers() } returns 2
} }
val roomStub3Joined = mockk<Room> { val roomMembershipService3 = mockk<MembershipService> {
every { getNumberOfJoinedMembers() } returns 3 every { getNumberOfJoinedMembers() } returns 3
} }
val roomStub2Joined = mockk<Room> {
every { membershipService() } returns roomMembershipService2
}
val roomStub3Joined = mockk<Room> {
every { membershipService() } returns roomMembershipService3
}
val roomGetterStub = mockk<RoomGetter> { val roomGetterStub = mockk<RoomGetter> {
every { getRoom(room2JoinedId) } returns roomStub2Joined every { getRoom(room2JoinedId) } returns roomStub2Joined
every { getRoom(room3JoinedId) } returns roomStub3Joined every { getRoom(room3JoinedId) } returns roomStub3Joined

View file

@ -47,6 +47,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject

View file

@ -102,7 +102,7 @@ class AppStateHandler @Inject constructor(
if (spaceId != null) { if (spaceId != null) {
uSession.coroutineScope.launch(Dispatchers.IO) { uSession.coroutineScope.launch(Dispatchers.IO) {
tryOrNull { tryOrNull {
uSession.getRoom(spaceId)?.loadRoomMembersIfNeeded() uSession.getRoom(spaceId)?.membershipService()?.loadRoomMembersIfNeeded()
} }
} }
} }

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.ui.views
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.View
import nl.dionsegijn.konfetti.xml.KonfettiView
/**
* Konfetti workaround to avoid crashes on API 21/22
* https://github.com/DanielMartinus/Konfetti/issues/297
*/
class CompatKonfetti @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : KonfettiView(context, attrs) {
override fun onVisibilityChanged(changedView: View, visibility: Int) {
when (Build.VERSION.SDK_INT) {
Build.VERSION_CODES.LOLLIPOP, Build.VERSION_CODES.LOLLIPOP_MR1 -> safeOnVisibilityChanged(changedView, visibility)
else -> super.onVisibilityChanged(changedView, visibility)
}
}
private fun safeOnVisibilityChanged(changedView: View, visibility: Int) {
runCatching { super.onVisibilityChanged(changedView, visibility) }
}
}

View file

@ -33,18 +33,20 @@ fun Int?.toAnalyticsRoomSize(): JoinedRoom.RoomSize {
} }
} }
fun RoomSummary?.toAnalyticsJoinedRoom(): JoinedRoom { fun RoomSummary?.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom {
return JoinedRoom( return JoinedRoom(
isDM = this?.isDirect.orFalse(), isDM = this?.isDirect.orFalse(),
isSpace = this?.roomType == RoomType.SPACE, isSpace = this?.roomType == RoomType.SPACE,
roomSize = this?.joinedMembersCount?.toAnalyticsRoomSize() ?: JoinedRoom.RoomSize.Two roomSize = this?.joinedMembersCount?.toAnalyticsRoomSize() ?: JoinedRoom.RoomSize.Two,
trigger = trigger
) )
} }
fun PublicRoom.toAnalyticsJoinedRoom(): JoinedRoom { fun PublicRoom.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom {
return JoinedRoom( return JoinedRoom(
isDM = false, isDM = false,
isSpace = false, isSpace = false,
roomSize = numJoinedMembers.toAnalyticsRoomSize() roomSize = numJoinedMembers.toAnalyticsRoomSize(),
trigger = trigger
) )
} }

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.analytics.extensions
import im.vector.app.RoomGroupingMethod
import im.vector.app.features.analytics.plan.ViewRoom
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomType
fun RoomSummary?.toAnalyticsViewRoom(trigger: ViewRoom.Trigger?, groupingMethod: RoomGroupingMethod? = null, viaKeyboard: Boolean? = null): ViewRoom {
val activeSpace = groupingMethod?.let {
when (it) {
is RoomGroupingMethod.BySpace -> it.spaceSummary?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home
else -> null
}
}
return ViewRoom(
isDM = this?.isDirect.orFalse(),
isSpace = this?.roomType == RoomType.SPACE,
trigger = trigger,
activeSpace = activeSpace,
viaKeyboard = viaKeyboard
)
}
private fun RoomSummary.toActiveSpace(): ViewRoom.ActiveSpace {
return if (isPublic) ViewRoom.ActiveSpace.Public else ViewRoom.ActiveSpace.Private
}

View file

@ -127,7 +127,8 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
) )
private fun createMemberItems(queryParams: RoomMemberQueryParams) = private fun createMemberItems(queryParams: RoomMemberQueryParams) =
room.getRoomMembers(queryParams) room.membershipService()
.getRoomMembers(queryParams)
.asSequence() .asSequence()
.sortedBy { it.displayName } .sortedBy { it.displayName }
.disambiguate() .disambiguate()

View file

@ -30,7 +30,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
fun nativeRoomForVirtualRoom(roomId: String): String? { fun nativeRoomForVirtualRoom(roomId: String): String? {
if (!protocolsChecker.supportVirtualRooms) return null if (!protocolsChecker.supportVirtualRooms) return null
val virtualRoom = session.getRoom(roomId) ?: return null val virtualRoom = session.getRoom(roomId) ?: return null
val virtualRoomEvent = virtualRoom.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM) val virtualRoomEvent = virtualRoom.roomAccountDataService().getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM)
return virtualRoomEvent?.content?.toModel<RoomVirtualContent>()?.nativeRoomId return virtualRoomEvent?.content?.toModel<RoomVirtualContent>()?.nativeRoomId
} }
@ -79,7 +79,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
private suspend fun Room.markVirtual(nativeRoomId: String) { private suspend fun Room.markVirtual(nativeRoomId: String) {
val virtualRoomContent = RoomVirtualContent(nativeRoomId = nativeRoomId) val virtualRoomContent = RoomVirtualContent(nativeRoomId = nativeRoomId)
updateAccountData(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM, virtualRoomContent.toContent()) roomAccountDataService().updateAccountData(RoomAccountDataTypes.EVENT_TYPE_VIRTUAL_ROOM, virtualRoomContent.toContent())
} }
private suspend fun ensureVirtualRoomExists(userId: String, nativeRoomId: String): String { private suspend fun ensureVirtualRoomExists(userId: String, nativeRoomId: String): String {

View file

@ -30,7 +30,7 @@ fun WebRtcCall.getOpponentAsMatrixItem(session: Session): MatrixItem? {
roomSummary.toMatrixItem() roomSummary.toMatrixItem()
} else { } else {
val userId = roomSummary.otherMemberIds.first() val userId = roomSummary.otherMemberIds.first()
return room.getRoomMember(userId)?.toMatrixItem() return room.membershipService().getRoomMember(userId)?.toMatrixItem()
} }
} }
} }

View file

@ -43,6 +43,7 @@ import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.onPermissionDeniedSnackbar
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookFragment
import im.vector.app.features.qrcode.QrCodeScannerEvents import im.vector.app.features.qrcode.QrCodeScannerEvents
import im.vector.app.features.qrcode.QrCodeScannerFragment import im.vector.app.features.qrcode.QrCodeScannerFragment
@ -206,7 +207,11 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
} }
private fun renderCreationSuccess(roomId: String) { private fun renderCreationSuccess(roomId: String) {
navigator.openRoom(this, roomId) navigator.openRoom(
context = this,
roomId = roomId,
trigger = ViewRoom.Trigger.MessageUser
)
finish() finish()
} }

View file

@ -19,6 +19,7 @@ import android.content.Context
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.time.Clock import im.vector.app.core.time.Clock
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailActivity
@ -159,7 +160,12 @@ class IncomingVerificationRequestHandler @Inject constructor(
if (roomId.isNullOrBlank()) { if (roomId.isNullOrBlank()) {
it.navigator.waitSessionVerification(it) it.navigator.waitSessionVerification(it)
} else { } else {
it.navigator.openRoom(it, roomId, pr.transactionId) it.navigator.openRoom(
context = it,
roomId = roomId,
eventId = pr.transactionId,
trigger = ViewRoom.Trigger.VerificationRequest
)
} }
} }
} }

View file

@ -174,11 +174,10 @@ class RoomDevToolViewModel @AssistedInject constructor(
val json = adapter.fromJson(state.editedContent ?: "") val json = adapter.fromJson(state.editedContent ?: "")
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content)) ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
room.sendStateEvent( room.stateService().sendStateEvent(
state.selectedEvent?.type.orEmpty(), state.selectedEvent?.type.orEmpty(),
state.selectedEvent?.stateKey.orEmpty(), state.selectedEvent?.stateKey.orEmpty(),
json json
) )
_viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_state_event))) _viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_state_event)))
setState { setState {
@ -212,7 +211,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_message_type)) ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_message_type))
if (isState) { if (isState) {
room.sendStateEvent( room.stateService().sendStateEvent(
eventType, eventType,
state.sendEventDraft.stateKey.orEmpty(), state.sendEventDraft.stateKey.orEmpty(),
json json
@ -222,7 +221,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
// val validParse = MoshiProvider.providesMoshi().adapter(MessageContent::class.java).fromJson(it.sendEventDraft.content ?: "") // val validParse = MoshiProvider.providesMoshi().adapter(MessageContent::class.java).fromJson(it.sendEventDraft.content ?: "")
json.toModel<MessageContent>(catchError = false) json.toModel<MessageContent>(catchError = false)
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_malformed_event)) ?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_malformed_event))
room.sendEvent( room.sendService().sendEvent(
eventType, eventType,
json json
) )

View file

@ -49,8 +49,10 @@ import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs import im.vector.app.features.MainActivityArgs
import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.disclaimer.showDisclaimerDialog import im.vector.app.features.disclaimer.showDisclaimerDialog
import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.navigation.Navigator import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.NavigationInterceptor
@ -243,7 +245,7 @@ class HomeActivity :
} }
if (args?.inviteNotificationRoomId != null) { if (args?.inviteNotificationRoomId != null) {
activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(args.inviteNotificationRoomId)?.let { activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(args.inviteNotificationRoomId)?.let {
navigator.openMatrixToBottomSheet(this, it) navigator.openMatrixToBottomSheet(this, it, OriginOfMatrixTo.NOTIFICATION)
} }
} }
@ -480,7 +482,7 @@ class HomeActivity :
activeSessionHolder.getSafeActiveSession() activeSessionHolder.getSafeActiveSession()
?.permalinkService() ?.permalinkService()
?.createPermalink(parcelableExtra.inviteNotificationRoomId)?.let { ?.createPermalink(parcelableExtra.inviteNotificationRoomId)?.let {
navigator.openMatrixToBottomSheet(this, it) navigator.openMatrixToBottomSheet(this, it, OriginOfMatrixTo.NOTIFICATION)
} }
} }
handleIntent(intent) handleIntent(intent)
@ -567,14 +569,14 @@ class HomeActivity :
override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean {
// TODO check if there is already one?? // TODO check if there is already one??
MatrixToBottomSheet.withLink(deepLink.toString()) MatrixToBottomSheet.withLink(deepLink.toString(), OriginOfMatrixTo.LINK)
.show(supportFragmentManager, "HA#MatrixToBottomSheet") .show(supportFragmentManager, "HA#MatrixToBottomSheet")
return true return true
} }
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean {
if (roomId == null) return false if (roomId == null) return false
MatrixToBottomSheet.withLink(deepLink.toString()) MatrixToBottomSheet.withLink(deepLink.toString(), OriginOfMatrixTo.LINK)
.show(supportFragmentManager, "HA#MatrixToBottomSheet") .show(supportFragmentManager, "HA#MatrixToBottomSheet")
return true return true
} }
@ -608,8 +610,8 @@ class HomeActivity :
} }
} }
override fun mxToBottomSheetNavigateToRoom(roomId: String) { override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) {
navigator.openRoom(this, roomId) navigator.openRoom(this, roomId, trigger = trigger)
} }
override fun mxToBottomSheetSwitchToSpace(spaceId: String) { override fun mxToBottomSheetSwitchToSpace(spaceId: String) {

View file

@ -36,6 +36,7 @@ import im.vector.app.core.extensions.replaceFragment
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityRoomDetailBinding import im.vector.app.databinding.ActivityRoomDetailBinding
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
@ -205,8 +206,8 @@ class RoomDetailActivity :
} }
} }
override fun mxToBottomSheetNavigateToRoom(roomId: String) { override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) {
navigator.openRoom(this, roomId) navigator.openRoom(this, roomId, trigger = trigger)
} }
override fun mxToBottomSheetSwitchToSpace(spaceId: String) { override fun mxToBottomSheetSwitchToSpace(spaceId: String) {

View file

@ -40,6 +40,7 @@ import im.vector.app.core.utils.BehaviorDataSource
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.analytics.DecryptionFailureTracker
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.analytics.plan.JoinedRoom
import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.ConferenceEvent
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
import im.vector.app.features.call.conference.JitsiService import im.vector.app.features.call.conference.JitsiService
@ -88,6 +89,8 @@ 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.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.getTimelineEvent
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.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
@ -182,7 +185,7 @@ class TimelineViewModel @AssistedInject constructor(
setupPreviewUrlObservers() setupPreviewUrlObservers()
room.getRoomSummaryLive() room.getRoomSummaryLive()
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
} }
// Inform the SDK that the room is displayed // Inform the SDK that the room is displayed
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
@ -193,7 +196,7 @@ class TimelineViewModel @AssistedInject constructor(
chatEffectManager.delegate = this chatEffectManager.delegate = this
// Ensure to share the outbound session keys with all members // Ensure to share the outbound session keys with all members
if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) { if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) {
prepareForEncryption() prepareForEncryption()
} }
@ -249,7 +252,7 @@ class TimelineViewModel @AssistedInject constructor(
prepareToEncrypt = Loading() prepareToEncrypt = Loading()
viewModelScope.launch { viewModelScope.launch {
runCatching { runCatching {
room.prepareToEncrypt() room.roomCryptoService().prepareToEncrypt()
}.fold({ }.fold({
prepareToEncrypt = Success(Unit) prepareToEncrypt = Success(Unit)
}, { }, {
@ -353,7 +356,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun markThreadTimelineAsReadLocal() { private fun markThreadTimelineAsReadLocal() {
initialState.rootThreadEventId?.let { initialState.rootThreadEventId?.let {
session.coroutineScope.launch { session.coroutineScope.launch {
room.markThreadAsRead(it) room.threadsLocalService().markThreadAsRead(it)
} }
} }
} }
@ -489,7 +492,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) { private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
room.updateAvatar(action.newAvatarUri, action.newAvatarFileName) room.stateService().updateAvatar(action.newAvatarUri, action.newAvatarFileName)
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
@ -506,7 +509,7 @@ class TimelineViewModel @AssistedInject constructor(
} }
private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) { private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
room.getUserReadReceipt(action.userId) room.readService().getUserReadReceipt(action.userId)
?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) } ?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }
} }
@ -518,7 +521,7 @@ class TimelineViewModel @AssistedInject constructor(
eventId = it)) eventId = it))
} ?: action.stickerContent } ?: action.stickerContent
room.sendEvent(EventType.STICKER, content.toContent()) room.sendService().sendEvent(EventType.STICKER, content.toContent())
} }
private fun handleStartCall(action: RoomDetailAction.StartCall) { private fun handleStartCall(action: RoomDetailAction.StartCall) {
@ -638,7 +641,7 @@ class TimelineViewModel @AssistedInject constructor(
if (trackUnreadMessages.getAndSet(false)) { if (trackUnreadMessages.getAndSet(false)) {
mostRecentDisplayedEvent?.root?.eventId?.also { mostRecentDisplayedEvent?.root?.eventId?.also {
session.coroutineScope.launch { session.coroutineScope.launch {
tryOrNull { room.setReadMarker(it) } tryOrNull { room.readService().setReadMarker(it) }
} }
} }
mostRecentDisplayedEvent = null mostRecentDisplayedEvent = null
@ -651,12 +654,12 @@ class TimelineViewModel @AssistedInject constructor(
} }
fun getMember(userId: String): RoomMemberSummary? { fun getMember(userId: String): RoomMemberSummary? {
return room.getRoomMember(userId) return room.membershipService().getRoomMember(userId)
} }
private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) { private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) {
// Ensure outbound session keys // Ensure outbound session keys
if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) { if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) {
if (action.focused) { if (action.focused) {
// Should we add some rate limit here, or do it only once per model lifecycle? // Should we add some rate limit here, or do it only once per model lifecycle?
prepareForEncryption() prepareForEncryption()
@ -737,36 +740,36 @@ class TimelineViewModel @AssistedInject constructor(
// PRIVATE METHODS ***************************************************************************** // PRIVATE METHODS *****************************************************************************
private fun handleSendReaction(action: RoomDetailAction.SendReaction) { private fun handleSendReaction(action: RoomDetailAction.SendReaction) {
room.sendReaction(action.targetEventId, action.reaction) room.relationService().sendReaction(action.targetEventId, action.reaction)
} }
private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { private fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
val event = room.getTimelineEvent(action.targetEventId) ?: return val event = room.getTimelineEvent(action.targetEventId) ?: return
room.redactEvent(event.root, action.reason) room.sendService().redactEvent(event.root, action.reason)
} }
private fun handleUndoReact(action: RoomDetailAction.UndoReaction) { private fun handleUndoReact(action: RoomDetailAction.UndoReaction) {
viewModelScope.launch { viewModelScope.launch {
tryOrNull { tryOrNull {
room.undoReaction(action.targetEventId, action.reaction) room.relationService().undoReaction(action.targetEventId, action.reaction)
} }
} }
} }
private fun handleUpdateQuickReaction(action: RoomDetailAction.UpdateQuickReactAction) { private fun handleUpdateQuickReaction(action: RoomDetailAction.UpdateQuickReactAction) {
if (action.add) { if (action.add) {
room.sendReaction(action.targetEventId, action.selectedReaction) room.relationService().sendReaction(action.targetEventId, action.selectedReaction)
} else { } else {
viewModelScope.launch { viewModelScope.launch {
tryOrNull { tryOrNull {
room.undoReaction(action.targetEventId, action.selectedReaction) room.relationService().undoReaction(action.targetEventId, action.selectedReaction)
} }
} }
} }
} }
private fun handleSendMedia(action: RoomDetailAction.SendMedia) { private fun handleSendMedia(action: RoomDetailAction.SendMedia) {
room.sendMedias( room.sendService().sendMedias(
action.attachments, action.attachments,
action.compressBeforeSending, action.compressBeforeSending,
emptySet(), emptySet(),
@ -821,13 +824,22 @@ class TimelineViewModel @AssistedInject constructor(
viewModelScope.launch { viewModelScope.launch {
try { try {
session.roomService().joinRoom(room.roomId) session.roomService().joinRoom(room.roomId)
analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom()) trackRoomJoined()
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
_viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true)) _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
} }
} }
} }
private fun trackRoomJoined() {
val trigger = if (initialState.isInviteAlreadyAccepted) {
JoinedRoom.Trigger.Invite
} else {
JoinedRoom.Trigger.Timeline
}
analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom(trigger))
}
private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) { private fun handleOpenOrDownloadFile(action: RoomDetailAction.DownloadOrOpen) {
val mxcUrl = action.messageFileContent.getFileUrl() ?: return val mxcUrl = action.messageFileContent.getFileUrl() ?: return
val isLocalSendingFile = action.senderId == session.myUserId && val isLocalSendingFile = action.senderId == session.myUserId &&
@ -892,8 +904,8 @@ class TimelineViewModel @AssistedInject constructor(
return return
} }
when { when {
it.root.isTextMessage() -> room.resendTextMessage(it) it.root.isTextMessage() -> room.sendService().resendTextMessage(it)
it.root.isAttachmentMessage() -> room.resendMediaMessage(it) it.root.isAttachmentMessage() -> room.sendService().resendMediaMessage(it)
else -> { else -> {
// TODO // TODO
} }
@ -909,13 +921,13 @@ class TimelineViewModel @AssistedInject constructor(
Timber.e("Cannot resend message, it is not failed, Cancel first") Timber.e("Cannot resend message, it is not failed, Cancel first")
return return
} }
room.deleteFailedEcho(it) room.sendService().deleteFailedEcho(it)
} }
} }
private fun handleCancel(action: RoomDetailAction.CancelSend) { private fun handleCancel(action: RoomDetailAction.CancelSend) {
if (action.force) { if (action.force) {
room.cancelSend(action.eventId) room.sendService().cancelSend(action.eventId)
return return
} }
val targetEventId = action.eventId val targetEventId = action.eventId
@ -925,16 +937,16 @@ class TimelineViewModel @AssistedInject constructor(
Timber.e("Cannot cancel message, it is not sending") Timber.e("Cannot cancel message, it is not sending")
return return
} }
room.cancelSend(targetEventId) room.sendService().cancelSend(targetEventId)
} }
} }
private fun handleResendAll() { private fun handleResendAll() {
room.resendAllFailedMessages() room.sendService().resendAllFailedMessages()
} }
private fun handleRemoveAllFailedMessages() { private fun handleRemoveAllFailedMessages() {
room.cancelAllFailedMessages() room.sendService().cancelAllFailedMessages()
} }
private fun observeEventDisplayedActions() { private fun observeEventDisplayedActions() {
@ -957,7 +969,7 @@ class TimelineViewModel @AssistedInject constructor(
} }
bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId ->
session.coroutineScope.launch { session.coroutineScope.launch {
tryOrNull { room.setReadReceipt(eventId) } tryOrNull { room.readService().setReadReceipt(eventId) }
} }
} }
} }
@ -974,14 +986,14 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleMarkAllAsRead() { private fun handleMarkAllAsRead() {
setState { copy(unreadState = UnreadState.HasNoUnread) } setState { copy(unreadState = UnreadState.HasNoUnread) }
viewModelScope.launch { viewModelScope.launch {
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) } tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.BOTH) }
} }
} }
private fun handleReportContent(action: RoomDetailAction.ReportContent) { private fun handleReportContent(action: RoomDetailAction.ReportContent) {
viewModelScope.launch { viewModelScope.launch {
val event = try { val event = try {
room.reportContent(action.eventId, -100, action.reason) room.reportingService().reportContent(action.eventId, -100, action.reason)
RoomDetailViewEvents.ActionSuccess(action) RoomDetailViewEvents.ActionSuccess(action)
} catch (failure: Exception) { } catch (failure: Exception) {
RoomDetailViewEvents.ActionFailure(action, failure) RoomDetailViewEvents.ActionFailure(action, failure)
@ -1071,13 +1083,13 @@ class TimelineViewModel @AssistedInject constructor(
room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent -> room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent ->
val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote
if (currentVote != action.optionKey) { if (currentVote != action.optionKey) {
room.voteToPoll(action.eventId, action.optionKey) room.sendService().voteToPoll(action.eventId, action.optionKey)
} }
} }
} }
private fun handleEndPoll(eventId: String) { private fun handleEndPoll(eventId: String) {
room.endPoll(eventId) room.sendService().endPoll(eventId)
} }
private fun observeSyncState() { private fun observeSyncState() {
@ -1255,7 +1267,7 @@ class TimelineViewModel @AssistedInject constructor(
timeline.removeAllListeners() timeline.removeAllListeners()
decryptionFailureTracker.onTimeLineDisposed(room.roomId) decryptionFailureTracker.onTimeLineDisposed(room.roomId)
if (vectorPreferences.sendTypingNotifs()) { if (vectorPreferences.sendTypingNotifs()) {
room.userStopsTyping() room.typingService().userStopsTyping()
} }
chatEffectManager.delegate = null chatEffectManager.delegate = null
chatEffectManager.dispose() chatEffectManager.dispose()

View file

@ -28,6 +28,7 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsComposer import im.vector.app.features.analytics.extensions.toAnalyticsComposer
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.analytics.plan.JoinedRoom
import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.command.CommandParser import im.vector.app.features.command.CommandParser
import im.vector.app.features.command.ParsedCommand import im.vector.app.features.command.ParsedCommand
@ -52,6 +53,8 @@ import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm
@ -204,12 +207,12 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.ErrorNotACommand -> { is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room // Send the text message to the room
if (state.rootThreadEventId != null) { if (state.rootThreadEventId != null) {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId, rootThreadEventId = state.rootThreadEventId,
replyInThreadText = action.text, replyInThreadText = action.text,
autoMarkdown = action.autoMarkdown) autoMarkdown = action.autoMarkdown)
} else { } else {
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) room.sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
} }
_viewEvents.post(MessageComposerViewEvents.MessageSent) _viewEvents.post(MessageComposerViewEvents.MessageSent)
@ -230,12 +233,12 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.SendPlainText -> { is ParsedCommand.SendPlainText -> {
// Send the text message to the room, without markdown // Send the text message to the room, without markdown
if (state.rootThreadEventId != null) { if (state.rootThreadEventId != null) {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId, rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message, replyInThreadText = parsedCommand.message,
autoMarkdown = false) autoMarkdown = false)
} else { } else {
room.sendTextMessage(parsedCommand.message, autoMarkdown = false) room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
} }
_viewEvents.post(MessageComposerViewEvents.MessageSent) _viewEvents.post(MessageComposerViewEvents.MessageSent)
popDraft() popDraft()
@ -285,13 +288,16 @@ class MessageComposerViewModel @AssistedInject constructor(
} }
is ParsedCommand.SendEmote -> { is ParsedCommand.SendEmote -> {
if (state.rootThreadEventId != null) { if (state.rootThreadEventId != null) {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId, rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message, replyInThreadText = parsedCommand.message,
msgType = MessageType.MSGTYPE_EMOTE, msgType = MessageType.MSGTYPE_EMOTE,
autoMarkdown = action.autoMarkdown) autoMarkdown = action.autoMarkdown)
} else { } else {
room.sendTextMessage(parsedCommand.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) room.sendService().sendTextMessage(
text = parsedCommand.message,
msgType = MessageType.MSGTYPE_EMOTE,
autoMarkdown = action.autoMarkdown)
} }
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
popDraft() popDraft()
@ -299,12 +305,12 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.SendRainbow -> { is ParsedCommand.SendRainbow -> {
val message = parsedCommand.message.toString() val message = parsedCommand.message.toString()
if (state.rootThreadEventId != null) { if (state.rootThreadEventId != null) {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId, rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message, replyInThreadText = parsedCommand.message,
formattedText = rainbowGenerator.generate(message)) formattedText = rainbowGenerator.generate(message))
} else { } else {
room.sendFormattedTextMessage(message, rainbowGenerator.generate(message)) room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
} }
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
popDraft() popDraft()
@ -312,13 +318,13 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.SendRainbowEmote -> { is ParsedCommand.SendRainbowEmote -> {
val message = parsedCommand.message.toString() val message = parsedCommand.message.toString()
if (state.rootThreadEventId != null) { if (state.rootThreadEventId != null) {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId, rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message, replyInThreadText = parsedCommand.message,
msgType = MessageType.MSGTYPE_EMOTE, msgType = MessageType.MSGTYPE_EMOTE,
formattedText = rainbowGenerator.generate(message)) formattedText = rainbowGenerator.generate(message))
} else { } else {
room.sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE) room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE)
} }
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
@ -328,12 +334,12 @@ class MessageComposerViewModel @AssistedInject constructor(
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})" val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
val formattedText = "<span data-mx-spoiler>${parsedCommand.message}</span>" val formattedText = "<span data-mx-spoiler>${parsedCommand.message}</span>"
if (state.rootThreadEventId != null) { if (state.rootThreadEventId != null) {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId, rootThreadEventId = state.rootThreadEventId,
replyInThreadText = text, replyInThreadText = text,
formattedText = formattedText) formattedText = formattedText)
} else { } else {
room.sendFormattedTextMessage( room.sendService().sendFormattedTextMessage(
text, text,
formattedText) formattedText)
} }
@ -376,7 +382,7 @@ class MessageComposerViewModel @AssistedInject constructor(
popDraft() popDraft()
} }
is ParsedCommand.DiscardSession -> { is ParsedCommand.DiscardSession -> {
if (room.isEncrypted()) { if (room.roomCryptoService().isEncrypted()) {
session.cryptoService().discardOutboundSession(room.roomId) session.cryptoService().discardOutboundSession(room.roomId)
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand)) _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
popDraft() popDraft()
@ -488,13 +494,13 @@ class MessageComposerViewModel @AssistedInject constructor(
if (inReplyTo != null) { if (inReplyTo != null) {
// TODO check if same content? // TODO check if same content?
room.getTimelineEvent(inReplyTo)?.let { room.getTimelineEvent(inReplyTo)?.let {
room.editReply(state.sendMode.timelineEvent, it, action.text.toString()) room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString())
} }
} else { } else {
val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
val existingBody = messageContent?.body ?: "" val existingBody = messageContent?.body ?: ""
if (existingBody != action.text) { if (existingBody != action.text) {
room.editTextMessage(state.sendMode.timelineEvent, room.relationService().editTextMessage(state.sendMode.timelineEvent,
messageContent?.msgType ?: MessageType.MSGTYPE_TEXT, messageContent?.msgType ?: MessageType.MSGTYPE_TEXT,
action.text, action.text,
action.autoMarkdown) action.autoMarkdown)
@ -506,7 +512,7 @@ class MessageComposerViewModel @AssistedInject constructor(
popDraft() popDraft()
} }
is SendMode.Quote -> { is SendMode.Quote -> {
room.sendQuotedTextMessage( room.sendService().sendQuotedTextMessage(
quotedEvent = state.sendMode.timelineEvent, quotedEvent = state.sendMode.timelineEvent,
text = action.text.toString(), text = action.text.toString(),
autoMarkdown = action.autoMarkdown, autoMarkdown = action.autoMarkdown,
@ -520,12 +526,12 @@ class MessageComposerViewModel @AssistedInject constructor(
// If threads are disabled this will make the fallback replies visible to clients with threads enabled // If threads are disabled this will make the fallback replies visible to clients with threads enabled
val rootThreadEventId = if (showInThread) timelineEvent.root.getRootThreadEventId() else null val rootThreadEventId = if (showInThread) timelineEvent.root.getRootThreadEventId() else null
state.rootThreadEventId?.let { state.rootThreadEventId?.let {
room.replyInThread( room.relationService().replyInThread(
rootThreadEventId = it, rootThreadEventId = it,
replyInThreadText = action.text.toString(), replyInThreadText = action.text.toString(),
autoMarkdown = action.autoMarkdown, autoMarkdown = action.autoMarkdown,
eventReplied = timelineEvent) eventReplied = timelineEvent)
} ?: room.replyToMessage( } ?: room.relationService().replyToMessage(
eventReplied = timelineEvent, eventReplied = timelineEvent,
replyText = action.text.toString(), replyText = action.text.toString(),
autoMarkdown = action.autoMarkdown, autoMarkdown = action.autoMarkdown,
@ -551,13 +557,13 @@ class MessageComposerViewModel @AssistedInject constructor(
// Otherwise we clear the composer and remove the draft from db // Otherwise we clear the composer and remove the draft from db
setState { copy(sendMode = SendMode.Regular("", false)) } setState { copy(sendMode = SendMode.Regular("", false)) }
viewModelScope.launch { viewModelScope.launch {
room.deleteDraft() room.draftService().deleteDraft()
} }
} }
} }
private fun loadDraftIfAny() { private fun loadDraftIfAny() {
val currentDraft = room.getDraft() val currentDraft = room.draftService().getDraft()
setState { setState {
copy( copy(
// Create a sendMode from a draft and retrieve the TimelineEvent // Create a sendMode from a draft and retrieve the TimelineEvent
@ -588,9 +594,9 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) { private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) {
if (vectorPreferences.sendTypingNotifs()) { if (vectorPreferences.sendTypingNotifs()) {
if (action.isTyping) { if (action.isTyping) {
room.userIsTyping() room.typingService().userIsTyping()
} else { } else {
room.userStopsTyping() room.typingService().userStopsTyping()
} }
} }
} }
@ -602,9 +608,9 @@ class MessageComposerViewModel @AssistedInject constructor(
ChatEffect.CONFETTI -> R.string.default_message_emote_confetti ChatEffect.CONFETTI -> R.string.default_message_emote_confetti
ChatEffect.SNOWFALL -> R.string.default_message_emote_snow ChatEffect.SNOWFALL -> R.string.default_message_emote_snow
}) })
room.sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE) room.sendService().sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE)
} else { } else {
room.sendTextMessage(sendChatEffect.message, sendChatEffect.chatEffect.toMessageType()) room.sendService().sendTextMessage(sendChatEffect.message, sendChatEffect.chatEffect.toMessageType())
} }
} }
@ -617,7 +623,7 @@ class MessageComposerViewModel @AssistedInject constructor(
return@launch return@launch
} }
session.getRoomSummary(command.roomAlias) session.getRoomSummary(command.roomAlias)
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom()) } ?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.SlashCommand)) }
?.roomId ?.roomId
?.let { ?.let {
_viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it)) _viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it))
@ -647,19 +653,19 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
launchSlashCommandFlowSuspendable(changeTopic) { launchSlashCommandFlowSuspendable(changeTopic) {
room.updateTopic(changeTopic.topic) room.stateService().updateTopic(changeTopic.topic)
} }
} }
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
launchSlashCommandFlowSuspendable(invite) { launchSlashCommandFlowSuspendable(invite) {
room.invite(invite.userId, invite.reason) room.membershipService().invite(invite.userId, invite.reason)
} }
} }
private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) { private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
launchSlashCommandFlowSuspendable(invite) { launchSlashCommandFlowSuspendable(invite) {
room.invite3pid(invite.threePid) room.membershipService().invite3pid(invite.threePid)
} }
} }
@ -672,7 +678,7 @@ class MessageComposerViewModel @AssistedInject constructor(
?: return ?: return
launchSlashCommandFlowSuspendable(setUserPowerLevel) { launchSlashCommandFlowSuspendable(setUserPowerLevel) {
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent) room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent)
} }
} }
@ -700,25 +706,25 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) { private fun handleRemoveSlashCommand(removeUser: ParsedCommand.RemoveUser) {
launchSlashCommandFlowSuspendable(removeUser) { launchSlashCommandFlowSuspendable(removeUser) {
room.remove(removeUser.userId, removeUser.reason) room.membershipService().remove(removeUser.userId, removeUser.reason)
} }
} }
private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) { private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) {
launchSlashCommandFlowSuspendable(ban) { launchSlashCommandFlowSuspendable(ban) {
room.ban(ban.userId, ban.reason) room.membershipService().ban(ban.userId, ban.reason)
} }
} }
private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) { private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) {
launchSlashCommandFlowSuspendable(unban) { launchSlashCommandFlowSuspendable(unban) {
room.unban(unban.userId, unban.reason) room.membershipService().unban(unban.userId, unban.reason)
} }
} }
private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) { private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) {
launchSlashCommandFlowSuspendable(changeRoomName) { launchSlashCommandFlowSuspendable(changeRoomName) {
room.updateName(changeRoomName.name) room.stateService().updateName(changeRoomName.name)
} }
} }
@ -734,14 +740,14 @@ class MessageComposerViewModel @AssistedInject constructor(
?.copy(displayName = changeDisplayName.displayName) ?.copy(displayName = changeDisplayName.displayName)
?.toContent() ?.toContent()
?.let { ?.let {
room.sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it) room.stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it)
} }
} }
} }
private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) { private fun handleChangeRoomAvatarSlashCommand(changeAvatar: ParsedCommand.ChangeRoomAvatar) {
launchSlashCommandFlowSuspendable(changeAvatar) { launchSlashCommandFlowSuspendable(changeAvatar) {
room.sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent()) room.stateService().sendStateEvent(EventType.STATE_ROOM_AVATAR, stateKey = "", RoomAvatarContent(changeAvatar.url).toContent())
} }
} }
@ -751,7 +757,7 @@ class MessageComposerViewModel @AssistedInject constructor(
?.copy(avatarUrl = changeAvatar.url) ?.copy(avatarUrl = changeAvatar.url)
?.toContent() ?.toContent()
?.let { ?.let {
room.sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it) room.stateService().sendStateEvent(EventType.STATE_ROOM_MEMBER, session.myUserId, it)
} }
} }
} }
@ -792,8 +798,8 @@ class MessageComposerViewModel @AssistedInject constructor(
} }
} }
rootThreadEventId?.let { rootThreadEventId?.let {
room.replyInThread(it, sequence) room.relationService().replyInThread(it, sequence)
} ?: room.sendTextMessage(sequence) } ?: room.sendService().sendTextMessage(sequence)
} }
/** /**
@ -804,19 +810,19 @@ class MessageComposerViewModel @AssistedInject constructor(
when { when {
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> { it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
setState { copy(sendMode = it.sendMode.copy(text = draft)) } setState { copy(sendMode = it.sendMode.copy(text = draft)) }
room.saveDraft(UserDraft.Regular(draft)) room.draftService().saveDraft(UserDraft.Regular(draft))
} }
it.sendMode is SendMode.Reply -> { it.sendMode is SendMode.Reply -> {
setState { copy(sendMode = it.sendMode.copy(text = draft)) } setState { copy(sendMode = it.sendMode.copy(text = draft)) }
room.saveDraft(UserDraft.Reply(it.sendMode.timelineEvent.root.eventId!!, draft)) room.draftService().saveDraft(UserDraft.Reply(it.sendMode.timelineEvent.root.eventId!!, draft))
} }
it.sendMode is SendMode.Quote -> { it.sendMode is SendMode.Quote -> {
setState { copy(sendMode = it.sendMode.copy(text = draft)) } setState { copy(sendMode = it.sendMode.copy(text = draft)) }
room.saveDraft(UserDraft.Quote(it.sendMode.timelineEvent.root.eventId!!, draft)) room.draftService().saveDraft(UserDraft.Quote(it.sendMode.timelineEvent.root.eventId!!, draft))
} }
it.sendMode is SendMode.Edit -> { it.sendMode is SendMode.Edit -> {
setState { copy(sendMode = it.sendMode.copy(text = draft)) } setState { copy(sendMode = it.sendMode.copy(text = draft)) }
room.saveDraft(UserDraft.Edit(it.sendMode.timelineEvent.root.eventId!!, draft)) room.draftService().saveDraft(UserDraft.Edit(it.sendMode.timelineEvent.root.eventId!!, draft))
} }
} }
} }
@ -837,7 +843,7 @@ class MessageComposerViewModel @AssistedInject constructor(
} else { } else {
audioMessageHelper.stopRecording(convertForSending = true)?.let { audioType -> audioMessageHelper.stopRecording(convertForSending = true)?.let { audioType ->
if (audioType.duration > 1000) { if (audioType.duration > 1000) {
room.sendMedia( room.sendService().sendMedia(
attachment = audioType.toContentAttachmentData(isVoiceMessage = true), attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
compressBeforeSending = false, compressBeforeSending = false,
roomIds = emptySet(), roomIds = emptySet(),
@ -904,7 +910,7 @@ class MessageComposerViewModel @AssistedInject constructor(
viewModelScope.launch { viewModelScope.launch {
playingAudioContent?.toContentAttachmentData()?.let { voiceDraft -> playingAudioContent?.toContentAttachmentData()?.let { voiceDraft ->
val content = voiceDraft.toJsonString() val content = voiceDraft.toJsonString()
room.saveDraft(UserDraft.Voice(content)) room.draftService().saveDraft(UserDraft.Voice(content))
setState { copy(sendMode = SendMode.Voice(content)) } setState { copy(sendMode = SendMode.Voice(content)) }
} }
} }

View file

@ -37,6 +37,7 @@ import im.vector.app.core.extensions.trackItemsVisibilityChange
import im.vector.app.core.platform.StateView import im.vector.app.core.platform.StateView
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentSearchBinding import im.vector.app.databinding.FragmentSearchBinding
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
@ -134,7 +135,16 @@ class SearchFragment @Inject constructor(
roomEncryptionTrustLevel = null, roomEncryptionTrustLevel = null,
rootThreadEventId = it) rootThreadEventId = it)
navigator.openThread(requireContext(), threadTimelineArgs, event.eventId) navigator.openThread(requireContext(), threadTimelineArgs, event.eventId)
} ?: navigator.openRoom(requireContext(), roomId, event.eventId) } ?: openRoom(roomId, event.eventId)
}
private fun openRoom(roomId: String, eventId: String?) {
navigator.openRoom(
context = requireContext(),
roomId = roomId,
eventId = eventId,
trigger = ViewRoom.Trigger.MessageSearch
)
} }
override fun loadMore() { override fun loadMore() {

View file

@ -62,7 +62,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(
viewModelScope.launch { viewModelScope.launch {
val data = try { val data = try {
room.fetchEditHistory(eventId) room.relationService().fetchEditHistory(eventId)
} catch (failure: Throwable) { } catch (failure: Throwable) {
setState { setState {
copy(editList = Fail(failure)) copy(editList = Fail(failure))

View file

@ -24,7 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import javax.inject.Inject import javax.inject.Inject
class LiveLocationMessageItemFactory @Inject constructor( class LiveLocationMessageItemFactory @Inject constructor(
@ -34,19 +34,20 @@ class LiveLocationMessageItemFactory @Inject constructor(
) { ) {
fun create( fun create(
liveLocationContent: LiveLocationBeaconContent, beaconInfoContent: MessageBeaconInfoContent,
highlight: Boolean, highlight: Boolean,
attributes: AbsMessageItem.Attributes, attributes: AbsMessageItem.Attributes,
): VectorEpoxyModel<*>? { ): VectorEpoxyModel<*>? {
// TODO handle location received and stopped states // TODO handle location received and stopped states
return when { return when {
isLiveRunning(liveLocationContent) -> buildStartLiveItem(highlight, attributes) isLiveRunning(beaconInfoContent) -> buildStartLiveItem(highlight, attributes)
else -> null else -> null
} }
} }
private fun isLiveRunning(liveLocationContent: LiveLocationBeaconContent): Boolean { private fun isLiveRunning(beaconInfoContent: MessageBeaconInfoContent): Boolean {
return liveLocationContent.isLive.orFalse() && liveLocationContent.hasTimedOut.not() // TODO when we will use aggregatedSummary, check if the live has timed out as well
return beaconInfoContent.isLive.orFalse()
} }
private fun buildStartLiveItem( private fun buildStartLiveItem(

View file

@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
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.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getStateEvent
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper

View file

@ -99,8 +99,8 @@ import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.isThread
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.room.model.livelocation.LiveLocationBeaconContent
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent
@ -216,7 +216,7 @@ class MessageItemFactory @Inject constructor(
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
} }
} }
is LiveLocationBeaconContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
} }
return messageItem?.apply { return messageItem?.apply {

View file

@ -45,18 +45,18 @@ class TimelineFactory @Inject constructor(private val session: Session, private
val settings = timelineSettingsFactory.create(rootThreadEventId) val settings = timelineSettingsFactory.create(rootThreadEventId)
if (!session.vectorCallService.protocolChecker.supportVirtualRooms) { if (!session.vectorCallService.protocolChecker.supportVirtualRooms) {
return mainRoom.createTimeline(eventId, settings) return mainRoom.timelineService().createTimeline(eventId, settings)
} }
val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(mainRoom.roomId) val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(mainRoom.roomId)
return if (virtualRoomId == null) { return if (virtualRoomId == null) {
mainRoom.createTimeline(eventId, settings) mainRoom.timelineService().createTimeline(eventId, settings)
} else { } else {
val virtualRoom = session.getRoom(virtualRoomId)!! val virtualRoom = session.getRoom(virtualRoomId)!!
MergedTimelines( MergedTimelines(
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
mainTimeline = mainRoom.createTimeline(eventId, settings), mainTimeline = mainRoom.timelineService().createTimeline(eventId, settings),
secondaryTimelineParams = MergedTimelines.SecondaryTimelineParams( secondaryTimelineParams = MergedTimelines.SecondaryTimelineParams(
timeline = virtualRoom.createTimeline(null, settings), timeline = virtualRoom.timelineService().createTimeline(null, settings),
shouldFilterTypes = true, shouldFilterTypes = true,
allowedTypes = secondaryTimelineAllowedTypes allowedTypes = secondaryTimelineAllowedTypes
) )

View file

@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.RelationType
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.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent
import javax.inject.Inject import javax.inject.Inject

View file

@ -34,6 +34,7 @@ import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFrag
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.flow.unwrap

View file

@ -59,12 +59,12 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_STICKER_LOCAL, MessageType.MSGTYPE_STICKER_LOCAL,
MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_LIVE_LOCATION_STATE, MessageType.MSGTYPE_BEACON_INFO,
) )
private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf( private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf(
MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_LIVE_LOCATION_STATE, MessageType.MSGTYPE_BEACON_INFO,
) )
} }
@ -151,7 +151,7 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
private fun MessageContent?.shouldAddMessageOverlay(): Boolean { private fun MessageContent?.shouldAddMessageOverlay(): Boolean {
return when { return when {
this == null || msgType == MessageType.MSGTYPE_LIVE_LOCATION_STATE -> false this == null || msgType == MessageType.MSGTYPE_BEACON_INFO -> false
msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline()
else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE
} }

View file

@ -43,7 +43,7 @@ class MigrateRoomViewModel @AssistedInject constructor(
val summary = session.getRoomSummary(initialState.roomId) val summary = session.getRoomSummary(initialState.roomId)
setState { setState {
copy( copy(
currentVersion = room?.getRoomVersion(), currentVersion = room?.roomVersionService()?.getRoomVersion(),
isPublic = summary?.isPublic ?: false, isPublic = summary?.isPublic ?: false,
otherMemberCount = summary?.otherMemberIds?.count() ?: 0, otherMemberCount = summary?.otherMemberIds?.count() ?: 0,
knownParents = summary?.flattenParentIds ?: emptyList() knownParents = summary?.flattenParentIds ?: emptyList()

View file

@ -50,12 +50,12 @@ class UpgradeRoomViewModelTask @Inject constructor(
val room = session.getRoom(params.roomId) val room = session.getRoom(params.roomId)
?: return Result.UnknownRoom ?: return Result.UnknownRoom
if (!room.userMayUpgradeRoom(session.myUserId)) { if (!room.roomVersionService().userMayUpgradeRoom(session.myUserId)) {
return Result.NotAllowed return Result.NotAllowed
} }
val updatedRoomId = try { val updatedRoomId = try {
room.upgradeToVersion(params.newVersion) room.roomVersionService().upgradeToVersion(params.newVersion)
} catch (failure: Throwable) { } catch (failure: Throwable) {
return Result.ErrorFailure(failure) return Result.ErrorFailure(failure)
} }
@ -65,7 +65,7 @@ class UpgradeRoomViewModelTask @Inject constructor(
params.userIdsToAutoInvite.forEach { params.userIdsToAutoInvite.forEach {
params.progressReporter?.invoke(false, currentStep, totalStep) params.progressReporter?.invoke(false, currentStep, totalStep)
tryOrNull { tryOrNull {
session.getRoom(updatedRoomId)?.invite(it) session.getRoom(updatedRoomId)?.membershipService()?.invite(it)
} }
currentStep++ currentStep++
} }

View file

@ -44,12 +44,14 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.databinding.FragmentRoomListBinding
import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.notifications.NotificationDrawerManager
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -179,7 +181,7 @@ class RoomListFragment @Inject constructor(
} }
private fun handleShowMxToLink(link: String) { private fun handleShowMxToLink(link: String) {
navigator.openMatrixToBottomSheet(requireContext(), link) navigator.openMatrixToBottomSheet(requireContext(), link, OriginOfMatrixTo.ROOM_LIST)
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -196,7 +198,12 @@ class RoomListFragment @Inject constructor(
} }
private fun handleSelectRoom(event: RoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) { private fun handleSelectRoom(event: RoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) {
navigator.openRoom(context = requireActivity(), roomId = event.roomSummary.roomId, isInviteAlreadyAccepted = isInviteAlreadyAccepted) navigator.openRoom(
context = requireActivity(),
roomId = event.roomSummary.roomId,
isInviteAlreadyAccepted = isInviteAlreadyAccepted,
trigger = ViewRoom.Trigger.RoomList
)
} }
private fun setupCreateRoomButton() { private fun setupCreateRoomButton() {

View file

@ -32,6 +32,8 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
import im.vector.app.features.analytics.plan.JoinedRoom
import im.vector.app.features.displayname.getBestName import im.vector.app.features.displayname.getBestName
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
@ -43,6 +45,7 @@ import org.matrix.android.sdk.api.extensions.orFalse
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
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
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.tag.RoomTag import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
@ -167,7 +170,7 @@ class RoomListViewModel @AssistedInject constructor(
} }
fun isPublicRoom(roomId: String): Boolean { fun isPublicRoom(roomId: String): Boolean {
return session.getRoom(roomId)?.isPublic().orFalse() return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
} }
// PRIVATE METHODS ***************************************************************************** // PRIVATE METHODS *****************************************************************************
@ -251,7 +254,7 @@ class RoomListViewModel @AssistedInject constructor(
if (room != null) { if (room != null) {
viewModelScope.launch { viewModelScope.launch {
try { try {
room.setRoomNotificationState(action.notificationState) room.roomPushRuleService().setRoomNotificationState(action.notificationState)
} catch (failure: Exception) { } catch (failure: Exception) {
_viewEvents.post(RoomListViewEvents.Failure(failure)) _viewEvents.post(RoomListViewEvents.Failure(failure))
} }
@ -276,6 +279,8 @@ class RoomListViewModel @AssistedInject constructor(
this[action.roomId] = Fail(failure) this[action.roomId] = Fail(failure)
}.toMap()) }.toMap())
} }
session.getRoomSummary(action.roomId)
?.let { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.RoomDirectory)) }
} }
} }
@ -294,13 +299,13 @@ class RoomListViewModel @AssistedInject constructor(
action.tag.otherTag() action.tag.otherTag()
?.takeIf { room.roomSummary()?.hasTag(it).orFalse() } ?.takeIf { room.roomSummary()?.hasTag(it).orFalse() }
?.let { tagToRemove -> ?.let { tagToRemove ->
room.deleteTag(tagToRemove) room.tagsService().deleteTag(tagToRemove)
} }
// Set the tag. We do not handle the order for the moment // Set the tag. We do not handle the order for the moment
room.addTag(action.tag, 0.5) room.tagsService().addTag(action.tag, 0.5)
} else { } else {
room.deleteTag(action.tag) room.tagsService().deleteTag(action.tag)
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(RoomListViewEvents.Failure(failure)) _viewEvents.post(RoomListViewEvents.Failure(failure))

View file

@ -85,7 +85,7 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
private fun observeThreadSummaries() { private fun observeThreadSummaries() {
room?.flow() room?.flow()
?.liveThreadSummaries() ?.liveThreadSummaries()
?.map { room.enhanceThreadWithEditions(it) } ?.map { room.threadsService().enhanceThreadWithEditions(it) }
?.flowOn(room.coroutineDispatchers.io) ?.flowOn(room.coroutineDispatchers.io)
?.execute { asyncThreads -> ?.execute { asyncThreads ->
copy(threadSummaryList = asyncThreads) copy(threadSummaryList = asyncThreads)
@ -99,10 +99,10 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
private fun observeThreadsList() { private fun observeThreadsList() {
room?.flow() room?.flow()
?.liveThreadList() ?.liveThreadList()
?.map { room.mapEventsWithEdition(it) } ?.map { room.threadsLocalService().mapEventsWithEdition(it) }
?.map { ?.map {
it.map { threadRootEvent -> it.map { threadRootEvent ->
val isParticipating = room.isUserParticipatingInThread(threadRootEvent.eventId) val isParticipating = room.threadsLocalService().isUserParticipatingInThread(threadRootEvent.eventId)
ThreadTimelineEvent(threadRootEvent, isParticipating) ThreadTimelineEvent(threadRootEvent, isParticipating)
} }
} }
@ -115,7 +115,7 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
private fun fetchThreadList() { private fun fetchThreadList() {
viewModelScope.launch { viewModelScope.launch {
setLoading(true) setLoading(true)
room?.fetchThreadSummaries() room?.threadsService()?.fetchThreadSummaries()
setLoading(false) setLoading(false)
} }
} }

Some files were not shown because too many files have changed in this diff Show more