diff --git a/CHANGES.md b/CHANGES.md
index cac9ab2608..5bd85efc67 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 42c23e9b76..b6af5d39d0 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
@@ -84,7 +84,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",
@@ -99,7 +99,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",
@@ -130,7 +130,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 0000000000..91c25cf053
--- /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 d37b5f0906..73cb60bb68 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 4558f4e8b5..f839a6c263 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 8edecb273d..eeb2def582 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 a9753e2407..84650da72f 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 13993149f4..cf0f4bdce0 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 40ce6ecb5c..9b5f4ac19f 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 a7de7f5579..02c5b0f8ef 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 bc3309132a..c9eabeab48 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 89cd91c22b..ac585120a0 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
@@ -70,6 +70,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 timber.log.Timber
@@ -93,7 +94,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val scSchemaVersion = 7L
private val scSchemaVersionOffset = (1L shl 12)
- val schemaVersion = 46L +
+ val schemaVersion = 47L +
scSchemaVersion * scSchemaVersionOffset
}
@@ -156,6 +157,7 @@ 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()
if (oldScVersion <= 0) MigrateScSessionTo001(realm).perform()
if (oldScVersion <= 1) MigrateScSessionTo002(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 645cb41af5..0000000000
--- 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 0000000000..5bfaaa760c
--- /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 0ab30657ed..0d998e8fe1 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 679c5085ef..1af904bbc7 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 4e5b005584..f70b4d1799 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 c54e7de07a..0000000000
--- 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 ca9f798fd9..5ae2c2a47d 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 71d7391e87..d0ec4b98bb 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 76805c5c51..5c7027f8b3 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 ddb7d6a8e6..34b6ee525d 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 7fddb5e7ce..02b8b4d8fd 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
@@ -101,6 +101,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
@@ -359,4 +361,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 455ccabbc6..a424becbd6 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 93c7f143fd..50439f51eb 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 0000000000..e7dd8c57eb
--- /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 4cf6445920..1e9a785c80 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 895d456772..b8db3e167a 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
@@ -261,6 +261,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 e0751865ad..3707205aef 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 54bb63753c..519112b1b7 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 ad55b26dfd..d58b9d3765 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 c1fd615e25..0888d82907 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 0000000000..8d50bac38f
--- /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 201423685c..f3ab65f6c4 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 0000000000..f2b62ad3ba
--- /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 7347bee165..cb75d8b708 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 b8225f21d6..27a39120f8 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 afdcf111f8..ba124a86aa 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 0000000000..68dbbe7ea6
--- /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 c00bd10371..0dcf9ccb25 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 353ad3d41f..ff24cf8ed6 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
ext.scVersion = 62
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 72137ed8e8..68a54e9901 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 52607bd9a1..3439bcfced 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 e5147c2085..ad6d5e5df3 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 d04746bcd6..73a063857a 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 dd04cb2986..a6d6fcd14b 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 b58d584dad..d22ab51e7a 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 0000000000..5a817b989e
--- /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 fbf89b76a4..c6a2635e6c 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 71f7a5817e..ec8f638842 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
@@ -130,6 +130,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 b732fb8c2f..9bd91dcd12 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
@@ -72,6 +72,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 8f95598d9e..83e71d58ea 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
@@ -447,6 +447,7 @@ class TimelineFragment :
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
is RoomDetailViewEvents.ScDbgReadTracking -> handleScDbgReadTracking(it)
RoomDetailViewEvents.OpenElementCallWidget -> handleOpenElementCallWidget()
+ RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast -> displayPromptToStopVoiceBroadcast()
RoomDetailViewEvents.JumpToBottom -> doJumpToBottom()
is RoomDetailViewEvents.SetInitialForceScroll -> setInitialForceScrollEnabled(it.enabled, stickToBottom = it.stickToBottom)
}
@@ -2345,6 +2346,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 1eaea80b2b..4fcde6c8ee 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
@@ -718,7 +718,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 4c313f4e1f..5c59e644f7 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
@@ -82,6 +82,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
@@ -156,6 +159,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 (isRichTextEditorEnabled) {
@@ -225,6 +229,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 ->
@@ -398,6 +410,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 22603946f5..9174dc383c 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
@@ -46,4 +46,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 88c2e59287..580a3167b1 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
@@ -50,6 +50,7 @@ import im.vector.app.databinding.ViewRichTextMenuButtonBinding
import im.vector.app.features.home.room.detail.TimelineViewModel
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
@@ -232,8 +233,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 9be59d31fd..5cc31022ea 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 0000000000..008a8017ee
--- /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 0000000000..fb9f3f0d5b
--- /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 0000000000..cd42651c22
--- /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 0000000000..9a5b5cd8dd
--- /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 0000000000..ea61f7eb72
--- /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 38fe1e8f17..b788d79214 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 455f4778e8..e231686c27 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 7b3412dc0d..5bbd9937da 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
@@ -658,7 +658,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()
@@ -893,13 +893,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 c21b044f1f..15375ef679 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 d4b3345fea..bcfa1c30db 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 ff6ce3faad..f76c21da8e 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 07202274ad..2a43a9aade 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 eecec72b0a..5d2daf2941 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 5d3c4b4f4b..6c7ca809ea 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 22ca06eb1e..502d9abca3 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 f3df0cced0..399f99201b 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 2f671492e3..d2cbbbdee5 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 e3408d8814..0000000000
--- 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 d7df489e2a..1e7d672cd4 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
@@ -258,20 +258,7 @@ object ThemeUtils {
currentLightThemeAccent.set(aLightAccent)
currentDarkThemeAccent.set(aDarkAccent)
val aTheme = if (useDarkTheme(context)) aDarkTheme else aLightTheme
- context.setTheme(
- when (aTheme) {
- //SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.Theme_Vector_Dark else R.style.Theme_Vector_Light
- THEME_LIGHT_VALUE -> R.style.Theme_Vector_Light
- THEME_DARK_VALUE -> R.style.Theme_Vector_Dark
- THEME_BLACK_VALUE -> R.style.Theme_Vector_Black
- THEME_SC_LIGHT_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Light, aLightAccent)
- THEME_SC_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC, aDarkAccent)
- THEME_SC_DARK_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Dark, aDarkAccent)
- THEME_SC_COLORED_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Colored, aDarkAccent)
- THEME_SC_DARK_COLORED_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Dark_Colored, aDarkAccent)
- else -> getAccentedThemeRes(R.style.AppTheme_SC_Light, aLightAccent)
- }
- )
+ context.setTheme(themeToRes(context, aTheme, aLightAccent, aDarkAccent))
// Clear the cache
mColorByAttr.clear()
@@ -343,6 +330,10 @@ object ThemeUtils {
getApplicationLightThemeAccent(context), themeAccent)
}
+ @StyleRes
+ fun getApplicationThemeRes(context: Context) =
+ themeToRes(context, getCurrentActiveTheme(context), currentLightThemeAccent.get(), currentDarkThemeAccent.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.
@@ -587,4 +578,19 @@ object ThemeUtils {
}
}
+ @StyleRes
+ @Suppress("UNUSED_PARAMETER")
+ private fun themeToRes(context: Context, theme: String, lightAccent: String, darkAccent: String): Int =
+ when (theme) {
+ //SYSTEM_THEME_VALUE -> if (isSystemDarkTheme(context.resources)) R.style.Theme_Vector_Dark else R.style.Theme_Vector_Light
+ THEME_LIGHT_VALUE -> R.style.Theme_Vector_Light
+ THEME_DARK_VALUE -> R.style.Theme_Vector_Dark
+ THEME_BLACK_VALUE -> R.style.Theme_Vector_Black
+ THEME_SC_LIGHT_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Light, lightAccent)
+ THEME_SC_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC, darkAccent)
+ THEME_SC_DARK_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Dark, darkAccent)
+ THEME_SC_COLORED_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Colored, darkAccent)
+ THEME_SC_DARK_COLORED_VALUE -> getAccentedThemeRes(R.style.AppTheme_SC_Dark_Colored, darkAccent)
+ else -> getAccentedThemeRes(R.style.AppTheme_SC_Light, lightAccent)
+ }
}
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 1bc3078c8b..d56f4ad715 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 0000000000..6d0f731ed9
--- /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 a7987e70b5..fd66aec1ea 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 e25b8b185f..ce289bd125 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="wrap_content">
-
+ android:layout_height="match_parent"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed"
+ app:titleEnabled="false"
+ app:toolbarId="@id/otherSessionsToolbar">
-
+ android:layout_marginTop="?attr/actionBarSize"
+ android:layout_marginBottom="16dp"
+ app:layout_collapseMode="parallax"
+ app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
+ app:sessionsListHeaderHasLearnMoreLink="false"
+ app:sessionsListHeaderTitle="" />
-
+
+
+
+
+ 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 1c59abfd12..4719357802 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 0000000000..36b3421253
--- /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 731049f3a2..266f8df46f 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 a6205e7d50..8f6e4f64e4 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 98a21aa923..c847090b3f 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 2d02870174..dc3d35494c 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 6710864048..4a41ca961f 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 be51bc6915..eff0eef4b6 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 0000000000..53dfc707b1
--- /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 24582c75d8..e53e9f7151 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 82f40d911d..687d03926f 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 c40e4a8fc4..e368fbbcf2 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 58bc1a18b8..3d3f415778 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) }
+ }
}