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

View File

@ -106,7 +106,7 @@ body:
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
- [ ] Commit and push directly on `main`
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
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.

View File

@ -30,7 +30,7 @@ buildscript {
// ktlint Plugin
plugins {
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
id "org.jlleitschuh.gradle.ktlint" version "10.3.0"
}
// 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.session.events.model.Event
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.model.EventAnnotationsSummary
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>> {
return room.getRoomMembersLive(queryParams).asFlow()
return room.membershipService().getRoomMembersLive(queryParams).asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getRoomMembers(queryParams)
room.membershipService().getRoomMembers(queryParams)
}
}
fun liveAnnotationSummary(eventId: String): Flow<Optional<EventAnnotationsSummary>> {
return room.getEventAnnotationsSummaryLive(eventId).asFlow()
return room.relationService().getEventAnnotationsSummaryLive(eventId).asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getEventAnnotationsSummary(eventId).toOptional()
room.relationService().getEventAnnotationsSummary(eventId).toOptional()
}
}
fun liveTimelineEvent(eventId: String): Flow<Optional<TimelineEvent>> {
return room.getTimelineEventLive(eventId).asFlow()
return room.timelineService().getTimelineEventLive(eventId).asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getTimelineEvent(eventId).toOptional()
}
}
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) {
room.getStateEvent(eventType, stateKey).toOptional()
}
}
fun liveStateEvents(eventTypes: Set<String>): Flow<List<Event>> {
return room.getStateEventsLive(eventTypes).asFlow()
return room.stateService().getStateEventsLive(eventTypes).asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getStateEvents(eventTypes)
room.stateService().getStateEvents(eventTypes)
}
}
fun liveReadMarker(): Flow<Optional<String>> {
return room.getReadMarkerLive().asFlow()
return room.readService().getReadMarkerLive().asFlow()
}
fun liveReadReceipt(): Flow<Optional<String>> {
return room.getMyReadReceiptLive().asFlow()
return room.readService().getMyReadReceiptLive().asFlow()
}
fun liveEventReadReceipts(eventId: String): Flow<List<ReadReceipt>> {
return room.getEventReadReceiptsLive(eventId).asFlow()
return room.readService().getEventReadReceiptsLive(eventId).asFlow()
}
fun liveDraft(): Flow<Optional<UserDraft>> {
return room.getDraftLive().asFlow()
return room.draftService().getDraftLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getDraft().toOptional()
room.draftService().getDraft().toOptional()
}
}
fun liveNotificationState(): Flow<RoomNotificationState> {
return room.getLiveRoomNotificationState().asFlow()
return room.roomPushRuleService().getLiveRoomNotificationState().asFlow()
}
fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
return room.getAllThreadSummariesLive().asFlow()
return room.threadsService().getAllThreadSummariesLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getAllThreadSummaries()
room.threadsService().getAllThreadSummaries()
}
}
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
return room.getAllThreadsLive().asFlow()
return room.threadsLocalService().getAllThreadsLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getAllThreads()
room.threadsLocalService().getAllThreads()
}
}
fun liveLocalUnreadThreadList(): Flow<List<ThreadRootEvent>> {
return room.getMarkedThreadNotificationsLive().asFlow()
return room.threadsLocalService().getMarkedThreadNotificationsLive().asFlow()
.startWith(room.coroutineDispatchers.io) {
room.getMarkedThreadNotifications()
room.threadsLocalService().getMarkedThreadNotifications()
}
}
}

View File

@ -19,13 +19,18 @@ dokkaHtml {
configureEach {
// Emit warnings about not documented members.
reportUndocumented.set(true)
// Suppress package legacy riot. Sadly this does not work
/*
// Suppress legacy Riot's packages.
perPackageOption {
prefix.set("org.matrix.android.sdk.internal.legacy.riot")
matchingRegex.set("org.matrix.android.sdk.internal.legacy.riot")
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
*/
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()
val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout)
timeline.dispose()
@ -166,12 +166,12 @@ class CommonTestHelper(context: Context) {
.forEach { batchedMessages ->
batchedMessages.forEach { formattedMessage ->
if (rootThreadEventId != null) {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = rootThreadEventId,
replyInThreadText = formattedMessage
)
} else {
room.sendTextMessage(formattedMessage)
room.sendService().sendTextMessage(formattedMessage)
}
}
waitWithLatch(timeout) { latch ->
@ -216,7 +216,7 @@ class CommonTestHelper(context: Context) {
numberOfMessages: Int,
rootThreadEventId: String,
timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
val timeline = room.createTimeline(null, TimelineSettings(10))
val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
timeline.start()
val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId)
timeline.dispose()

View File

@ -70,7 +70,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
if (encryptedRoom) {
testHelper.waitWithLatch { latch ->
val room = aliceSession.getRoom(roomId)!!
room.enableEncryption()
room.roomCryptoService().enableEncryption()
val roomSummaryLive = room.getRoomSummaryLive()
val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
override fun onChanged(roomSummary: Optional<RoomSummary>) {
@ -109,7 +109,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
}
}
bobRoomSummariesLive.observeForever(newRoomObserver)
aliceRoom.invite(bobSession.myUserId)
aliceRoom.membershipService().invite(bobSession.myUserId)
}
testHelper.waitWithLatch { latch ->
@ -117,6 +117,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (bobSession.getRoom(aliceRoomId)
?.membershipService()
?.getRoomMember(bobSession.myUserId)
?.membership == Membership.JOIN) {
bobRoomSummariesLive.removeObserver(this)
@ -161,7 +162,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
testHelper.runBlockingTest {
room.invite(samSession.myUserId, null)
room.membershipService().invite(samSession.myUserId, null)
}
testHelper.runBlockingTest {
@ -261,6 +262,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (bob.getRoom(roomId)
?.membershipService()
?.getRoomMember(bob.myUserId)
?.membership == Membership.JOIN) {
bobRoomSummariesLive.removeObserver(this)
@ -373,13 +375,13 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
val room = aliceSession.getRoom(roomId)!!
testHelper.runBlockingTest {
room.enableEncryption()
room.roomCryptoService().enableEncryption()
}
val sessions = mutableListOf(aliceSession)
for (index in 1 until numberOfMembers) {
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")
testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) }
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.room.Room
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.message.MessageContent
import org.matrix.android.sdk.api.session.room.send.SendState
@ -88,7 +89,7 @@ class E2eeSanityTests : InstrumentedTest {
otherAccounts.forEach {
testHelper.runBlockingTest {
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 {
testHelper.runBlockingTest {
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? {
aliceRoomPOV.sendTextMessage(text)
aliceRoomPOV.sendService().sendTextMessage(text)
var sentEventId: String? = null
testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start()
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.toModel
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.CryptoTestHelper

View File

@ -98,7 +98,7 @@ class UnwedgingTest : InstrumentedTest {
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(20))
bobTimeline.start()
val bobFinalLatch = CountDownLatch(1)
@ -129,7 +129,7 @@ class UnwedgingTest : InstrumentedTest {
messagesReceivedByBob = emptyList()
// - 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
testHelper.await(latch)
@ -157,7 +157,7 @@ class UnwedgingTest : InstrumentedTest {
Timber.i("## CRYPTO | testUnwedging: 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
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")
// - 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
}
bobTimeline.removeListener(bobEventsListener)

View File

@ -49,7 +49,7 @@ class EncryptionTest : InstrumentedTest {
fun test_EncryptionEvent() {
performTest(roomShouldBeEncrypted = false) { room ->
// Send an encryption Event as an Event (and not as a state event)
room.sendEvent(
room.sendService().sendEvent(
eventType = EventType.STATE_ROOM_ENCRYPTION,
content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
)
@ -61,7 +61,7 @@ class EncryptionTest : InstrumentedTest {
performTest(roomShouldBeEncrypted = true) { room ->
runBlocking {
// Send an encryption Event as a State Event
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "",
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
@ -76,9 +76,9 @@ class EncryptionTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
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 timelineListener = object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
@ -106,7 +106,7 @@ class EncryptionTest : InstrumentedTest {
testHelper.await(latch)
timeline.dispose()
testHelper.waitWithLatch {
room.isEncrypted() shouldBe roomShouldBeEncrypted
room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
it.countDown()
}
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.toModel
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.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@ -84,7 +85,7 @@ class KeyShareTests : InstrumentedTest {
val room = aliceSession.getRoom(roomId)
assertNotNull(room)
Thread.sleep(4_000)
assertTrue(room?.isEncrypted() == true)
assertTrue(room?.roomCryptoService()?.isEncrypted() == true)
val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
// Open a new sessionx
@ -351,7 +352,7 @@ class KeyShareTests : InstrumentedTest {
val roomAlicePov = aliceSession.getRoom(roomId)
assertNotNull(roomAlicePov)
Thread.sleep(1_000)
assertTrue(roomAlicePov?.isEncrypted() == true)
assertTrue(roomAlicePov?.roomCryptoService()?.isEncrypted() == true)
val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
// Create bob session
@ -375,7 +376,7 @@ class KeyShareTests : InstrumentedTest {
// Let alice invite bob
commonTestHelper.runBlockingTest {
roomAlicePov.invite(bobSession.myUserId, null)
roomAlicePov.membershipService().invite(bobSession.myUserId, null)
}
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.toModel
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.CryptoTestHelper
import org.matrix.android.sdk.common.MockOkHttpInterceptor

View File

@ -81,7 +81,7 @@ class ThreadMessagingTest : InstrumentedTest {
replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
// 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()
aliceSession.startSync(true)
@ -142,7 +142,7 @@ class ThreadMessagingTest : InstrumentedTest {
replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
// 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()
aliceSession.startSync(true)
@ -215,7 +215,7 @@ class ThreadMessagingTest : InstrumentedTest {
}
// 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()
aliceSession.startSync(true)
@ -310,7 +310,7 @@ class ThreadMessagingTest : InstrumentedTest {
}
// 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()
aliceSession.startSync(true)

View File

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

View File

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

View File

@ -72,7 +72,7 @@ class TimelineWithManyMembersTest : InstrumentedTest {
for (index in 1 until cryptoTestData.sessions.size) {
val session = cryptoTestData.sessions[index]
val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!!
val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30))
val timelineForCurrentMember = roomForCurrentMember.timelineService().createTimeline(null, TimelineSettings(30))
timelineForCurrentMember.start()
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.api.session.events.model.EventType
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.PowerLevelsContent
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.getRoom
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.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -499,7 +500,7 @@ class SpaceHierarchyTest : InstrumentedTest {
))
commonTestHelper.runBlockingTest {
aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null)
aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
}
commonTestHelper.runBlockingTest {
@ -509,7 +510,7 @@ class SpaceHierarchyTest : InstrumentedTest {
var bobRoomId = ""
commonTestHelper.waitWithLatch {
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()
}
@ -554,7 +555,7 @@ class SpaceHierarchyTest : InstrumentedTest {
?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
?.toContent()
room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
it.countDown()
}

View File

@ -44,7 +44,7 @@ class RoomMemberCountCondition(
// Parse the is field into prefix and number the first time
val (prefix, count) = parseIsField() ?: return false
val numMembers = room.getNumberOfJoinedMembers()
val numMembers = room.membershipService().getNumberOfJoinedMembers()
return when (prefix) {
"<" -> 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.
*/
interface Room :
TimelineService,
ThreadsService,
ThreadsLocalService,
SendService,
DraftService,
ReadService,
TypingService,
AliasService,
TagsService,
MembershipService,
StateService,
UploadsService,
ReportingService,
RoomCallService,
RelationService,
RoomCryptoService,
RoomPushRuleService,
RoomAccountDataService,
RoomVersionService {
interface Room {
val coroutineDispatchers: MatrixCoroutineDispatchers
@ -87,4 +68,99 @@ interface Room :
* Use this room as a Space, if the type is correct.
*/
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
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
data class EventAnnotationsSummary(
val eventId: String,
val reactionsSummary: List<ReactionAggregatedSummary> = emptyList(),
val editSummary: EditAggregatedSummary? = 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
*/
data class ReferencesAggregatedSummary(
val eventId: String,
val content: Content?,
val sourceEvents: 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.
*/
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.JsonClass
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
/**
* 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)
data class LiveLocationBeaconContent(
data class MessageBeaconInfoContent(
/**
* Local message type, not from server
*/
@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 = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@ -54,26 +57,16 @@ data class LiveLocationBeaconContent(
/**
* Beacon creation timestamp.
*/
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
@Json(name = "m.ts") val timestampAsMilliseconds: Long? = null,
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val timestampMillis: Long? = null,
/**
* Live location asset type.
*/
@Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
@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 {
fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
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.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)
data class MessageLiveLocationContent(
data class MessageBeaconLocationDataContent(
/**
* Local message type, not from server
*/
@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 = "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)
*/
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
@Json(name = "m.ts") val timestampAsMilliseconds: Long? = null
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val timestampMillis: Long? = null
) : MessageContent {
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)
*/
@Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null,
@Json(name = "m.ts") val ts: Long? = null,
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val timestampMillis: Long? = null,
@Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
@Json(name = "m.text") val text: String? = null,
/**
@ -66,7 +66,7 @@ data class MessageLocationContent(
fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
fun getBestTs() = ts ?: unstableTs
fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
fun getBestText() = text ?: unstableText

View File

@ -41,6 +41,6 @@ object MessageType {
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
// 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_LIVE_LOCATION = "org.matrix.android.sdk.livelocation"
const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
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 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
* @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

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.room.model.EventAnnotationsSummary
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.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
@ -139,7 +139,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
return when (root.getClearType()) {
EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
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()
}
}

View File

@ -19,6 +19,9 @@ package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy
import io.realm.RealmConfiguration
import io.realm.RealmResults
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.matrix.android.sdk.internal.database.mapper.asDomain
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)
}
private val onResultsChangedFlow = MutableSharedFlow<RealmResults<EventInsertEntity>>()
init {
onResultsChangedFlow
.onEach { handleChange(it) }
.launchIn(observerScope)
}
override fun onChange(results: RealmResults<EventInsertEntity>) {
if (!results.isLoaded || results.isEmpty()) {
return
}
observerScope.launch { onResultsChangedFlow.emit(results) }
}
private suspend fun handleChange(results: RealmResults<EventInsertEntity>) {
val idsToDeleteAfterProcess = ArrayList<String>()
val filteredEvents = ArrayList<EventInsertEntity>(results.size)
Timber.v("EventInsertEntity updated with ${results.size} results in db")
@ -58,30 +73,29 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
}
idsToDeleteAfterProcess.add(it.eventId)
}
observerScope.launch {
awaitTransaction(realmConfiguration) { realm ->
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
filteredEvents.forEach { eventInsert ->
val eventId = eventInsert.eventId
val event = EventEntity.where(realm, eventId).findFirst()
if (event == null) {
Timber.v("Event $eventId not found")
return@forEach
}
val domainEvent = event.asDomain()
processors.filter {
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
}.forEach {
it.process(realm, domainEvent)
}
awaitTransaction(realmConfiguration) { realm ->
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
filteredEvents.forEach { eventInsert ->
val eventId = eventInsert.eventId
val event = EventEntity.where(realm, eventId).findFirst()
if (event == null) {
Timber.v("Event $eventId not found")
return@forEach
}
val domainEvent = event.asDomain()
processors.filter {
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
}.forEach {
it.process(realm, domainEvent)
}
realm.where(EventInsertEntity::class.java)
.`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
.findAll()
.deleteAllFromRealm()
}
processors.forEach { it.onPostProcess() }
realm.where(EventInsertEntity::class.java)
.`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
.findAll()
.deleteAllFromRealm()
}
processors.forEach { it.onPostProcess() }
}
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {

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.MigrateSessionTo025
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 timber.log.Timber
import javax.inject.Inject
@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000
val schemaVersion = 26L
val schemaVersion = 27L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
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 < 25) MigrateSessionTo025(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 {
fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
return EventAnnotationsSummary(
eventId = annotationsSummary.eventId,
reactionsSummary = annotationsSummary.reactionsSummary.toList().map {
ReactionAggregatedSummary(
it.key,
@ -50,7 +49,6 @@ internal object EventAnnotationsSummaryMapper {
},
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
ReferencesAggregatedSummary(
it.eventId,
ContentMapper.map(it.content),
it.sourceEvents.toList(),
it.sourceLocalEcho.toList()
@ -58,8 +56,10 @@ internal object EventAnnotationsSummaryMapper {
},
pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
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.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import timber.log.Timber
internal open class EventAnnotationsSummaryEntity(
@ -27,7 +28,8 @@ internal open class EventAnnotationsSummaryEntity(
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
var editSummary: EditAggregatedSummaryEntity? = null,
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null,
var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null,
) : RealmObject() {
/**

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.database.model
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.threads.ThreadSummaryEntity
@ -47,6 +48,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
EditAggregatedSummaryEntity::class,
EditionOfEvent::class,
PollResponseAggregatedSummaryEntity::class,
LiveLocationShareAggregatedSummaryEntity::class,
ReferencesAggregatedSummaryEntity::class,
PushRulesEntity::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> {
return roomGetterProvider.get().getRoom(roomId)
?.membershipService()
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
.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
fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
?.membershipService()
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
?.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.RoomMemberCountCondition
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.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter
@ -60,7 +61,7 @@ internal class DefaultConditionResolver @Inject constructor(
condition: ContainsDisplayNameCondition): Boolean {
val roomId = event.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)
}
}

View File

@ -18,13 +18,11 @@ package org.matrix.android.sdk.internal.session.room
import androidx.lifecycle.LiveData
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.accountdata.RoomAccountDataService
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.crypto.RoomCryptoService
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.RoomType
@ -44,57 +42,35 @@ 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.space.Space
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.room.state.SendStateTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.space.DefaultSpace
import java.security.InvalidParameterException
internal class DefaultRoom(override val roomId: String,
private val roomSummaryDataSource: RoomSummaryDataSource,
private val timelineService: TimelineService,
private val threadsService: ThreadsService,
private val threadsLocalService: ThreadsLocalService,
private val sendService: SendService,
private val draftService: DraftService,
private val stateService: StateService,
private val uploadsService: UploadsService,
private val reportingService: ReportingService,
private val roomCallService: RoomCallService,
private val readService: ReadService,
private val typingService: TypingService,
private val aliasService: AliasService,
private val tagsService: TagsService,
private val cryptoService: CryptoService,
private val relationService: RelationService,
private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService,
private val roomAccountDataService: RoomAccountDataService,
private val roomVersionService: RoomVersionService,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
override val coroutineDispatchers: MatrixCoroutineDispatchers
) :
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 {
internal class DefaultRoom(
override val roomId: String,
private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomCryptoService: RoomCryptoService,
private val timelineService: TimelineService,
private val threadsService: ThreadsService,
private val threadsLocalService: ThreadsLocalService,
private val sendService: SendService,
private val draftService: DraftService,
private val stateService: StateService,
private val uploadsService: UploadsService,
private val reportingService: ReportingService,
private val roomCallService: RoomCallService,
private val readService: ReadService,
private val typingService: TypingService,
private val aliasService: AliasService,
private val tagsService: TagsService,
private val relationService: RelationService,
private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService,
private val roomAccountDataService: RoomAccountDataService,
private val roomVersionService: RoomVersionService,
private val viaParameterFinder: ViaParameterFinder,
override val coroutineDispatchers: MatrixCoroutineDispatchers
) : Room {
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
return roomSummaryDataSource.getRoomSummaryLive(roomId)
@ -104,49 +80,28 @@ internal class DefaultRoom(override val roomId: String,
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? {
if (roomSummary()?.roomType != RoomType.SPACE) return null
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.toContent
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.PowerLevelsContent
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.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.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.MessagePollResponseContent
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_KEY,
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 {
return allowedTypes.contains(eventType)
@ -108,12 +110,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
when (event.type) {
EventType.REACTION -> {
EventType.REACTION -> {
// we got a reaction!!
Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
handleReaction(realm, event, roomId, isLocalEcho)
}
EventType.MESSAGE -> {
EventType.MESSAGE -> {
if (event.unsignedData?.relations?.annotations != null) {
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
@ -139,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_MAC,
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY -> {
EventType.KEY_VERIFICATION_KEY -> {
Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
event.content.toModel<MessageRelationContent>()?.relatesTo?.let {
if (it.type == RelationType.REFERENCE && it.eventId != null) {
@ -148,7 +150,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
}
EventType.ENCRYPTED -> {
EventType.ENCRYPTED -> {
// Relation type is in clear
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE ||
@ -191,8 +193,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
}
in EventType.BEACON_LOCATION_DATA -> {
event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho)
}
}
}
@ -215,7 +217,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// }
// }
}
EventType.REDACTION -> {
EventType.REDACTION -> {
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
?: return
when (eventToPrune.type) {
@ -235,7 +237,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
}
}
in EventType.POLL_START -> {
in EventType.POLL_START -> {
val content: MessagePollContent? = event.content.toModel()
if (content?.relatesTo?.type == RelationType.REPLACE) {
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
@ -243,22 +245,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
handleReplace(realm, event, content, roomId, isLocalEcho)
}
}
in EventType.POLL_RESPONSE -> {
in EventType.POLL_RESPONSE -> {
event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
handleResponse(realm, event, it, roomId, isLocalEcho)
}
}
in EventType.POLL_END -> {
in EventType.POLL_END -> {
event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
handleEndPoll(realm, event, it, roomId, isLocalEcho)
}
}
in EventType.BEACON_LOCATION_DATA -> {
event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
in EventType.STATE_ROOM_BEACON_INFO -> {
event.content.toModel<MessageBeaconInfoContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho)
}
}
else -> Timber.v("UnHandled event ${event.eventId}")
else -> Timber.v("UnHandled event ${event.eventId}")
}
} catch (t: Throwable) {
Timber.e(t, "## Should not happen ")

View File

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

View File

@ -17,13 +17,13 @@
package org.matrix.android.sdk.internal.session.room
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.internal.session.SessionScope
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.alias.DefaultAliasService
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.membership.DefaultMembershipService
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.send.DefaultSendService
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.tags.DefaultTagsService
import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService
@ -48,35 +47,36 @@ internal interface RoomFactory {
}
@SessionScope
internal class DefaultRoomFactory @Inject constructor(private val cryptoService: CryptoService,
private val roomSummaryDataSource: RoomSummaryDataSource,
private val timelineServiceFactory: DefaultTimelineService.Factory,
private val threadsServiceFactory: DefaultThreadsService.Factory,
private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory,
private val sendServiceFactory: DefaultSendService.Factory,
private val draftServiceFactory: DefaultDraftService.Factory,
private val stateServiceFactory: DefaultStateService.Factory,
private val uploadsServiceFactory: DefaultUploadsService.Factory,
private val reportingServiceFactory: DefaultReportingService.Factory,
private val roomCallServiceFactory: DefaultRoomCallService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
private val typingServiceFactory: DefaultTypingService.Factory,
private val aliasServiceFactory: DefaultAliasService.Factory,
private val tagsServiceFactory: DefaultTagsService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
private val coroutineDispatchers: MatrixCoroutineDispatchers) :
RoomFactory {
internal class DefaultRoomFactory @Inject constructor(
private val roomSummaryDataSource: RoomSummaryDataSource,
private val timelineServiceFactory: DefaultTimelineService.Factory,
private val threadsServiceFactory: DefaultThreadsService.Factory,
private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory,
private val sendServiceFactory: DefaultSendService.Factory,
private val draftServiceFactory: DefaultDraftService.Factory,
private val stateServiceFactory: DefaultStateService.Factory,
private val uploadsServiceFactory: DefaultUploadsService.Factory,
private val reportingServiceFactory: DefaultReportingService.Factory,
private val roomCallServiceFactory: DefaultRoomCallService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
private val typingServiceFactory: DefaultTypingService.Factory,
private val aliasServiceFactory: DefaultAliasService.Factory,
private val tagsServiceFactory: DefaultTagsService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val roomCryptoServiceFactory: DefaultRoomCryptoService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
private val viaParameterFinder: ViaParameterFinder,
private val coroutineDispatchers: MatrixCoroutineDispatchers
) : RoomFactory {
override fun create(roomId: String): Room {
return DefaultRoom(
roomId = roomId,
roomSummaryDataSource = roomSummaryDataSource,
roomCryptoService = roomCryptoServiceFactory.create(roomId),
timelineService = timelineServiceFactory.create(roomId),
threadsService = threadsServiceFactory.create(roomId),
threadsLocalService = threadsLocalServiceFactory.create(roomId),
@ -90,13 +90,11 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
typingService = typingServiceFactory.create(roomId),
aliasService = aliasServiceFactory.create(roomId),
tagsService = tagsServiceFactory.create(roomId),
cryptoService = cryptoService,
relationService = relationServiceFactory.create(roomId),
roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
roomVersionService = roomVersionServiceFactory.create(roomId),
sendStateTask = sendStateTask,
viaParameterFinder = viaParameterFinder,
coroutineDispatchers = coroutineDispatchers
)

View File

@ -17,69 +17,78 @@
package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
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.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
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
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.query.getOrNull
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import timber.log.Timber
import javax.inject.Inject
internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor {
override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) {
val locationSenderId = event.senderId ?: return
// We shouldn't process local echos
if (isLocalEcho) {
override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
return
}
// A beacon info state event has to be sent before sending location
// TODO handle missing check of m_relatesTo field
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
val targetEventId = if (content.isLive.orTrue()) {
event.eventId
} 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
}
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
realm = realm,
roomId = roomId,
eventId = targetEventId
)
Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")
aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
aggregatedSummary.isActive = content.isLive
}
private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent,
liveLocationContent: MessageLiveLocationContent): Boolean {
val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0
val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0
val timeout = beaconInfoContent.timeout ?: 0
return liveLocationEventTime - beaconInfoStartTime > timeout
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 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 {
fun handleLiveLocation(realm: Realm,
event: Event,
content: MessageLiveLocationContent,
roomId: String,
isLocalEcho: Boolean)
fun handleBeaconInfo(
realm: Realm,
event: Event,
content: MessageBeaconInfoContent,
roomId: String,
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.LocationInfo
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.MessageContentWithFormattedBody
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.MessageFormat
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.MessagePollContent
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.util.time.Clock
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
/**
@ -245,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor(
body = geoUri,
unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
unstableLocationAsset = LocationAsset(type = assetType),
unstableTs = TimeUnit.MILLISECONDS.toSeconds(clock.epochMillis()),
unstableTimestampMillis = clock.epochMillis(),
unstableText = geoUri
)
return createMessageEvent(roomId, content)
@ -257,14 +256,14 @@ internal class LocalEchoEventFactory @Inject constructor(
longitude: Double,
uncertainty: Double?): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val content = MessageLiveLocationContent(
val content = MessageBeaconLocationDataContent(
body = geoUri,
relatesTo = RelationDefaultContent(
type = RelationType.REFERENCE,
eventId = beaconInfoEventId
),
unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
unstableTimestampAsMilliseconds = TimeUnit.MILLISECONDS.toSeconds(clock.epochMillis()),
unstableTimestampMillis = clock.epochMillis(),
)
val localId = LocalEcho.createLocalEchoId()
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.RoomJoinRulesAllowEntry
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.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes
@ -73,14 +73,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
eventType: String,
stateKey: String,
body: JsonDict
) {
): String {
val params = SendStateTask.Params(
roomId = roomId,
stateKey = stateKey,
eventType = eventType,
body = body.toSafeJson(eventType)
)
sendStateTask.executeRetry(params, 3)
return sendStateTask.executeRetry(params, 3)
}
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
@ -192,7 +192,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
override suspend fun stopLiveLocation(userId: String) {
getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent ->
beaconInfoStateEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.let { content ->
beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.let { content ->
val updatedContent = content.copy(isLive = false).toContent()
beaconInfoStateEvent.stateKey?.let {
@ -217,7 +217,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
}
.firstOrNull { beaconInfoEvent ->
!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.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
import javax.inject.Inject
internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
internal interface SendStateTask : Task<SendStateTask.Params, String> {
data class Params(
val roomId: String,
val stateKey: String,
@ -37,9 +38,9 @@ internal class DefaultSendStateTask @Inject constructor(
private val globalErrorReceiver: GlobalErrorReceiver
) : SendStateTask {
override suspend fun execute(params: SendStateTask.Params) {
override suspend fun execute(params: SendStateTask.Params): String {
return executeRequest(globalErrorReceiver) {
if (params.stateKey.isEmpty()) {
val response = if (params.stateKey.isEmpty()) {
roomAPI.sendStateEvent(
roomId = params.roomId,
stateEventType = params.eventType,
@ -53,6 +54,9 @@ internal class DefaultSendStateTask @Inject constructor(
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.RealmQuery
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.RoomCategoryFilter
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.util.Optional
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.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@ -57,10 +55,8 @@ import javax.inject.Inject
internal class RoomSummaryDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
private val roomSummaryMapper: RoomSummaryMapper,
private val queryStringValueProcessor: QueryStringValueProcessor,
private val coroutineDispatchers: MatrixCoroutineDispatchers
) {
fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {

View File

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

View File

@ -60,7 +60,7 @@ internal class DefaultSpace(
}
?: viaParameterFinder.computeViaParams(roomId, 3))
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId,
body = SpaceChildContent(
@ -79,7 +79,7 @@ internal class DefaultSpace(
// return
// edit state event and set via to null
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId,
body = SpaceChildContent(
@ -91,19 +91,19 @@ internal class DefaultSpace(
}
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()
?.content.toModel<SpaceChildContent>()
}
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()
?.content.toModel<SpaceChildContent>()
?: throw IllegalArgumentException("$roomId is not a child of this space")
// edit state event and set via to null
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId,
body = SpaceChildContent(
@ -140,7 +140,7 @@ internal class DefaultSpace(
// }
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()
?.content.toModel<SpaceChildContent>()
?: throw IllegalArgumentException("$roomId is not a child of this space")
@ -150,7 +150,7 @@ internal class DefaultSpace(
return
}
// edit state event and set via to null
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_CHILD,
stateKey = roomId,
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.toModel
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.Membership
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
@ -258,7 +259,7 @@ internal class DefaultSpaceService @Inject constructor(
val room = roomGetter.getRoom(childRoomId)
?: throw IllegalArgumentException("Unknown Room $childRoomId")
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_PARENT,
stateKey = parentSpaceId,
body = SpaceParentContent(
@ -276,7 +277,7 @@ internal class DefaultSpaceService @Inject constructor(
if (existingEvent != null) {
// Should i check if it was sent by me?
// we don't check power level, it will throw if you cannot do that
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType = EventType.STATE_SPACE_PARENT,
stateKey = parentSpaceId,
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.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import timber.log.Timber
import javax.inject.Inject
internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, Unit> {
internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, String> {
data class Params(
val roomId: String,
@ -45,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask {
override suspend fun execute(params: CreateWidgetTask.Params) {
executeRequest(globalErrorReceiver) {
override suspend fun execute(params: CreateWidgetTask.Params): String {
val response = executeRequest(globalErrorReceiver) {
roomAPI.sendStateEvent(
roomId = params.roomId,
stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY,
@ -60,5 +61,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
.and()
.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.toContent
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.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
@ -148,14 +149,22 @@ class PushRulesConditionTest : MatrixTest {
val room2JoinedId = "2joined"
val room3JoinedId = "3joined"
val roomStub2Joined = mockk<Room> {
val roomMembershipService2 = mockk<MembershipService> {
every { getNumberOfJoinedMembers() } returns 2
}
val roomStub3Joined = mockk<Room> {
val roomMembershipService3 = mockk<MembershipService> {
every { getNumberOfJoinedMembers() } returns 3
}
val roomStub2Joined = mockk<Room> {
every { membershipService() } returns roomMembershipService2
}
val roomStub3Joined = mockk<Room> {
every { membershipService() } returns roomMembershipService3
}
val roomGetterStub = mockk<RoomGetter> {
every { getRoom(room2JoinedId) } returns roomStub2Joined
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.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.getTimelineEvent
import timber.log.Timber
import javax.inject.Inject

View File

@ -102,7 +102,7 @@ class AppStateHandler @Inject constructor(
if (spaceId != null) {
uSession.coroutineScope.launch(Dispatchers.IO) {
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(
isDM = this?.isDirect.orFalse(),
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(
isDM = 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) =
room.getRoomMembers(queryParams)
room.membershipService()
.getRoomMembers(queryParams)
.asSequence()
.sortedBy { it.displayName }
.disambiguate()

View File

@ -30,7 +30,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
fun nativeRoomForVirtualRoom(roomId: String): String? {
if (!protocolsChecker.supportVirtualRooms) 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
}
@ -79,7 +79,7 @@ class CallUserMapper(private val session: Session, private val protocolsChecker:
private suspend fun Room.markVirtual(nativeRoomId: String) {
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 {

View File

@ -30,7 +30,7 @@ fun WebRtcCall.getOpponentAsMatrixItem(session: Session): MatrixItem? {
roomSummary.toMatrixItem()
} else {
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.registerForPermissionsResult
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.qrcode.QrCodeScannerEvents
import im.vector.app.features.qrcode.QrCodeScannerFragment
@ -206,7 +207,11 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
}
private fun renderCreationSuccess(roomId: String) {
navigator.openRoom(this, roomId)
navigator.openRoom(
context = this,
roomId = roomId,
trigger = ViewRoom.Trigger.MessageUser
)
finish()
}

View File

@ -19,6 +19,7 @@ import android.content.Context
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity
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.home.AvatarRenderer
import im.vector.app.features.home.room.detail.RoomDetailActivity
@ -159,7 +160,12 @@ class IncomingVerificationRequestHandler @Inject constructor(
if (roomId.isNullOrBlank()) {
it.navigator.waitSessionVerification(it)
} 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 ?: "")
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_content))
room.sendStateEvent(
room.stateService().sendStateEvent(
state.selectedEvent?.type.orEmpty(),
state.selectedEvent?.stateKey.orEmpty(),
json
)
_viewEvents.post(DevToolsViewEvents.ShowSnackMessage(stringProvider.getString(R.string.dev_tools_success_state_event)))
setState {
@ -212,7 +211,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_no_message_type))
if (isState) {
room.sendStateEvent(
room.stateService().sendStateEvent(
eventType,
state.sendEventDraft.stateKey.orEmpty(),
json
@ -222,7 +221,7 @@ class RoomDevToolViewModel @AssistedInject constructor(
// val validParse = MoshiProvider.providesMoshi().adapter(MessageContent::class.java).fromJson(it.sendEventDraft.content ?: "")
json.toModel<MessageContent>(catchError = false)
?: throw IllegalArgumentException(stringProvider.getString(R.string.dev_tools_error_malformed_event))
room.sendEvent(
room.sendService().sendEvent(
eventType,
json
)

View File

@ -49,8 +49,10 @@ import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
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.matrixto.MatrixToBottomSheet
import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.permalink.NavigationInterceptor
@ -243,7 +245,7 @@ class HomeActivity :
}
if (args?.inviteNotificationRoomId != null) {
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()
?.permalinkService()
?.createPermalink(parcelableExtra.inviteNotificationRoomId)?.let {
navigator.openMatrixToBottomSheet(this, it)
navigator.openMatrixToBottomSheet(this, it, OriginOfMatrixTo.NOTIFICATION)
}
}
handleIntent(intent)
@ -567,14 +569,14 @@ class HomeActivity :
override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean {
// TODO check if there is already one??
MatrixToBottomSheet.withLink(deepLink.toString())
MatrixToBottomSheet.withLink(deepLink.toString(), OriginOfMatrixTo.LINK)
.show(supportFragmentManager, "HA#MatrixToBottomSheet")
return true
}
override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean {
if (roomId == null) return false
MatrixToBottomSheet.withLink(deepLink.toString())
MatrixToBottomSheet.withLink(deepLink.toString(), OriginOfMatrixTo.LINK)
.show(supportFragmentManager, "HA#MatrixToBottomSheet")
return true
}
@ -608,8 +610,8 @@ class HomeActivity :
}
}
override fun mxToBottomSheetNavigateToRoom(roomId: String) {
navigator.openRoom(this, roomId)
override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) {
navigator.openRoom(this, roomId, trigger = trigger)
}
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.databinding.ActivityRoomDetailBinding
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.detail.arguments.TimelineArgs
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
@ -205,8 +206,8 @@ class RoomDetailActivity :
}
}
override fun mxToBottomSheetNavigateToRoom(roomId: String) {
navigator.openRoom(this, roomId)
override fun mxToBottomSheetNavigateToRoom(roomId: String, trigger: ViewRoom.Trigger?) {
navigator.openRoom(this, roomId, trigger = trigger)
}
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.DecryptionFailureTracker
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.JitsiActiveConferenceHolder
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.getRoom
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.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
@ -182,7 +185,7 @@ class TimelineViewModel @AssistedInject constructor(
setupPreviewUrlObservers()
room.getRoomSummaryLive()
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
viewModelScope.launch(Dispatchers.IO) {
@ -193,7 +196,7 @@ class TimelineViewModel @AssistedInject constructor(
chatEffectManager.delegate = this
// 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()
}
@ -249,7 +252,7 @@ class TimelineViewModel @AssistedInject constructor(
prepareToEncrypt = Loading()
viewModelScope.launch {
runCatching {
room.prepareToEncrypt()
room.roomCryptoService().prepareToEncrypt()
}.fold({
prepareToEncrypt = Success(Unit)
}, {
@ -353,7 +356,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun markThreadTimelineAsReadLocal() {
initialState.rootThreadEventId?.let {
session.coroutineScope.launch {
room.markThreadAsRead(it)
room.threadsLocalService().markThreadAsRead(it)
}
}
}
@ -489,7 +492,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) {
viewModelScope.launch(Dispatchers.IO) {
try {
room.updateAvatar(action.newAvatarUri, action.newAvatarFileName)
room.stateService().updateAvatar(action.newAvatarUri, action.newAvatarFileName)
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
} catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
@ -506,7 +509,7 @@ class TimelineViewModel @AssistedInject constructor(
}
private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
room.getUserReadReceipt(action.userId)
room.readService().getUserReadReceipt(action.userId)
?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }
}
@ -518,7 +521,7 @@ class TimelineViewModel @AssistedInject constructor(
eventId = it))
} ?: action.stickerContent
room.sendEvent(EventType.STICKER, content.toContent())
room.sendService().sendEvent(EventType.STICKER, content.toContent())
}
private fun handleStartCall(action: RoomDetailAction.StartCall) {
@ -638,7 +641,7 @@ class TimelineViewModel @AssistedInject constructor(
if (trackUnreadMessages.getAndSet(false)) {
mostRecentDisplayedEvent?.root?.eventId?.also {
session.coroutineScope.launch {
tryOrNull { room.setReadMarker(it) }
tryOrNull { room.readService().setReadMarker(it) }
}
}
mostRecentDisplayedEvent = null
@ -651,12 +654,12 @@ class TimelineViewModel @AssistedInject constructor(
}
fun getMember(userId: String): RoomMemberSummary? {
return room.getRoomMember(userId)
return room.membershipService().getRoomMember(userId)
}
private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) {
// Ensure outbound session keys
if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) {
if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) {
if (action.focused) {
// Should we add some rate limit here, or do it only once per model lifecycle?
prepareForEncryption()
@ -737,36 +740,36 @@ class TimelineViewModel @AssistedInject constructor(
// PRIVATE METHODS *****************************************************************************
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) {
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) {
viewModelScope.launch {
tryOrNull {
room.undoReaction(action.targetEventId, action.reaction)
room.relationService().undoReaction(action.targetEventId, action.reaction)
}
}
}
private fun handleUpdateQuickReaction(action: RoomDetailAction.UpdateQuickReactAction) {
if (action.add) {
room.sendReaction(action.targetEventId, action.selectedReaction)
room.relationService().sendReaction(action.targetEventId, action.selectedReaction)
} else {
viewModelScope.launch {
tryOrNull {
room.undoReaction(action.targetEventId, action.selectedReaction)
room.relationService().undoReaction(action.targetEventId, action.selectedReaction)
}
}
}
}
private fun handleSendMedia(action: RoomDetailAction.SendMedia) {
room.sendMedias(
room.sendService().sendMedias(
action.attachments,
action.compressBeforeSending,
emptySet(),
@ -821,13 +824,22 @@ class TimelineViewModel @AssistedInject constructor(
viewModelScope.launch {
try {
session.roomService().joinRoom(room.roomId)
analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom())
trackRoomJoined()
} catch (throwable: Throwable) {
_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) {
val mxcUrl = action.messageFileContent.getFileUrl() ?: return
val isLocalSendingFile = action.senderId == session.myUserId &&
@ -892,8 +904,8 @@ class TimelineViewModel @AssistedInject constructor(
return
}
when {
it.root.isTextMessage() -> room.resendTextMessage(it)
it.root.isAttachmentMessage() -> room.resendMediaMessage(it)
it.root.isTextMessage() -> room.sendService().resendTextMessage(it)
it.root.isAttachmentMessage() -> room.sendService().resendMediaMessage(it)
else -> {
// TODO
}
@ -909,13 +921,13 @@ class TimelineViewModel @AssistedInject constructor(
Timber.e("Cannot resend message, it is not failed, Cancel first")
return
}
room.deleteFailedEcho(it)
room.sendService().deleteFailedEcho(it)
}
}
private fun handleCancel(action: RoomDetailAction.CancelSend) {
if (action.force) {
room.cancelSend(action.eventId)
room.sendService().cancelSend(action.eventId)
return
}
val targetEventId = action.eventId
@ -925,16 +937,16 @@ class TimelineViewModel @AssistedInject constructor(
Timber.e("Cannot cancel message, it is not sending")
return
}
room.cancelSend(targetEventId)
room.sendService().cancelSend(targetEventId)
}
}
private fun handleResendAll() {
room.resendAllFailedMessages()
room.sendService().resendAllFailedMessages()
}
private fun handleRemoveAllFailedMessages() {
room.cancelAllFailedMessages()
room.sendService().cancelAllFailedMessages()
}
private fun observeEventDisplayedActions() {
@ -957,7 +969,7 @@ class TimelineViewModel @AssistedInject constructor(
}
bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId ->
session.coroutineScope.launch {
tryOrNull { room.setReadReceipt(eventId) }
tryOrNull { room.readService().setReadReceipt(eventId) }
}
}
}
@ -974,14 +986,14 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleMarkAllAsRead() {
setState { copy(unreadState = UnreadState.HasNoUnread) }
viewModelScope.launch {
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) }
tryOrNull { room.readService().markAsRead(ReadService.MarkAsReadParams.BOTH) }
}
}
private fun handleReportContent(action: RoomDetailAction.ReportContent) {
viewModelScope.launch {
val event = try {
room.reportContent(action.eventId, -100, action.reason)
room.reportingService().reportContent(action.eventId, -100, action.reason)
RoomDetailViewEvents.ActionSuccess(action)
} catch (failure: Exception) {
RoomDetailViewEvents.ActionFailure(action, failure)
@ -1071,13 +1083,13 @@ class TimelineViewModel @AssistedInject constructor(
room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent ->
val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote
if (currentVote != action.optionKey) {
room.voteToPoll(action.eventId, action.optionKey)
room.sendService().voteToPoll(action.eventId, action.optionKey)
}
}
}
private fun handleEndPoll(eventId: String) {
room.endPoll(eventId)
room.sendService().endPoll(eventId)
}
private fun observeSyncState() {
@ -1255,7 +1267,7 @@ class TimelineViewModel @AssistedInject constructor(
timeline.removeAllListeners()
decryptionFailureTracker.onTimeLineDisposed(room.roomId)
if (vectorPreferences.sendTypingNotifs()) {
room.userStopsTyping()
room.typingService().userStopsTyping()
}
chatEffectManager.delegate = null
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.extensions.toAnalyticsComposer
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.command.CommandParser
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.getRoom
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.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm
@ -204,12 +207,12 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.ErrorNotACommand -> {
// Send the text message to the room
if (state.rootThreadEventId != null) {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId,
replyInThreadText = action.text,
autoMarkdown = action.autoMarkdown)
} else {
room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
room.sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
}
_viewEvents.post(MessageComposerViewEvents.MessageSent)
@ -230,12 +233,12 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.SendPlainText -> {
// Send the text message to the room, without markdown
if (state.rootThreadEventId != null) {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message,
autoMarkdown = false)
} else {
room.sendTextMessage(parsedCommand.message, autoMarkdown = false)
room.sendService().sendTextMessage(parsedCommand.message, autoMarkdown = false)
}
_viewEvents.post(MessageComposerViewEvents.MessageSent)
popDraft()
@ -285,13 +288,16 @@ class MessageComposerViewModel @AssistedInject constructor(
}
is ParsedCommand.SendEmote -> {
if (state.rootThreadEventId != null) {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message,
msgType = MessageType.MSGTYPE_EMOTE,
autoMarkdown = action.autoMarkdown)
} 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))
popDraft()
@ -299,12 +305,12 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.SendRainbow -> {
val message = parsedCommand.message.toString()
if (state.rootThreadEventId != null) {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message,
formattedText = rainbowGenerator.generate(message))
} else {
room.sendFormattedTextMessage(message, rainbowGenerator.generate(message))
room.sendService().sendFormattedTextMessage(message, rainbowGenerator.generate(message))
}
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
popDraft()
@ -312,13 +318,13 @@ class MessageComposerViewModel @AssistedInject constructor(
is ParsedCommand.SendRainbowEmote -> {
val message = parsedCommand.message.toString()
if (state.rootThreadEventId != null) {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId,
replyInThreadText = parsedCommand.message,
msgType = MessageType.MSGTYPE_EMOTE,
formattedText = rainbowGenerator.generate(message))
} 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))
@ -328,12 +334,12 @@ class MessageComposerViewModel @AssistedInject constructor(
val text = "[${stringProvider.getString(R.string.spoiler)}](${parsedCommand.message})"
val formattedText = "<span data-mx-spoiler>${parsedCommand.message}</span>"
if (state.rootThreadEventId != null) {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = state.rootThreadEventId,
replyInThreadText = text,
formattedText = formattedText)
} else {
room.sendFormattedTextMessage(
room.sendService().sendFormattedTextMessage(
text,
formattedText)
}
@ -376,7 +382,7 @@ class MessageComposerViewModel @AssistedInject constructor(
popDraft()
}
is ParsedCommand.DiscardSession -> {
if (room.isEncrypted()) {
if (room.roomCryptoService().isEncrypted()) {
session.cryptoService().discardOutboundSession(room.roomId)
_viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk(parsedCommand))
popDraft()
@ -488,13 +494,13 @@ class MessageComposerViewModel @AssistedInject constructor(
if (inReplyTo != null) {
// TODO check if same content?
room.getTimelineEvent(inReplyTo)?.let {
room.editReply(state.sendMode.timelineEvent, it, action.text.toString())
room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString())
}
} else {
val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
val existingBody = messageContent?.body ?: ""
if (existingBody != action.text) {
room.editTextMessage(state.sendMode.timelineEvent,
room.relationService().editTextMessage(state.sendMode.timelineEvent,
messageContent?.msgType ?: MessageType.MSGTYPE_TEXT,
action.text,
action.autoMarkdown)
@ -506,7 +512,7 @@ class MessageComposerViewModel @AssistedInject constructor(
popDraft()
}
is SendMode.Quote -> {
room.sendQuotedTextMessage(
room.sendService().sendQuotedTextMessage(
quotedEvent = state.sendMode.timelineEvent,
text = action.text.toString(),
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
val rootThreadEventId = if (showInThread) timelineEvent.root.getRootThreadEventId() else null
state.rootThreadEventId?.let {
room.replyInThread(
room.relationService().replyInThread(
rootThreadEventId = it,
replyInThreadText = action.text.toString(),
autoMarkdown = action.autoMarkdown,
eventReplied = timelineEvent)
} ?: room.replyToMessage(
} ?: room.relationService().replyToMessage(
eventReplied = timelineEvent,
replyText = action.text.toString(),
autoMarkdown = action.autoMarkdown,
@ -551,13 +557,13 @@ class MessageComposerViewModel @AssistedInject constructor(
// Otherwise we clear the composer and remove the draft from db
setState { copy(sendMode = SendMode.Regular("", false)) }
viewModelScope.launch {
room.deleteDraft()
room.draftService().deleteDraft()
}
}
}
private fun loadDraftIfAny() {
val currentDraft = room.getDraft()
val currentDraft = room.draftService().getDraft()
setState {
copy(
// Create a sendMode from a draft and retrieve the TimelineEvent
@ -588,9 +594,9 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) {
if (vectorPreferences.sendTypingNotifs()) {
if (action.isTyping) {
room.userIsTyping()
room.typingService().userIsTyping()
} else {
room.userStopsTyping()
room.typingService().userStopsTyping()
}
}
}
@ -602,9 +608,9 @@ class MessageComposerViewModel @AssistedInject constructor(
ChatEffect.CONFETTI -> R.string.default_message_emote_confetti
ChatEffect.SNOWFALL -> R.string.default_message_emote_snow
})
room.sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE)
room.sendService().sendTextMessage(defaultMessage, MessageType.MSGTYPE_EMOTE)
} 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
}
session.getRoomSummary(command.roomAlias)
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom()) }
?.also { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.SlashCommand)) }
?.roomId
?.let {
_viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it))
@ -647,19 +653,19 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
launchSlashCommandFlowSuspendable(changeTopic) {
room.updateTopic(changeTopic.topic)
room.stateService().updateTopic(changeTopic.topic)
}
}
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
launchSlashCommandFlowSuspendable(invite) {
room.invite(invite.userId, invite.reason)
room.membershipService().invite(invite.userId, invite.reason)
}
}
private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) {
launchSlashCommandFlowSuspendable(invite) {
room.invite3pid(invite.threePid)
room.membershipService().invite3pid(invite.threePid)
}
}
@ -672,7 +678,7 @@ class MessageComposerViewModel @AssistedInject constructor(
?: return
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) {
launchSlashCommandFlowSuspendable(removeUser) {
room.remove(removeUser.userId, removeUser.reason)
room.membershipService().remove(removeUser.userId, removeUser.reason)
}
}
private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) {
launchSlashCommandFlowSuspendable(ban) {
room.ban(ban.userId, ban.reason)
room.membershipService().ban(ban.userId, ban.reason)
}
}
private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) {
launchSlashCommandFlowSuspendable(unban) {
room.unban(unban.userId, unban.reason)
room.membershipService().unban(unban.userId, unban.reason)
}
}
private fun handleChangeRoomNameSlashCommand(changeRoomName: ParsedCommand.ChangeRoomName) {
launchSlashCommandFlowSuspendable(changeRoomName) {
room.updateName(changeRoomName.name)
room.stateService().updateName(changeRoomName.name)
}
}
@ -734,14 +740,14 @@ class MessageComposerViewModel @AssistedInject constructor(
?.copy(displayName = changeDisplayName.displayName)
?.toContent()
?.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) {
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)
?.toContent()
?.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 {
room.replyInThread(it, sequence)
} ?: room.sendTextMessage(sequence)
room.relationService().replyInThread(it, sequence)
} ?: room.sendService().sendTextMessage(sequence)
}
/**
@ -804,19 +810,19 @@ class MessageComposerViewModel @AssistedInject constructor(
when {
it.sendMode is SendMode.Regular && !it.sendMode.fromSharing -> {
setState { copy(sendMode = it.sendMode.copy(text = draft)) }
room.saveDraft(UserDraft.Regular(draft))
room.draftService().saveDraft(UserDraft.Regular(draft))
}
it.sendMode is SendMode.Reply -> {
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 -> {
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 -> {
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 {
audioMessageHelper.stopRecording(convertForSending = true)?.let { audioType ->
if (audioType.duration > 1000) {
room.sendMedia(
room.sendService().sendMedia(
attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
compressBeforeSending = false,
roomIds = emptySet(),
@ -904,7 +910,7 @@ class MessageComposerViewModel @AssistedInject constructor(
viewModelScope.launch {
playingAudioContent?.toContentAttachmentData()?.let { voiceDraft ->
val content = voiceDraft.toJsonString()
room.saveDraft(UserDraft.Voice(content))
room.draftService().saveDraft(UserDraft.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.VectorBaseFragment
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 kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.events.model.Event
@ -134,7 +135,16 @@ class SearchFragment @Inject constructor(
roomEncryptionTrustLevel = null,
rootThreadEventId = it)
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() {

View File

@ -62,7 +62,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(
viewModelScope.launch {
val data = try {
room.fetchEditHistory(eventId)
room.relationService().fetchEditHistory(eventId)
} catch (failure: Throwable) {
setState {
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 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
class LiveLocationMessageItemFactory @Inject constructor(
@ -34,19 +34,20 @@ class LiveLocationMessageItemFactory @Inject constructor(
) {
fun create(
liveLocationContent: LiveLocationBeaconContent,
beaconInfoContent: MessageBeaconInfoContent,
highlight: Boolean,
attributes: AbsMessageItem.Attributes,
): VectorEpoxyModel<*>? {
// TODO handle location received and stopped states
return when {
isLiveRunning(liveLocationContent) -> buildStartLiveItem(highlight, attributes)
else -> null
isLiveRunning(beaconInfoContent) -> buildStartLiveItem(highlight, attributes)
else -> null
}
}
private fun isLiveRunning(liveLocationContent: LiveLocationBeaconContent): Boolean {
return liveLocationContent.isLive.orFalse() && liveLocationContent.hasTimedOut.not()
private fun isLiveRunning(beaconInfoContent: MessageBeaconInfoContent): Boolean {
// TODO when we will use aggregatedSummary, check if the live has timed out as well
return beaconInfoContent.isLive.orFalse()
}
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.toModel
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.create.RoomCreateContent
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.isThread
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.MessageBeaconInfoContent
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.MessageEmoteContent
@ -209,15 +209,15 @@ class MessageItemFactory @Inject constructor(
is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
is MessageLocationContent -> {
is MessageLocationContent -> {
if (vectorPreferences.labsRenderLocationsInTimeline()) {
buildLocationItem(messageContent, informationData, highlight, attributes)
} else {
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
}
}
is LiveLocationBeaconContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
}
return messageItem?.apply {
layout(informationData.messageLayout.layoutRes)

View File

@ -45,18 +45,18 @@ class TimelineFactory @Inject constructor(private val session: Session, private
val settings = timelineSettingsFactory.create(rootThreadEventId)
if (!session.vectorCallService.protocolChecker.supportVirtualRooms) {
return mainRoom.createTimeline(eventId, settings)
return mainRoom.timelineService().createTimeline(eventId, settings)
}
val virtualRoomId = session.vectorCallService.userMapper.virtualRoomForNativeRoom(mainRoom.roomId)
return if (virtualRoomId == null) {
mainRoom.createTimeline(eventId, settings)
mainRoom.timelineService().createTimeline(eventId, settings)
} else {
val virtualRoom = session.getRoom(virtualRoomId)!!
MergedTimelines(
coroutineScope = coroutineScope,
mainTimeline = mainRoom.createTimeline(eventId, settings),
mainTimeline = mainRoom.timelineService().createTimeline(eventId, settings),
secondaryTimelineParams = MergedTimelines.SecondaryTimelineParams(
timeline = virtualRoom.createTimeline(null, settings),
timeline = virtualRoom.timelineService().createTimeline(null, settings),
shouldFilterTypes = true,
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.toModel
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.MessageVerificationCancelContent
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 org.matrix.android.sdk.api.session.Session
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.unwrap

View File

@ -59,12 +59,12 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_STICKER_LOCAL,
MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_LIVE_LOCATION_STATE,
MessageType.MSGTYPE_BEACON_INFO,
)
private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf(
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_LIVE_LOCATION_STATE,
MessageType.MSGTYPE_BEACON_INFO,
)
}
@ -151,8 +151,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
private fun MessageContent?.shouldAddMessageOverlay(): Boolean {
return when {
this == null || msgType == MessageType.MSGTYPE_LIVE_LOCATION_STATE -> false
msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline()
this == null || msgType == MessageType.MSGTYPE_BEACON_INFO -> false
msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline()
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)
setState {
copy(
currentVersion = room?.getRoomVersion(),
currentVersion = room?.roomVersionService()?.getRoomVersion(),
isPublic = summary?.isPublic ?: false,
otherMemberCount = summary?.otherMemberIds?.count() ?: 0,
knownParents = summary?.flattenParentIds ?: emptyList()

View File

@ -50,12 +50,12 @@ class UpgradeRoomViewModelTask @Inject constructor(
val room = session.getRoom(params.roomId)
?: return Result.UnknownRoom
if (!room.userMayUpgradeRoom(session.myUserId)) {
if (!room.roomVersionService().userMayUpgradeRoom(session.myUserId)) {
return Result.NotAllowed
}
val updatedRoomId = try {
room.upgradeToVersion(params.newVersion)
room.roomVersionService().upgradeToVersion(params.newVersion)
} catch (failure: Throwable) {
return Result.ErrorFailure(failure)
}
@ -65,7 +65,7 @@ class UpgradeRoomViewModelTask @Inject constructor(
params.userIdsToAutoInvite.forEach {
params.progressReporter?.invoke(false, currentStep, totalStep)
tryOrNull {
session.getRoom(updatedRoomId)?.invite(it)
session.getRoom(updatedRoomId)?.membershipService()?.invite(it)
}
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.databinding.FragmentRoomListBinding
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.room.filtered.FilteredRoomFooterItem
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.RoomListQuickActionsSharedActionViewModel
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 kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
@ -179,7 +181,7 @@ class RoomListFragment @Inject constructor(
}
private fun handleShowMxToLink(link: String) {
navigator.openMatrixToBottomSheet(requireContext(), link)
navigator.openMatrixToBottomSheet(requireContext(), link, OriginOfMatrixTo.ROOM_LIST)
}
override fun onDestroyView() {
@ -196,7 +198,12 @@ class RoomListFragment @Inject constructor(
}
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() {

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.resources.StringProvider
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.invite.AutoAcceptInvites
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.session.Session
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.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
@ -167,7 +170,7 @@ class RoomListViewModel @AssistedInject constructor(
}
fun isPublicRoom(roomId: String): Boolean {
return session.getRoom(roomId)?.isPublic().orFalse()
return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
}
// PRIVATE METHODS *****************************************************************************
@ -251,7 +254,7 @@ class RoomListViewModel @AssistedInject constructor(
if (room != null) {
viewModelScope.launch {
try {
room.setRoomNotificationState(action.notificationState)
room.roomPushRuleService().setRoomNotificationState(action.notificationState)
} catch (failure: Exception) {
_viewEvents.post(RoomListViewEvents.Failure(failure))
}
@ -276,6 +279,8 @@ class RoomListViewModel @AssistedInject constructor(
this[action.roomId] = Fail(failure)
}.toMap())
}
session.getRoomSummary(action.roomId)
?.let { analyticsTracker.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.RoomDirectory)) }
}
}
@ -294,13 +299,13 @@ class RoomListViewModel @AssistedInject constructor(
action.tag.otherTag()
?.takeIf { room.roomSummary()?.hasTag(it).orFalse() }
?.let { tagToRemove ->
room.deleteTag(tagToRemove)
room.tagsService().deleteTag(tagToRemove)
}
// 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 {
room.deleteTag(action.tag)
room.tagsService().deleteTag(action.tag)
}
} catch (failure: Throwable) {
_viewEvents.post(RoomListViewEvents.Failure(failure))

View File

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

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