diff --git a/CHANGES.md b/CHANGES.md
index cac9ab26086..5bd85efc675 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,34 @@
+Changes in Element v.5.16 (2022-12-29)
+======================================
+
+Features ✨
+----------
+ - [Rich text editor] Add support for links ([#7746](https://github.com/vector-im/element-android/issues/7746))
+ - [Poll] When a poll is ended, use /relations API to ensure poll results are correct ([#7767](https://github.com/vector-im/element-android/issues/7767))
+ - [Session manager] Security recommendations cards: whole view should be tappable ([#7795](https://github.com/vector-im/element-android/issues/7795))
+ - [Session manager] Other sessions list: header should not be sticky ([#7797](https://github.com/vector-im/element-android/issues/7797))
+
+Bugfixes 🐛
+----------
+ - Do not show typing notification of ignored users. ([#2965](https://github.com/vector-im/element-android/issues/2965))
+ - [Push Notifications, Threads] - quick reply to threaded notification now sent to thread except main timeline ([#7475](https://github.com/vector-im/element-android/issues/7475))
+ - [Session manager] Other sessions list: filter option is displayed when selection mode is enabled ([#7784](https://github.com/vector-im/element-android/issues/7784))
+ - [Session manager] Other sessions: Filter bottom sheet cut in landscape mode ([#7786](https://github.com/vector-im/element-android/issues/7786))
+ - Automatically show keyboard after learn more bottom sheet is dismissed ([#7790](https://github.com/vector-im/element-android/issues/7790))
+ - [Session Manager] Other sessions list: cannot select/deselect session by a long press when in select mode ([#7792](https://github.com/vector-im/element-android/issues/7792))
+ - Fix current session ip address visibility ([#7794](https://github.com/vector-im/element-android/issues/7794))
+ - Device Manager UI review fixes ([#7798](https://github.com/vector-im/element-android/issues/7798))
+
+SDK API changes ⚠️
+------------------
+ - [Sync] Sync Filter params are moved to MatrixConfiguration and will not be stored in session realm to avoid bug when session cache is cleared ([#7843](https://github.com/vector-im/element-android/issues/7843))
+
+Other changes
+-------------
+ - [Voice Broadcast] Replace the player timeline ([#7821](https://github.com/vector-im/element-android/issues/7821))
+ - Increase session manager test coverage ([#7836](https://github.com/vector-im/element-android/issues/7836))
+
+
Changes in Element v1.5.14 (2022-12-20)
=======================================
diff --git a/dependencies.gradle b/dependencies.gradle
index dbb5f5fe053..b81c1c20179 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -26,7 +26,7 @@ def jjwt = "0.11.5"
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
// the whole commit which set version 0.16.0-SNAPSHOT
def vanniktechEmoji = "0.16.0-SNAPSHOT"
-def sentry = "6.9.0"
+def sentry = "6.9.2"
def fragment = "1.5.5"
// Testing
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
@@ -83,7 +83,7 @@ ext.libs = [
'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
// Phone number https://github.com/google/libphonenumber
- 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.1"
+ 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.3"
],
dagger : [
'dagger' : "com.google.dagger:dagger:$dagger",
@@ -98,7 +98,7 @@ ext.libs = [
],
element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0",
- 'wysiwyg' : "io.element.android:wysiwyg:0.9.0"
+ 'wysiwyg' : "io.element.android:wysiwyg:0.10.0"
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",
@@ -129,7 +129,7 @@ ext.libs = [
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
],
maplibre : [
- 'androidSdk' : "org.maplibre.gl:android-sdk:9.5.2",
+ 'androidSdk' : "org.maplibre.gl:android-sdk:9.6.0",
'pluginAnnotation' : "org.maplibre.gl:android-plugin-annotation-v9:1.0.0"
],
mockk : [
diff --git a/fastlane/metadata/android/en-US/changelogs/40105160.txt b/fastlane/metadata/android/en-US/changelogs/40105160.txt
new file mode 100644
index 00000000000..91c25cf053b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40105160.txt
@@ -0,0 +1,2 @@
+Main changes in this version: Thread are now enabled by default.
+Full changelog: https://github.com/vector-im/element-android/releases
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index d37b5f09067..73cb60bb689 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -419,6 +419,7 @@
Got it
Select all
Deselect all
+ Yes, Stop
Copied to clipboard
@@ -3120,6 +3121,8 @@
You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.
%1$s left
+ Stop live broadcasting?
+ Are you sure you want to stop your live broadcast? This will end the broadcast and the full recording will be available in the room.
Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.
Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime.
@@ -3335,7 +3338,7 @@
- Consider signing out from old sessions (%1$d day or more) that you don’t use anymore.
- Consider signing out from old sessions (%1$d days or more) that you don’t use anymore.
- Current Session
+ Current session
Session
Device
@@ -3476,13 +3479,19 @@
Confirm
Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.
-
+
Apply bold format
Apply italic format
Apply strikethrough format
Apply underline format
+ Set link
Toggle full screen mode
+ Text
+ Link
+ Create a link
+ Edit link
+
In reply to
sent a file.
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 4558f4e8b58..f839a6c2633 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -62,7 +62,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
- buildConfigField "String", "SDK_VERSION", "\"1.5.14\""
+ buildConfigField "String", "SDK_VERSION", "\"1.5.16\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index 8edecb273d1..eeb2def5827 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -50,7 +50,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
-import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
import timber.log.Timber
import java.util.UUID
import java.util.concurrent.CountDownLatch
@@ -347,10 +346,6 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
assertTrue(registrationResult is RegistrationResult.Success)
val session = (registrationResult as RegistrationResult.Success).session
session.open()
- session.filterService().setSyncFilter(
- SyncFilterBuilder()
- .lazyLoadMembersForStateEvents(true)
- )
if (sessionTestParams.withInitialSync) {
syncSession(session, 120_000)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/SyncConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/SyncConfig.kt
index a9753e2407f..84650da72f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/SyncConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/SyncConfig.kt
@@ -16,9 +16,13 @@
package org.matrix.android.sdk.api
+import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
+
data class SyncConfig(
/**
* Time to keep sync connection alive for before making another request in milliseconds.
*/
val longPollTimeout: Long = 30_000L,
+
+ val syncFilterParams: SyncFilterParams = SyncFilterParams()
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
index 13993149f4e..cf0f4bdce0b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
@@ -50,7 +50,6 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi
import org.matrix.android.sdk.api.session.signout.SignOutService
import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.api.session.statistics.StatisticsListener
-import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.sync.SyncService
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
@@ -163,11 +162,6 @@ interface Session {
*/
fun signOutService(): SignOutService
- /**
- * Returns the FilterService associated with the session.
- */
- fun filterService(): FilterService
-
/**
* Returns the PushRuleService associated with the session.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 40ce6ecb5c1..9b5f4ac19f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -388,7 +388,13 @@ fun Event.isLocationMessage(): Boolean {
}
}
-fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START.values || getClearType() in EventType.POLL_END.values
+fun Event.isPoll(): Boolean = isPollStart() || isPollEnd()
+
+fun Event.isPollStart(): Boolean = getClearType() in EventType.POLL_START.values
+
+fun Event.isPollResponse(): Boolean = getClearType() in EventType.POLL_RESPONSE.values
+
+fun Event.isPollEnd(): Boolean = getClearType() in EventType.POLL_END.values
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterParams.kt
similarity index 91%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterParams.kt
index a7de7f55796..02c5b0f8efd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterParams.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package org.matrix.android.sdk.internal.sync.filter
+package org.matrix.android.sdk.api.session.sync.filter
-internal data class SyncFilterParams(
+data class SyncFilterParams(
val lazyLoadMembersForStateEvents: Boolean? = null,
val lazyLoadMembersForMessageEvents: Boolean? = null,
val useThreadNotifications: Boolean? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
index bc3309132a8..c9eabeab480 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
@@ -24,10 +24,12 @@ import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
+import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
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.content.OlmEventContent
@@ -85,6 +87,27 @@ internal class EventDecryptor @Inject constructor(
return internalDecryptEvent(event, timeline)
}
+ /**
+ * Decrypt an event and save the result in the given event.
+ *
+ * @param event the raw event.
+ * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
+ */
+ suspend fun decryptEventAndSaveResult(event: Event, timeline: String) {
+ tryOrNull(message = "Unable to decrypt the event") {
+ decryptEvent(event, timeline)
+ }
+ ?.let { result ->
+ event.mxDecryptionResult = OlmDecryptionResult(
+ payload = result.clearEvent,
+ senderKey = result.senderCurve25519Key,
+ keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
+ forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
+ isSafe = result.isSafe
+ )
+ }
+ }
+
/**
* Decrypt an event asynchronously.
*
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 5295abffe32..ba102a7a48c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -63,6 +63,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@@ -71,7 +72,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
- schemaVersion = 46L,
+ schemaVersion = 47L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@@ -127,5 +128,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 44) MigrateSessionTo044(realm).perform()
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
+ if (oldVersion < 47) MigrateSessionTo047(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt
deleted file mode 100644
index 645cb41af5c..00000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/FilterParamsMapper.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 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 io.realm.RealmList
-import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity
-import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams
-import javax.inject.Inject
-
-internal class FilterParamsMapper @Inject constructor() {
-
- fun map(entity: SyncFilterParamsEntity): SyncFilterParams {
- val eventTypes = if (entity.listOfSupportedEventTypesHasBeenSet) {
- entity.listOfSupportedEventTypes?.toList()
- } else {
- null
- }
- val stateEventTypes = if (entity.listOfSupportedStateEventTypesHasBeenSet) {
- entity.listOfSupportedStateEventTypes?.toList()
- } else {
- null
- }
- return SyncFilterParams(
- useThreadNotifications = entity.useThreadNotifications,
- lazyLoadMembersForMessageEvents = entity.lazyLoadMembersForMessageEvents,
- lazyLoadMembersForStateEvents = entity.lazyLoadMembersForStateEvents,
- listOfSupportedEventTypes = eventTypes,
- listOfSupportedStateEventTypes = stateEventTypes,
- )
- }
-
- fun map(params: SyncFilterParams): SyncFilterParamsEntity {
- return SyncFilterParamsEntity(
- useThreadNotifications = params.useThreadNotifications,
- lazyLoadMembersForMessageEvents = params.lazyLoadMembersForMessageEvents,
- lazyLoadMembersForStateEvents = params.lazyLoadMembersForStateEvents,
- listOfSupportedEventTypes = params.listOfSupportedEventTypes.toRealmList(),
- listOfSupportedEventTypesHasBeenSet = params.listOfSupportedEventTypes != null,
- listOfSupportedStateEventTypes = params.listOfSupportedStateEventTypes.toRealmList(),
- listOfSupportedStateEventTypesHasBeenSet = params.listOfSupportedStateEventTypes != null,
- )
- }
-
- private fun List?.toRealmList(): RealmList? {
- return this?.toTypedArray()?.let { RealmList(*it) }
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo047.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo047.kt
new file mode 100644
index 00000000000..5bfaaa760cd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo047.kt
@@ -0,0 +1,27 @@
+/*
+ * 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 org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateSessionTo047(realm: DynamicRealm) : RealmMigrator(realm, 47) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.remove("SyncFilterParamsEntity")
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index 0ab30657ed4..0d998e8fe15 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -72,7 +72,6 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
SpaceParentSummaryEntity::class,
UserPresenceEntity::class,
ThreadSummaryEntity::class,
- SyncFilterParamsEntity::class,
ThreadListPageEntity::class
]
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index 679c5085efc..1af904bbc74 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -57,7 +57,6 @@ import org.matrix.android.sdk.api.session.search.SearchService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.session.signout.SignOutService
import org.matrix.android.sdk.api.session.space.SpaceService
-import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.sync.SyncService
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
@@ -97,7 +96,6 @@ internal class DefaultSession @Inject constructor(
private val roomService: Lazy,
private val roomDirectoryService: Lazy,
private val userService: Lazy,
- private val filterService: Lazy,
private val federationService: Lazy,
private val cacheService: Lazy,
private val signOutService: Lazy,
@@ -209,7 +207,6 @@ internal class DefaultSession @Inject constructor(
override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get()
override fun userService(): UserService = userService.get()
override fun signOutService(): SignOutService = signOutService.get()
- override fun filterService(): FilterService = filterService.get()
override fun pushRuleService(): PushRuleService = pushRuleService.get()
override fun pushersService(): PushersService = pushersService.get()
override fun eventService(): EventService = eventService.get()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt
index 4e5b005584f..f70b4d17996 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterRepository.kt
@@ -17,20 +17,15 @@
package org.matrix.android.sdk.internal.session.filter
import com.zhuinden.monarchy.Monarchy
-import io.realm.kotlin.where
-import org.matrix.android.sdk.internal.database.mapper.FilterParamsMapper
import org.matrix.android.sdk.internal.database.model.FilterEntity
-import org.matrix.android.sdk.internal.database.model.SyncFilterParamsEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams
import org.matrix.android.sdk.internal.util.awaitTransaction
import javax.inject.Inject
internal class DefaultFilterRepository @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
- private val filterParamsMapper: FilterParamsMapper
) : FilterRepository {
override suspend fun storeSyncFilter(filter: Filter, filterId: String, roomEventFilter: RoomEventFilter) {
@@ -69,19 +64,4 @@ internal class DefaultFilterRepository @Inject constructor(
FilterEntity.getOrCreate(it).roomEventFilterJson
}
}
-
- override suspend fun getStoredFilterParams(): SyncFilterParams? {
- return monarchy.awaitTransaction { realm ->
- realm.where().findFirst()?.let {
- filterParamsMapper.map(it)
- }
- }
- }
-
- override suspend fun storeFilterParams(params: SyncFilterParams) {
- return monarchy.awaitTransaction { realm ->
- val entity = filterParamsMapper.map(params)
- realm.insertOrUpdate(entity)
- }
- }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt
deleted file mode 100644
index c54e7de07ae..00000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/DefaultFilterService.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.session.filter
-
-import org.matrix.android.sdk.api.session.sync.FilterService
-import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
-import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
-import javax.inject.Inject
-
-internal class DefaultFilterService @Inject constructor(
- private val saveFilterTask: SaveFilterTask,
- private val filterRepository: FilterRepository,
- private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
-) : FilterService {
-
- // TODO Pass a list of support events instead
- override suspend fun setSyncFilter(filterBuilder: SyncFilterBuilder) {
- filterRepository.storeFilterParams(filterBuilder.extractParams())
-
- // don't upload/store filter until homeserver capabilities are fetched
- homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.let { homeServerCapabilities ->
- saveFilterTask.execute(
- SaveFilterTask.Params(
- filter = filterBuilder.build(homeServerCapabilities)
- )
- )
- }
- }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt
index ca9f798fd96..5ae2c2a47d9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterModule.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.filter
import dagger.Binds
import dagger.Module
import dagger.Provides
-import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.internal.session.SessionScope
import retrofit2.Retrofit
@@ -39,9 +38,6 @@ internal abstract class FilterModule {
@Binds
abstract fun bindFilterRepository(repository: DefaultFilterRepository): FilterRepository
- @Binds
- abstract fun bindFilterService(service: DefaultFilterService): FilterService
-
@Binds
abstract fun bindSaveFilterTask(task: DefaultSaveFilterTask): SaveFilterTask
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt
index 71d7391e87c..d0ec4b98bb2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterRepository.kt
@@ -16,8 +16,6 @@
package org.matrix.android.sdk.internal.session.filter
-import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams
-
/**
* Repository for request filters.
*/
@@ -44,14 +42,4 @@ internal interface FilterRepository {
* Return the room filter.
*/
suspend fun getRoomFilterBody(): String
-
- /**
- * Returns filter params stored in local storage if it exists.
- */
- suspend fun getStoredFilterParams(): SyncFilterParams?
-
- /**
- * Stores filter params to local storage.
- */
- suspend fun storeFilterParams(params: SyncFilterParams)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt
index 76805c5c514..5c7027f8b3a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/GetCurrentFilterTask.kt
@@ -16,9 +16,10 @@
package org.matrix.android.sdk.internal.session.filter
+import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
-import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
+import org.matrix.android.sdk.internal.sync.filter.SyncFilterBuilder
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
@@ -27,7 +28,8 @@ internal interface GetCurrentFilterTask : Task
internal class DefaultGetCurrentFilterTask @Inject constructor(
private val filterRepository: FilterRepository,
private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
- private val saveFilterTask: SaveFilterTask
+ private val saveFilterTask: SaveFilterTask,
+ private val matrixConfiguration: MatrixConfiguration
) : GetCurrentFilterTask {
override suspend fun execute(params: Unit): String {
@@ -35,7 +37,7 @@ internal class DefaultGetCurrentFilterTask @Inject constructor(
val storedFilterBody = filterRepository.getStoredSyncFilterBody()
val homeServerCapabilities = homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities()
val currentFilter = SyncFilterBuilder()
- .with(filterRepository.getStoredFilterParams())
+ .with(matrixConfiguration.syncConfig.syncFilterParams)
.build(homeServerCapabilities)
val currentFilterBody = currentFilter.toJSONString()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index ddb7d6a8e6b..34b6ee525d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomStrippedState
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
@@ -251,7 +250,7 @@ internal interface RoomAPI {
* @param limit max number of Event to retrieve
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}")
- suspend fun getRelations(
+ suspend fun getRelationsWithEventType(
@Path("roomId") roomId: String,
@Path("eventId") eventId: String,
@Path("relationType") relationType: String,
@@ -262,7 +261,7 @@ internal interface RoomAPI {
): RelationsResponse
/**
- * Paginate relations for thread events based in normal topological order.
+ * Paginate relations for events based in normal topological order.
*
* @param roomId the room Id
* @param eventId the event Id
@@ -272,10 +271,10 @@ internal interface RoomAPI {
* @param limit max number of Event to retrieve
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}")
- suspend fun getThreadsRelations(
+ suspend fun getRelations(
@Path("roomId") roomId: String,
@Path("eventId") eventId: String,
- @Path("relationType") relationType: String = RelationType.THREAD,
+ @Path("relationType") relationType: String,
@Query("from") from: String? = null,
@Query("to") to: String? = null,
@Query("limit") limit: Int? = null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
index 1475b672767..c28d24995fd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
@@ -99,6 +99,8 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultUpdateQuickR
import org.matrix.android.sdk.internal.session.room.relation.FetchEditHistoryTask
import org.matrix.android.sdk.internal.session.room.relation.FindReactionEventForUndoTask
import org.matrix.android.sdk.internal.session.room.relation.UpdateQuickReactionTask
+import org.matrix.android.sdk.internal.session.room.relation.poll.DefaultFetchPollResponseEventsTask
+import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollResponseEventsTask
import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadSummariesTask
import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask
@@ -354,4 +356,7 @@ internal abstract class RoomModule {
@Binds
abstract fun bindRedactLiveLocationShareTask(task: DefaultRedactLiveLocationShareTask): RedactLiveLocationShareTask
+
+ @Binds
+ abstract fun bindFetchPollResponseEventsTask(task: DefaultFetchPollResponseEventsTask): FetchPollResponseEventsTask
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
index 455ccabbc62..a424becbd68 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
@@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.room.aggregation.poll
import io.realm.Realm
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
@@ -40,9 +41,14 @@ import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSumm
import org.matrix.android.sdk.internal.database.query.create
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollResponseEventsTask
+import org.matrix.android.sdk.internal.task.TaskExecutor
import javax.inject.Inject
-class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationProcessor {
+internal class DefaultPollAggregationProcessor @Inject constructor(
+ private val taskExecutor: TaskExecutor,
+ private val fetchPollResponseEventsTask: FetchPollResponseEventsTask,
+) : PollAggregationProcessor {
override fun handlePollStartEvent(realm: Realm, event: Event): Boolean {
val content = event.getClearContent()?.toModel()
@@ -174,6 +180,10 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
}
+ if (!isLocalEcho) {
+ ensurePollIsFullyAggregated(roomId, pollEventId)
+ }
+
return true
}
@@ -200,4 +210,20 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
eventAnnotationsSummaryEntity.pollResponseSummary = it
}
}
+
+ /**
+ * Check that all related votes to a given poll are all retrieved and aggregated.
+ */
+ private fun ensurePollIsFullyAggregated(
+ roomId: String,
+ pollEventId: String
+ ) {
+ taskExecutor.executorScope.launch {
+ val params = FetchPollResponseEventsTask.Params(
+ roomId = roomId,
+ startPollEventId = pollEventId,
+ )
+ fetchPollResponseEventsTask.execute(params)
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
index 93c7f143fd3..50439f51ebb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
@@ -43,7 +43,7 @@ internal class DefaultFetchEditHistoryTask @Inject constructor(
override suspend fun execute(params: FetchEditHistoryTask.Params): List {
val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId)
val response = executeRequest(globalErrorReceiver) {
- roomAPI.getRelations(
+ roomAPI.getRelationsWithEventType(
roomId = params.roomId,
eventId = params.eventId,
relationType = RelationType.REPLACE,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/poll/FetchPollResponseEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/poll/FetchPollResponseEventsTask.kt
new file mode 100644
index 00000000000..e7dd8c57eb3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/poll/FetchPollResponseEventsTask.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.relation.poll
+
+import androidx.annotation.VisibleForTesting
+import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.RelationType
+import org.matrix.android.sdk.api.session.events.model.isPollResponse
+import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.internal.crypto.EventDecryptor
+import org.matrix.android.sdk.internal.database.mapper.toEntity
+import org.matrix.android.sdk.internal.database.model.EventEntity
+import org.matrix.android.sdk.internal.database.model.EventInsertType
+import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
+import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.di.SessionDatabase
+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.session.room.relation.RelationsResponse
+import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
+import javax.inject.Inject
+
+@VisibleForTesting
+const val FETCH_RELATED_EVENTS_LIMIT = 50
+
+/**
+ * Task to fetch all the vote events to ensure full aggregation for a given poll.
+ */
+internal interface FetchPollResponseEventsTask : Task> {
+ data class Params(
+ val roomId: String,
+ val startPollEventId: String,
+ )
+}
+
+internal class DefaultFetchPollResponseEventsTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val globalErrorReceiver: GlobalErrorReceiver,
+ @SessionDatabase private val monarchy: Monarchy,
+ private val clock: Clock,
+ private val eventDecryptor: EventDecryptor,
+) : FetchPollResponseEventsTask {
+
+ override suspend fun execute(params: FetchPollResponseEventsTask.Params): Result = runCatching {
+ var nextBatch: String? = fetchAndProcessRelatedEventsFrom(params)
+
+ while (nextBatch?.isNotEmpty() == true) {
+ nextBatch = fetchAndProcessRelatedEventsFrom(params, from = nextBatch)
+ }
+ }
+
+ private suspend fun fetchAndProcessRelatedEventsFrom(params: FetchPollResponseEventsTask.Params, from: String? = null): String? {
+ val response = getRelatedEvents(params, from)
+
+ val filteredEvents = response.chunks
+ .map { decryptEventIfNeeded(it) }
+ .filter { it.isPollResponse() }
+
+ addMissingEventsInDB(params.roomId, filteredEvents)
+
+ return response.nextBatch
+ }
+
+ private suspend fun getRelatedEvents(params: FetchPollResponseEventsTask.Params, from: String? = null): RelationsResponse {
+ return executeRequest(globalErrorReceiver, canRetry = true) {
+ roomAPI.getRelations(
+ roomId = params.roomId,
+ eventId = params.startPollEventId,
+ relationType = RelationType.REFERENCE,
+ from = from,
+ limit = FETCH_RELATED_EVENTS_LIMIT,
+ )
+ }
+ }
+
+ private suspend fun addMissingEventsInDB(roomId: String, events: List) {
+ monarchy.awaitTransaction { realm ->
+ val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() }
+ if (eventIdsToCheck.isNotEmpty()) {
+ val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId }
+
+ events.filterNot { it.eventId in existingIds }
+ .map { it.toEntity(roomId = roomId, sendState = SendState.SYNCED, ageLocalTs = computeLocalTs(it)) }
+ .forEach { it.copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) }
+ }
+ }
+ }
+
+ private suspend fun decryptEventIfNeeded(event: Event): Event {
+ if (event.isEncrypted()) {
+ eventDecryptor.decryptEventAndSaveResult(event, timeline = "")
+ }
+
+ event.ageLocalTs = computeLocalTs(event)
+
+ return event
+ }
+
+ private fun computeLocalTs(event: Event) = clock.epochMillis() - (event.unsignedData?.age ?: 0)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index 4cf6445920c..1e9a785c803 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
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.RelationType
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
@@ -102,11 +103,12 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
override suspend fun execute(params: FetchThreadTimelineTask.Params): Result {
val response = executeRequest(globalErrorReceiver) {
- roomAPI.getThreadsRelations(
+ roomAPI.getRelations(
roomId = params.roomId,
eventId = params.rootThreadEventId,
+ relationType = RelationType.THREAD,
from = params.from,
- limit = params.limit
+ limit = params.limit,
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 69beb8d599a..8adfdc5dbbc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -196,6 +196,11 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
+ if (roomSummary?.joinedMembersCount == null) {
+ // in case m.joined_member_count from sync summary was null?
+ // better to use what we know
+ roomSummaryEntity.joinedMembersCount = otherRoomMembers.size + 1
+ }
if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) {
if (aggregator == null) {
// Do it now
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index e0751865ad5..3707205aefb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -16,8 +16,6 @@
package org.matrix.android.sdk.internal.session.room.timeline
-import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@@ -48,18 +46,7 @@ internal class DefaultGetEventTask @Inject constructor(
// Try to decrypt the Event
if (event.isEncrypted()) {
- tryOrNull(message = "Unable to decrypt the event") {
- eventDecryptor.decryptEvent(event, "")
- }
- ?.let { result ->
- event.mxDecryptionResult = OlmDecryptionResult(
- payload = result.clearEvent,
- senderKey = result.senderCurve25519Key,
- keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
- forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
- isSafe = result.isSafe
- )
- }
+ eventDecryptor.decryptEventAndSaveResult(event, timeline = "")
}
event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt
index 54bb63753c9..519112b1b78 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
import io.realm.Realm
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
+import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
@@ -30,8 +31,15 @@ internal class RoomTypingUsersHandler @Inject constructor(
// TODO This could be handled outside of the Realm transaction. Use the new aggregator?
fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) {
+ val typingUserIds = ephemeralResult?.typingUserIds
+ if (typingUserIds.isNullOrEmpty()) {
+ typingUsersTracker.setTypingUsersFromRoom(roomId, emptyList())
+ return
+ }
+ // Filter ignored users and current user
+ val filteredUserIds = realm.where(IgnoredUserEntity::class.java).findAll().map { it.userId } + userId
val roomMemberHelper = RoomMemberHelper(realm, roomId)
- val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId }.orEmpty()
+ val typingIds = typingUserIds.filter { it !in filteredUserIds }
val senderInfo = typingIds.map { userId ->
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(userId)
SenderInfo(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterBuilder.kt
similarity index 89%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterBuilder.kt
index ad55b26dfd4..d58b9d3765b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/filter/SyncFilterBuilder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/sync/filter/SyncFilterBuilder.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package org.matrix.android.sdk.api.session.sync.filter
+package org.matrix.android.sdk.internal.sync.filter
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
import org.matrix.android.sdk.internal.session.filter.Filter
import org.matrix.android.sdk.internal.session.filter.RoomEventFilter
import org.matrix.android.sdk.internal.session.filter.RoomFilter
-import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams
-class SyncFilterBuilder {
+internal class SyncFilterBuilder {
private var lazyLoadMembersForStateEvents: Boolean? = null
private var lazyLoadMembersForMessageEvents: Boolean? = null
private var useThreadNotifications: Boolean? = null
@@ -54,16 +54,6 @@ class SyncFilterBuilder {
}
}
- internal fun extractParams(): SyncFilterParams {
- return SyncFilterParams(
- useThreadNotifications = useThreadNotifications,
- lazyLoadMembersForMessageEvents = lazyLoadMembersForMessageEvents,
- lazyLoadMembersForStateEvents = lazyLoadMembersForStateEvents,
- listOfSupportedEventTypes = listOfSupportedEventTypes,
- listOfSupportedStateEventTypes = listOfSupportedStateEventTypes,
- )
- }
-
internal fun build(homeServerCapabilities: HomeServerCapabilities): Filter {
return Filter(
room = buildRoomFilter(homeServerCapabilities)
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt
index c1fd615e254..0888d829079 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt
@@ -16,9 +16,13 @@
package org.matrix.android.sdk.internal.session.room.aggregation.poll
+import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.realm.RealmList
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeFalse
import org.amshove.kluent.shouldBeTrue
import org.junit.Before
@@ -34,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSumm
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.AN_EVENT_ID
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.AN_INVALID_POLL_RESPONSE_EVENT
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_BROKEN_POLL_REPLACE_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_END_CONTENT
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_END_EVENT
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_REFERENCE_EVENT
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_REPLACE_EVENT
@@ -43,13 +48,22 @@ import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsT
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_ROOM_ID
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_TIMELINE_EVENT
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_USER_ID_1
+import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollResponseEventsTask
+import org.matrix.android.sdk.test.fakes.FakeFetchPollResponseEventsTask
import org.matrix.android.sdk.test.fakes.FakeRealm
+import org.matrix.android.sdk.test.fakes.FakeTaskExecutor
import org.matrix.android.sdk.test.fakes.givenEqualTo
import org.matrix.android.sdk.test.fakes.givenFindFirst
+@OptIn(ExperimentalCoroutinesApi::class)
class DefaultPollAggregationProcessorTest {
- private val pollAggregationProcessor: PollAggregationProcessor = DefaultPollAggregationProcessor()
+ private val fakeTaskExecutor = FakeTaskExecutor()
+ private val fakeFetchPollResponseEventsTask = FakeFetchPollResponseEventsTask()
+ private val pollAggregationProcessor: PollAggregationProcessor = DefaultPollAggregationProcessor(
+ taskExecutor = fakeTaskExecutor.instance,
+ fetchPollResponseEventsTask = fakeFetchPollResponseEventsTask
+ )
private val realm = FakeRealm()
private val session = mockk()
@@ -114,16 +128,28 @@ class DefaultPollAggregationProcessorTest {
}
@Test
- fun `given a poll end event, when processing, then is processed and return true`() {
+ fun `given a poll end event, when processing, then is processed and return true`() = runTest {
+ // Given
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ every { fakeTaskExecutor.instance.executorScope } returns this
+
+ // When
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true)
+
+ // Then
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue()
}
@Test
- fun `given a poll end event for my own poll without enough redaction power level, when processing, then is processed and returns true`() {
+ fun `given a poll end event for my own poll without enough redaction power level, when processing, then is processed and returns true`() = runTest {
+ // Given
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ every { fakeTaskExecutor.instance.executorScope } returns this
+
+ // When
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false)
+
+ // Then
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue()
}
@@ -135,6 +161,28 @@ class DefaultPollAggregationProcessorTest {
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event).shouldBeFalse()
}
+ @Test
+ fun `given a non local echo poll end event, when is processed, then ensure to aggregate all poll responses`() = runTest {
+ // Given
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ val powerLevelsHelper = mockRedactionPowerLevels("another-sender-id", true)
+ val event = A_POLL_END_EVENT.copy(senderId = "another-sender-id")
+ every { fakeTaskExecutor.instance.executorScope } returns this
+ val expectedParams = FetchPollResponseEventsTask.Params(
+ roomId = A_POLL_END_EVENT.roomId.orEmpty(),
+ startPollEventId = A_POLL_END_CONTENT.relatesTo?.eventId.orEmpty(),
+ )
+
+ // When
+ pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event)
+ advanceUntilIdle()
+
+ // Then
+ coVerify {
+ fakeFetchPollResponseEventsTask.execute(expectedParams)
+ }
+ }
+
private fun mockEventAnnotationsSummaryEntity() {
realm.givenWhere()
.givenFindFirst(EventAnnotationsSummaryEntity())
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt
new file mode 100644
index 00000000000..8d50bac38f7
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.relation.poll
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import io.mockk.verify
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.events.model.RelationType
+import org.matrix.android.sdk.api.session.events.model.isPollResponse
+import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.internal.database.mapper.toEntity
+import org.matrix.android.sdk.internal.database.model.EventEntity
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.EventInsertType
+import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
+import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
+import org.matrix.android.sdk.test.fakes.FakeClock
+import org.matrix.android.sdk.test.fakes.FakeEventDecryptor
+import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver
+import org.matrix.android.sdk.test.fakes.FakeMonarchy
+import org.matrix.android.sdk.test.fakes.FakeRoomApi
+import org.matrix.android.sdk.test.fakes.givenFindAll
+import org.matrix.android.sdk.test.fakes.givenIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class DefaultFetchPollResponseEventsTaskTest {
+
+ private val fakeRoomAPI = FakeRoomApi()
+ private val fakeGlobalErrorReceiver = FakeGlobalErrorReceiver()
+ private val fakeMonarchy = FakeMonarchy()
+ private val fakeClock = FakeClock()
+ private val fakeEventDecryptor = FakeEventDecryptor()
+
+ private val defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask(
+ roomAPI = fakeRoomAPI.instance,
+ globalErrorReceiver = fakeGlobalErrorReceiver,
+ monarchy = fakeMonarchy.instance,
+ clock = fakeClock,
+ eventDecryptor = fakeEventDecryptor.instance,
+ )
+
+ @Before
+ fun setup() {
+ mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt")
+ mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt")
+ mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt")
+ }
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun `given a room and a poll when execute then fetch related events and store them in local if needed`() = runTest {
+ // Given
+ val aRoomId = "roomId"
+ val aPollEventId = "eventId"
+ val params = givenTaskParams(roomId = aRoomId, eventId = aPollEventId)
+ val aNextBatchToken = "nextBatch"
+ val anEventId1 = "eventId1"
+ val anEventId2 = "eventId2"
+ val anEventId3 = "eventId3"
+ val anEventId4 = "eventId4"
+ val event1 = givenAnEvent(eventId = anEventId1, isPollResponse = true, isEncrypted = true)
+ val event2 = givenAnEvent(eventId = anEventId2, isPollResponse = true, isEncrypted = true)
+ val event3 = givenAnEvent(eventId = anEventId3, isPollResponse = false, isEncrypted = false)
+ val event4 = givenAnEvent(eventId = anEventId4, isPollResponse = false, isEncrypted = false)
+ val firstEvents = listOf(event1, event2)
+ val secondEvents = listOf(event3, event4)
+ val firstResponse = givenARelationsResponse(events = firstEvents, nextBatch = aNextBatchToken)
+ fakeRoomAPI.givenGetRelationsReturns(from = null, relationsResponse = firstResponse)
+ val secondResponse = givenARelationsResponse(events = secondEvents, nextBatch = null)
+ fakeRoomAPI.givenGetRelationsReturns(from = aNextBatchToken, relationsResponse = secondResponse)
+ fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1)
+ fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event2)
+ fakeClock.givenEpoch(123)
+ givenExistingEventEntities(eventIdsToCheck = listOf(anEventId1, anEventId2), existingIds = listOf(anEventId1))
+ val eventEntityToSave = EventEntity(eventId = anEventId2)
+ every { event2.toEntity(any(), any(), any()) } returns eventEntityToSave
+ every { eventEntityToSave.copyToRealmOrIgnore(any(), any()) } returns eventEntityToSave
+
+ // When
+ defaultFetchPollResponseEventsTask.execute(params)
+
+ // Then
+ fakeRoomAPI.verifyGetRelations(
+ roomId = params.roomId,
+ eventId = params.startPollEventId,
+ relationType = RelationType.REFERENCE,
+ from = null,
+ limit = FETCH_RELATED_EVENTS_LIMIT
+ )
+ fakeRoomAPI.verifyGetRelations(
+ roomId = params.roomId,
+ eventId = params.startPollEventId,
+ relationType = RelationType.REFERENCE,
+ from = aNextBatchToken,
+ limit = FETCH_RELATED_EVENTS_LIMIT
+ )
+ fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "")
+ fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "")
+ // Check we save in DB the event2 which is a non stored poll response
+ verify {
+ event2.toEntity(aRoomId, SendState.SYNCED, any())
+ eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION)
+ }
+ }
+
+ private fun givenTaskParams(roomId: String, eventId: String) = FetchPollResponseEventsTask.Params(
+ roomId = roomId,
+ startPollEventId = eventId,
+ )
+
+ private fun givenARelationsResponse(events: List, nextBatch: String?): RelationsResponse {
+ return RelationsResponse(
+ chunks = events,
+ nextBatch = nextBatch,
+ prevBatch = null,
+ )
+ }
+
+ private fun givenAnEvent(
+ eventId: String,
+ isPollResponse: Boolean,
+ isEncrypted: Boolean,
+ ): Event {
+ val event = mockk(relaxed = true)
+ every { event.eventId } returns eventId
+ every { event.isPollResponse() } returns isPollResponse
+ every { event.isEncrypted() } returns isEncrypted
+ return event
+ }
+
+ private fun givenExistingEventEntities(eventIdsToCheck: List, existingIds: List) {
+ val eventEntities = existingIds.map { EventEntity(eventId = it) }
+ fakeMonarchy.givenWhere()
+ .givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck)
+ .givenFindAll(eventEntities)
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt
index 201423685ca..f3ab65f6c43 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/sync/DefaultGetCurrentFilterTaskTest.kt
@@ -16,14 +16,17 @@
package org.matrix.android.sdk.internal.sync
+import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
+import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.SyncConfig
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
-import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
+import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
import org.matrix.android.sdk.internal.session.filter.DefaultGetCurrentFilterTask
-import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams
+import org.matrix.android.sdk.internal.sync.filter.SyncFilterBuilder
import org.matrix.android.sdk.test.fakes.FakeFilterRepository
import org.matrix.android.sdk.test.fakes.FakeHomeServerCapabilitiesDataSource
import org.matrix.android.sdk.test.fakes.FakeSaveFilterTask
@@ -31,7 +34,6 @@ import org.matrix.android.sdk.test.fakes.FakeSaveFilterTask
private const val A_FILTER_ID = "filter-id"
private val A_HOMESERVER_CAPABILITIES = HomeServerCapabilities()
private val A_SYNC_FILTER_PARAMS = SyncFilterParams(
- lazyLoadMembersForMessageEvents = true,
lazyLoadMembersForStateEvents = true,
useThreadNotifications = true
)
@@ -46,13 +48,16 @@ class DefaultGetCurrentFilterTaskTest {
private val getCurrentFilterTask = DefaultGetCurrentFilterTask(
filterRepository = filterRepository,
homeServerCapabilitiesDataSource = homeServerCapabilitiesDataSource.instance,
- saveFilterTask = saveFilterTask
+ saveFilterTask = saveFilterTask,
+ matrixConfiguration = MatrixConfiguration(
+ applicationFlavor = "TestFlavor",
+ roomDisplayNameFallbackProvider = mockk(),
+ syncConfig = SyncConfig(syncFilterParams = SyncFilterParams(lazyLoadMembersForStateEvents = true, useThreadNotifications = true)),
+ )
)
@Test
fun `given no filter is stored, when execute, then executes task to save new filter`() = runTest {
- filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS)
-
homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES)
filterRepository.givenFilterStored(null, null)
@@ -68,8 +73,6 @@ class DefaultGetCurrentFilterTaskTest {
@Test
fun `given filter is stored and didn't change, when execute, then returns stored filter id`() = runTest {
- filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS)
-
homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES)
val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES)
@@ -82,8 +85,6 @@ class DefaultGetCurrentFilterTaskTest {
@Test
fun `given filter is set and home server capabilities has changed, when execute, then executes task to save new filter`() = runTest {
- filterRepository.givenFilterParamsAreStored(A_SYNC_FILTER_PARAMS)
-
homeServerCapabilitiesDataSource.givenHomeServerCapabilities(A_HOMESERVER_CAPABILITIES)
val filter = SyncFilterBuilder().with(A_SYNC_FILTER_PARAMS).build(A_HOMESERVER_CAPABILITIES)
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventDecryptor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventDecryptor.kt
new file mode 100644
index 00000000000..f2b62ad3ba3
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventDecryptor.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.test.fakes
+
+import io.mockk.coJustRun
+import io.mockk.coVerify
+import io.mockk.mockk
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.internal.crypto.EventDecryptor
+
+internal class FakeEventDecryptor {
+ val instance: EventDecryptor = mockk()
+
+ fun givenDecryptEventAndSaveResultSuccess(event: Event) {
+ coJustRun { instance.decryptEventAndSaveResult(event, any()) }
+ }
+
+ fun verifyDecryptEventAndSaveResult(event: Event, timeline: String) {
+ coVerify { instance.decryptEventAndSaveResult(event, timeline) }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFetchPollResponseEventsTask.kt
similarity index 63%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFetchPollResponseEventsTask.kt
index 7347bee1657..cb75d8b7084 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/sync/FilterService.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFetchPollResponseEventsTask.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
+ * 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.
@@ -14,14 +14,9 @@
* limitations under the License.
*/
-package org.matrix.android.sdk.api.session.sync
+package org.matrix.android.sdk.test.fakes
-import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollResponseEventsTask
-interface FilterService {
-
- /**
- * Configure the filter for the sync.
- */
- suspend fun setSyncFilter(filterBuilder: SyncFilterBuilder)
-}
+class FakeFetchPollResponseEventsTask : FetchPollResponseEventsTask by mockk(relaxed = true)
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt
index b8225f21d66..27a39120f8b 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeFilterRepository.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.test.fakes
import io.mockk.coEvery
import io.mockk.mockk
import org.matrix.android.sdk.internal.session.filter.FilterRepository
-import org.matrix.android.sdk.internal.sync.filter.SyncFilterParams
internal class FakeFilterRepository : FilterRepository by mockk() {
@@ -27,8 +26,4 @@ internal class FakeFilterRepository : FilterRepository by mockk() {
coEvery { getStoredSyncFilterId() } returns filterId
coEvery { getStoredSyncFilterBody() } returns filterBody
}
-
- fun givenFilterParamsAreStored(syncFilterParams: SyncFilterParams?) {
- coEvery { getStoredFilterParams() } returns syncFilterParams
- }
}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
index afdcf111f89..ba124a86aa3 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
@@ -109,6 +109,14 @@ inline fun RealmQuery.givenLessThan(
return this
}
+inline fun RealmQuery.givenIn(
+ fieldName: String,
+ values: List,
+): RealmQuery {
+ every { `in`(fieldName, values.toTypedArray()) } returns this
+ return this
+}
+
/**
* Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked.
*/
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomApi.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomApi.kt
new file mode 100644
index 00000000000..68dbbe7ea69
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomApi.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.test.fakes
+
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
+
+internal class FakeRoomApi {
+
+ val instance: RoomAPI = mockk()
+
+ fun givenGetRelationsReturns(
+ from: String?,
+ relationsResponse: RelationsResponse,
+ ) {
+ coEvery {
+ instance.getRelations(
+ roomId = any(),
+ eventId = any(),
+ relationType = any(),
+ from = from,
+ limit = any()
+ )
+ } returns relationsResponse
+ }
+
+ fun verifyGetRelations(
+ roomId: String,
+ eventId: String,
+ relationType: String,
+ from: String?,
+ limit: Int,
+ ) {
+ coVerify {
+ instance.getRelations(
+ roomId = roomId,
+ eventId = eventId,
+ relationType = relationType,
+ from = from,
+ limit = limit
+ )
+ }
+ }
+}
diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json
index c00bd10371e..0dcf9ccb251 100644
--- a/tools/emojis/emoji_picker_datasource_formatted.json
+++ b/tools/emojis/emoji_picker_datasource_formatted.json
@@ -3013,7 +3013,11 @@
"begging",
"mercy",
"puppy eyes",
- "face"
+ "face",
+ "cry",
+ "tears",
+ "sad",
+ "grievance"
]
},
"face-holding-back-tears": {
@@ -3060,9 +3064,7 @@
"fearful",
"scared",
"terrified",
- "nervous",
- "oops",
- "huh"
+ "nervous"
]
},
"anxious-face-with-sweat": {
diff --git a/vector-app/build.gradle b/vector-app/build.gradle
index 7d1073764d0..3bf77bf912f 100644
--- a/vector-app/build.gradle
+++ b/vector-app/build.gradle
@@ -37,7 +37,7 @@ ext.versionMinor = 5
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
-ext.versionPatch = 14
+ext.versionPatch = 16
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
diff --git a/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt
index 72137ed8e8b..68a54e99014 100644
--- a/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt
@@ -89,7 +89,7 @@ fun getString(@StringRes id: Int): String {
return EspressoHelper.getCurrentActivity()!!.resources.getString(id)
}
-fun waitForView(viewMatcher: Matcher, timeout: Long = 10_000, waitForDisplayed: Boolean = true): ViewAction {
+fun waitForView(viewMatcher: Matcher, timeout: Long = 20_000, waitForDisplayed: Boolean = true): ViewAction {
return object : ViewAction {
private val clock = DefaultClock()
diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index 52607bd9a1c..3439bcfcede 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -28,7 +28,6 @@ import im.vector.app.espresso.tools.ScreenshotFailureRule
import im.vector.app.features.MainActivity
import im.vector.app.getString
import im.vector.app.ui.robot.ElementRobot
-import im.vector.app.ui.robot.settings.labs.LabFeature
import im.vector.app.ui.robot.settings.labs.LabFeaturesPreferences
import im.vector.app.ui.robot.withDeveloperMode
import org.junit.Rule
@@ -133,6 +132,10 @@ class UiAllScreensSanityTest {
}
}
+ // Some instability with the bottomsheet
+ // not sure what's the source, maybe the expanded state?
+ Thread.sleep(10_000)
+
elementRobot.space { selectSpace(spaceName) }
elementRobot.layoutPreferences {
@@ -175,7 +178,6 @@ class UiAllScreensSanityTest {
* Testing multiple threads screens
*/
private fun testThreadScreens() {
- elementRobot.toggleLabFeature(LabFeature.THREAD_MESSAGES)
elementRobot.newRoom {
createNewRoom {
crawl()
@@ -189,6 +191,5 @@ class UiAllScreensSanityTest {
}
}
}
- elementRobot.toggleLabFeature(LabFeature.THREAD_MESSAGES)
}
}
diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt
index e5147c2085e..ad6d5e5df35 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt
@@ -28,7 +28,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
import im.vector.app.R
import im.vector.app.espresso.tools.waitUntilActivityVisible
-import im.vector.app.espresso.tools.waitUntilDialogVisible
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.room.detail.RoomDetailActivity
@@ -86,14 +85,17 @@ class SpaceCreateRobot {
clickOn(R.id.nextButton)
waitUntilViewVisible(withId(R.id.recyclerView))
clickOn(R.id.nextButton)
+// waitUntilActivityVisible {
+// waitUntilDialogVisible(withId(R.id.inviteByMxidButton))
+// }
+// // close invite dialog
+// pressBack()
waitUntilActivityVisible {
- waitUntilDialogVisible(withId(R.id.inviteByMxidButton))
+ pressBack()
}
- // close invite dialog
- pressBack()
- waitUntilViewVisible(withId(R.id.timelineRecyclerView))
+// waitUntilViewVisible(withId(R.id.timelineRecyclerView))
// close room
- pressBack()
+// pressBack()
waitUntilViewVisible(withId(R.id.roomListContainer))
}
}
diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt
index d04746bcd66..73a063857a4 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt
@@ -89,9 +89,8 @@ class SpaceMenuRobot {
clickOnSheet(R.id.leaveSpace)
waitUntilActivityVisible {
waitUntilViewVisible(ViewMatchers.withId(R.id.roomList))
+ clickOn(R.id.spaceLeaveSelectAll)
+ clickOn(R.id.spaceLeaveButton)
}
- clickOn(R.id.spaceLeaveSelectAll)
- clickOn(R.id.spaceLeaveButton)
- waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView))
}
}
diff --git a/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt
index dd04cb29869..a6d6fcd14bb 100644
--- a/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt
+++ b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt
@@ -70,11 +70,13 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.SyncConfig
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.sync.filter.SyncFilterParams
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
import javax.inject.Singleton
@@ -157,6 +159,9 @@ import javax.inject.Singleton
),
metricPlugins = vectorPlugins.plugins(),
customEventTypesProvider = vectorCustomEventTypesProvider,
+ syncConfig = SyncConfig(
+ syncFilterParams = SyncFilterParams(lazyLoadMembersForStateEvents = true, useThreadNotifications = true)
+ )
)
}
diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
index b58d584dad5..d22ab51e7ae 100644
--- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt
@@ -46,6 +46,7 @@ import im.vector.app.features.home.UserColorAccountDataViewModel
import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel
import im.vector.app.features.home.room.detail.TimelineViewModel
import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel
+import im.vector.app.features.home.room.detail.composer.link.SetLinkViewModel
import im.vector.app.features.home.room.detail.search.SearchViewModel
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsViewModel
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryViewModel
@@ -691,4 +692,9 @@ interface MavericksViewModelModule {
fun vectorSettingsNotificationPreferenceViewModelFactory(
factory: VectorSettingsNotificationPreferenceViewModel.Factory
): MavericksAssistedViewModelFactory<*, *>
+
+ @Binds
+ @IntoMap
+ @MavericksViewModelKey(SetLinkViewModel::class)
+ fun setLinkViewModelFactory(factory: SetLinkViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
}
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt
new file mode 100644
index 00000000000..5a817b989e9
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseDialogFragment.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package im.vector.app.core.platform
+
+import android.content.Context
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.CallSuper
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.viewbinding.ViewBinding
+import com.airbnb.mvrx.MavericksView
+import dagger.hilt.android.EntryPointAccessors
+import im.vector.app.R
+import im.vector.app.core.di.ActivityEntryPoint
+import im.vector.app.core.extensions.singletonEntryPoint
+import im.vector.app.core.extensions.toMvRxBundle
+import im.vector.app.features.analytics.AnalyticsTracker
+import im.vector.app.features.analytics.plan.MobileScreen
+import im.vector.app.features.themes.ThemeUtils
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import reactivecircus.flowbinding.android.view.clicks
+import timber.log.Timber
+
+/**
+ * Add Mavericks capabilities, handle DI and bindings.
+ */
+abstract class VectorBaseDialogFragment : DialogFragment(), MavericksView {
+ /* ==========================================================================================
+ * Analytics
+ * ========================================================================================== */
+
+ protected var analyticsScreenName: MobileScreen.ScreenName? = null
+
+ protected lateinit var analyticsTracker: AnalyticsTracker
+
+ /* ==========================================================================================
+ * View
+ * ========================================================================================== */
+
+ private var _binding: VB? = null
+
+ // This property is only valid between onCreateView and onDestroyView.
+ protected val views: VB
+ get() = _binding!!
+
+ abstract fun getBinding(inflater: LayoutInflater, container: ViewGroup?): VB
+
+ /* ==========================================================================================
+ * View model
+ * ========================================================================================== */
+
+ private lateinit var viewModelFactory: ViewModelProvider.Factory
+
+ protected val activityViewModelProvider
+ get() = ViewModelProvider(requireActivity(), viewModelFactory)
+
+ protected val fragmentViewModelProvider
+ get() = ViewModelProvider(this, viewModelFactory)
+
+ val vectorBaseActivity: VectorBaseActivity<*> by lazy {
+ activity as VectorBaseActivity<*>
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setStyle(STYLE_NORMAL, ThemeUtils.getApplicationThemeRes(requireContext()))
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ _binding = getBinding(inflater, container)
+ return views.root
+ }
+
+ @CallSuper
+ override fun onDestroyView() {
+ _binding = null
+ super.onDestroyView()
+ }
+
+ @CallSuper
+ override fun onDestroy() {
+ super.onDestroy()
+ }
+
+ override fun onAttach(context: Context) {
+ val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
+ viewModelFactory = activityEntryPoint.viewModelFactory()
+ val singletonEntryPoint = context.singletonEntryPoint()
+ analyticsTracker = singletonEntryPoint.analyticsTracker()
+ super.onAttach(context)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ Timber.i("onResume BottomSheet ${javaClass.simpleName}")
+ analyticsScreenName?.let {
+ analyticsTracker.screen(MobileScreen(screenName = it))
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ // This ensures that invalidate() is called for static screens that don't
+ // subscribe to a ViewModel.
+ postInvalidate()
+ requireDialog().window?.setWindowAnimations(R.style.Animation_AppCompat_Dialog)
+ }
+
+ protected fun setArguments(args: Parcelable? = null) {
+ arguments = args.toMvRxBundle()
+ }
+
+ /* ==========================================================================================
+ * Views
+ * ========================================================================================== */
+
+ protected fun View.debouncedClicks(onClicked: () -> Unit) {
+ clicks()
+ .onEach { onClicked() }
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ /* ==========================================================================================
+ * ViewEvents
+ * ========================================================================================== */
+
+ protected fun VectorViewModel<*, *, T>.observeViewEvents(observer: (T) -> Unit) {
+ viewEvents
+ .stream()
+ .onEach {
+ observer(it)
+ }
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt b/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt
index fbf89b76a48..c6a2635e6ce 100644
--- a/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt
+++ b/vector/src/main/java/im/vector/app/core/session/ConfigureAndStartSessionUseCase.kt
@@ -25,7 +25,6 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.devices.v2.notification.UpdateNotificationSettingsAccountDataUseCase
-import im.vector.app.features.sync.SyncUtils
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
@@ -43,9 +42,6 @@ class ConfigureAndStartSessionUseCase @Inject constructor(
fun execute(session: Session, startSyncing: Boolean = true) {
Timber.i("Configure and start session for ${session.myUserId}. startSyncing: $startSyncing")
session.open()
- session.coroutineScope.launch {
- session.filterService().setSyncFilter(SyncUtils.getSyncFilterBuilder())
- }
if (startSyncing) {
session.startSyncing(context)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
index faee8f652c6..9fe296e1f99 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
@@ -127,6 +127,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
object Pause : Recording()
object Resume : Recording()
object Stop : Recording()
+ object StopConfirmed : Recording()
}
sealed class Listening : VoiceBroadcastAction() {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
index 399d5e0abe8..77dd826cfe6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
@@ -71,6 +71,8 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
object DisplayEnableIntegrationsWarning : RoomDetailViewEvents()
+ object DisplayPromptToStopVoiceBroadcast : RoomDetailViewEvents()
+
data class OpenStickerPicker(val widget: Widget) : RoomDetailViewEvents()
object OpenIntegrationManager : RoomDetailViewEvents()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
index 6ab20275c25..980e8ebac55 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@@ -413,6 +413,7 @@ class TimelineFragment :
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
RoomDetailViewEvents.OpenElementCallWidget -> handleOpenElementCallWidget()
+ RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast -> displayPromptToStopVoiceBroadcast()
}
}
@@ -2006,6 +2007,20 @@ class TimelineFragment :
}
}
+ private fun displayPromptToStopVoiceBroadcast() {
+ ConfirmationDialogBuilder
+ .show(
+ activity = requireActivity(),
+ askForReason = false,
+ confirmationRes = R.string.stop_voice_broadcast_content,
+ positiveRes = R.string.action_stop,
+ reasonHintRes = 0,
+ titleRes = R.string.stop_voice_broadcast_dialog_title
+ ) {
+ timelineViewModel.handle(RoomDetailAction.VoiceBroadcastAction.Recording.StopConfirmed)
+ }
+ }
+
override fun onTapToReturnToCall() {
callManager.getCurrentCall()?.let { call ->
VectorCallActivity.newIntent(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index 02782783b84..ff24872ab83 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
@@ -633,7 +633,8 @@ class TimelineViewModel @AssistedInject constructor(
}
VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
- VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
+ VoiceBroadcastAction.Recording.Stop -> _viewEvents.post(RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast)
+ VoiceBroadcastAction.Recording.StopConfirmed -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
is VoiceBroadcastAction.Listening.PlayOrResume -> voiceBroadcastHelper.playOrResumePlayback(action.voiceBroadcast)
VoiceBroadcastAction.Listening.Pause -> voiceBroadcastHelper.pausePlayback()
VoiceBroadcastAction.Listening.Stop -> voiceBroadcastHelper.stopPlayback()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
index d56ea8b7339..4849e20b6db 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
@@ -80,6 +80,9 @@ import im.vector.app.features.home.room.detail.AutoCompleter
import im.vector.app.features.home.room.detail.RoomDetailAction
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
import im.vector.app.features.home.room.detail.TimelineViewModel
+import im.vector.app.features.home.room.detail.composer.link.SetLinkFragment
+import im.vector.app.features.home.room.detail.composer.link.SetLinkSharedAction
+import im.vector.app.features.home.room.detail.composer.link.SetLinkSharedActionViewModel
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
@@ -147,6 +150,7 @@ class MessageComposerFragment : VectorBaseFragment(), A
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
private val attachmentViewModel: AttachmentTypeSelectorViewModel by fragmentViewModel()
private val attachmentActionsViewModel: AttachmentTypeSelectorSharedActionViewModel by viewModels()
+ private val setLinkActionsViewModel: SetLinkSharedActionViewModel by viewModels()
private val composer: MessageComposerView get() {
return if (vectorPreferences.isRichTextEditorEnabled()) {
@@ -212,6 +216,14 @@ class MessageComposerFragment : VectorBaseFragment(), A
.onEach { onTypeSelected(it.attachmentType) }
.launchIn(lifecycleScope)
+ setLinkActionsViewModel.stream()
+ .onEach { when (it) {
+ is SetLinkSharedAction.Insert -> views.richTextComposerLayout.insertLink(it.link, it.text)
+ is SetLinkSharedAction.Set -> views.richTextComposerLayout.setLink(it.link)
+ SetLinkSharedAction.Remove -> views.richTextComposerLayout.removeLink()
+ } }
+ .launchIn(lifecycleScope)
+
messageComposerViewModel.stateFlow.map { it.isFullScreen }
.distinctUntilChanged()
.onEach { isFullScreen ->
@@ -385,6 +397,10 @@ class MessageComposerFragment : VectorBaseFragment(), A
override fun onFullScreenModeChanged() = withState(messageComposerViewModel) { state ->
messageComposerViewModel.handle(MessageComposerAction.SetFullScreen(!state.isFullScreen))
}
+
+ override fun onSetLink(isTextSupported: Boolean, initialLink: String?) {
+ SetLinkFragment.show(isTextSupported, initialLink, childFragmentManager)
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt
index 44fcf22d4a8..b68f4046c8c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt
@@ -45,4 +45,5 @@ interface Callback : ComposerEditText.Callback {
fun onAddAttachment()
fun onExpandOrCompactChange()
fun onFullScreenModeChanged()
+ fun onSetLink(isTextSupported: Boolean, initialLink: String?)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt
index d69fe8edeb9..543210e006d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt
@@ -49,6 +49,7 @@ import im.vector.app.databinding.ComposerRichTextLayoutBinding
import im.vector.app.databinding.ViewRichTextMenuButtonBinding
import io.element.android.wysiwyg.EditorEditText
import io.element.android.wysiwyg.inputhandlers.models.InlineFormat
+import io.element.android.wysiwyg.inputhandlers.models.LinkAction
import io.element.android.wysiwyg.utils.RustErrorCollector
import uniffi.wysiwyg_composer.ActionState
import uniffi.wysiwyg_composer.ComposerAction
@@ -231,8 +232,25 @@ internal class RichTextComposerLayout @JvmOverloads constructor(
addRichTextMenuItem(R.drawable.ic_composer_strikethrough, R.string.rich_text_editor_format_strikethrough, ComposerAction.STRIKE_THROUGH) {
views.richTextComposerEditText.toggleInlineFormat(InlineFormat.StrikeThrough)
}
+ addRichTextMenuItem(R.drawable.ic_composer_link, R.string.rich_text_editor_link, ComposerAction.LINK) {
+ views.richTextComposerEditText.getLinkAction()?.let {
+ when (it) {
+ LinkAction.InsertLink -> callback?.onSetLink(isTextSupported = true, initialLink = null)
+ is LinkAction.SetLink -> callback?.onSetLink(isTextSupported = false, initialLink = it.currentLink)
+ }
+ }
+ }
}
+ fun setLink(link: String?) =
+ views.richTextComposerEditText.setLink(link)
+
+ fun insertLink(link: String, text: String) =
+ views.richTextComposerEditText.insertLink(link, text)
+
+ fun removeLink() =
+ views.richTextComposerEditText.removeLink()
+
@SuppressLint("ClickableViewAccessibility")
private fun disallowParentInterceptTouchEvent(view: View) {
view.setOnTouchListener { v, event ->
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFilterService.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkAction.kt
similarity index 52%
rename from vector/src/test/java/im/vector/app/test/fakes/FakeFilterService.kt
rename to vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkAction.kt
index 9be59d31fdf..5cc31022ea0 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeFilterService.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkAction.kt
@@ -14,23 +14,17 @@
* limitations under the License.
*/
-package im.vector.app.test.fakes
+package im.vector.app.features.home.room.detail.composer.link
-import io.mockk.coEvery
-import io.mockk.coVerify
-import io.mockk.just
-import io.mockk.mockk
-import io.mockk.runs
-import org.matrix.android.sdk.api.session.sync.FilterService
-import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
+import im.vector.app.core.platform.VectorViewModelAction
-class FakeFilterService : FilterService by mockk() {
+sealed class SetLinkAction : VectorViewModelAction {
+ data class LinkChanged(
+ val newLink: String
+ ) : SetLinkAction()
- fun givenSetFilterSucceeds() {
- coEvery { setSyncFilter(any()) } just runs
- }
-
- fun verifySetSyncFilter(filterBuilder: SyncFilterBuilder) {
- coVerify { setSyncFilter(filterBuilder) }
- }
+ data class Save(
+ val link: String,
+ val text: String,
+ ) : SetLinkAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkFragment.kt
new file mode 100644
index 00000000000..008a8017eee
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkFragment.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.composer.link
+
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isGone
+import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import com.airbnb.mvrx.args
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.platform.VectorBaseDialogFragment
+import im.vector.app.databinding.FragmentSetLinkBinding
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.parcelize.Parcelize
+import reactivecircus.flowbinding.android.widget.textChanges
+
+@AndroidEntryPoint
+class SetLinkFragment :
+ VectorBaseDialogFragment() {
+
+ @Parcelize
+ data class Args(
+ val isTextSupported: Boolean,
+ val initialLink: String?,
+ ) : Parcelable
+
+ private val viewModel: SetLinkViewModel by fragmentViewModel()
+ private val sharedActionViewModel: SetLinkSharedActionViewModel by viewModels(
+ ownerProducer = { requireParentFragment() }
+ )
+ private val args: Args by args()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSetLinkBinding {
+ return FragmentSetLinkBinding.inflate(inflater, container, false)
+ }
+
+ companion object {
+ fun show(isTextSupported: Boolean, initialLink: String?, fragmentManager: FragmentManager) =
+ SetLinkFragment().apply {
+ setArguments(Args(isTextSupported, initialLink))
+ }.show(fragmentManager, "SetLinkBottomSheet")
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.link.setText(args.initialLink)
+ views.link.textChanges()
+ .onEach {
+ viewModel.handle(SetLinkAction.LinkChanged(it.toString()))
+ }
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+
+ views.save.debouncedClicks {
+ viewModel.handle(
+ SetLinkAction.Save(
+ link = views.link.text.toString(),
+ text = views.text.text.toString(),
+ )
+ )
+ }
+
+ views.cancel.debouncedClicks(::onCancel)
+ views.remove.debouncedClicks(::onRemove)
+
+ viewModel.observeViewEvents {
+ when (it) {
+ is SetLinkViewEvents.SavedLinkAndText -> handleInsert(link = it.link, text = it.text)
+ is SetLinkViewEvents.SavedLink -> handleSet(link = it.link)
+ }
+ }
+
+ views.toolbar.setNavigationOnClickListener {
+ dismiss()
+ }
+ }
+
+ override fun invalidate() = withState(viewModel) { viewState ->
+ views.toolbar.title = getString(
+ if (viewState.initialLink != null) {
+ R.string.set_link_edit
+ } else {
+ R.string.set_link_create
+ }
+ )
+
+ views.remove.isGone = !viewState.removeVisible
+ views.save.isEnabled = viewState.saveEnabled
+ views.textLayout.isGone = !viewState.isTextSupported
+ }
+
+ private fun handleInsert(link: String, text: String) {
+ sharedActionViewModel.post(SetLinkSharedAction.Insert(text, link))
+ dismiss()
+ }
+
+ private fun handleSet(link: String) {
+ sharedActionViewModel.post(SetLinkSharedAction.Set(link))
+ dismiss()
+ }
+
+ private fun onRemove() {
+ sharedActionViewModel.post(SetLinkSharedAction.Remove)
+ dismiss()
+ }
+
+ private fun onCancel() = dismiss()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkSharedActionViewModel.kt
new file mode 100644
index 00000000000..fb9f3f0d5b0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkSharedActionViewModel.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.home.room.detail.composer.link
+
+import im.vector.app.core.platform.VectorSharedAction
+import im.vector.app.core.platform.VectorSharedActionViewModel
+import javax.inject.Inject
+
+class SetLinkSharedActionViewModel @Inject constructor() :
+ VectorSharedActionViewModel()
+
+sealed interface SetLinkSharedAction : VectorSharedAction {
+ data class Set(
+ val link: String,
+ ) : SetLinkSharedAction
+
+ data class Insert(
+ val text: String,
+ val link: String,
+ ) : SetLinkSharedAction
+
+ object Remove : SetLinkSharedAction
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewEvents.kt
new file mode 100644
index 00000000000..cd42651c22c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewEvents.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.composer.link
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class SetLinkViewEvents : VectorViewEvents {
+
+ data class SavedLink(
+ val link: String,
+ ) : SetLinkViewEvents()
+
+ data class SavedLinkAndText(
+ val link: String,
+ val text: String,
+ ) : SetLinkViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewModel.kt
new file mode 100644
index 00000000000..9a5b5cd8dd0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewModel.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.composer.link
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.VectorViewModel
+
+class SetLinkViewModel @AssistedInject constructor(
+ @Assisted private val initialState: SetLinkViewState,
+) : VectorViewModel(initialState) {
+
+ @AssistedFactory
+ interface Factory : MavericksAssistedViewModelFactory {
+ override fun create(initialState: SetLinkViewState): SetLinkViewModel
+ }
+
+ companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory()
+
+ override fun handle(action: SetLinkAction) = when (action) {
+ is SetLinkAction.LinkChanged -> handleLinkChanged(action.newLink)
+ is SetLinkAction.Save -> handleSave(action.link, action.text)
+ }
+
+ private fun handleLinkChanged(newLink: String) = setState {
+ copy(saveEnabled = newLink != initialLink.orEmpty())
+ }
+
+ private fun handleSave(
+ link: String,
+ text: String
+ ) = if (initialState.isTextSupported) {
+ _viewEvents.post(SetLinkViewEvents.SavedLinkAndText(link, text))
+ } else {
+ _viewEvents.post(SetLinkViewEvents.SavedLink(link))
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewState.kt
new file mode 100644
index 00000000000..ea61f7eb72a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/link/SetLinkViewState.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.composer.link
+
+import com.airbnb.mvrx.MavericksState
+
+data class SetLinkViewState(
+ val isTextSupported: Boolean,
+ val initialLink: String?,
+ val saveEnabled: Boolean,
+) : MavericksState {
+
+ constructor(args: SetLinkFragment.Args) : this(
+ isTextSupported = args.isTextSupported,
+ initialLink = args.initialLink,
+ saveEnabled = false,
+ )
+
+ val removeVisible = initialLink != null
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt
index 38fe1e8f177..b788d792142 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt
@@ -93,7 +93,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
override fun renderLiveIndicator(holder: Holder) {
when {
voiceBroadcastState == null || voiceBroadcastState == VoiceBroadcastState.STOPPED -> renderNoLiveIndicator(holder)
- voiceBroadcastState == VoiceBroadcastState.PAUSED || !player.isLiveListening -> renderPausedLiveIndicator(holder)
+ voiceBroadcastState == VoiceBroadcastState.PAUSED -> renderPausedLiveIndicator(holder)
else -> renderPlayingLiveIndicator(holder)
}
}
@@ -122,10 +122,14 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
private fun bindSeekBar(holder: Holder) {
with(holder) {
- durationView.text = formatPlaybackTime(duration)
+ remainingTimeView.text = formatRemainingTime(duration)
+ elapsedTimeView.text = formatPlaybackTime(0)
seekBar.max = duration
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
- override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ remainingTimeView.text = formatRemainingTime(duration - progress)
+ elapsedTimeView.text = formatPlaybackTime(progress)
+ }
override fun onStartTrackingTouch(seekBar: SeekBar) {
isUserSeeking = true
@@ -156,6 +160,7 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
}
private fun formatPlaybackTime(time: Int) = DateUtils.formatElapsedTime((time / 1000).toLong())
+ private fun formatRemainingTime(time: Int) = if (time < 1000) formatPlaybackTime(time) else String.format("-%s", formatPlaybackTime(time))
override fun unbind(holder: Holder) {
super.unbind(holder)
@@ -177,7 +182,8 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem
val fastBackwardButton by bind(R.id.fastBackwardButton)
val fastForwardButton by bind(R.id.fastForwardButton)
val seekBar by bind(R.id.seekBar)
- val durationView by bind(R.id.playbackDuration)
+ val remainingTimeView by bind(R.id.remainingTime)
+ val elapsedTimeView by bind(R.id.elapsedTime)
val broadcasterNameMetadata by bind(R.id.broadcasterNameMetadata)
val voiceBroadcastMetadata by bind(R.id.voiceBroadcastMetadata)
val listenersCountMetadata by bind(R.id.listenersCountMetadata)
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt
index 455f4778e87..e231686c276 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt
@@ -118,6 +118,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
private fun handleSmartReply(intent: Intent, context: Context) {
val message = getReplyMessage(intent)
val roomId = intent.getStringExtra(KEY_ROOM_ID)
+ val threadId = intent.getStringExtra(KEY_THREAD_ID)
if (message.isNullOrBlank() || roomId.isNullOrBlank()) {
// ignore this event
@@ -126,13 +127,20 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
}
activeSessionHolder.getActiveSession().let { session ->
session.getRoom(roomId)?.let { room ->
- sendMatrixEvent(message, session, room, context)
+ sendMatrixEvent(message, threadId, session, room, context)
}
}
}
- private fun sendMatrixEvent(message: String, session: Session, room: Room, context: Context?) {
- room.sendService().sendTextMessage(message)
+ private fun sendMatrixEvent(message: String, threadId: String?, session: Session, room: Room, context: Context?) {
+ if (threadId != null) {
+ room.relationService().replyInThread(
+ rootThreadEventId = threadId,
+ replyInThreadText = message,
+ )
+ } else {
+ room.sendService().sendTextMessage(message)
+ }
// Create a new event to be displayed in the notification drawer, right now
@@ -148,7 +156,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
body = message,
imageUriString = null,
roomId = room.roomId,
- threadId = null, // needs to be changed: https://github.com/vector-im/element-android/issues/7475
+ threadId = threadId,
roomName = room.roomSummary()?.displayName ?: room.roomId,
roomIsDirect = room.roomSummary()?.isDirect == true,
outGoingMessage = true,
@@ -223,6 +231,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
companion object {
const val KEY_ROOM_ID = "roomID"
+ const val KEY_THREAD_ID = "threadID"
const val KEY_TEXT_REPLY = "key_text_reply"
}
}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
index 7bf78bdb950..5b3a2441373 100755
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
@@ -657,7 +657,7 @@ class NotificationUtils @Inject constructor(
// Quick reply
if (!roomInfo.hasSmartReplyError) {
- buildQuickReplyIntent(roomInfo.roomId, senderDisplayNameForReplyCompat)?.let { replyPendingIntent ->
+ buildQuickReplyIntent(roomInfo.roomId, threadId, senderDisplayNameForReplyCompat)?.let { replyPendingIntent ->
val remoteInput = RemoteInput.Builder(NotificationBroadcastReceiver.KEY_TEXT_REPLY)
.setLabel(stringProvider.getString(R.string.action_quick_reply))
.build()
@@ -892,13 +892,17 @@ class NotificationUtils @Inject constructor(
However, for Android devices running Marshmallow and below (API level 23 and below),
it will be more appropriate to use an activity. Since you have to provide your own UI.
*/
- private fun buildQuickReplyIntent(roomId: String, senderName: String?): PendingIntent? {
+ private fun buildQuickReplyIntent(roomId: String, threadId: String?, senderName: String?): PendingIntent? {
val intent: Intent
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent = Intent(context, NotificationBroadcastReceiver::class.java)
intent.action = actionIds.smartReply
intent.data = createIgnoredUri(roomId)
intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId)
+ threadId?.let {
+ intent.putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, it)
+ }
+
return PendingIntent.getBroadcast(
context,
clock.epochMillis().toInt(),
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index c21b044f1fb..15375ef679b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -223,7 +223,6 @@ class VectorSettingsDevicesFragment :
override fun onViewAllClicked() {
viewNavigator.navigateToOtherSessions(
requireActivity(),
- R.string.device_manager_header_section_security_recommendations_title,
DeviceManagerFilterType.UNVERIFIED,
excludeCurrentDevice = true
)
@@ -233,7 +232,6 @@ class VectorSettingsDevicesFragment :
override fun onViewAllClicked() {
viewNavigator.navigateToOtherSessions(
requireActivity(),
- R.string.device_manager_header_section_security_recommendations_title,
DeviceManagerFilterType.INACTIVE,
excludeCurrentDevice = true
)
@@ -447,7 +445,6 @@ class VectorSettingsDevicesFragment :
override fun onViewAllOtherSessionsClicked() {
viewNavigator.navigateToOtherSessions(
context = requireActivity(),
- titleResourceId = R.string.device_manager_sessions_other_title,
defaultFilter = DeviceManagerFilterType.ALL_SESSIONS,
excludeCurrentDevice = true
)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
index d4b3345fead..bcfa1c30db7 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
@@ -31,12 +31,11 @@ class VectorSettingsDevicesViewNavigator @Inject constructor() {
fun navigateToOtherSessions(
context: Context,
- titleResourceId: Int,
defaultFilter: DeviceManagerFilterType,
excludeCurrentDevice: Boolean,
) {
context.startActivity(
- OtherSessionsActivity.newIntent(context, titleResourceId, defaultFilter, excludeCurrentDevice)
+ OtherSessionsActivity.newIntent(context, defaultFilter, excludeCurrentDevice)
)
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsHeaderItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsHeaderItem.kt
index ff6ce3faadd..f76c21da8e0 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsHeaderItem.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsHeaderItem.kt
@@ -27,7 +27,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.utils.DimensionConverter
-private const val EXTRA_TOP_MARGIN_DP = 48
+private const val EXTRA_TOP_MARGIN_DP = 32
@EpoxyModelClass
abstract class SessionDetailsHeaderItem : VectorEpoxyModel(R.layout.item_session_details_header) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SecurityRecommendationView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SecurityRecommendationView.kt
index 07202274add..2a43a9aade6 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SecurityRecommendationView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SecurityRecommendationView.kt
@@ -53,6 +53,9 @@ class SecurityRecommendationView @JvmOverloads constructor(
setImage(it)
}
+ setOnClickListener {
+ callback?.onViewAllClicked()
+ }
views.recommendationViewAllButton.setOnClickListener {
callback?.onViewAllClicked()
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index eecec72b0a2..5d2daf2941c 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -75,7 +75,7 @@ class SessionInfoView @JvmOverloads constructor(
renderDeviceLastSeenDetails(
sessionInfoViewState.deviceFullInfo.isInactive,
sessionInfoViewState.deviceFullInfo.deviceInfo,
- sessionInfoViewState.isLastSeenDetailsVisible,
+ sessionInfoViewState.isLastActivityVisible,
sessionInfoViewState.isShowingIpAddress,
dateFormatter,
drawableProvider,
@@ -197,7 +197,7 @@ class SessionInfoView @JvmOverloads constructor(
} else {
views.sessionInfoLastActivityTextView.isGone = true
}
- views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isLastSeenDetailsVisible && isShowingIpAddress })
+ views.sessionInfoLastIPAddressTextView.setTextOrHide(deviceInfo.lastSeenIp?.takeIf { isShowingIpAddress })
}
private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
index 5d3c4b4f4b6..6c7ca809ea7 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
@@ -24,6 +24,6 @@ data class SessionInfoViewState(
val isVerifyButtonVisible: Boolean = true,
val isDetailsButtonVisible: Boolean = true,
val isLearnMoreLinkVisible: Boolean = false,
- val isLastSeenDetailsVisible: Boolean = false,
+ val isLastActivityVisible: Boolean = false,
val isShowingIpAddress: Boolean = false,
)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt
index 22ca06eb1ef..502d9abca3e 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.settings.devices.v2.more
+import android.content.DialogInterface
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
@@ -42,6 +43,8 @@ class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment Unit)? = null
+
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSessionLearnMoreBinding {
return BottomSheetSessionLearnMoreBinding.inflate(inflater, container, false)
}
@@ -57,6 +60,11 @@ class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment
super.invalidate()
views.bottomSheetSessionLearnMoreTitle.text = viewState.title
@@ -65,11 +73,12 @@ class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment
updateLoading(state.isLoading)
+ updateFilterView(state.isSelectModeEnabled)
if (state.devices is Success) {
val devices = state.devices.invoke()
renderDevices(devices, state.currentFilter, state.isShowingIpAddress)
@@ -240,13 +243,17 @@ class OtherSessionsFragment :
}
}
+ private fun updateFilterView(isSelectModeEnabled: Boolean) {
+ views.otherSessionsFilterFrameLayout.isVisible = isSelectModeEnabled.not()
+ }
+
private fun updateToolbar(devices: List, isSelectModeEnabled: Boolean) {
invalidateOptionsMenu()
val title = if (isSelectModeEnabled) {
val selection = devices.count { it.isSelected }
stringProvider.getQuantityString(R.plurals.x_selected, selection, selection)
} else {
- getString(args.titleResourceId)
+ getString(R.string.device_manager_sessions_other_title)
}
toolbar?.title = title
}
@@ -341,6 +348,8 @@ class OtherSessionsFragment :
override fun onOtherSessionLongClicked(deviceId: String) = withState(viewModel) { state ->
if (!state.isSelectModeEnabled) {
enableSelectMode(true, deviceId)
+ } else {
+ viewModel.handle(OtherSessionsAction.ToggleSelectionForDevice(deviceId))
}
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index f3df0cced08..399f99201b2 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -224,7 +224,7 @@ class SessionOverviewFragment :
isVerifyButtonVisible = isCurrentSession || viewState.isCurrentSessionTrusted,
isDetailsButtonVisible = false,
isLearnMoreLinkVisible = deviceInfo.roomEncryptionTrustLevel != RoomEncryptionTrustLevel.Default,
- isLastSeenDetailsVisible = !isCurrentSession,
+ isLastActivityVisible = !isCurrentSession,
isShowingIpAddress = viewState.isShowingIpAddress,
)
views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt
index 2f671492e34..d2cbbbdee59 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt
@@ -20,6 +20,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.ViewTreeObserver
import androidx.core.widget.doOnTextChanged
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@@ -62,12 +63,24 @@ class RenameSessionFragment :
}
private fun initEditText() {
- views.renameSessionEditText.showKeyboard(andRequestFocus = true)
+ showKeyboard()
views.renameSessionEditText.doOnTextChanged { text, _, _, _ ->
viewModel.handle(RenameSessionAction.EditLocally(text.toString()))
}
}
+ private fun showKeyboard() {
+ val focusChangeListener = object : ViewTreeObserver.OnWindowFocusChangeListener {
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ if (hasFocus) {
+ views.renameSessionEditText.showKeyboard(andRequestFocus = true)
+ }
+ views.renameSessionEditText.viewTreeObserver.removeOnWindowFocusChangeListener(this)
+ }
+ }
+ views.renameSessionEditText.viewTreeObserver.addOnWindowFocusChangeListener(focusChangeListener)
+ }
+
private fun initSaveButton() {
views.renameSessionSave.debouncedClicks {
viewModel.handle(RenameSessionAction.SaveModifications)
@@ -89,7 +102,9 @@ class RenameSessionFragment :
title = getString(R.string.device_manager_learn_more_session_rename_title),
description = getString(R.string.device_manager_learn_more_session_rename),
)
- SessionLearnMoreBottomSheet.show(childFragmentManager, args)
+ SessionLearnMoreBottomSheet
+ .show(childFragmentManager, args)
+ .onDismiss = { showKeyboard() }
}
private fun observeViewEvents() {
diff --git a/vector/src/main/java/im/vector/app/features/sync/SyncUtils.kt b/vector/src/main/java/im/vector/app/features/sync/SyncUtils.kt
deleted file mode 100644
index e3408d8814a..00000000000
--- a/vector/src/main/java/im/vector/app/features/sync/SyncUtils.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.sync
-
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
-
-object SyncUtils {
- // Get only managed types by Element
- private val listOfSupportedTimelineEventTypes = listOf(
- // TODO Complete the list
- EventType.MESSAGE
- )
-
- // Get only managed types by Element
- private val listOfSupportedStateEventTypes = listOf(
- // TODO Complete the list
- EventType.STATE_ROOM_MEMBER
- )
-
- fun getSyncFilterBuilder(): SyncFilterBuilder {
- return SyncFilterBuilder()
- .useThreadNotifications(true)
- .lazyLoadMembersForStateEvents(true)
- /**
- * Currently we don't set [lazy_load_members = true] for Filter.room.timeline even though we set it for RoomFilter which is used later to
- * fetch messages in a room. It's not clear if it's done so by mistake or intentionally, so changing it could case side effects and need
- * careful testing
- * */
-// .lazyLoadMembersForMessageEvents(true)
-// .listOfSupportedStateEventTypes(listOfSupportedStateEventTypes)
-// .listOfSupportedTimelineEventTypes(listOfSupportedTimelineEventTypes)
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt
index b5c7b162d8c..3c902d162e3 100644
--- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt
@@ -24,6 +24,7 @@ import android.graphics.drawable.Drawable
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
+import androidx.annotation.StyleRes
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.graphics.drawable.DrawableCompat
@@ -113,19 +114,16 @@ object ThemeUtils {
*/
fun setApplicationTheme(context: Context, aTheme: String) {
currentTheme.set(aTheme)
- context.setTheme(
- when (aTheme) {
- SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.Theme_Vector_Dark else R.style.Theme_Vector_Light
- THEME_DARK_VALUE -> R.style.Theme_Vector_Dark
- THEME_BLACK_VALUE -> R.style.Theme_Vector_Black
- else -> R.style.Theme_Vector_Light
- }
- )
+ context.setTheme(themeToRes(context, aTheme))
// Clear the cache
mColorByAttr.clear()
}
+ @StyleRes
+ fun getApplicationThemeRes(context: Context) =
+ themeToRes(context, currentTheme.get())
+
/**
* Set the activity theme according to the selected one. Default is Light, so if this is the current
* theme, the theme is not changed.
@@ -200,4 +198,13 @@ object ThemeUtils {
DrawableCompat.setTint(tinted, color)
return tinted
}
+
+ @StyleRes
+ private fun themeToRes(context: Context, theme: String): Int =
+ when (theme) {
+ SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.Theme_Vector_Dark else R.style.Theme_Vector_Light
+ THEME_DARK_VALUE -> R.style.Theme_Vector_Dark
+ THEME_BLACK_VALUE -> R.style.Theme_Vector_Black
+ else -> R.style.Theme_Vector_Light
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt
index 1bc3078c8b4..d56f4ad7156 100644
--- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt
+++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt
@@ -130,7 +130,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
listeners[voiceBroadcast.voiceBroadcastId] = CopyOnWriteArrayList().apply { add(listener) }
}
listener.onPlayingStateChanged(if (voiceBroadcast == currentVoiceBroadcast) playingState else State.IDLE)
- listener.onLiveModeChanged(voiceBroadcast == currentVoiceBroadcast && isLiveListening)
+ listener.onLiveModeChanged(voiceBroadcast == currentVoiceBroadcast)
}
override fun removeListener(voiceBroadcast: VoiceBroadcast, listener: Listener) {
@@ -373,11 +373,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
}
private fun onLiveListeningChanged(isLiveListening: Boolean) {
- currentVoiceBroadcast?.voiceBroadcastId?.let { voiceBroadcastId ->
- // Notify live mode change to all the listeners attached to the current voice broadcast id
- listeners[voiceBroadcastId]?.forEach { listener -> listener.onLiveModeChanged(isLiveListening) }
- }
-
// Live has ended and last chunk has been reached, we can stop the playback
if (!isLiveListening && playingState == State.BUFFERING && playlist.currentSequence == mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence) {
stop()
diff --git a/vector/src/main/res/drawable/ic_composer_link.xml b/vector/src/main/res/drawable/ic_composer_link.xml
new file mode 100644
index 00000000000..6d0f731ed97
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_composer_link.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
index a7987e70b50..fd66aec1eac 100644
--- a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
+++ b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
@@ -2,9 +2,7 @@
+ android:orientation="vertical">
-
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:paddingHorizontal="24dp"
+ android:paddingBottom="32dp"
+ android:scrollbarStyle="outsideOverlay">
-
+ android:paddingTop="24dp"
+ android:showDividers="none">
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
-
+
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index e25b8b185f9..ce289bd125a 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -1,120 +1,132 @@
-
-
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
-
+ android:layout_marginStart="72dp"
+ android:layout_marginTop="32dp"
+ android:layout_marginEnd="16dp"
+ android:orientation="vertical"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+
-
+ android:layout_marginTop="20dp"
+ android:gravity="start"
+ android:padding="0dp"
+ android:text="@string/device_manager_other_sessions_clear_filter" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_gravity="end"
+ android:layout_marginEnd="8dp"
+ android:padding="8dp">
-
+
-
+
-
+
-
+
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
diff --git a/vector/src/main/res/layout/fragment_session_overview.xml b/vector/src/main/res/layout/fragment_session_overview.xml
index 1c59abfd122..47193578027 100644
--- a/vector/src/main/res/layout/fragment_session_overview.xml
+++ b/vector/src/main/res/layout/fragment_session_overview.xml
@@ -47,6 +47,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
+ android:layout_marginTop="4dp"
android:text="@string/device_manager_session_overview_signout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
diff --git a/vector/src/main/res/layout/fragment_set_link.xml b/vector/src/main/res/layout/fragment_set_link.xml
new file mode 100644
index 00000000000..36b3421253b
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_set_link.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml
index 731049f3a2a..266f8df46f2 100644
--- a/vector/src/main/res/layout/fragment_settings_devices.xml
+++ b/vector/src/main/res/layout/fragment_settings_devices.xml
@@ -75,7 +75,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
- android:layout_marginVertical="16dp"
+ android:layout_marginVertical="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/deviceListHeaderCurrentSession" />
diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml
index a6205e7d50d..8f6e4f64e46 100644
--- a/vector/src/main/res/layout/item_other_session.xml
+++ b/vector/src/main/res/layout/item_other_session.xml
@@ -5,9 +5,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
- android:paddingHorizontal="8dp"
android:paddingTop="8dp">
+
+
@@ -52,8 +58,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
android:ellipsize="end"
android:lines="1"
app:layout_constraintEnd_toEndOf="parent"
@@ -89,7 +95,7 @@
android:id="@+id/otherSessionSeparator"
android:layout_width="0dp"
android:layout_height="1dp"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="16dp"
android:background="?vctr_content_quinary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/otherSessionNameTextView"
diff --git a/vector/src/main/res/layout/item_session_details_content.xml b/vector/src/main/res/layout/item_session_details_content.xml
index 98a21aa9237..c847090b3f0 100644
--- a/vector/src/main/res/layout/item_session_details_content.xml
+++ b/vector/src/main/res/layout/item_session_details_content.xml
@@ -9,7 +9,7 @@
android:id="@+id/sessionDetailsContentTitle"
style="@style/TextAppearance.Vector.Body.DevicesManagement"
android:layout_width="0dp"
- android:layout_height="wrap_content"
+ android:layout_height="0dp"
android:layout_marginStart="@dimen/layout_horizontal_margin"
app:layout_constraintBottom_toTopOf="@id/sessionDetailsContentDivider"
app:layout_constraintEnd_toStartOf="@id/sessionDetailsContentDescription"
@@ -22,14 +22,14 @@
style="@style/TextAppearance.Vector.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
+ android:layout_marginStart="12dp"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:gravity="end"
app:layout_constraintBottom_toTopOf="@id/sessionDetailsContentDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/sessionDetailsContentTitle"
app:layout_constraintTop_toTopOf="@id/sessionDetailsContentTop"
- tools:text="Element Web: Firefox" />
+ tools:text="app.element.io: Firefox on macOS" />
+ tools:progress="50" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/seekBar"
+ tools:ignore="NegativeMargin"
+ tools:text="-0:12" />
diff --git a/vector/src/main/res/layout/view_other_sessions.xml b/vector/src/main/res/layout/view_other_sessions.xml
index 2d028701742..dc3d35494c5 100644
--- a/vector/src/main/res/layout/view_other_sessions.xml
+++ b/vector/src/main/res/layout/view_other_sessions.xml
@@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:paddingBottom="8dp">
diff --git a/vector/src/main/res/layout/view_security_recommendation.xml b/vector/src/main/res/layout/view_security_recommendation.xml
index 67108640480..4a41ca961f7 100644
--- a/vector/src/main/res/layout/view_security_recommendation.xml
+++ b/vector/src/main/res/layout/view_security_recommendation.xml
@@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_current_session"
+ android:foreground="?attr/selectableItemBackground"
android:paddingHorizontal="16dp"
android:paddingTop="16dp"
android:paddingBottom="8dp">
diff --git a/vector/src/main/res/layout/view_session_info.xml b/vector/src/main/res/layout/view_session_info.xml
index be51bc69155..eff0eef4b6a 100644
--- a/vector/src/main/res/layout/view_session_info.xml
+++ b/vector/src/main/res/layout/view_session_info.xml
@@ -6,7 +6,7 @@
android:layout_height="wrap_content"
android:background="@drawable/bg_current_session"
android:paddingHorizontal="24dp"
- android:paddingBottom="16dp">
+ android:paddingBottom="8dp">
()
+ private val verifiedTransaction = mockk().apply {
+ every { state } returns VerificationTxState.Verified
+ }
+
private fun createViewModel(): DevicesViewModel {
return DevicesViewModel(
initialState = DevicesViewState(),
@@ -375,6 +381,18 @@ class DevicesViewModelTest {
viewModelTest.finish()
}
+ @Test
+ fun `given the view model when a verified transaction is updated then device list is refreshed`() {
+ // Given
+ val viewModel = createViewModel()
+
+ // When
+ viewModel.transactionUpdated(verifiedTransaction)
+
+ // Then
+ verify { viewModel.refreshDeviceList() }
+ }
+
private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
val currentSessionCrossSigningInfo = mockk()
every { currentSessionCrossSigningInfo.deviceId } returns A_CURRENT_DEVICE_ID
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/ToggleIpAddressVisibilityUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/ToggleIpAddressVisibilityUseCaseTest.kt
new file mode 100644
index 00000000000..53dfc707b19
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/ToggleIpAddressVisibilityUseCaseTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.settings.devices.v2
+
+import im.vector.app.test.fakes.FakeVectorPreferences
+import org.junit.Test
+
+class ToggleIpAddressVisibilityUseCaseTest {
+
+ private val fakeVectorPreferences = FakeVectorPreferences()
+
+ private val toggleIpAddressVisibilityUseCase = ToggleIpAddressVisibilityUseCase(
+ vectorPreferences = fakeVectorPreferences.instance,
+ )
+
+ @Test
+ fun `given ip addresses are currently visible then then visibility is set as false`() {
+ // Given
+ fakeVectorPreferences.givenShowIpAddressInSessionManagerScreens(true)
+
+ // When
+ toggleIpAddressVisibilityUseCase.execute()
+
+ // Then
+ fakeVectorPreferences.verifySetIpAddressVisibilityInDeviceManagerScreens(false)
+ }
+
+ @Test
+ fun `given ip addresses are currently not visible then then visibility is set as true`() {
+ // Given
+ fakeVectorPreferences.givenShowIpAddressInSessionManagerScreens(false)
+
+ // When
+ toggleIpAddressVisibilityUseCase.execute()
+
+ // Then
+ fakeVectorPreferences.verifySetIpAddressVisibilityInDeviceManagerScreens(true)
+ }
+}
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
index 24582c75d8b..e53e9f71513 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
@@ -31,7 +31,6 @@ import org.junit.Before
import org.junit.Test
private const val A_SESSION_ID = "session_id"
-private const val A_TITLE_RESOURCE_ID = 1234
private val A_DEFAULT_FILTER = DeviceManagerFilterType.INACTIVE
class VectorSettingsDevicesViewNavigatorTest {
@@ -67,11 +66,11 @@ class VectorSettingsDevicesViewNavigatorTest {
@Test
fun `given an intent when navigating to other sessions list then it starts the correct activity`() {
// Given
- val intent = givenIntentForOtherSessions(A_TITLE_RESOURCE_ID, A_DEFAULT_FILTER, true)
+ val intent = givenIntentForOtherSessions(A_DEFAULT_FILTER, true)
context.givenStartActivity(intent)
// When
- vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance, A_TITLE_RESOURCE_ID, A_DEFAULT_FILTER, true)
+ vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance, A_DEFAULT_FILTER, true)
// Then
context.verifyStartActivity(intent)
@@ -96,9 +95,9 @@ class VectorSettingsDevicesViewNavigatorTest {
return intent
}
- private fun givenIntentForOtherSessions(titleResourceId: Int, defaultFilter: DeviceManagerFilterType, excludeCurrentDevice: Boolean): Intent {
+ private fun givenIntentForOtherSessions(defaultFilter: DeviceManagerFilterType, excludeCurrentDevice: Boolean): Intent {
val intent = mockk()
- every { OtherSessionsActivity.newIntent(context.instance, titleResourceId, defaultFilter, excludeCurrentDevice) } returns intent
+ every { OtherSessionsActivity.newIntent(context.instance, defaultFilter, excludeCurrentDevice) } returns intent
return intent
}
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt
index 82f40d911df..687d03926f6 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt
@@ -47,7 +47,6 @@ import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
-private const val A_TITLE_RES_ID = 1
private const val A_DEVICE_ID_1 = "device-id-1"
private const val A_DEVICE_ID_2 = "device-id-2"
private const val A_PASSWORD = "password"
@@ -58,7 +57,6 @@ class OtherSessionsViewModelTest {
val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
private val defaultArgs = OtherSessionsArgs(
- titleResourceId = A_TITLE_RES_ID,
defaultFilter = DeviceManagerFilterType.ALL_SESSIONS,
excludeCurrentDevice = false,
)
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
index c40e4a8fc44..e368fbbcf25 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
@@ -43,8 +43,7 @@ class FakeSession(
val fakeRoomService: FakeRoomService = FakeRoomService(),
val fakePushersService: FakePushersService = FakePushersService(),
private val fakeEventService: FakeEventService = FakeEventService(),
- val fakeSessionAccountDataService: FakeSessionAccountDataService = FakeSessionAccountDataService(),
- val fakeFilterService: FakeFilterService = FakeFilterService(),
+ val fakeSessionAccountDataService: FakeSessionAccountDataService = FakeSessionAccountDataService()
) : Session by mockk(relaxed = true) {
init {
@@ -63,7 +62,6 @@ class FakeSession(
override fun eventService() = fakeEventService
override fun pushersService() = fakePushersService
override fun accountDataService() = fakeSessionAccountDataService
- override fun filterService() = fakeFilterService
fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
coEvery {
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt
index 58bc1a18b87..3d3f415778b 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt
@@ -77,4 +77,12 @@ class FakeVectorPreferences {
fun givenIsBackgroundSyncEnabled(isEnabled: Boolean) {
every { instance.isBackgroundSyncEnabled() } returns isEnabled
}
+
+ fun givenShowIpAddressInSessionManagerScreens(show: Boolean) {
+ every { instance.showIpAddressInSessionManagerScreens() } returns show
+ }
+
+ fun verifySetIpAddressVisibilityInDeviceManagerScreens(isVisible: Boolean) {
+ verify { instance.setIpAddressVisibilityInDeviceManagerScreens(isVisible) }
+ }
}