diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml index 2086eab3aa..65b3004510 100644 --- a/.github/workflows/sync-localazy.yml +++ b/.github/workflows/sync-localazy.yml @@ -36,7 +36,7 @@ jobs: ./tools/localazy/importSupportedLocalesFromLocalazy.py ./tools/test/generateAllScreenshots.py - name: Create Pull Request for Strings - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 with: token: ${{ secrets.DANGER_GITHUB_API_TOKEN }} commit-message: Sync Strings from Localazy diff --git a/.github/workflows/sync-sas-strings.yml b/.github/workflows/sync-sas-strings.yml index 2f5f22a2d5..bc6bf94008 100644 --- a/.github/workflows/sync-sas-strings.yml +++ b/.github/workflows/sync-sas-strings.yml @@ -23,7 +23,7 @@ jobs: - name: Run SAS String script run: ./tools/sas/import_sas_strings.py - name: Create Pull Request for SAS Strings - uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 with: commit-message: Sync SAS Strings title: Sync SAS Strings diff --git a/CHANGES.md b/CHANGES.md index 164739cb60..7d5f35a6ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,26 @@ +Changes in Element X v26.01.1 +============================= + + + +## What's Changed +### 🐛 Bugfixes +* Ensure that log files are not too big. by @bmarty in https://github.com/element-hq/element-x-android/pull/6003 +* Make the number view scrollable by @bmarty in https://github.com/element-hq/element-x-android/pull/6017 +* Ensure that room with long names are rendered correctly in the room list. by @bmarty in https://github.com/element-hq/element-x-android/pull/6019 +* Create `AppMigration09` to remove the cached `well-known` config from the SDK by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6026 +### 🚧 In development 🚧 +* [POC] Signin with Element Classic by @bmarty in https://github.com/element-hq/element-x-android/pull/6013 +* Space : manage rooms by @ganfra in https://github.com/element-hq/element-x-android/pull/6022 +### Dependency upgrades +* fix(deps): update dependency androidx.compose:compose-bom to v2026 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6010 +* fix(deps): update dependency io.sentry:sentry-android to v8.30.0 - autoclosed by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6014 +* fix(deps): update dependency com.google.firebase:firebase-bom to v34.8.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/6018 +* Upgrade androidx.biometric:biometric-ktx to 1.4.0-alpha02 by @bmarty in https://github.com/element-hq/element-x-android/pull/6020 + + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.01.0...v26.01.1 + Changes in Element X v26.01.0 ============================= diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 322ad47911..969f401385 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -132,8 +132,23 @@ android { optimization { enable = true keepRules { - files.add(File(projectDir, "proguard-rules.pro")) + files.add(File(projectDir, "common-proguard-rules.pro")) files.add(getDefaultProguardFile("proguard-android-optimize.txt")) + + // Depending on whether the app flavor is enterprise or not we want to use different proguard rules. + val flavorProguardFile = if (isEnterpriseBuild) { + // Custom rules for enterprise builds + File(projectDir, "enterprise-proguard-rules.pro") + } else { + // These default rules prevent the OSS app from being obfuscated + File(projectDir, "default-proguard-rules.pro") + } + + if (flavorProguardFile.exists()) { + files.add(flavorProguardFile) + } else { + logger.warn("Proguard file ${flavorProguardFile.absolutePath} does not exist") + } } } } diff --git a/app/proguard-rules.pro b/app/common-proguard-rules.pro similarity index 88% rename from app/proguard-rules.pro rename to app/common-proguard-rules.pro index b09ecf69ca..cce03a546e 100644 --- a/app/proguard-rules.pro +++ b/app/common-proguard-rules.pro @@ -5,6 +5,9 @@ # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html +# Increase optimizations passes to 3 +-optimizationpasses 3 + # JNA -dontwarn java.awt.* -keep class com.sun.jna.** { *; } @@ -41,7 +44,6 @@ static int windowAttachCount(android.view.View); } - # Keep LogSessionId class and related classes (https://github.com/androidx/media/issues/2535) -keep class android.media.metrics.LogSessionId { *; } -keep class android.media.metrics.** { *; } @@ -66,10 +68,9 @@ -dontwarn androidx.window.sidecar.SidecarWindowLayoutInfo # Also needed after AGP 8.13.1 upgrade, it seems like proguard is now more aggressive on removing unused code --keep,allowshrinking class org.matrix.rustcomponents.sdk.** { *;} --keep,allowshrinking class uniffi.** { *;} --keep,allowshrinking class io.element.android.x.di.** { *; } --keepclasseswithmembernames,allowoptimization,allowshrinking class io.element.android.** { *; } +-keep,allowoptimization,allowshrinking class org.matrix.rustcomponents.sdk.** { *;} +-keep,allowoptimization,allowshrinking class uniffi.** { *;} +-keep,allowoptimization,allowshrinking class io.element.android.x.di.** { *; } # Keep Metro classes --keep,allowshrinking class dev.zacsweers.metro.** { *; } +-keep,allowoptimization,allowshrinking class dev.zacsweers.metro.** { *; } diff --git a/app/default-proguard-rules.pro b/app/default-proguard-rules.pro new file mode 100644 index 0000000000..a6dce2f0ce --- /dev/null +++ b/app/default-proguard-rules.pro @@ -0,0 +1,2 @@ +# Don't obfuscate anything for non-enterprise builds +-dontobfuscate diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt index 38dcd67c9e..6dd2143d95 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt @@ -30,4 +30,9 @@ object RageshakeConfig { * The maximum size of a single log file. */ const val MAX_LOG_CONTENT_SIZE = 100 * 1024 * 1024L + + /** + * The maximum number of log lines a rageshake can contain. + */ + const val MAX_LOG_LINES_SIZE = 1_000_000 } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 21bdd99a47..c76150a67e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -484,7 +484,10 @@ class LoggedInFlowNode( backstack.replace(NavTarget.Room(roomIdOrAlias = RoomIdOrAlias.Id(roomId), serverNames = emptyList())) } } - createRoomEntryPoint.createNode(isSpace = true, parentNode = this, buildContext = buildContext, callback = callback) + createRoomEntryPoint + .builder(parentNode = this, buildContext = buildContext, callback = callback) + .setIsSpace(true) + .build() } is NavTarget.SecureBackup -> { secureBackupEntryPoint.createNode( diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index 6446f754b0..7080b34f8e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -214,6 +214,10 @@ class RoomFlowNode( ) } is NavTarget.JoinRoom -> { + // Clear analytics transactions for opening a joined room, since we're display a non-joined one + analyticsService.removeLongRunningTransaction(LoadJoinedRoomFlow) + analyticsService.removeLongRunningTransaction(OpenRoom) + val inputs = JoinRoomEntryPoint.Inputs( roomId = navTarget.roomId, roomIdOrAlias = inputs.roomIdOrAlias, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt index 504bdfec58..0c9abbc571 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt @@ -41,6 +41,10 @@ import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.ui.room.LoadingRoomState import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadJoinedRoomFlow +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.NotificationToMessage +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.OpenRoom +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -53,6 +57,7 @@ class JoinedRoomFlowNode( @Assisted val buildContext: BuildContext, @Assisted plugins: List, loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory, + private val analyticsService: AnalyticsService, ) : BaseFlowNode( backstack = BackStack( @@ -81,6 +86,11 @@ class JoinedRoomFlowNode( override fun onBuilt() { super.onBuilt() + + val parentTransaction = analyticsService.getLongRunningTransaction(NotificationToMessage) + val openRoomTransaction = analyticsService.startLongRunningTransaction(OpenRoom, parentTransaction) + analyticsService.startLongRunningTransaction(LoadJoinedRoomFlow, openRoomTransaction) + loadingRoomStateStateFlow .map { it is LoadingRoomState.Loaded diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt index 05ebab2b10..d0d3df590d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt @@ -48,8 +48,6 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadJoinedRoomFlow -import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadMessagesUi -import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.OpenRoom import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.finishLongRunningTransaction import io.element.android.services.appnavstate.api.ActiveRoomsHolder @@ -106,8 +104,6 @@ class JoinedRoomLoadedFlowNode( init { lifecycle.subscribe( onCreate = { - val parent = analyticsService.getLongRunningTransaction(OpenRoom) - analyticsService.startLongRunningTransaction(LoadMessagesUi, parent) Timber.v("OnCreate => ${inputs.room.roomId}") appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId) activeRoomsHolder.addRoom(inputs.room) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt index 2bc76d7a5b..32c8e52084 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt @@ -15,9 +15,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.features.rageshake.api.crash.CrashDetectionEvents +import io.element.android.features.rageshake.api.crash.CrashDetectionEvent import io.element.android.features.rageshake.api.crash.CrashDetectionView -import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvents +import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvent import io.element.android.features.rageshake.api.detection.RageshakeDetectionView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -39,8 +39,8 @@ fun RootView( children() fun onOpenBugReport() { - state.crashDetectionState.eventSink(CrashDetectionEvents.ResetAppHasCrashed) - state.rageshakeDetectionState.eventSink(RageshakeDetectionEvents.Dismiss) + state.crashDetectionState.eventSink(CrashDetectionEvent.ResetAppHasCrashed) + state.rageshakeDetectionState.eventSink(RageshakeDetectionEvent.Dismiss) onOpenBugReport.invoke() } diff --git a/build.gradle.kts b/build.gradle.kts index 61e17c149c..0d4c58ec22 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -46,7 +46,7 @@ allprojects { config.from(files("$rootDir/tools/detekt/detekt.yml")) } dependencies { - detektPlugins("io.nlopez.compose.rules:detekt:0.5.3") + detektPlugins("io.nlopez.compose.rules:detekt:0.5.6") detektPlugins(project(":tests:detekt-rules")) } diff --git a/fastlane/metadata/android/en-US/changelogs/202601020.txt b/fastlane/metadata/android/en-US/changelogs/202601020.txt new file mode 100644 index 0000000000..a4b397f1bb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202601020.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and improvements. +Full changelog: https://github.com/element-hq/element-x-android/releases \ No newline at end of file diff --git a/features/announcement/impl/src/main/res/values-ru/translations.xml b/features/announcement/impl/src/main/res/values-ru/translations.xml index 7ff445bd69..25d6222014 100644 --- a/features/announcement/impl/src/main/res/values-ru/translations.xml +++ b/features/announcement/impl/src/main/res/values-ru/translations.xml @@ -1,11 +1,11 @@ - "Просмотр пространств, которые вы создали или к которым присоединились" + "Просмотреть пространства, которые вы создали или к которым присоединились" "Принимать или отклонять приглашения в пространства" - "Откройте для себя все комнаты, к которым вы можете присоединиться в своих пространствах." - "Присоединиться к публичному пространству" + "Найти все комнаты, к которым можно присоединиться в ваших пространствах" + "Присоединяться к публичным пространствам" "Покинуть все пространства, к которым вы присоединились" "Работа с пространствами скоро станет доступна" - "Добро пожаловать в бета-версию Spaces! В этой первой версии вы сможете:" + "Добро пожаловать в бета-версию пространств! В этой первой версии вы сможете:" "Представляем пространства" diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt index 22757aba06..271fdb2332 100644 --- a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt @@ -15,12 +15,13 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId interface CreateRoomEntryPoint : FeatureEntryPoint { - fun createNode( - isSpace: Boolean, - parentNode: Node, - buildContext: BuildContext, - callback: Callback, - ): Node + interface Builder { + fun setIsSpace(isSpace: Boolean): Builder + fun setParentSpace(parentSpaceId: RoomId): Builder + fun build(): Node + } + + fun builder(parentNode: Node, buildContext: BuildContext, callback: Callback): Builder interface Callback : Plugin { fun onRoomCreated(roomId: RoomId) diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index 1adaac1f6d..7201f7dc9c 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.libraries.mediapickers.api) implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.permissions.api) + implementation(projects.libraries.previewutils) implementation(projects.libraries.usersearch.impl) implementation(projects.services.analytics.api) implementation(libs.coil.compose) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 89dbddd186..d2cd1915f4 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -38,7 +38,7 @@ class CreateRoomFlowNode( @Assisted plugins: List, ) : BaseFlowNode( backstack = BackStack( - initialElement = NavTarget.ConfigureRoom(isSpace = plugins.filterIsInstance().first().isSpace), + initialElement = initialElementFromInputs(plugins.filterIsInstance().first()), savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -46,7 +46,8 @@ class CreateRoomFlowNode( ) { @Parcelize data class Inputs( - val isSpace: Boolean + val isSpace: Boolean, + val parentSpaceId: RoomId?, ) : NodeInputs, Parcelable private val callback: CreateRoomEntryPoint.Callback = callback() @@ -54,7 +55,7 @@ class CreateRoomFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.ConfigureRoom -> { - val inputs = ConfigureRoomNode.Inputs(isSpace = navTarget.isSpace) + val inputs = ConfigureRoomNode.Inputs(isSpace = navTarget.isSpace, parentSpaceId = navTarget.parentSpaceId) val callback = object : ConfigureRoomNode.Callback { override fun onCreateRoomSuccess(roomId: RoomId) { backstack.replace(NavTarget.AddPeople(roomId)) @@ -81,9 +82,14 @@ class CreateRoomFlowNode( sealed interface NavTarget : Parcelable { @Parcelize - data class ConfigureRoom(val isSpace: Boolean) : NavTarget + data class ConfigureRoom(val isSpace: Boolean, val parentSpaceId: RoomId?) : NavTarget @Parcelize data class AddPeople(val roomId: RoomId) : NavTarget } } + +private fun initialElementFromInputs(inputs: CreateRoomFlowNode.Inputs) = CreateRoomFlowNode.NavTarget.ConfigureRoom( + isSpace = inputs.isSpace, + parentSpaceId = inputs.parentSpaceId, +) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt index 63163e7a28..798467e48c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt @@ -14,16 +14,35 @@ import dev.zacsweers.metro.ContributesBinding import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.RoomId @ContributesBinding(SessionScope::class) class DefaultCreateRoomEntryPoint : CreateRoomEntryPoint { - override fun createNode( - isSpace: Boolean, - parentNode: Node, - buildContext: BuildContext, - callback: CreateRoomEntryPoint.Callback, - ): Node { - val inputs = CreateRoomFlowNode.Inputs(isSpace) - return parentNode.createNode(buildContext, listOf(inputs, callback)) + class Builder( + private val parentNode: Node, + private val buildContext: BuildContext, + private val callback: CreateRoomEntryPoint.Callback, + ) : CreateRoomEntryPoint.Builder { + private var isSpace = false + private var parentSpaceId: RoomId? = null + + override fun setIsSpace(isSpace: Boolean): Builder { + this.isSpace = isSpace + return this + } + + override fun setParentSpace(parentSpaceId: RoomId): Builder { + this.parentSpaceId = parentSpaceId + return this + } + + override fun build(): Node { + val inputs = CreateRoomFlowNode.Inputs(isSpace = isSpace, parentSpaceId = parentSpaceId) + return parentNode.createNode(buildContext, listOf(inputs, callback)) + } + } + + override fun builder(parentNode: Node, buildContext: BuildContext, callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.Builder { + return Builder(parentNode, buildContext, callback) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt index fed8404143..5de99e519d 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt @@ -8,15 +8,16 @@ package io.element.android.features.createroom.impl.configureroom +import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.ui.media.AvatarAction sealed interface ConfigureRoomEvents { data class RoomNameChanged(val name: String) : ConfigureRoomEvents data class TopicChanged(val topic: String) : ConfigureRoomEvents - data class RoomVisibilityChanged(val visibilityItem: RoomVisibilityItem) : ConfigureRoomEvents - data class RoomAccessChanged(val roomAccess: RoomAccessItem) : ConfigureRoomEvents + data class JoinRuleChanged(val joinRuleItem: JoinRuleItem) : ConfigureRoomEvents data class RoomAddressChanged(val roomAddress: String) : ConfigureRoomEvents data object CreateRoom : ConfigureRoomEvents data class HandleAvatarAction(val action: AvatarAction) : ConfigureRoomEvents + data class SetParentSpace(val space: SpaceRoom?) : ConfigureRoomEvents data object CancelCreateRoom : ConfigureRoomEvents } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt index e021a7a0f9..8652db76f8 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt @@ -42,11 +42,12 @@ class ConfigureRoomNode( @Parcelize data class Inputs( val isSpace: Boolean, + val parentSpaceId: RoomId?, ) : NodeInputs, Parcelable private val inputs = inputs() - private val presenter = presenterFactory.create(inputs.isSpace) + private val presenter = presenterFactory.create(inputs.isSpace, inputs.parentSpaceId) init { lifecycle.subscribe( diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index aef55c77df..85c64adc83 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -18,6 +18,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.core.net.toUri import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory @@ -35,7 +36,9 @@ import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.RoomPreset import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility +import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEffect @@ -45,15 +48,22 @@ import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull import timber.log.Timber import kotlin.jvm.optionals.getOrDefault +import kotlin.jvm.optionals.getOrNull +import kotlin.time.Duration.Companion.seconds @AssistedInject class ConfigureRoomPresenter( @Assisted private val isSpace: Boolean, + @Assisted private val initialParentSpaceId: RoomId?, private val dataStore: CreateRoomConfigStore, private val matrixClient: MatrixClient, private val mediaPickerProvider: PickerProvider, @@ -66,7 +76,7 @@ class ConfigureRoomPresenter( ) : Presenter { @AssistedFactory interface Factory { - fun create(isSpace: Boolean): ConfigureRoomPresenter + fun create(isSpace: Boolean, parentSpaceId: RoomId?): ConfigureRoomPresenter } private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA) @@ -78,6 +88,7 @@ class ConfigureRoomPresenter( @Composable override fun present(): ConfigureRoomState { + val canAddRoomToSpace by featureFlagService.isFeatureEnabledFlow(FeatureFlags.CreateSpaces).collectAsState(false) val cameraPermissionState = cameraPermissionPresenter.present() val createRoomConfig by dataStore.getCreateRoomConfigFlow().collectAsState() val homeserverName = remember { matrixClient.userIdServerName() } @@ -105,6 +116,18 @@ class ConfigureRoomPresenter( } } + var spaces by remember { mutableStateOf>(persistentListOf()) } + LaunchedEffect(canAddRoomToSpace) { + spaces = if (canAddRoomToSpace) { + matrixClient.spaceService.editableSpaces().getOrElse { emptyList() }.toImmutableList() + } else { + persistentListOf() + } + + val parentSpace = spaces.find { it.roomId == initialParentSpaceId } + parentSpace?.let { dataStore.setParentSpace(it) } + } + LaunchedEffect(cameraPermissionState.permissionGranted) { if (cameraPermissionState.permissionGranted && pendingPermissionRequest) { pendingPermissionRequest = false @@ -115,7 +138,7 @@ class ConfigureRoomPresenter( RoomAddressValidityEffect( client = matrixClient, roomAliasHelper = roomAliasHelper, - newRoomAddress = createRoomConfig.roomVisibility.roomAddress().getOrDefault(""), + newRoomAddress = createRoomConfig.visibilityState.roomAddress().getOrDefault(""), knownRoomAddress = null, ) { newRoomAddressValidity -> roomAddressValidity.value = newRoomAddressValidity @@ -124,6 +147,27 @@ class ConfigureRoomPresenter( val localCoroutineScope = rememberCoroutineScope() val createRoomAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + // Calculate available join rules based: + // 1. If we are creating a space. + // 2. If it has a parent space. + // 3. If knocking is enabled. + val parentSpace = createRoomConfig.parentSpace + val availableJoinRules = remember(createRoomConfig.parentSpace, isSpace, isKnockFeatureEnabled) { + when { + isSpace && parentSpace != null -> TODO("Adding a space to a parent space is not supported yet! How did you get here?") + parentSpace == null || parentSpace.joinRule == JoinRule.Public -> listOfNotNull( + JoinRuleItem.PublicVisibility.Public, + JoinRuleItem.PublicVisibility.AskToJoin.takeIf { !isSpace && isKnockFeatureEnabled }, + JoinRuleItem.Private, + ).toImmutableList() + else -> listOfNotNull( + JoinRuleItem.PublicVisibility.Restricted(parentSpace.roomId), + JoinRuleItem.PublicVisibility.AskToJoinRestricted(parentSpace.roomId).takeIf { !isSpace && isKnockFeatureEnabled }, + JoinRuleItem.Private, + ).toImmutableList() + } + } + fun createRoom(config: CreateRoomConfig) { createRoomAction.value = AsyncAction.Uninitialized localCoroutineScope.createRoom(config, createRoomAction) @@ -133,8 +177,7 @@ class ConfigureRoomPresenter( when (event) { is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name) is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic) - is ConfigureRoomEvents.RoomVisibilityChanged -> dataStore.setRoomVisibility(event.visibilityItem) - is ConfigureRoomEvents.RoomAccessChanged -> dataStore.setRoomAccess(event.roomAccess) + is ConfigureRoomEvents.JoinRuleChanged -> dataStore.setJoinRule(event.joinRuleItem) is ConfigureRoomEvents.RoomAddressChanged -> dataStore.setRoomAddress(event.roomAddress) is ConfigureRoomEvents.CreateRoom -> createRoom(createRoomConfig) is ConfigureRoomEvents.HandleAvatarAction -> { @@ -149,19 +192,24 @@ class ConfigureRoomPresenter( AvatarAction.Remove -> dataStore.setAvatarUri(uri = null) } } - - ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = AsyncAction.Uninitialized + is ConfigureRoomEvents.SetParentSpace -> { + dataStore.setParentSpace(event.space) + } + ConfigureRoomEvents.CancelCreateRoom -> { + createRoomAction.value = AsyncAction.Uninitialized + } } } return ConfigureRoomState( - isKnockFeatureEnabled = isKnockFeatureEnabled, config = createRoomConfig, avatarActions = avatarActions, createRoomAction = createRoomAction.value, cameraPermissionState = cameraPermissionState, homeserverName = homeserverName, roomAddressValidity = roomAddressValidity.value, + availableJoinRules = availableJoinRules, + spaces = spaces, eventSink = ::handleEvent, ) } @@ -172,25 +220,27 @@ class ConfigureRoomPresenter( ) = launch { suspend { val avatarUrl = config.avatarUri?.let { uploadAvatar(it.toUri()) } - val params = if (config.roomVisibility is RoomVisibilityState.Public) { + val params = if (config.visibilityState is RoomVisibilityState.Public) { CreateRoomParameters( name = config.roomName, topic = config.topic, isEncrypted = false, isDirect = false, visibility = RoomVisibility.Public, - joinRuleOverride = config.roomVisibility.roomAccess.toJoinRule(), + joinRuleOverride = config.visibilityState.joinRuleItem.toJoinRule() + // No need to specify the public join rule override, since the preset is already PUBLIC_CHAT + .takeIf { it != JoinRule.Public }, preset = RoomPreset.PUBLIC_CHAT, invite = config.invites.map { it.userId }, avatar = avatarUrl, - roomAliasName = config.roomVisibility.roomAddress(), + roomAliasName = config.visibilityState.roomAddress(), isSpace = isSpace, ) } else { CreateRoomParameters( name = config.roomName, topic = config.topic, - isEncrypted = config.roomVisibility is RoomVisibilityState.Private, + isEncrypted = config.visibilityState is RoomVisibilityState.Private, isDirect = false, visibility = RoomVisibility.Private, historyVisibilityOverride = RoomHistoryVisibility.Invited, @@ -200,7 +250,7 @@ class ConfigureRoomPresenter( isSpace = isSpace, ) } - matrixClient.createRoom(params) + val roomId = matrixClient.createRoom(params) .onFailure { failure -> Timber.e(failure, "Failed to create room") } @@ -209,7 +259,22 @@ class ConfigureRoomPresenter( analyticsService.capture(CreatedRoom(isDM = false)) } .getOrThrow() + + // Add the newly created room to the parent space too + if (config.parentSpace != null) { + Timber.d("Adding room $roomId to parent space ${config.parentSpace.roomId}") + // Wait until we receive the power level info for the room, as it's needed to check if it can be added to a space + // TODO create some SDK function that does this instead? + withTimeoutOrNull(30.seconds) { + matrixClient.getRoomInfoFlow(roomId).first { it.getOrNull()?.roomPowerLevels != null } + } ?: error("Did not receive created room power levels for room $roomId, needed for adding it to a space") + + matrixClient.spaceService.addChildToSpace(spaceId = config.parentSpace.roomId, childId = roomId).getOrThrow() + } + + roomId }.runCatchingUpdatingState(createRoomAction) + .onFailure { Timber.e(it, "Could not create room or add it to parent space ${config.parentSpace?.roomId}") } } private suspend fun uploadAvatar(avatarUri: Uri): String { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt index 127aa20ccb..9e88a7af79 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt @@ -10,21 +10,23 @@ package io.element.android.features.createroom.impl.configureroom import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.libraries.permissions.api.PermissionsState import kotlinx.collections.immutable.ImmutableList data class ConfigureRoomState( - val isKnockFeatureEnabled: Boolean, val config: CreateRoomConfig, val avatarActions: ImmutableList, val createRoomAction: AsyncAction, val cameraPermissionState: PermissionsState, val roomAddressValidity: RoomAddressValidity, val homeserverName: String, + val availableJoinRules: ImmutableList, + val spaces: ImmutableList, val eventSink: (ConfigureRoomEvents) -> Unit ) { val isValid: Boolean = config.roomName?.isNotEmpty() == true && - (config.roomVisibility is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid) + (config.visibilityState is RoomVisibilityState.Private || roomAddressValidity == RoomAddressValidity.Valid) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt index dc78ed7aab..68a1f4b43b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt @@ -10,12 +10,15 @@ package io.element.android.features.createroom.impl.configureroom import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.libraries.permissions.api.PermissionsState import io.element.android.libraries.permissions.api.aPermissionsState +import io.element.android.libraries.previewutils.room.aSpaceRoom import kotlinx.collections.immutable.toImmutableList open class ConfigureRoomStateProvider : PreviewParameterProvider { @@ -28,9 +31,9 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider = if (config.parentSpace != null) { + listOfNotNull( + JoinRuleItem.PublicVisibility.Restricted(config.parentSpace.roomId), + JoinRuleItem.PublicVisibility.AskToJoinRestricted(config.parentSpace.roomId).takeIf { isKnockFeatureEnabled }, + JoinRuleItem.Private, + ) + } else { + listOfNotNull( + JoinRuleItem.PublicVisibility.Public, + JoinRuleItem.PublicVisibility.AskToJoin.takeIf { isKnockFeatureEnabled }, + JoinRuleItem.Private, + ) + }, + spaces: List = emptyList(), eventSink: (ConfigureRoomEvents) -> Unit = { }, ) = ConfigureRoomState( config = config, - isKnockFeatureEnabled = isKnockFeatureEnabled, avatarActions = avatarActions.toImmutableList(), createRoomAction = createRoomAction, cameraPermissionState = cameraPermissionState, homeserverName = homeserverName, roomAddressValidity = roomAddressValidity, + availableJoinRules = availableVisibilityOptions.toImmutableList(), + spaces = spaces.toImmutableList(), eventSink = eventSink, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt index 2e0cbc6d5d..39849174dd 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.createroom.impl.R import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom @@ -46,7 +47,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.list.ListItemContent -import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight @@ -59,12 +59,14 @@ import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet import io.element.android.libraries.matrix.ui.components.AvatarPickerState import io.element.android.libraries.matrix.ui.components.AvatarPickerView import io.element.android.libraries.matrix.ui.room.address.RoomAddressField import io.element.android.libraries.permissions.api.PermissionsView import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList import kotlin.jvm.optionals.getOrNull @Composable @@ -119,27 +121,30 @@ fun ConfigureRoomView( onTopicChange = { state.eventSink(ConfigureRoomEvents.TopicChanged(it)) }, ) - RoomVisibilityAndAccessOptions( - selected = when (state.config.roomVisibility) { - is RoomVisibilityState.Private -> RoomVisibilityItem.Private - is RoomVisibilityState.Public -> when (state.config.roomVisibility.roomAccess) { - RoomAccess.Knocking -> RoomVisibilityItem.AskToJoin - RoomAccess.Anyone -> RoomVisibilityItem.Public - } - }, - isKnockingEnabled = state.isKnockFeatureEnabled, + if (!state.config.isSpace && state.spaces.isNotEmpty()) { + SelectParentSpaceOptions( + spaces = state.spaces, + selectedSpace = state.config.parentSpace, + onSelectSpace = { state.eventSink(ConfigureRoomEvents.SetParentSpace(it)) }, + ) + } + + RoomJoinRuleOptions( + options = state.availableJoinRules, + selected = state.config.visibilityState.joinRuleItem, + parentSpace = state.config.parentSpace, onOptionClick = { focusManager.clearFocus() - state.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(it)) + state.eventSink(ConfigureRoomEvents.JoinRuleChanged(it)) }, ) - if (state.config.roomVisibility !is RoomVisibilityState.Private) { + if (state.config.visibilityState !is RoomVisibilityState.Private) { Column { ListSectionHeader(title = stringResource(R.string.screen_create_room_room_address_section_title)) RoomAddressField( modifier = Modifier.padding(horizontal = 16.dp), - address = state.config.roomVisibility.roomAddress().getOrNull().orEmpty(), + address = state.config.visibilityState.roomAddress().getOrNull().orEmpty(), homeserverName = state.homeserverName, addressValidity = state.roomAddressValidity, onAddressChange = { state.eventSink(ConfigureRoomEvents.RoomAddressChanged(it)) }, @@ -264,7 +269,7 @@ private fun RoomTopic( } @Composable -private fun ConfigureRoomOptions( +internal fun ConfigureRoomOptions( title: String, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit, @@ -278,30 +283,29 @@ private fun ConfigureRoomOptions( } @Composable -private fun RoomVisibilityAndAccessOptions( - selected: RoomVisibilityItem, - isKnockingEnabled: Boolean, - onOptionClick: (RoomVisibilityItem) -> Unit, +private fun RoomJoinRuleOptions( + options: ImmutableList, + selected: JoinRuleItem, + onOptionClick: (JoinRuleItem) -> Unit, + parentSpace: SpaceRoom?, modifier: Modifier = Modifier, ) { ConfigureRoomOptions( title = stringResource(R.string.screen_create_room_room_access_section_title), modifier = modifier, ) { - RoomVisibilityItem.entries.forEach { item -> - if (item == RoomVisibilityItem.AskToJoin && !isKnockingEnabled) { - return@forEach - } - + options.forEach { item -> val isSelected = item == selected ListItem( leadingContent = ListItemContent.Custom { RoundedIconAtom( size = RoundedIconAtomSize.Big, - resourceId = when (item) { - RoomVisibilityItem.Public -> CompoundDrawables.ic_compound_public - RoomVisibilityItem.AskToJoin -> CompoundDrawables.ic_compound_user_add - RoomVisibilityItem.Private -> CompoundDrawables.ic_compound_lock + imageVector = when (item) { + JoinRuleItem.PublicVisibility.Public -> CompoundIcons.Public() + is JoinRuleItem.PublicVisibility.Restricted -> CompoundIcons.Space() + JoinRuleItem.PublicVisibility.AskToJoin, + is JoinRuleItem.PublicVisibility.AskToJoinRestricted -> CompoundIcons.UserAdd() + JoinRuleItem.Private -> CompoundIcons.Lock() }, tint = if (isSelected) ElementTheme.colors.iconPrimary else ElementTheme.colors.iconSecondary, backgroundTint = Color.Transparent, @@ -309,18 +313,29 @@ private fun RoomVisibilityAndAccessOptions( }, headlineContent = { val title = when (item) { - RoomVisibilityItem.Public -> stringResource(R.string.screen_create_room_public_option_title) - RoomVisibilityItem.AskToJoin -> stringResource(R.string.screen_create_room_room_access_section_knocking_option_title) - RoomVisibilityItem.Private -> stringResource(R.string.screen_create_room_private_option_title) + JoinRuleItem.PublicVisibility.Public -> stringResource(R.string.screen_create_room_room_access_section_public_option_title) + is JoinRuleItem.PublicVisibility.Restricted -> stringResource(R.string.screen_create_room_room_access_section_restricted_option_title) + JoinRuleItem.PublicVisibility.AskToJoin -> stringResource(R.string.screen_create_room_room_access_section_knocking_option_title) + is JoinRuleItem.PublicVisibility.AskToJoinRestricted -> stringResource( + R.string.screen_create_room_room_access_section_knocking_restricted_option_title + ) + JoinRuleItem.Private -> stringResource(R.string.screen_create_room_room_access_section_private_option_title) } Text(text = title) }, supportingContent = { - // TODO handle description of items in a certain space/org val description = when (item) { - RoomVisibilityItem.Public -> stringResource(R.string.screen_create_room_public_option_short_description) - RoomVisibilityItem.AskToJoin -> stringResource(R.string.screen_create_room_room_access_section_knocking_option_description) - RoomVisibilityItem.Private -> stringResource(R.string.screen_create_room_private_option_description) + JoinRuleItem.PublicVisibility.Public -> stringResource(R.string.screen_create_room_room_access_section_public_option_description) + is JoinRuleItem.PublicVisibility.Restricted -> stringResource( + R.string.screen_create_room_room_access_section_restricted_option_description, + parentSpace?.displayName.orEmpty() + ) + JoinRuleItem.PublicVisibility.AskToJoin -> stringResource(R.string.screen_create_room_room_access_section_knocking_option_description) + is JoinRuleItem.PublicVisibility.AskToJoinRestricted -> stringResource( + R.string.screen_create_room_room_access_section_knocking_restricted_option_description, + parentSpace?.displayName.orEmpty() + ) + JoinRuleItem.Private -> stringResource(R.string.screen_create_room_room_access_section_private_option_description) } Text(text = description) }, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt index 8355047ddf..dc24db1516 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt @@ -8,6 +8,7 @@ package io.element.android.features.createroom.impl.configureroom +import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -18,5 +19,6 @@ data class CreateRoomConfig( val topic: String? = null, val avatarUri: String? = null, val invites: ImmutableList = persistentListOf(), - val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private, + val visibilityState: RoomVisibilityState = RoomVisibilityState.Private(), + val parentSpace: SpaceRoom? = null, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt index 81c9638f0c..d1d4f22322 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt @@ -12,6 +12,7 @@ import android.net.Uri import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper +import io.element.android.libraries.matrix.api.spaces.SpaceRoom import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.getAndUpdate @@ -33,23 +34,20 @@ class CreateRoomConfigStore( fun setRoomName(roomName: String) { createRoomConfigFlow.getAndUpdate { config -> - val newVisibility = when (config.roomVisibility) { - is RoomVisibilityState.Public -> { - val roomAddress = config.roomVisibility.roomAddress - if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) { - val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(roomName) - config.roomVisibility.copy( - roomAddress = RoomAddress.AutoFilled(roomAliasName), - ) - } else { - config.roomVisibility - } + val roomAccessWithNewAddress = if (config.visibilityState is RoomVisibilityState.Public) { + val roomAddress = config.visibilityState.roomAddress + if (roomAddress is RoomAddress.AutoFilled || roomName.isEmpty()) { + val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(roomName) + config.visibilityState.copy(roomAddress = RoomAddress.AutoFilled(roomAliasName)) + } else { + config.visibilityState } - else -> config.roomVisibility + } else { + config.visibilityState } config.copy( roomName = roomName.takeIf { it.isNotEmpty() }, - roomVisibility = newVisibility, + visibilityState = roomAccessWithNewAddress, ) } } @@ -67,16 +65,19 @@ class CreateRoomConfigStore( } } - fun setRoomVisibility(visibility: RoomVisibilityItem) { + /** + * Sets both the room visibility and its access based on the provided join rule. + */ + fun setJoinRule(joinRule: JoinRuleItem) { createRoomConfigFlow.getAndUpdate { config -> config.copy( - roomVisibility = when (visibility) { - RoomVisibilityItem.Private -> RoomVisibilityState.Private - RoomVisibilityItem.Public, RoomVisibilityItem.AskToJoin -> { + visibilityState = when (joinRule) { + JoinRuleItem.Private -> RoomVisibilityState.Private() + is JoinRuleItem.PublicVisibility -> { val roomAliasName = roomAliasHelper.roomAliasNameFromRoomDisplayName(config.roomName.orEmpty()) RoomVisibilityState.Public( roomAddress = RoomAddress.AutoFilled(roomAliasName), - roomAccess = if (visibility == RoomVisibilityItem.AskToJoin) RoomAccess.Knocking else RoomAccess.Anyone, + joinRuleItem = joinRule, ) } } @@ -87,28 +88,12 @@ class CreateRoomConfigStore( fun setRoomAddress(address: String) { createRoomConfigFlow.getAndUpdate { config -> config.copy( - roomVisibility = when (config.roomVisibility) { + visibilityState = when (config.visibilityState) { is RoomVisibilityState.Public -> { val sanitizedAddress = address.lowercase() - config.roomVisibility.copy(roomAddress = RoomAddress.Edited(sanitizedAddress)) + config.visibilityState.copy(roomAddress = RoomAddress.Edited(sanitizedAddress)) } - else -> config.roomVisibility - } - ) - } - } - - fun setRoomAccess(access: RoomAccessItem) { - createRoomConfigFlow.getAndUpdate { config -> - config.copy( - roomVisibility = when (config.roomVisibility) { - is RoomVisibilityState.Public -> { - when (access) { - RoomAccessItem.Anyone -> config.roomVisibility.copy(roomAccess = RoomAccess.Anyone) - RoomAccessItem.AskToJoin -> config.roomVisibility.copy(roomAccess = RoomAccess.Knocking) - } - } - else -> config.roomVisibility + else -> config.visibilityState } ) } @@ -120,6 +105,15 @@ class CreateRoomConfigStore( } } + fun setParentSpace(parentSpace: SpaceRoom?) { + createRoomConfigFlow.getAndUpdate { config -> + config.copy( + parentSpace = parentSpace, + visibilityState = RoomVisibilityState.Private(), + ) + } + } + fun clearCachedData() { cachedAvatarUri = null } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/JoinRuleItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/JoinRuleItem.kt new file mode 100644 index 0000000000..428cf648ec --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/JoinRuleItem.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.configureroom + +import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.join.AllowRule +import io.element.android.libraries.matrix.api.room.join.JoinRule +import kotlinx.collections.immutable.persistentListOf + +/** + * Join rule items to display in UI. + */ +@Immutable +sealed interface JoinRuleItem { + data object Private : JoinRuleItem + + /** + * Those join rule items that represent public visibility of the room/space. + */ + @Immutable + sealed interface PublicVisibility : JoinRuleItem { + data object Public : PublicVisibility + data object AskToJoin : PublicVisibility + data class Restricted(val parentSpaceId: RoomId) : PublicVisibility + data class AskToJoinRestricted(val parentSpaceId: RoomId) : PublicVisibility + } + + /** + * Transforms a [JoinRuleItem] option into a [JoinRule]. + */ + fun toJoinRule(): JoinRule = when (this) { + Private -> JoinRule.Private + PublicVisibility.Public -> JoinRule.Public + PublicVisibility.AskToJoin -> JoinRule.Knock + is PublicVisibility.Restricted -> JoinRule.Restricted(persistentListOf(AllowRule.RoomMembership(parentSpaceId))) + is PublicVisibility.AskToJoinRestricted -> JoinRule.KnockRestricted(persistentListOf(AllowRule.RoomMembership(parentSpaceId))) + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt deleted file mode 100644 index 9d8167cce2..0000000000 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2024, 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.createroom.impl.configureroom - -import io.element.android.libraries.matrix.api.room.join.JoinRule - -enum class RoomAccess { - Anyone, - Knocking -} - -fun RoomAccess.toJoinRule(): JoinRule? { - return when (this) { - RoomAccess.Anyone -> null - RoomAccess.Knocking -> JoinRule.Knock - } -} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt deleted file mode 100644 index 9d140b952c..0000000000 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2024, 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.createroom.impl.configureroom - -enum class RoomAccessItem { - Anyone, - AskToJoin, -} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt deleted file mode 100644 index feff1ee90f..0000000000 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.createroom.impl.configureroom - -enum class RoomVisibilityItem { - Public, - AskToJoin, - Private -} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt index 82af5a5614..7fd8bd888c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt @@ -11,11 +11,12 @@ package io.element.android.features.createroom.impl.configureroom import java.util.Optional sealed interface RoomVisibilityState { - data object Private : RoomVisibilityState + val joinRuleItem: JoinRuleItem + data class Private(override val joinRuleItem: JoinRuleItem.Private = JoinRuleItem.Private) : RoomVisibilityState data class Public( val roomAddress: RoomAddress, - val roomAccess: RoomAccess, + override val joinRuleItem: JoinRuleItem.PublicVisibility, ) : RoomVisibilityState fun roomAddress(): Optional { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/SelectParentSpaceOptions.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/SelectParentSpaceOptions.kt new file mode 100644 index 0000000000..26c36f3e60 --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/SelectParentSpaceOptions.kt @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl.configureroom + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.createroom.impl.R +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListSectionHeader +import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.hide +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.spaces.SpaceRoom +import io.element.android.libraries.previewutils.room.aSpaceRoom +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun SelectParentSpaceOptions( + spaces: ImmutableList, + selectedSpace: SpaceRoom?, + onSelectSpace: (SpaceRoom?) -> Unit, + modifier: Modifier = Modifier, +) { + val coroutineScope = rememberCoroutineScope() + var displaySelectSpaceBottomSheet by remember { mutableStateOf(false) } + ConfigureRoomOptions( + title = stringResource(CommonStrings.common_space), + modifier = modifier + ) { + ListItem( + headlineContent = { + Text( + text = selectedSpace?.displayName + ?: stringResource(R.string.screen_create_room_space_selection_no_space_title), + maxLines = 1 + ) + }, + supportingContent = { + Text( + text = if (selectedSpace != null) { + selectedSpace.canonicalAlias?.value.orEmpty() + } else { + stringResource(R.string.screen_create_room_space_selection_no_space_description) + }, + maxLines = 1 + ) + }, + leadingContent = if (selectedSpace == null) { + ListItemContent.Icon(IconSource.Vector(CompoundIcons.Home())) + } else { + ListItemContent.Custom({ + val avatarData = AvatarData( + id = selectedSpace.roomId.value, + name = selectedSpace.displayName, + url = selectedSpace.avatarUrl, + size = AvatarSize.SelectParentSpace, + ) + Avatar(avatarData = avatarData, avatarType = AvatarType.Space()) + }) + }, + onClick = { displaySelectSpaceBottomSheet = true } + ) + + if (displaySelectSpaceBottomSheet) { + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true, + confirmValueChange = { true }, + ) + ModalBottomSheet( + sheetState = sheetState, + onDismissRequest = { + sheetState.hide(coroutineScope) { + displaySelectSpaceBottomSheet = false + } + } + ) { + SelectParentSpaceBottomSheet( + spaces = spaces, + selectedSpace = selectedSpace, + ) { + sheetState.hide(coroutineScope) { + displaySelectSpaceBottomSheet = false + } + onSelectSpace(it) + } + } + } + } +} + +@Composable +private fun ColumnScope.SelectParentSpaceBottomSheet( + spaces: ImmutableList, + selectedSpace: SpaceRoom?, + onSelectSpace: (SpaceRoom?) -> Unit, +) { + ListSectionHeader( + title = stringResource(R.string.screen_create_room_space_selection_sheet_title), + hasDivider = false + ) + LazyColumn(modifier = Modifier.fillMaxWidth()) { + item { + ListItem( + headlineContent = { + Text( + stringResource(R.string.screen_create_room_space_selection_no_space_title), + maxLines = 1 + ) + }, + supportingContent = { + Text( + stringResource(R.string.screen_create_room_space_selection_no_space_description), + maxLines = 1 + ) + }, + leadingContent = ListItemContent.Icon( + IconSource.Vector(CompoundIcons.Home()) + ), + trailingContent = ListItemContent.RadioButton( + selected = selectedSpace == null + ), + onClick = { onSelectSpace(null) }, + ) + } + for (space in spaces) { + item { + ListItem( + headlineContent = { + Text( + space.displayName, + maxLines = 1 + ) + }, + supportingContent = { + Text( + space.canonicalAlias?.value.orEmpty(), + maxLines = 1 + ) + }, + leadingContent = ListItemContent.Custom({ + val avatarData = + AvatarData( + id = space.roomId.value, + name = space.displayName, + url = space.avatarUrl, + size = AvatarSize.SelectParentSpace, + ) + Avatar( + avatarData = avatarData, + avatarType = AvatarType.Space() + ) + }), + trailingContent = ListItemContent.RadioButton( + selected = selectedSpace == space + ), + onClick = { onSelectSpace(space) }, + ) + } + } + } +} + +@PreviewsDayNight +@Composable +internal fun SelectParentSpaceBottomSheetPreview() = + ElementPreview { + Column { + SelectParentSpaceBottomSheet( + spaces = persistentListOf( + aSpaceRoom( + canonicalAlias = RoomAlias( + "#a-room-alias:example.org" + ) + ) + ), + selectedSpace = null, + ) {} + } + } diff --git a/features/createroom/impl/src/main/res/values-da/translations.xml b/features/createroom/impl/src/main/res/values-da/translations.xml index 13d8b01b62..7e589d217e 100644 --- a/features/createroom/impl/src/main/res/values-da/translations.xml +++ b/features/createroom/impl/src/main/res/values-da/translations.xml @@ -3,15 +3,30 @@ "Nyt rum" "Invitér andre" "Der opstod en fejl ved oprettelsen af rummet" - "Kun inviterede personer kan få adgang til dette rum. Alle meddelelser er ende-til-ende krypteret." + "Gruppen kunne ikke oprettes på grund af en ukendt fejl. Prøv igen senere." + "Tilføj navn…" + "Nyt rum" + "Ny gruppe" + "Kun inviterede personer kan deltage." + "Privat" "Alle kan finde dette rum. Du kan ændre dette når som helst i rummets indstillinger." + "Alle kan deltage." + "Offentlig" "Alle kan bede om at deltage i rummet, men en administrator eller en moderator skal acceptere anmodningen" - "Spørg om at deltage" + "Tillad at man kan anmode om deltagelse" + "Enhver i %1$s kan deltage, men alle andre skal anmode om adgang." + "Anmod om at deltage" + "Kun inviterede brugere kan deltage." + "Privat" "Alle kan deltage i dette rum" - "Enhver" - "Hvis dette rum skal være synligt i det offentlige register, skal du bruge en rum-adresse." - "Rummets adresse" + "Offentlig" + "Alle i %1$s kan deltage." + "Standard" + "Hvem har adgang" + "Hvis dette rum skal være synligt i det offentlige register, skal du bruge en adresse." + "Adresse" "Rummets synlighed" "Emne (valgfrit)" + "Tilføj beskrivelse…" diff --git a/features/createroom/impl/src/main/res/values-de/translations.xml b/features/createroom/impl/src/main/res/values-de/translations.xml index 55d7a0b66e..298cae3a8f 100644 --- a/features/createroom/impl/src/main/res/values-de/translations.xml +++ b/features/createroom/impl/src/main/res/values-de/translations.xml @@ -15,14 +15,20 @@ Du kannst dies jederzeit in den Einstellungen des Chats ändern." "Öffentlicher Chatroom" "Jeder kann den Beitritt zum Chat erbitten, aber ein Admin oder Moderator muss die Anfrage akzeptieren." "Anfrage zum Beitritt zulassen" + "Jeder in %1$s kann beitreten, aber alle anderen müssen den Beitritt anfragen." + "Beitritt anfragen" "Nur eingeladene Personen können beitreten." "Privat" "Jeder darf diesem Chat beitreten." "Jeder" + "Jeder in %1$s kann beitreten." + "Standard" "Wer hat Zugang" "Du benötigst eine Adresse, um diesen Chat im öffentlichen Verzeichnis sichtbar zu machen." "Adresse" " Sichtbarkeit des Chats" + "(kein Space)" + "Space hinzufügen" "Thema (optional)" "Beschreibung hinzufügen…" diff --git a/features/createroom/impl/src/main/res/values-fr/translations.xml b/features/createroom/impl/src/main/res/values-fr/translations.xml index 72b5314893..37e19a4b79 100644 --- a/features/createroom/impl/src/main/res/values-fr/translations.xml +++ b/features/createroom/impl/src/main/res/values-fr/translations.xml @@ -15,14 +15,21 @@ Vous pouvez modifier cela à tout moment dans les paramètres du salon.""Salon public" "Tout le monde peut demander à joindre, mais un administrateur ou un modérateur devra accepter la demande" "Autoriser la demande à joindre" + "Tout membre de %1$s peut joindre, mais les autres doivent demander un accès." + "Demander à joindre" "Seules les personnes invitées peuvent joindre." "Privé" "Tout le monde peut joindre" "Tout le monde" + "Toute membre de %1$s peut joindre le salon." + "Standard" "Qui a accès" "Vous aurez besoin d’une adresse pour qu’il soit visible dans le répertoire public." "Adresse" "Visibilité du salon" + "(pas d’espace)" + "Accueil" + "Ajouter à l’espace" "Sujet (facultatif)" "Ajouter une description…" diff --git a/features/createroom/impl/src/main/res/values-hu/translations.xml b/features/createroom/impl/src/main/res/values-hu/translations.xml index 2e80833b2e..1e7f84cdba 100644 --- a/features/createroom/impl/src/main/res/values-hu/translations.xml +++ b/features/createroom/impl/src/main/res/values-hu/translations.xml @@ -3,16 +3,33 @@ "Új szoba" "Ismerősök meghívása" "Hiba történt a szoba létrehozásakor" - "Csak a meghívottak léphetnek be ebbe a szobába. Az összes üzenet végpontok közti titkosítással van védve." + "A teret ismeretlen hiba miatt nem sikerült létrehozni. Próbálja újra később." + "Név hozzáadása…" + "Új szoba" + "Új tér" + "Csak a meghívottak léphetnek be." + "Privát" "Bárki megtalálhatja ezt a szobát. Ezt bármikor módosíthatja a szobabeállításokban." + "Bárki csatlakozhat." "Nyilvános szoba" - "Bárki kérheti, hogy csatlakozzon a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést" + "Bárki kérheti, hogy csatlakozhasson a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést." "Csatlakozás kérése" - "Bárki csatlakozhat ehhez a szobához" - "Bárki" + "Bárki csatlakozhat innen: %1$s, de mindenki másnak hozzáférést kell kérnie." + "Csatlakozás kérése" + "Csak a meghívottak léphetnek be." + "Privát" + "Bárki csatlakozhat." + "Nyilvános" + "Bárki csatlakozhat innen: %1$s." + "Szokásos" + "Hozzáférésre jogosultak" "Ahhoz, hogy ez a szoba látható legyen a nyilvános szobák címtárában, meg kell adnia a szoba címét." - "Szoba címe" + "Cím" "Szoba láthatósága" + "(nincs tér)" + "Kezdőlap" + "Hozzáadás a térhez" "Téma (nem kötelező)" + "Leírás hozzáadása…" diff --git a/features/createroom/impl/src/main/res/values-nb/translations.xml b/features/createroom/impl/src/main/res/values-nb/translations.xml index e5ad63c84b..dda611e8a7 100644 --- a/features/createroom/impl/src/main/res/values-nb/translations.xml +++ b/features/createroom/impl/src/main/res/values-nb/translations.xml @@ -3,16 +3,17 @@ "Nytt rom" "Inviter folk" "Det oppsto en feil under opprettelsen av rommet" - "Bare inviterte personer har tilgang til dette rommet. Alle meldinger er ende-til-ende-kryptert." + "Bare inviterte personer kan bli med." + "Privat" "Alle kan finne dette rommet. Du kan endre dette når som helst i rominnstillingene." "Offentlig rom" - "Alle kan be om å få bli med i rommet, men en administrator eller moderator må godta forespørselen" + "Alle kan be om å få bli med, men en administrator eller moderator må godta forespørselen." "Be om å bli med" - "Alle kan bli med i dette rommet" + "Alle kan bli med." "Alle" - "For at dette rommet skal være synlig i den offentlige romkatalogen, trenger du en romadresse." - "Romadresse" + "Du trenger en adresse for å gjøre den synlig i den offentlige katalogen." + "Adresse" "Romsynlighet" "Emne (valgfritt)" diff --git a/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml b/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml index 174f42db06..73ef3bc157 100644 --- a/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/createroom/impl/src/main/res/values-pt-rBR/translations.xml @@ -4,6 +4,8 @@ "Convidar pessoas" "Ocorreu um erro ao criar a sala" "O espaço não pôde ser criado por conta de um erro desconhecido. Tente novamente mais tarde." + "Adicione o nome…" + "Nova sala" "Novo espaço" "Apenas pessoas convidadas podem entrar." "Privada" @@ -12,12 +14,22 @@ Você pode mudar isso a qualquer momento nas configurações da sala." "Qualquer um pode entrar." "Publica" "Qualquer um pode pedir para entrar, mas um administrador ou moderador deve aceitar a solicitação" - "Pedir para entrar" + "Permitir pedir para entrar" + "Qualquer um em %1$s pode entrar, mas todos os outros devem pedir acesso." + "Pedir para entrar" + "Apenas pessoas convidadas podem entrar." + "Privada" "Qualquer um pode entrar." "Qualquer pessoa" + "Qualquer um em %1$s pode participar." + "Normal" "Quem tem acesso" - "Para que esta sala fique visível no diretório público de salas, você precisará de um endereço de sala." - "Endereço da sala" + "Você precisará de um endereço para torná-la visível no diretório público." + "Endereço" "Visibilidade da sala" + "(sem espaço)" + "Início" + "Adicionar ao espaço" "Tópico (opcional)" + "Adicione a descrição…" diff --git a/features/createroom/impl/src/main/res/values-pt/translations.xml b/features/createroom/impl/src/main/res/values-pt/translations.xml index 6225f531a5..5849896740 100644 --- a/features/createroom/impl/src/main/res/values-pt/translations.xml +++ b/features/createroom/impl/src/main/res/values-pt/translations.xml @@ -3,16 +3,17 @@ "Nova sala" "Convidar pessoas" "Ocorreu um erro ao criar a sala" - "Apenas as pessoas convidadas podem aceder a esta sala. Todas as mensagens são cifradas ponta-a-ponta." + "Apenas as pessoas convidadas podem entrar." + "Privada" "Qualquer um pode encontrar esta sala. Pode alterar esta opção nas definições da sala." "Sala pública" - "Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou um moderador terá de aceitar o pedido" + "Qualquer pessoa pode pedir para entrar, mas um administrador ou um moderador terá de aceitar o pedido." "Pedir para participar" - "Qualquer pessoa pode entrar nesta sala" - "Qualquer pessoa" - "Para que esta sala seja visível no diretório público de salas, precisas de um endereço de sala." - "Endereço da sala" + "Qualquer pessoa pode entrar." + "Pública" + "Para que esta sala seja visível no diretório público, precisa de ter um endereço." + "Endereço" "Visibilidade da sala" "Descrição (opcional)" diff --git a/features/createroom/impl/src/main/res/values-ru/translations.xml b/features/createroom/impl/src/main/res/values-ru/translations.xml index 4a009eaa10..787449c1e1 100644 --- a/features/createroom/impl/src/main/res/values-ru/translations.xml +++ b/features/createroom/impl/src/main/res/values-ru/translations.xml @@ -3,16 +3,26 @@ "Создать новую комнату" "Пригласить в комнату" "Произошла ошибка при создании комнаты" - "Доступ в эту комнату имеют только приглашенные пользователи. Все сообщения защищены сквозным шифрованием." + "Не удалось создать это пространство из-за неизвестной ошибки. Попробуйте позже." + "Добавьте имя…" + "Новая комната" + "Новое пространство" + "Присоединиться могут только приглашенные." + "Частный" "Любой желающий может найти эту комнату. Вы можете изменить это в любое время в настройках комнаты." + "Присоединиться может любой." "Общедоступная комната" "Любой желающий может подать заявку на присоединение к комнате, но администратор или модератор должен будет принять запрос." - "Попросить присоединиться" - "Любой желающий может присоединиться к этой комнате" - "Любой" - "Чтобы эта комната была видна в каталоге общедоступных, вам необходим ее адрес" - "Адрес комнаты" + "Разрешить запрос на присоединение" + "Присоединиться могут только приглашенные." + "Частный" + "Присоединиться может любой желающий." + "Публичный" + "Кто имеет доступ" + "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." + "Адрес" "Видимость комнаты" "Тема (необязательно)" + "Добавить описание…" diff --git a/features/createroom/impl/src/main/res/values-sk/translations.xml b/features/createroom/impl/src/main/res/values-sk/translations.xml index 555571fba9..f6c53bda17 100644 --- a/features/createroom/impl/src/main/res/values-sk/translations.xml +++ b/features/createroom/impl/src/main/res/values-sk/translations.xml @@ -3,16 +3,33 @@ "Nová miestnosť" "Pozvať ľudí" "Pri vytváraní miestnosti došlo k chybe" - "Do tejto miestnosti majú prístup iba pozvaní ľudia. Všetky správy sú end-to-end šifrované." + "Priestor sa nepodarilo vytvoriť z dôvodu neznámej chyby. Skúste to znova neskôr." + "Pridať názov…" + "Nová miestnosť" + "Nový priestor" + "Pripojiť sa môžu iba pozvaní ľudia." + "Súkromná" "Túto miestnosť môže nájsť ktokoľvek. Môžete to kedykoľvek zmeniť v nastaveniach miestnosti." + "Pripojiť sa môže ktokoľvek." "Verejná miestnosť" - "Ktokoľvek môže požiadať o pripojenie sa k miestnosti, ale administrátor alebo moderátor bude musieť žiadosť schváliť" - "Požiadať o pripojenie" - "Do tejto miestnosti sa môže pripojiť ktokoľvek" + "O pripojenie sa môže požiadať ktokoľvek, ale žiadosť musí schváliť správca alebo moderátor." + "Povoliť požiadať o vstup" + "Ktokoľvek v %1$s sa môžu pripojiť, ale všetci ostatní musia požiadať o prístup." + "Požiadať o pripojenie" + "Pripojiť sa môžu iba pozvaní ľudia." + "Súkromná" + "Pripojiť sa môže ktokoľvek." "Ktokoľvek" + "Ktokoľvek v %1$s sa môže pripojiť." + "Štandardná" + "Kto má prístup" "Aby bola táto miestnosť viditeľná v adresári verejných miestností, budete potrebovať adresu miestnosti." - "Adresa miestnosti" + "Adresa" "Viditeľnosť miestnosti" + "(žiadny priestor)" + "Domov" + "Pridať do priestoru" "Téma (voliteľné)" + "Pridať popis…" diff --git a/features/createroom/impl/src/main/res/values/localazy.xml b/features/createroom/impl/src/main/res/values/localazy.xml index 3ec2d9d6f3..b2a0f1d5be 100644 --- a/features/createroom/impl/src/main/res/values/localazy.xml +++ b/features/createroom/impl/src/main/res/values/localazy.xml @@ -7,6 +7,8 @@ "Add name…" "New room" "New space" + "(no space)" + "Home" "Only people invited can join." "Private" "Anyone can find this room. @@ -15,14 +17,21 @@ You can change this anytime in room settings." "Public" "Anyone can ask to join but an administrator or a moderator must accept the request." "Allow ask to join" + "Anyone in %1$s can join but everyone else must request access." + "Ask to join" "Only people invited can join." "Private" "Anyone can join." "Public" + "Anyone in %1$s can join." + "Standard" "Who has access" "You’ll need an address in order to make it visible in the public directory." "Address" "Room visibility" + "(no space)" + "Home" + "Add to space" "Topic (optional)" "Add description…" diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/startchat/impl/configureroom/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt similarity index 68% rename from features/createroom/impl/src/test/kotlin/io/element/android/features/startchat/impl/configureroom/ConfigureRoomPresenterTest.kt rename to features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt index e82d8c3912..d3150ea517 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/startchat/impl/configureroom/ConfigureRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt @@ -1,12 +1,11 @@ /* - * Copyright (c) 2025 Element Creations Ltd. - * Copyright 2023-2025 New Vector Ltd. + * Copyright (c) 2026 Element Creations Ltd. * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.startchat.impl.configureroom +package io.element.android.features.createroom.impl import android.net.Uri import androidx.core.net.toUri @@ -18,24 +17,28 @@ import io.element.android.features.createroom.impl.configureroom.ConfigureRoomPr import io.element.android.features.createroom.impl.configureroom.ConfigureRoomState import io.element.android.features.createroom.impl.configureroom.CreateRoomConfig import io.element.android.features.createroom.impl.configureroom.CreateRoomConfigStore -import io.element.android.features.createroom.impl.configureroom.RoomAccess +import io.element.android.features.createroom.impl.configureroom.JoinRuleItem import io.element.android.features.createroom.impl.configureroom.RoomAddress -import io.element.android.features.createroom.impl.configureroom.RoomVisibilityItem import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper +import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.libraries.mediapickers.api.PickerProvider @@ -47,12 +50,17 @@ import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory +import io.element.android.libraries.previewutils.room.aSpaceRoom import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.test import io.mockk.mockk +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -81,7 +89,7 @@ class ConfigureRoomPresenterTest { assertThat(initialState.config.topic).isNull() assertThat(initialState.config.invites).isEmpty() assertThat(initialState.config.avatarUri).isNull() - assertThat(initialState.config.roomVisibility).isEqualTo(RoomVisibilityState.Private) + assertThat(initialState.config.visibilityState).isEqualTo(RoomVisibilityState.Private()) assertThat(initialState.createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) assertThat(initialState.homeserverName).isEqualTo("matrix.org") } @@ -174,12 +182,12 @@ class ConfigureRoomPresenterTest { assertThat(newState.config).isEqualTo(expectedConfig) // Room privacy - newState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public)) + newState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) newState = awaitItem() expectedConfig = expectedConfig.copy( - roomVisibility = RoomVisibilityState.Public( + visibilityState = RoomVisibilityState.Public( roomAddress = RoomAddress.AutoFilled(roomAliasHelper.roomAliasNameFromRoomDisplayName(expectedConfig.roomName ?: "")), - roomAccess = RoomAccess.Anyone, + joinRuleItem = JoinRuleItem.PublicVisibility.Public, ) ) assertThat(newState.config).isEqualTo(expectedConfig) @@ -206,6 +214,109 @@ class ConfigureRoomPresenterTest { } } + @Test + fun `present - when creating a room in a space if the room doesn't receive the power levels value it can't be added to the space`() = runTest { + val addChildToSpaceResult = lambdaRecorder> { _, _ -> Result.success(Unit) } + val spaceService = FakeSpaceService( + addChildToSpaceResult = addChildToSpaceResult, + ) + val roomInfoFlow = MutableStateFlow>(Optional.empty()) + val getRoomInfoFlowLambda = lambdaRecorder>> { roomInfoFlow } + val matrixClient = createMatrixClient(spaceService = spaceService).apply { + this.getRoomInfoFlowLambda = getRoomInfoFlowLambda + } + val presenter = createConfigureRoomPresenter( + matrixClient = matrixClient + ) + presenter.test { + val initialState = initialState() + val createRoomResult = Result.success(RoomId("!createRoomResult:domain")) + + matrixClient.givenCreateRoomResult(createRoomResult) + + val parentSpace = aSpaceRoom() + initialState.eventSink(ConfigureRoomEvents.SetParentSpace(parentSpace)) + assertThat(awaitItem().config.parentSpace).isEqualTo(parentSpace) + + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.AskToJoin)) + assertThat(awaitItem().config.visibilityState.joinRuleItem).isEqualTo(JoinRuleItem.PublicVisibility.AskToJoin) + + initialState.eventSink(ConfigureRoomEvents.CreateRoom) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) + val stateAfterCreateRoom = awaitItem() + + // getRoomInfoFlow is called, but it contains no updates + getRoomInfoFlowLambda.assertions().isCalledOnce() + // So adding the child room to the parent space is never done + addChildToSpaceResult.assertions().isNeverCalled() + + // And the operation fails + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) + } + } + + @Test + fun `present - creating a room and adding it into a parent space works when all the data is available`() = runTest { + val addChildToSpaceResult = lambdaRecorder> { _, _ -> Result.success(Unit) } + val spaceService = FakeSpaceService( + addChildToSpaceResult = addChildToSpaceResult, + ) + val roomInfoFlow = MutableStateFlow>(Optional.empty()) + val getRoomInfoFlowLambda = lambdaRecorder>> { roomInfoFlow } + val matrixClient = createMatrixClient(spaceService = spaceService).apply { + this.getRoomInfoFlowLambda = getRoomInfoFlowLambda + } + val presenter = createConfigureRoomPresenter( + matrixClient = matrixClient + ) + presenter.test { + val initialState = initialState() + val createRoomResult = Result.success(RoomId("!createRoomResult:domain")) + + matrixClient.givenCreateRoomResult(createRoomResult) + + val parentSpace = aSpaceRoom() + initialState.eventSink(ConfigureRoomEvents.SetParentSpace(parentSpace)) + assertThat(awaitItem().config.parentSpace).isEqualTo(parentSpace) + + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.AskToJoin)) + assertThat(awaitItem().config.visibilityState.joinRuleItem).isEqualTo(JoinRuleItem.PublicVisibility.AskToJoin) + + initialState.eventSink(ConfigureRoomEvents.CreateRoom) + + // We immediately receive the room power levels info needed for adding the child to a space + val powerLevels = RoomPowerLevels( + RoomPowerLevelsValues( + ban = 0, + invite = 0, + kick = 0, + eventsDefault = 0, + stateDefault = 0, + redactEvents = 0, + roomName = 0, + roomAvatar = 0, + roomTopic = 0, + spaceChild = 0 + ), + users = persistentMapOf(), + ) + roomInfoFlow.value = Optional.of(aRoomInfo(roomPowerLevels = powerLevels)) + + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) + + val stateAfterCreateRoom = awaitItem() + + // The room info flow was read + getRoomInfoFlowLambda.assertions().isCalledOnce() + // Since it contained the power levels, the operation continued + addChildToSpaceResult.assertions().isCalledOnce() + + // And the child room was created and then added to the parent space + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Success::class.java) + assertThat(stateAfterCreateRoom.createRoomAction.dataOrNull()).isEqualTo(createRoomResult.getOrNull()) + } + } + @Test fun `present - record analytics when creating room`() = runTest { val matrixClient = createMatrixClient() @@ -311,7 +422,7 @@ class ConfigureRoomPresenterTest { ) presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public)) + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) skipItems(1) initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("invalid address")) skipItems(1) @@ -331,7 +442,7 @@ class ConfigureRoomPresenterTest { ) presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public)) + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) skipItems(1) initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address")) skipItems(1) @@ -351,7 +462,7 @@ class ConfigureRoomPresenterTest { ) presenter.test { val initialState = initialState() - initialState.eventSink(ConfigureRoomEvents.RoomVisibilityChanged(RoomVisibilityItem.Public)) + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) skipItems(1) initialState.eventSink(ConfigureRoomEvents.RoomAddressChanged("address")) skipItems(1) @@ -362,12 +473,51 @@ class ConfigureRoomPresenterTest { } } + @Test + fun `present - when a space is selected, the selected join rule is reset to private`() = runTest { + val presenter = createConfigureRoomPresenter() + presenter.test { + val initialState = initialState() + + // First change the join rule to public + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + assertThat(awaitItem().config.visibilityState).isInstanceOf(RoomVisibilityState.Public::class.java) + + // Then check changing the parent space resets it to private + initialState.eventSink(ConfigureRoomEvents.SetParentSpace(aSpaceRoom())) + assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private()) + + // If we change the join rule back to public + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PublicVisibility.Public)) + assertThat(awaitItem().config.visibilityState).isInstanceOf(RoomVisibilityState.Public::class.java) + + // Then remove the parent space, it'll be private again + initialState.eventSink(ConfigureRoomEvents.SetParentSpace(null)) + assertThat(awaitItem().config.visibilityState).isEqualTo(RoomVisibilityState.Private()) + } + } + + @Test + fun `present - setting a parent space for a space currently throws an error`() = runTest { + val presenter = createConfigureRoomPresenter(isSpace = true) + presenter.test { + val initialState = initialState() + + initialState.eventSink(ConfigureRoomEvents.SetParentSpace(aSpaceRoom())) + + assertThat(awaitError()) + } + } + private suspend fun TurbineTestContext.initialState(): ConfigureRoomState { skipItems(1) return awaitItem() } - private fun createMatrixClient(isAliasAvailable: Boolean = true) = FakeMatrixClient( + private fun createMatrixClient( + isAliasAvailable: Boolean = true, + spaceService: FakeSpaceService = FakeSpaceService(), + ) = FakeMatrixClient( userIdServerNameLambda = { "matrix.org" }, resolveRoomAliasResult = { val resolvedRoomAlias = if (isAliasAvailable) { @@ -376,11 +526,13 @@ class ConfigureRoomPresenterTest { Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList())) } Result.success(resolvedRoomAlias) - } + }, + spaceService = spaceService, ) private fun createConfigureRoomPresenter( isSpace: Boolean = false, + initialParenSpaceId: RoomId? = null, roomAliasHelper: RoomAliasHelper = FakeRoomAliasHelper(), dataStore: CreateRoomConfigStore = CreateRoomConfigStore(roomAliasHelper), matrixClient: MatrixClient = createMatrixClient(), @@ -392,6 +544,7 @@ class ConfigureRoomPresenterTest { mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), ) = ConfigureRoomPresenter( isSpace = isSpace, + initialParentSpaceId = initialParenSpaceId, dataStore = dataStore, matrixClient = matrixClient, mediaPickerProvider = pickerProvider, diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt index 5b7a6c1142..13f02e381a 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt @@ -14,6 +14,7 @@ import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode import org.junit.Rule @@ -36,15 +37,16 @@ class DefaultCreateRoomEntryPointTest { plugins = plugins, ) } + val buildContext = BuildContext.root(null) + val callback = object : CreateRoomEntryPoint.Callback { override fun onRoomCreated(roomId: RoomId) = lambdaError() } - val result = entryPoint.createNode( - isSpace = false, - parentNode = parentNode, - buildContext = BuildContext.root(null), - callback = callback, - ) + val result = entryPoint + .builder(parentNode, buildContext, callback) + .setIsSpace(true) + .setParentSpace(A_ROOM_ID) + .build() assertThat(result.plugins).contains(callback) } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/JoinRuleItemTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/JoinRuleItemTest.kt new file mode 100644 index 0000000000..1d6083e050 --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/JoinRuleItemTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.impl + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.createroom.impl.configureroom.JoinRuleItem +import io.element.android.libraries.matrix.api.room.join.AllowRule +import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.test.A_ROOM_ID +import kotlinx.collections.immutable.persistentListOf +import org.junit.Test + +class JoinRuleItemTest { + @Test + fun `toJoinRule works as expected`() { + assertThat(JoinRuleItem.Private.toJoinRule()).isEqualTo(JoinRule.Private) + assertThat(JoinRuleItem.PublicVisibility.Public.toJoinRule()).isEqualTo(JoinRule.Public) + assertThat(JoinRuleItem.PublicVisibility.AskToJoin.toJoinRule()).isEqualTo(JoinRule.Knock) + assertThat(JoinRuleItem.PublicVisibility.Restricted(A_ROOM_ID).toJoinRule()) + .isEqualTo(JoinRule.Restricted(persistentListOf(AllowRule.RoomMembership(A_ROOM_ID)))) + assertThat(JoinRuleItem.PublicVisibility.AskToJoinRestricted(A_ROOM_ID).toJoinRule()) + .isEqualTo(JoinRule.KnockRestricted(persistentListOf(AllowRule.RoomMembership(A_ROOM_ID)))) + } +} diff --git a/features/createroom/test/build.gradle.kts b/features/createroom/test/build.gradle.kts index 98aeac8a4c..afc98bdc0d 100644 --- a/features/createroom/test/build.gradle.kts +++ b/features/createroom/test/build.gradle.kts @@ -16,5 +16,6 @@ android { dependencies { implementation(projects.features.createroom.api) implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) implementation(projects.tests.testutils) } diff --git a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/api/FakeCreateRoomEntryPoint.kt b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/api/FakeCreateRoomEntryPoint.kt index bbeb69c26b..a28e47e5a0 100644 --- a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/api/FakeCreateRoomEntryPoint.kt +++ b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/api/FakeCreateRoomEntryPoint.kt @@ -10,13 +10,19 @@ package io.element.android.features.createroom.api import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.tests.testutils.lambda.lambdaError class FakeCreateRoomEntryPoint : CreateRoomEntryPoint { - override fun createNode( - isSpace: Boolean, + class Builder : CreateRoomEntryPoint.Builder { + override fun setIsSpace(isSpace: Boolean): Builder = this + override fun setParentSpace(parentSpaceId: RoomId): Builder = this + override fun build(): Node = lambdaError() + } + + override fun builder( parentNode: Node, buildContext: BuildContext, callback: CreateRoomEntryPoint.Callback, - ): Node = lambdaError() + ): Builder = lambdaError() } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt index 3f223135c1..e03b40e840 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt @@ -94,9 +94,9 @@ class HomePresenter( } } - LaunchedEffect(homeSpacesState.spaceRooms.isEmpty()) { - // If the last space is left, ensure that the Chat view is rendered. - if (homeSpacesState.spaceRooms.isEmpty()) { + LaunchedEffect(homeSpacesState.canCreateSpaces, homeSpacesState.spaceRooms.isEmpty()) { + // If the flag to create spaces is disabled and the last space is left, ensure that the Chat view is rendered. + if (!homeSpacesState.canCreateSpaces && homeSpacesState.spaceRooms.isEmpty()) { currentHomeNavigationBarItemOrdinal = HomeNavigationBarItem.Chats.ordinal } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt index 474fb6d5ba..1850d2d4cc 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt @@ -33,5 +33,5 @@ data class HomeState( ) { val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters - val showNavigationBar = homeSpacesState.spaceRooms.isNotEmpty() + val showNavigationBar = homeSpacesState.canCreateSpaces || homeSpacesState.spaceRooms.isNotEmpty() } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt index f55183feb0..52745108f6 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt @@ -268,7 +268,10 @@ private fun HomeScaffold( lazyListState = spacesLazyListState, onSpaceClick = { spaceId -> onRoomClick(spaceId) - } + }, + onCreateSpaceClick = onCreateSpaceClick, + // TODO use actual callbacks for this + onExploreClick = {}, ) } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt index 44f53a2663..8496a425d2 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt @@ -8,7 +8,9 @@ package io.element.android.features.home.impl.search -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import io.element.android.features.home.impl.datasource.RoomListRoomSummaryFactory import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -18,6 +20,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn @@ -25,16 +28,23 @@ import kotlinx.coroutines.flow.map private const val PAGE_SIZE = 30 -@Inject +@AssistedInject class RoomListSearchDataSource( + @Assisted coroutineScope: CoroutineScope, roomListService: RoomListService, coroutineDispatchers: CoroutineDispatchers, private val roomSummaryFactory: RoomListRoomSummaryFactory, ) { + @AssistedFactory + interface Factory { + fun create(coroutineScope: CoroutineScope): RoomListSearchDataSource + } + private val roomList = roomListService.createRoomList( pageSize = PAGE_SIZE, initialFilter = RoomListFilter.None, source = RoomList.Source.All, + coroutineScope = coroutineScope ) val roomSummaries: Flow> = roomList.filteredSummaries diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt index 49047ffaed..05aa88b7a4 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter @@ -23,7 +24,7 @@ import kotlinx.collections.immutable.persistentListOf @Inject class RoomListSearchPresenter( - private val dataSource: RoomListSearchDataSource, + private val dataSourceFactory: RoomListSearchDataSource.Factory, ) : Presenter { @Composable override fun present(): RoomListSearchState { @@ -33,6 +34,9 @@ class RoomListSearchPresenter( } val searchQuery = rememberTextFieldState() + val coroutineScope = rememberCoroutineScope() + val dataSource = remember { dataSourceFactory.create(coroutineScope) } + LaunchedEffect(isSearchActive) { dataSource.setIsActive(isSearchActive) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt index 00129235fe..d9e6aaa4d3 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt @@ -53,6 +53,8 @@ class HomeSpacesPresenter( seenSpaceInvites = seenSpaceInvites, hideInvitesAvatar = hideInvitesAvatar, canCreateSpaces = canCreateSpaces, + // TODO enable once we can link to the screen to explore public spaces + canExploreSpaces = false, eventSink = ::handleEvent, ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt index 9bcf7131c8..84b2dc7f52 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt @@ -19,6 +19,7 @@ data class HomeSpacesState( val seenSpaceInvites: ImmutableSet, val hideInvitesAvatar: Boolean, val canCreateSpaces: Boolean, + val canExploreSpaces: Boolean, val eventSink: (HomeSpacesEvents) -> Unit, ) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt index c1a32a1f34..a65f29cc2f 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt @@ -37,6 +37,11 @@ open class HomeSpacesStateProvider : PreviewParameterProvider { spaceRooms = aListOfSpaceRooms(), canCreateSpaces = false, ), + aHomeSpacesState( + space = CurrentSpace.Root, + spaceRooms = emptyList(), + canCreateSpaces = true, + ), ) } @@ -46,6 +51,7 @@ internal fun aHomeSpacesState( seenSpaceInvites: Set = emptySet(), hideInvitesAvatar: Boolean = false, canCreateSpaces: Boolean = true, + canExploreSpaces: Boolean = true, eventSink: (HomeSpacesEvents) -> Unit = {}, ) = HomeSpacesState( space = space, @@ -53,6 +59,7 @@ internal fun aHomeSpacesState( seenSpaceInvites = seenSpaceInvites.toImmutableSet(), hideInvitesAvatar = hideInvitesAvatar, canCreateSpaces = canCreateSpaces, + canExploreSpaces = canExploreSpaces, eventSink = eventSink, ) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt index 2505cf831d..7d7deab688 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt @@ -8,23 +8,40 @@ package io.element.android.features.home.impl.spaces +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule +import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.HorizontalDivider +import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView import io.element.android.libraries.matrix.ui.components.SpaceHeaderView import io.element.android.libraries.matrix.ui.components.SpaceRoomItemView import io.element.android.libraries.matrix.ui.model.getAvatarData +import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.toImmutableList @Composable @@ -32,56 +49,119 @@ fun HomeSpacesView( state: HomeSpacesState, lazyListState: LazyListState, onSpaceClick: (RoomId) -> Unit, + onCreateSpaceClick: () -> Unit, + onExploreClick: () -> Unit, modifier: Modifier = Modifier, ) { - LazyColumn( - modifier = modifier, - state = lazyListState - ) { - val space = state.space - when (space) { - CurrentSpace.Root -> { - item { - SpaceHeaderRootView(numberOfSpaces = state.spaceRooms.size) + if (state.canCreateSpaces && state.spaceRooms.isEmpty()) { + EmptySpaceHomeView( + modifier = modifier, + onCreateSpaceClick = onCreateSpaceClick, + onExploreClick = onExploreClick, + canExploreSpaces = state.canExploreSpaces, + ) + } else { + LazyColumn( + modifier = modifier, + state = lazyListState + ) { + val space = state.space + when (space) { + CurrentSpace.Root -> { + item { + SpaceHeaderRootView(numberOfSpaces = state.spaceRooms.size) + } + } + is CurrentSpace.Space -> { + item { + SpaceHeaderView( + avatarData = space.spaceRoom.getAvatarData(AvatarSize.SpaceHeader), + name = space.spaceRoom.displayName, + topic = space.spaceRoom.topic, + visibility = space.spaceRoom.visibility, + heroes = space.spaceRoom.heroes.toImmutableList(), + numberOfMembers = space.spaceRoom.numJoinedMembers, + ) + } } } - is CurrentSpace.Space -> item { - SpaceHeaderView( - avatarData = space.spaceRoom.getAvatarData(AvatarSize.SpaceHeader), - name = space.spaceRoom.displayName, - topic = space.spaceRoom.topic, - visibility = space.spaceRoom.visibility, - heroes = space.spaceRoom.heroes.toImmutableList(), - numberOfMembers = space.spaceRoom.numJoinedMembers, - ) - } - } - item { - HorizontalDivider() - } - itemsIndexed( - items = state.spaceRooms, - key = { _, spaceRoom -> spaceRoom.roomId } - ) { index, spaceRoom -> - val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED - SpaceRoomItemView( - spaceRoom = spaceRoom, - showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, - hideAvatars = isInvitation && state.hideInvitesAvatar, - onClick = { - onSpaceClick(spaceRoom.roomId) - }, - onLongClick = { - // TODO - }, - ) - if (index != state.spaceRooms.lastIndex) { + + item { HorizontalDivider() } + + itemsIndexed( + items = state.spaceRooms, + key = { _, spaceRoom -> spaceRoom.roomId } + ) { index, spaceRoom -> + val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED + SpaceRoomItemView( + spaceRoom = spaceRoom, + showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, + hideAvatars = isInvitation && state.hideInvitesAvatar, + onClick = { + onSpaceClick(spaceRoom.roomId) + }, + onLongClick = { + // TODO + }, + ) + if (index != state.spaceRooms.lastIndex) { + HorizontalDivider() + } + } } } } +@Composable +private fun EmptySpaceHomeView( + onCreateSpaceClick: () -> Unit, + onExploreClick: () -> Unit, + canExploreSpaces: Boolean, + modifier: Modifier = Modifier, +) { + HeaderFooterPage( + modifier = modifier, + topBar = { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 32.dp, bottom = 16.dp, start = 40.dp, end = 40.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + BigIcon( + style = BigIcon.Style.Default(CompoundIcons.SpaceSolid()) + ) + Text( + text = stringResource(CommonStrings.screen_space_list_empty_state_title), + style = ElementTheme.typography.fontHeadingLgBold, + color = ElementTheme.colors.textPrimary, + textAlign = TextAlign.Center, + ) + } + }, + footer = { + ButtonColumnMolecule { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_create_space), + onClick = onCreateSpaceClick, + ) + if (canExploreSpaces) { + TextButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_explore_public_spaces), + onClick = onExploreClick, + ) + } + } + } + ) { + } +} + @PreviewsDayNight @Composable internal fun HomeSpacesViewPreview( @@ -91,6 +171,7 @@ internal fun HomeSpacesViewPreview( state = state, lazyListState = rememberLazyListState(), onSpaceClick = {}, - modifier = Modifier, + onCreateSpaceClick = {}, + onExploreClick = {}, ) } diff --git a/features/home/impl/src/main/res/values-da/translations.xml b/features/home/impl/src/main/res/values-da/translations.xml index b0763591aa..aa2e6fb488 100644 --- a/features/home/impl/src/main/res/values-da/translations.xml +++ b/features/home/impl/src/main/res/values-da/translations.xml @@ -50,6 +50,7 @@ Du har ingen ulæste beskeder!" "Marker som læst" "Marker som ulæst" "Dette rum er blevet opgraderet" + "Dine grupper" "Det ser ud til, at du bruger en ny enhed. Bekræft med en anden enhed for at få adgang til dine krypterede meddelelser." "Bekræft, at det er dig" diff --git a/features/home/impl/src/main/res/values-de/translations.xml b/features/home/impl/src/main/res/values-de/translations.xml index 2502df5fc1..f504d5c8e1 100644 --- a/features/home/impl/src/main/res/values-de/translations.xml +++ b/features/home/impl/src/main/res/values-de/translations.xml @@ -50,6 +50,7 @@ Du hast keine ungelesenen Nachrichten!" "Als gelesen markieren" "Als ungelesen markieren" "Die Chat-Version wurde aktualisiert" + "Deine Spaces" "Es sieht aus, als würdest du ein neues Gerät verwenden. Verifiziere es mit einem anderen Gerät, damit du auf deine verschlüsselten Nachrichten zugreifen kannst." "Verifiziere deine Identität" diff --git a/features/home/impl/src/main/res/values-fr/translations.xml b/features/home/impl/src/main/res/values-fr/translations.xml index d54e8fb943..5d319b0a84 100644 --- a/features/home/impl/src/main/res/values-fr/translations.xml +++ b/features/home/impl/src/main/res/values-fr/translations.xml @@ -50,6 +50,7 @@ Vous n’avez plus de messages non-lus !" "Marquer comme lu" "Marquer comme non lu" "Ce salon a été mis à niveau." + "Vos espaces" "Il semblerait que vous utilisiez un nouvel appareil. Vérifiez la session avec un autre de vos appareils pour accéder à vos messages chiffrés." "Vérifier que c’est bien vous" diff --git a/features/home/impl/src/main/res/values-hu/translations.xml b/features/home/impl/src/main/res/values-hu/translations.xml index cca927910b..6f185224ce 100644 --- a/features/home/impl/src/main/res/values-hu/translations.xml +++ b/features/home/impl/src/main/res/values-hu/translations.xml @@ -50,6 +50,7 @@ Nincs olvasatlan üzenete!" "Megjelölés olvasottként" "Megjelölés olvasatlanként" "A szoba verzióját frissítették" + "Saját terek" "Úgy tűnik, hogy új eszközt használ. Ellenőrizze egy másik eszközzel, hogy a továbbiakban elérje a titkosított üzeneteket." "Ellenőrizze, hogy Ön az" diff --git a/features/home/impl/src/main/res/values-pt-rBR/translations.xml b/features/home/impl/src/main/res/values-pt-rBR/translations.xml index 10f5cf9585..575cfbf6b0 100644 --- a/features/home/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/home/impl/src/main/res/values-pt-rBR/translations.xml @@ -50,6 +50,7 @@ Você não tem nenhuma mensagem não lida!" "Marcar como lida" "Marcar como não lida" "Esta sala foi atualizada" + "Seus espaços" "Parece que você está usando um novo dispositivo. Verifique com outro dispositivo para acessar suas mensagens criptografadas." "Verifique se é você" diff --git a/features/home/impl/src/main/res/values-sk/translations.xml b/features/home/impl/src/main/res/values-sk/translations.xml index 275a4824b3..12e6284468 100644 --- a/features/home/impl/src/main/res/values-sk/translations.xml +++ b/features/home/impl/src/main/res/values-sk/translations.xml @@ -50,6 +50,7 @@ Nemáte žiadne neprečítané správy!" "Označiť ako prečítané" "Označiť ako neprečítané" "Táto miestnosť bola aktualizovaná" + "Vaše priestory" "Vyzerá to tak, že používate nové zariadenie. Overte svoj prístup k zašifrovaným správam pomocou vášho druhého zariadenia." "Overte, že ste to vy" diff --git a/features/home/impl/src/main/res/values/localazy.xml b/features/home/impl/src/main/res/values/localazy.xml index 80a263a179..3ada4ef1b6 100644 --- a/features/home/impl/src/main/res/values/localazy.xml +++ b/features/home/impl/src/main/res/values/localazy.xml @@ -50,6 +50,7 @@ You don’t have any unread messages!" "Mark as read" "Mark as unread" "This room has been upgraded" + "Your spaces" "Looks like you’re using a new device. Verify with another device to access your encrypted messages." "Verify it’s you" diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt index 266c33015d..37ed6e6909 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt @@ -173,7 +173,7 @@ class HomePresenterTest { } @Test - fun `present - NavigationBar is hidden when the last space is left`() = runTest { + fun `present - NavigationBar is hidden when the last space is left when the user can't create new spaces`() = runTest { val homeSpacesPresenter = MutablePresenter(aHomeSpacesState()) val presenter = createHomePresenter( sessionStore = InMemorySessionStore( @@ -193,7 +193,7 @@ class HomePresenterTest { val spaceState = awaitItem() assertThat(spaceState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces) // The last space is left - homeSpacesPresenter.updateState(aHomeSpacesState(spaceRooms = emptyList())) + homeSpacesPresenter.updateState(aHomeSpacesState(spaceRooms = emptyList(), canCreateSpaces = false)) skipItems(1) val finalState = awaitItem() // We are back to Chats diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt index 0fd6459057..bf453ea0d9 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -122,13 +123,18 @@ fun TestScope.createRoomListSearchPresenter( roomListService: RoomListService = FakeRoomListService(), ): RoomListSearchPresenter { return RoomListSearchPresenter( - dataSource = RoomListSearchDataSource( - roomListService = roomListService, - roomSummaryFactory = aRoomListRoomSummaryFactory( - dateFormatter = FakeDateFormatter(), - roomLatestEventFormatter = FakeRoomLatestEventFormatter(), - ), - coroutineDispatchers = testCoroutineDispatchers(), - ), + dataSourceFactory = object : RoomListSearchDataSource.Factory { + override fun create(coroutineScope: CoroutineScope): RoomListSearchDataSource { + return RoomListSearchDataSource( + roomListService = roomListService, + roomSummaryFactory = aRoomListRoomSummaryFactory( + dateFormatter = FakeDateFormatter(), + roomLatestEventFormatter = FakeRoomLatestEventFormatter(), + ), + coroutineDispatchers = testCoroutineDispatchers(), + coroutineScope = coroutineScope, + ) + } + } ) } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt index b0c8994a62..b1f18b1df9 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt @@ -13,6 +13,5 @@ import io.element.android.libraries.matrix.api.user.MatrixUser sealed interface DefaultInvitePeopleEvents : InvitePeopleEvents { data class ToggleUser(val user: MatrixUser) : DefaultInvitePeopleEvents - data class UpdateSearchQuery(val query: String) : DefaultInvitePeopleEvents data class OnSearchActiveChanged(val active: Boolean) : DefaultInvitePeopleEvents } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt index 5ac3bc2824..3450587e82 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt @@ -8,9 +8,12 @@ package io.element.android.features.invitepeople.impl +import androidx.compose.foundation.text.input.clearText +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState @@ -39,6 +42,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.filterMembers +import io.element.android.libraries.matrix.api.room.recent.getRecentDirectRooms import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.usersearch.api.UserRepository @@ -47,11 +51,16 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +private const val MAX_SUGGESTIONS_COUNT = 5 + @AssistedInject class DefaultInvitePeoplePresenter( @Assisted private val joinedRoom: JoinedRoom?, @@ -73,11 +82,39 @@ class DefaultInvitePeoplePresenter( val roomMembers = remember { mutableStateOf>>(AsyncData.Loading()) } val selectedUsers = remember { mutableStateOf>(persistentListOf()) } val searchResults = remember { mutableStateOf>>(SearchBarResultState.Initial()) } - var searchQuery by rememberSaveable { mutableStateOf("") } + val queryState = rememberTextFieldState() var searchActive by rememberSaveable { mutableStateOf(false) } val showSearchLoader = rememberSaveable { mutableStateOf(false) } val sendInvitesAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } + val recentDirectRooms by produceState(emptyList(), roomMembers.value) { + if (roomMembers.value.isSuccess()) { + val activeMemberIds = roomMembers.value.dataOrNull().orEmpty() + .filter { it.membership.isActive() } + .mapTo(mutableSetOf()) { it.userId } + + value = matrixClient.getRecentDirectRooms() + .filterNot { it.matrixUser.userId in activeMemberIds } + .take(MAX_SUGGESTIONS_COUNT) + .toList() + } + } + + // Convert recent direct rooms to InvitableUser for display + val suggestions by remember { + derivedStateOf { + recentDirectRooms.map { recentDirectRoom -> + InvitableUser( + matrixUser = recentDirectRoom.matrixUser, + isSelected = recentDirectRoom.matrixUser in selectedUsers.value, + isAlreadyJoined = false, + isAlreadyInvited = false, + isUnresolved = false, + ) + }.toImmutableList() + } + } + val room by produceState(if (joinedRoom != null) AsyncData.Success(joinedRoom) else AsyncData.Loading()) { if (joinedRoom == null) { val result = matrixClient.getJoinedRoom(roomId) @@ -94,6 +131,7 @@ class DefaultInvitePeoplePresenter( fetchMembers(it, roomMembers) } } + val searchQuery = queryState.text.toString() LaunchedEffect(searchQuery, roomMembers) { performSearch( searchResults = searchResults, @@ -108,16 +146,15 @@ class DefaultInvitePeoplePresenter( when (event) { is DefaultInvitePeopleEvents.OnSearchActiveChanged -> { searchActive = event.active - searchQuery = "" - } - - is DefaultInvitePeopleEvents.UpdateSearchQuery -> { - searchQuery = event.query + if (!event.active) { + queryState.clearText() + } } is DefaultInvitePeopleEvents.ToggleUser -> { selectedUsers.toggleUser(event.user) searchResults.toggleUser(event.user) + // suggestions will automatically update via derivedStateOf when selectedUsers changes } is InvitePeopleEvents.SendInvites -> { room.dataOrNull()?.let { @@ -126,7 +163,7 @@ class DefaultInvitePeoplePresenter( } is InvitePeopleEvents.CloseSearch -> { searchActive = false - searchQuery = "" + queryState.clearText() } } } @@ -135,11 +172,12 @@ class DefaultInvitePeoplePresenter( room = room.map { }, canInvite = selectedUsers.value.isNotEmpty() && !sendInvitesAction.value.isLoading(), selectedUsers = selectedUsers.value, - searchQuery = searchQuery, + searchQuery = queryState, isSearchActive = searchActive, searchResults = searchResults.value, showSearchLoader = showSearchLoader.value, sendInvitesAction = sendInvitesAction.value, + suggestions = suggestions, eventSink = ::handleEvent, ) } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt index 917915e4d6..842bcf1148 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt @@ -8,6 +8,7 @@ package io.element.android.features.invitepeople.impl +import androidx.compose.foundation.text.input.TextFieldState import io.element.android.features.invitepeople.api.InvitePeopleEvents import io.element.android.features.invitepeople.api.InvitePeopleState import io.element.android.libraries.architecture.AsyncAction @@ -19,11 +20,12 @@ import kotlinx.collections.immutable.ImmutableList data class DefaultInvitePeopleState( val room: AsyncData, override val canInvite: Boolean, - val searchQuery: String, + val searchQuery: TextFieldState, val showSearchLoader: Boolean, val searchResults: SearchBarResultState>, val selectedUsers: ImmutableList, override val isSearchActive: Boolean, override val sendInvitesAction: AsyncAction, + val suggestions: ImmutableList, override val eventSink: (InvitePeopleEvents) -> Unit ) : InvitePeopleState diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt index 2f28db8786..15ded2ae3f 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.invitepeople.impl +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData @@ -101,16 +102,20 @@ private fun aDefaultInvitePeopleState( isSearchActive: Boolean = false, showSearchLoader: Boolean = false, sendInvitesAction: AsyncAction = AsyncAction.Uninitialized, + suggestions: List = aMatrixUserList() + .take(5) + .map { user -> anInvitableUser(matrixUser = user, isSelected = user in selectedUsers) }, ): DefaultInvitePeopleState { return DefaultInvitePeopleState( room = room, canInvite = canInvite, - searchQuery = searchQuery, + searchQuery = TextFieldState(initialText = searchQuery), searchResults = searchResults, selectedUsers = selectedUsers, isSearchActive = isSearchActive, showSearchLoader = showSearchLoader, sendInvitesAction = sendInvitesAction, + suggestions = suggestions.toImmutableList(), eventSink = {}, ) } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt index a8e056c7aa..55c5dba030 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -31,6 +32,8 @@ import io.element.android.libraries.designsystem.components.async.AsyncLoading import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider +import io.element.android.libraries.designsystem.theme.components.ListSectionHeader import io.element.android.libraries.designsystem.theme.components.SearchBar import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.designsystem.theme.components.Text @@ -82,9 +85,13 @@ private fun InvitePeopleContentView( modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(16.dp), ) { + fun toggleUser(user: MatrixUser) { + state.eventSink(DefaultInvitePeopleEvents.ToggleUser(user)) + } + InvitePeopleSearchBar( modifier = Modifier.fillMaxWidth(), - query = state.searchQuery, + queryState = state.searchQuery, showLoader = state.showSearchLoader, selectedUsers = state.selectedUsers, state = state.searchResults, @@ -96,18 +103,45 @@ private fun InvitePeopleContentView( ) ) }, - onTextChange = { state.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery(it)) }, - onToggleUser = { state.eventSink(DefaultInvitePeopleEvents.ToggleUser(it)) }, + onToggleUser = ::toggleUser, ) if (!state.isSearchActive) { - SelectedUsersRowList( - modifier = Modifier.fillMaxWidth(), - selectedUsers = state.selectedUsers, - autoScroll = true, - onUserRemove = { state.eventSink(DefaultInvitePeopleEvents.ToggleUser(it)) }, - contentPadding = PaddingValues(16.dp), - ) + if (state.selectedUsers.isNotEmpty()) { + SelectedUsersRowList( + modifier = Modifier.fillMaxWidth(), + selectedUsers = state.selectedUsers, + autoScroll = true, + onUserRemove = ::toggleUser, + contentPadding = PaddingValues(all = 16.dp), + ) + } + if (state.suggestions.isNotEmpty()) { + LazyColumn { + item { + ListSectionHeader( + title = stringResource(id = CommonStrings.common_suggestions), + hasDivider = false, + ) + } + itemsIndexed(state.suggestions) { index, invitableUser -> + CheckableUserRow( + checked = invitableUser.isSelected, + onCheckedChange = { + state.eventSink(DefaultInvitePeopleEvents.ToggleUser(invitableUser.matrixUser)) + }, + data = CheckableUserRowData.Resolved( + avatarData = invitableUser.matrixUser.getAvatarData(AvatarSize.UserListItem), + name = invitableUser.matrixUser.getBestName(), + subtext = invitableUser.matrixUser.userId.value, + ), + ) + if (index < state.suggestions.lastIndex) { + HorizontalDivider() + } + } + } + } } } } @@ -115,20 +149,18 @@ private fun InvitePeopleContentView( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun InvitePeopleSearchBar( - query: String, + queryState: TextFieldState, state: SearchBarResultState>, showLoader: Boolean, selectedUsers: ImmutableList, active: Boolean, onActiveChange: (Boolean) -> Unit, - onTextChange: (String) -> Unit, onToggleUser: (MatrixUser) -> Unit, modifier: Modifier = Modifier, placeHolderTitle: String = stringResource(CommonStrings.common_search_for_someone), ) { SearchBar( - query = query, - onQueryChange = onTextChange, + queryState = queryState, active = active, onActiveChange = onActiveChange, modifier = modifier, @@ -140,7 +172,7 @@ private fun InvitePeopleSearchBar( selectedUsers = selectedUsers, autoScroll = true, onUserRemove = onToggleUser, - contentPadding = PaddingValues(16.dp), + contentPadding = PaddingValues(all = 16.dp), ) } }, diff --git a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt index 388baf15e6..ab9e20437e 100644 --- a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt +++ b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt @@ -8,6 +8,7 @@ package io.element.android.features.invitepeople.impl +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import app.cash.turbine.ReceiveTurbine import com.google.common.truth.Truth.assertThat import io.element.android.features.invitepeople.api.InvitePeopleEvents @@ -17,6 +18,7 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState @@ -26,7 +28,9 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.room.aRoomMemberList import io.element.android.libraries.matrix.ui.components.aMatrixUser @@ -65,31 +69,34 @@ internal class DefaultInvitePeoplePresenterTest { assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(initialState.isSearchActive).isFalse() assertThat(initialState.canInvite).isFalse() - assertThat(initialState.searchQuery).isEmpty() + assertThat(initialState.searchQuery.text.toString()).isEmpty() - skipItems(1) + cancelAndIgnoreRemainingEvents() } } @Test fun `present - updates search active state`() = runTest { - val presenter = createDefaultInvitePeoplePresenter() + val presenter = createDefaultInvitePeoplePresenter( + coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) + ) presenter.test { val initialState = awaitItem() skipItems(1) initialState.eventSink(DefaultInvitePeopleEvents.OnSearchActiveChanged(true)) - val resultState = awaitItem() + val resultState = awaitItemAsDefault() assertThat(resultState.isSearchActive).isTrue() - resultState.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery("some query")) - assertThat(awaitItemAsDefault().searchQuery).isEqualTo("some query") + resultState.searchQuery.setTextAndPlaceCursorAtEnd("some query") + assertThat(awaitItemAsDefault().searchQuery.text.toString()).isEqualTo("some query") resultState.eventSink(InvitePeopleEvents.CloseSearch) - skipItems(1) + skipItems(2) awaitItemAsDefault().also { assertThat(it.isSearchActive).isFalse() - assertThat(it.searchQuery).isEmpty() + assertThat(it.searchQuery.text.toString()).isEmpty() } + cancelAndIgnoreRemainingEvents() } } @@ -101,8 +108,8 @@ internal class DefaultInvitePeoplePresenterTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) presenter.test { - val initialState = awaitItem() - initialState.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery("some query")) + val initialState = awaitItemAsDefault() + initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query") assertThat(repository.providedQuery).isEqualTo("some query") repository.emitState(UserSearchResultState(results = emptyList(), isSearching = true)) skipItems(3) @@ -126,10 +133,10 @@ internal class DefaultInvitePeoplePresenterTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) presenter.test { - val initialState = awaitItem() + val initialState = awaitItemAsDefault() skipItems(1) - initialState.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery("some query")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query") skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") @@ -179,10 +186,10 @@ internal class DefaultInvitePeoplePresenterTest { coroutineDispatchers = coroutineDispatchers, ) presenter.test { - val initialState = awaitItem() + val initialState = awaitItemAsDefault() skipItems(1) - initialState.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery("some query")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query") skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") @@ -239,10 +246,10 @@ internal class DefaultInvitePeoplePresenterTest { ) presenter.test { - val initialState = awaitItem() + val initialState = awaitItemAsDefault() skipItems(1) - initialState.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery("some query")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query") skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") @@ -275,7 +282,7 @@ internal class DefaultInvitePeoplePresenterTest { val repository = FakeUserRepository() val presenter = createDefaultInvitePeoplePresenter( userRepository = repository, - coroutineDispatchers = testCoroutineDispatchers() + coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) presenter.test { val initialState = awaitItem() @@ -306,14 +313,14 @@ internal class DefaultInvitePeoplePresenterTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) presenter.test { - val initialState = awaitItem() + val initialState = awaitItemAsDefault() skipItems(1) val selectedUser = aMatrixUser() initialState.eventSink(DefaultInvitePeopleEvents.ToggleUser(selectedUser)) - initialState.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery("some query")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query") skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") @@ -344,13 +351,13 @@ internal class DefaultInvitePeoplePresenterTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) presenter.test { - val initialState = awaitItem() + val initialState = awaitItemAsDefault() skipItems(1) val selectedUser = aMatrixUser() // Given a query is made - initialState.eventSink(DefaultInvitePeopleEvents.UpdateSearchQuery("some query")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("some query") skipItems(1) assertThat(repository.providedQuery).isEqualTo("some query") @@ -519,6 +526,85 @@ internal class DefaultInvitePeoplePresenterTest { } } + @Test + fun `present - suggestions are loaded from recent direct rooms`() = runTest { + val dmRoomId = RoomId("!dm_room:server.org") + val otherUserId = UserId("@frank:server.org") + val matrixClient = FakeMatrixClient(sessionId = A_USER_ID).apply { + // Track the DM room as recently visited + trackRecentlyVisitedRoom(dmRoomId) + // Set up a DM room with the other user + givenGetRoomResult( + dmRoomId, + FakeBaseRoom( + sessionId = A_USER_ID, + roomId = dmRoomId, + initialRoomInfo = aRoomInfo( + id = dmRoomId, + isDirect = true, + activeMembersCount = 2, + currentUserMembership = CurrentUserMembership.JOINED, + ), + getDirectRoomMemberResult = { aRoomMember(userId = otherUserId, displayName = "Frank") } + ) + ) + } + val presenter = createDefaultInvitePeoplePresenter( + matrixClient = matrixClient, + // Use empty room members so the suggestion doesn't get filtered + roomMembersState = RoomMembersState.Ready(persistentListOf()), + coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), + ) + presenter.test { + skipItems(2) + val state = awaitItemAsDefault() + assertThat(state.suggestions).hasSize(1) + assertThat(state.suggestions.first().matrixUser.userId).isEqualTo(otherUserId) + assertThat(state.suggestions.first().isSelected).isFalse() + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - suggestions filters out existing room members`() = runTest { + val dmRoomId = RoomId("!dm_room:server.org") + val alreadyJoinedUserId = UserId("@frank:server.org") + val matrixClient = FakeMatrixClient(sessionId = A_USER_ID).apply { + trackRecentlyVisitedRoom(dmRoomId) + givenGetRoomResult( + dmRoomId, + FakeBaseRoom( + sessionId = A_USER_ID, + roomId = dmRoomId, + initialRoomInfo = aRoomInfo( + id = dmRoomId, + isDirect = true, + activeMembersCount = 2, + currentUserMembership = CurrentUserMembership.JOINED, + ), + getDirectRoomMemberResult = { aRoomMember(userId = alreadyJoinedUserId, displayName = "Frank") } + ) + ) + } + // The user in the suggestion is already a member of the target room + val presenter = createDefaultInvitePeoplePresenter( + matrixClient = matrixClient, + roomMembersState = RoomMembersState.Ready( + persistentListOf( + aRoomMember(userId = alreadyJoinedUserId, membership = RoomMembershipState.JOIN) + ) + ), + coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), + ) + presenter.test { + skipItems(1) + // The suggestion should be filtered out because the user is already a room member + val state = awaitItemAsDefault() + assertThat(state.suggestions).isEmpty() + cancelAndIgnoreRemainingEvents() + } + } + private suspend fun FakeUserRepository.emitStateWithUsers( users: List, isSearching: Boolean = false diff --git a/features/linknewdevice/impl/src/main/res/values-da/translations.xml b/features/linknewdevice/impl/src/main/res/values-da/translations.xml index a31175c1d5..5bc9f04fb9 100644 --- a/features/linknewdevice/impl/src/main/res/values-da/translations.xml +++ b/features/linknewdevice/impl/src/main/res/values-da/translations.xml @@ -1,16 +1,33 @@ "Scan QR-koden" + "Åbn %1$s på en bærbar eller stationær computer" "Scan QR-koden med denne enhed" "Klar til at scanne" + "Åbn %1$s på en stationær computer for at få QR-koden" + "Tallene stemmer ikke overens" + "Indtast 2-cifret kode" + "Dette vil bekræfte, at forbindelsen til din anden enhed er sikker." + "Indtast nummeret, der vises på din anden enhed" "Din kontoudbyder understøtter ikke %1$s." "%1$s understøttes ikke" + "Din kontoudbyder understøtter ikke login på en ny enhed med en QR-kode." "QR-kode understøttes ikke" "Login blev annulleret på den anden enhed." "Anmodning om login annulleret" "Login er udløbet. Prøv venligst igen." "Login blev ikke afsluttet i tide" + "Åbn %1$s på den anden enhed" "Vælg %1$s" + "\"Log ind med QR-kode\"" + "Scan QR-koden vist her med den anden enhed" + "Åbn %1$s på den anden enhed" + "Stationær computer" + "Indlæser QR-kode…" + "Mobil enhed" + "Hvilken type enhed vil du linke?" + "Prøv igen, og sørg for, at du har indtastet den 2-cifrede kode korrekt. Hvis tallene stadig ikke stemmer overens, skal du kontakte din kontoudbyder." + "Tallene stemmer ikke overens" "Der kunne ikke oprettes en sikker forbindelse til den nye enhed. Dine eksisterende enheder er stadig sikre, og du behøver ikke bekymre dig om dem." "Hvad nu?" "Prøv at logge ind igen med en QR-kode, hvis dette skyldtes et netværksproblem" @@ -21,6 +38,8 @@ "Anmodning om login annulleret" "Login blev afvist på den anden enhed." "Login afvist" + "Du behøver ikke gøre andet." + "Din anden enhed er allerede logget ind" "Login er udløbet. Prøv venligst igen." "Login blev ikke afsluttet i tide" "Din anden enhed understøtter ikke at logge ind på %s med en QR-kode. diff --git a/features/linknewdevice/impl/src/main/res/values-hu/translations.xml b/features/linknewdevice/impl/src/main/res/values-hu/translations.xml index b06d7e2fd6..51fe30bbd8 100644 --- a/features/linknewdevice/impl/src/main/res/values-hu/translations.xml +++ b/features/linknewdevice/impl/src/main/res/values-hu/translations.xml @@ -1,16 +1,33 @@ "Olvassa be a QR-kódot" + "Nyissa meg az %1$s alkalmazást egy laptopon vagy asztali számítógépen." "Olvassa be a QR-kódot ezzel az eszközzel" "Készen áll a beolvasásra" + "Nyissa meg az asztali %1$s alkalmazást, hogy megkapja a QR-kódot" + "A számok nem egyeznek" + "Írja be a kétjegyű kódot" + "Ezzel ellenőrizni fogja, hogy a másik eszközzel való kapcsolat biztonságos-e." + "Adja meg a másik eszközön megjelenő számot" "A fiókszolgáltatója nem támogatja az %1$s-et." "Az %1$s nem támogatott" + "Fiókszolgáltatója nem támogatja a QR-kóddal történő bejelentkezést egy új eszközre." "A QR-kód nem támogatott" "A bejelentkezést megszakították a másik eszközön." "Bejelentkezési kérés törölve" "A bejelentkezés lejárt. Próbálja újra." "A bejelentkezés nem fejeződött be időben" + "Nyissa meg a másik eszközön ezt: %1$s" "Válassza ezt: %1$s" + "„Bejelentkezés QR-kóddal”" + "Olvassa le az itt látható QR-kódot a másik eszközzel" + "Nyissa meg a másik eszközön ezt: %1$s" + "Asztali számítógép" + "QR-kód betöltése…" + "Mobil eszköz" + "Milyen típusú eszközt szeretne összekapcsolni?" + "Próbálja meg újra, és ellenőrizze, hogy helyesen adta meg a 2 számjegyű kódot. Ha a számok továbbra sem egyeznek, vegye fel a kapcsolatot a fiókszolgáltatójával." + "A számok nem egyeznek" "Nem sikerült biztonságos kapcsolatot létesíteni az új eszközzel. A meglévő eszközei továbbra is biztonságban vannak, és nem kell aggódnia miattuk." "Most mi lesz?" "Próbáljon meg újra bejelentkezni egy QR-kóddal, ha ez hálózati probléma volt." @@ -21,6 +38,8 @@ "Bejelentkezési kérés törölve" "A bejelentkezést elutasították a másik eszközön." "A bejelentkezés elutasítva" + "Semmi mást nem kell tennie." + "A másik eszköz már be van jelentkezve" "A bejelentkezés lejárt. Próbálja újra." "A bejelentkezés nem fejeződött be időben" "A másik eszköz nem támogatja QR-kóddal történő bejelentkezést az %sbe. diff --git a/features/linknewdevice/impl/src/main/res/values-ru/translations.xml b/features/linknewdevice/impl/src/main/res/values-ru/translations.xml index 16ed8eeb37..312eca4fd3 100644 --- a/features/linknewdevice/impl/src/main/res/values-ru/translations.xml +++ b/features/linknewdevice/impl/src/main/res/values-ru/translations.xml @@ -1,16 +1,33 @@ "Сканировать QR-код" + "Откройте %1$s на ноутбуке или компьютере" "Отсканируйте QR-код с помощью этого устройства" "Готово к сканированию" + "Открой %1$s на компьютере, чтобы получить QR-код" + "Цифры не совпадают" + "Введите 2-значный код" + "Это позволит убедиться в безопасности соединения с другим вашим устройством." + "Введите номер, отображаемый на другом устройстве" "Поставщик учетной записи не поддерживает %1$s." "%1$s не поддерживается" + "Поставщик учетной записи не поддерживает вход на новое устройство с помощью QR-кода." "QR-код не поддерживается" "Вход на другом устройстве был отменен." "Запрос на вход отменен" "Срок действия входа истек. Пожалуйста, попробуйте еще раз." "Вход в систему не был выполнен вовремя" + "Открыть %1$s на другом устройстве" "Выберите %1$s" + "«Вход с помощью QR-кода»" + "Отсканируйте показанный здесь QR-код на другом устройстве." + "Открыть %1$s на другом устройстве" + "Настольный компьютер" + "Загрузка QR-кода…" + "Мобильное устройство" + "Какой тип устройства вы хотите подключить?" + "Пожалуйста, попробуйте еще раз и убедитесь, что вы правильно ввели 2-значный код. Если цифры по-прежнему не совпадают, обратитесь к своему поставщику услуг." + "Цифры не совпадают" "Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них." "Что теперь?" "Попробуйте снова войти в систему с помощью QR-кода, если это была проблема с соединением" @@ -21,6 +38,8 @@ "Запрос на вход отменен" "Вход в систему был отклонен на другом устройстве." "Вход отклонен" + "Больше ничего не нужно делать." + "Вход уже выполнен на другом устройстве" "Срок действия входа истек. Пожалуйста, попробуйте еще раз." "Вход в систему не был выполнен вовремя" "Другое устройство не поддерживает вход в %s с помощью QR-кода. diff --git a/features/linknewdevice/impl/src/main/res/values-sk/translations.xml b/features/linknewdevice/impl/src/main/res/values-sk/translations.xml index 9617df3acc..cb430671bb 100644 --- a/features/linknewdevice/impl/src/main/res/values-sk/translations.xml +++ b/features/linknewdevice/impl/src/main/res/values-sk/translations.xml @@ -1,16 +1,33 @@ "Naskenovať QR kód" + "Otvorte %1$s na notebooku alebo stolnom počítači" "Naskenujte QR kód pomocou tohto zariadenia" "Pripravené na skenovanie" + "Otvorte %1$s na stolnom počítači, aby ste získali QR kód" + "Čísla sa nezhodujú" + "Zadajte 2-miestny kód" + "Týmto sa overí, že pripojenie k vášmu druhému zariadeniu je bezpečné." + "Zadajte číslo zobrazené na vašom druhom zariadení" "Poskytovateľ vášho účtu nepodporuje %1$s." "%1$s nie je podporovaný" + "Poskytovateľ vášho účtu nepodporuje prihlásenie do nového zariadenia pomocou QR kódu." "QR kód nie je podporovaný" "Prihlásenie bolo zrušené na druhom zariadení." "Žiadosť o prihlásenie bola zrušená" "Platnosť prihlásenia vypršala. Skúste to prosím znova." "Prihlásenie nebolo včas dokončené" + "Otvorte %1$s na druhom zariadení" "Vyberte %1$s" + "„Prihláste sa pomocou QR kódu“" + "Naskenujte tu zobrazený QR kód pomocou druhého zariadenia" + "Otvorte %1$s na druhom zariadení" + "Stolný počítač" + "Načítava sa QR kód…" + "Mobilné zariadenie" + "Aký typ zariadenia chcete prepojiť?" + "Skúste to prosím znova a uistite sa, že ste zadali 2-ciferný kód správne. Ak sa čísla stále nezhodujú, kontaktujte poskytovateľa vášho účtu." + "Čísla sa nezhodujú" "K novému zariadeniu sa nepodarilo vytvoriť bezpečné pripojenie. Vaše existujúce zariadenia sú stále v bezpečí a nemusíte sa o ne obávať." "Čo teraz?" "Skúste sa znova prihlásiť pomocou QR kódu v prípade, že ide o problém so sieťou" @@ -21,6 +38,8 @@ "Žiadosť o prihlásenie bola zrušená" "Prihlásenie bolo zamietnuté na druhom zariadení." "Prihlásenie bolo odmietnuté" + "Nemusíte spraviť nič iné." + "Vaše druhé zariadenie je už prihlásené" "Platnosť prihlásenia vypršala. Skúste to prosím znova." "Prihlásenie nebolo včas dokončené" "Vaše druhé zariadenie nepodporuje prihlásenie do aplikácie %s pomocou QR kódu. diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index 071408b2f5..e6381c9162 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -60,4 +60,6 @@ dependencies { testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.libraries.wellknown.test) + testImplementation(libs.androidx.camera.camera2) + testImplementation(libs.androidx.camera.lifecycle) } diff --git a/features/login/impl/src/main/res/values-da/translations.xml b/features/login/impl/src/main/res/values-da/translations.xml index cffa9c7e2e..2b2f00267b 100644 --- a/features/login/impl/src/main/res/values-da/translations.xml +++ b/features/login/impl/src/main/res/values-da/translations.xml @@ -60,6 +60,8 @@ "Anmodning om login annulleret" "Login blev afvist på den anden enhed." "Login afvist" + "Du behøver ikke gøre andet." + "Din anden enhed er allerede logget ind" "Login er udløbet. Prøv venligst igen." "Login blev ikke afsluttet i tide" "Din anden enhed understøtter ikke at logge ind på %s med en QR-kode. diff --git a/features/login/impl/src/main/res/values-hu/translations.xml b/features/login/impl/src/main/res/values-hu/translations.xml index fd05e194e3..06014b77b7 100644 --- a/features/login/impl/src/main/res/values-hu/translations.xml +++ b/features/login/impl/src/main/res/values-hu/translations.xml @@ -60,6 +60,8 @@ "Bejelentkezési kérés törölve" "A bejelentkezést elutasították a másik eszközön." "A bejelentkezés elutasítva" + "Semmi mást nem kell tennie." + "A másik eszköz már be van jelentkezve" "A bejelentkezés lejárt. Próbálja újra." "A bejelentkezés nem fejeződött be időben" "A másik eszköz nem támogatja QR-kóddal történő bejelentkezést az %sbe. diff --git a/features/login/impl/src/main/res/values-ru/translations.xml b/features/login/impl/src/main/res/values-ru/translations.xml index 8ce58135aa..0cbbed7791 100644 --- a/features/login/impl/src/main/res/values-ru/translations.xml +++ b/features/login/impl/src/main/res/values-ru/translations.xml @@ -60,6 +60,8 @@ "Запрос на вход отменен" "Вход в систему был отклонен на другом устройстве." "Вход отклонен" + "Больше ничего не нужно делать." + "Вход уже выполнен на другом устройстве" "Срок действия входа истек. Пожалуйста, попробуйте еще раз." "Вход в систему не был выполнен вовремя" "Другое устройство не поддерживает вход в %s с помощью QR-кода. diff --git a/features/login/impl/src/main/res/values-sk/translations.xml b/features/login/impl/src/main/res/values-sk/translations.xml index 6a8acf1f68..9bd494dd56 100644 --- a/features/login/impl/src/main/res/values-sk/translations.xml +++ b/features/login/impl/src/main/res/values-sk/translations.xml @@ -60,6 +60,8 @@ "Žiadosť o prihlásenie bola zrušená" "Prihlásenie bolo zamietnuté na druhom zariadení." "Prihlásenie bolo odmietnuté" + "Nemusíte spraviť nič iné." + "Vaše druhé zariadenie je už prihlásené" "Platnosť prihlásenia vypršala. Skúste to prosím znova." "Prihlásenie nebolo včas dokončené" "Vaše druhé zariadenie nepodporuje prihlásenie do aplikácie %s pomocou QR kódu. diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt index 454dc79067..b8becd545f 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanViewTest.kt @@ -9,9 +9,11 @@ package io.element.android.features.login.impl.screens.qrcode.scan import androidx.activity.ComponentActivity +import androidx.camera.lifecycle.ProcessCameraProvider import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.test.auth.qrlogin.FakeMatrixQrCodeLoginData @@ -20,6 +22,8 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBackKey +import org.junit.After +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule @@ -30,6 +34,19 @@ class QrCodeScanViewTest { @get:Rule val rule = createAndroidComposeRule() + private var provider: ProcessCameraProvider? = null + + @Before + fun setup() { + val context = InstrumentationRegistry.getInstrumentation().context + provider = ProcessCameraProvider.getInstance(context).get() + } + + @After + fun teardown() { + provider?.unbindAll() + } + @Test fun `on back pressed - calls the expected callback`() { ensureCalledOnce { callback -> diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index eb8aff66ed..ad6562a83c 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -68,7 +68,6 @@ dependencies { implementation(libs.jsoup) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout.compose) - implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.ui) implementation(libs.sigpwned.emoji4j) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvent.kt similarity index 78% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvent.kt index 2419d76038..bef8ca84d6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvent.kt @@ -13,13 +13,12 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.user.MatrixUser -sealed interface MessagesEvents { - data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents - data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvents - data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvents - data class OnUserClicked(val user: MatrixUser) : MessagesEvents - data object Dismiss : MessagesEvents - data object MarkAsFullyReadAndExit : MessagesEvents +sealed interface MessagesEvent { + data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvent + data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvent + data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvent + data class OnUserClicked(val user: MatrixUser) : MessagesEvent + data object MarkAsFullyReadAndExit : MessagesEvent } enum class InviteDialogAction { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 0692a98745..e892f80c7d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -36,7 +36,7 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelineController -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories @@ -151,7 +151,7 @@ class MessagesNode( activity: Activity, darkTheme: Boolean, url: String, - eventSink: (TimelineEvents) -> Unit, + eventSink: (TimelineEvent) -> Unit, customTab: Boolean ) { when (val permalink = permalinkParser.parse(url)) { @@ -178,12 +178,12 @@ class MessagesNode( private fun handleRoomLinkClick( roomLink: PermalinkData.RoomLink, - eventSink: (TimelineEvents) -> Unit, + eventSink: (TimelineEvent) -> Unit, ) { if (room.matches(roomLink.roomIdOrAlias)) { val eventId = roomLink.eventId if (eventId != null) { - eventSink(TimelineEvents.FocusOnEvent(eventId)) + eventSink(TimelineEvent.FocusOnEvent(eventId)) } else { // Click on the same room, ignore displaySameRoomToast() @@ -242,7 +242,7 @@ class MessagesNode( val state = presenter.present() BackHandler { - state.eventSink(MessagesEvents.MarkAsFullyReadAndExit) + state.eventSink(MessagesEvent.MarkAsFullyReadAndExit) } OnLifecycleEvent { _, event -> @@ -253,7 +253,7 @@ class MessagesNode( } MessagesView( state = state, - onBackClick = { state.eventSink(MessagesEvents.MarkAsFullyReadAndExit) }, + onBackClick = { state.eventSink(MessagesEvent.MarkAsFullyReadAndExit) }, onRoomDetailsClick = callback::navigateToRoomDetails, onEventContentClick = { isLive, event -> if (isLive) { @@ -305,7 +305,7 @@ class MessagesNode( } LaunchedEffect(focusedEventId) { if (focusedEventId != null) { - state.timelineState.eventSink(TimelineEvents.FocusOnEvent(focusedEventId!!)) + state.timelineState.eventSink(TimelineEvent.FocusOnEvent(focusedEventId!!)) focusedEventId = null } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 8a5f3cf423..22c4cdbda1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -27,10 +27,8 @@ import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.PinUnpinAction import io.element.android.appconfig.MessageComposerConfig import io.element.android.features.messages.api.timeline.HtmlConverterProvider -import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction -import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.link.LinkState import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent @@ -38,7 +36,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState import io.element.android.features.messages.impl.timeline.MarkAsFullyRead import io.element.android.features.messages.impl.timeline.TimelineController -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState @@ -76,6 +74,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMembersState +import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId @@ -102,7 +101,6 @@ class MessagesPresenter( @Assisted private val timelinePresenter: Presenter, private val timelineProtectionPresenter: Presenter, private val identityChangeStatePresenter: Presenter, - private val historyVisibleStatePresenter: Presenter, private val linkPresenter: Presenter, @Assisted private val actionListPresenter: Presenter, private val customReactionPresenter: Presenter, @@ -154,7 +152,6 @@ class MessagesPresenter( val timelineState = timelinePresenter.present() val timelineProtectionState = timelineProtectionPresenter.present() val identityChangeState = identityChangeStatePresenter.present() - val historyVisibleState = historyVisibleStatePresenter.present() val actionListState = actionListPresenter.present() val linkState = linkPresenter.present() val customReactionState = customReactionPresenter.present() @@ -208,6 +205,16 @@ class MessagesPresenter( val dmRoomMember by room.getDirectRoomMember(membersState) val roomMemberIdentityStateChanges = identityChangeState.roomMemberIdentityStateChanges + val isKeyShareOnInviteEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false) + // The top bar should show a "history" icon if: + // * History sharing is enabled, + // * The room is encrypted, and: + // * The room's history_visibility allows future users to see content. + val showSharedHistoryIcon = isKeyShareOnInviteEnabled && + roomInfo.isEncrypted == true && + (roomInfo.historyVisibility == RoomHistoryVisibility.Shared || + roomInfo.historyVisibility == RoomHistoryVisibility.WorldReadable) + LifecycleResumeEffect(dmRoomMember, roomInfo.isEncrypted) { if (roomInfo.isEncrypted == true) { val dmRoomMemberId = dmRoomMember?.userId @@ -221,9 +228,9 @@ class MessagesPresenter( onPauseOrDispose {} } - fun handleEvent(event: MessagesEvents) { + fun handleEvent(event: MessagesEvent) { when (event) { - is MessagesEvents.HandleAction -> { + is MessagesEvent.HandleAction -> { localCoroutineScope.handleTimelineAction( action = event.action, targetEvent = event.event, @@ -233,21 +240,20 @@ class MessagesPresenter( timelineProtectionState = timelineProtectionState, ) } - is MessagesEvents.ToggleReaction -> { + is MessagesEvent.ToggleReaction -> { localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId) } - is MessagesEvents.InviteDialogDismissed -> { + is MessagesEvent.InviteDialogDismissed -> { hasDismissedInviteDialog = true if (event.action == InviteDialogAction.Invite) { localCoroutineScope.reinviteOtherUser(inviteProgress) } } - is MessagesEvents.Dismiss -> actionListState.eventSink(ActionListEvents.Clear) - is MessagesEvents.OnUserClicked -> { + is MessagesEvent.OnUserClicked -> { roomMemberModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.user)) } - is MessagesEvents.MarkAsFullyReadAndExit -> coroutineScope.launch { + is MessagesEvent.MarkAsFullyReadAndExit -> coroutineScope.launch { if (!markingAsReadAndExiting.getAndSet(true)) { val latestEventId = room.liveTimeline.getLatestEventId().getOrElse { Timber.w(it, "Failed to get latest event id to mark as fully read") @@ -277,7 +283,6 @@ class MessagesPresenter( timelineState = timelineState, timelineProtectionState = timelineProtectionState, identityChangeState = identityChangeState, - historyVisibleState = historyVisibleState, linkState = linkState, actionListState = actionListState, customReactionState = customReactionState, @@ -292,6 +297,7 @@ class MessagesPresenter( pinnedMessagesBannerState = pinnedMessagesBannerState, dmUserVerificationState = dmUserVerificationState, roomMemberModerationState = roomMemberModerationState, + showSharedHistoryIcon = showSharedHistoryIcon, successorRoom = roomInfo.successorRoom, eventSink = ::handleEvent, ) @@ -529,7 +535,7 @@ class MessagesPresenter( event: TimelineItem.Event, timelineState: TimelineState, ) { - event.eventId?.let { timelineState.eventSink(TimelineEvents.EndPoll(it)) } + event.eventId?.let { timelineState.eventSink(TimelineEvent.EndPoll(it)) } } private suspend fun handleCopyLink(event: TimelineItem.Event) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index b9d86a6597..b37a916b2e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -10,7 +10,6 @@ package io.element.android.features.messages.impl import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.actionlist.ActionListState -import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.link.LinkState import io.element.android.features.messages.impl.messagecomposer.MessageComposerState @@ -41,7 +40,6 @@ data class MessagesState( val timelineState: TimelineState, val timelineProtectionState: TimelineProtectionState, val identityChangeState: IdentityChangeState, - val historyVisibleState: HistoryVisibleState, val linkState: LinkState, val actionListState: ActionListState, val customReactionState: CustomReactionState, @@ -56,8 +54,10 @@ data class MessagesState( val pinnedMessagesBannerState: PinnedMessagesBannerState, val dmUserVerificationState: IdentityState?, val roomMemberModerationState: RoomMemberModerationState, + /** Should the top bar include the "history" icon? */ + val showSharedHistoryIcon: Boolean, val successorRoom: SuccessorRoom?, - val eventSink: (MessagesEvents) -> Unit + val eventSink: (MessagesEvent) -> Unit ) { val isTombstoned = successorRoom != null } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index cec9f68b45..b657b4bbb4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -14,8 +14,6 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer. import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessagePreviewState import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.anActionListState -import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState -import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.crypto.identity.aRoomMemberIdentityStateChange import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState @@ -28,11 +26,11 @@ import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMess import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineState -import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents +import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState -import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents +import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState -import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvent import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent @@ -92,15 +90,6 @@ open class MessagesStateProvider : PreviewParameterProvider { composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())) ), - aMessagesState( - composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), - historyVisibleState = aHistoryVisibleState(showAlert = true) - ), - aMessagesState( - composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), - identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())), - historyVisibleState = aHistoryVisibleState(showAlert = true) - ) ) } @@ -121,7 +110,6 @@ fun aMessagesState( ), timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(), identityChangeState: IdentityChangeState = anIdentityChangeState(), - historyVisibleState: HistoryVisibleState = aHistoryVisibleState(), linkState: LinkState = aLinkState(), readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(), actionListState: ActionListState = anActionListState(), @@ -132,8 +120,9 @@ fun aMessagesState( pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(), dmUserVerificationState: IdentityState? = null, roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(), + showSharedHistoryIcon: Boolean = false, successorRoom: SuccessorRoom? = null, - eventSink: (MessagesEvents) -> Unit = {}, + eventSink: (MessagesEvent) -> Unit = {}, ) = MessagesState( roomId = RoomId("!id:domain"), roomName = roomName, @@ -144,7 +133,6 @@ fun aMessagesState( voiceMessageComposerState = voiceMessageComposerState, timelineProtectionState = timelineProtectionState, identityChangeState = identityChangeState, - historyVisibleState = historyVisibleState, linkState = linkState, timelineState = timelineState, readReceiptBottomSheetState = readReceiptBottomSheetState, @@ -160,6 +148,7 @@ fun aMessagesState( pinnedMessagesBannerState = pinnedMessagesBannerState, dmUserVerificationState = dmUserVerificationState, roomMemberModerationState = roomMemberModerationState, + showSharedHistoryIcon = showSharedHistoryIcon, successorRoom = successorRoom, eventSink = eventSink, ) @@ -187,7 +176,7 @@ fun aUserEventPermissions( fun aReactionSummaryState( target: ReactionSummaryState.Summary? = null, - eventSink: (ReactionSummaryEvents) -> Unit = {} + eventSink: (ReactionSummaryEvent) -> Unit = {} ) = ReactionSummaryState( target = target, eventSink = eventSink, @@ -196,7 +185,7 @@ fun aReactionSummaryState( fun aCustomReactionState( target: CustomReactionState.Target = CustomReactionState.Target.None, recentEmojis: ImmutableList = persistentListOf(), - eventSink: (CustomReactionEvents) -> Unit = {}, + eventSink: (CustomReactionEvent) -> Unit = {}, ) = CustomReactionState( target = target, recentEmojis = recentEmojis, @@ -206,7 +195,7 @@ fun aCustomReactionState( fun aReadReceiptBottomSheetState( selectedEvent: TimelineItem.Event? = null, - eventSink: (ReadReceiptBottomSheetEvents) -> Unit = {}, + eventSink: (ReadReceiptBottomSheetEvent) -> Unit = {}, ) = ReadReceiptBottomSheetState( selectedEvent = selectedEvent, eventSink = eventSink, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index ea50cf0fe2..5d4ccf2d78 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -51,12 +52,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent -import io.element.android.features.messages.impl.actionlist.ActionListEvents +import io.element.android.features.messages.impl.actionlist.ActionListEvent import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction -import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStateView import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView -import io.element.android.features.messages.impl.link.LinkEvents +import io.element.android.features.messages.impl.link.LinkEvent import io.element.android.features.messages.impl.link.LinkView import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet import io.element.android.features.messages.impl.messagecomposer.DisabledComposerView @@ -67,18 +67,18 @@ import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBan import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineView import io.element.android.features.messages.impl.timeline.aGroupedEvents import io.element.android.features.messages.impl.timeline.aTimelineItemDaySeparator import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet -import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents -import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents +import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent +import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheet -import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvent import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent @@ -168,7 +168,7 @@ fun MessagesView( Timber.v("OnMessageLongClicked= ${event.id}") hidingKeyboard { state.actionListState.eventSink( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = event, userEventPermissions = state.userEventPermissions, ) @@ -177,20 +177,20 @@ fun MessagesView( } fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) { - state.eventSink(MessagesEvents.HandleAction(action, event)) + state.eventSink(MessagesEvent.HandleAction(action, event)) } fun onEmojiReactionClick(emoji: String, event: TimelineItem.Event) { - state.eventSink(MessagesEvents.ToggleReaction(emoji, event.eventOrTransactionId)) + state.eventSink(MessagesEvent.ToggleReaction(emoji, event.eventOrTransactionId)) } fun onEmojiReactionLongClick(emoji: String, event: TimelineItem.Event) { if (event.eventId == null) return - state.reactionSummaryState.eventSink(ReactionSummaryEvents.ShowReactionSummary(event.eventId, event.reactionsState.reactions, emoji)) + state.reactionSummaryState.eventSink(ReactionSummaryEvent.ShowReactionSummary(event.eventId, event.reactionsState.reactions, emoji)) } fun onMoreReactionsClick(event: TimelineItem.Event) { - state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) + state.customReactionState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event)) } val expandableState = rememberExpandableBottomSheetLayoutState() @@ -225,6 +225,7 @@ fun MessagesView( heroes = state.heroes, roomCallState = state.roomCallState, dmUserIdentityState = state.dmUserVerificationState, + showSharedHistoryIcon = state.showSharedHistoryIcon, onBackClick = { hidingKeyboard { onBackClick() } }, onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } }, onJoinCallClick = onJoinCallClick, @@ -243,7 +244,7 @@ fun MessagesView( onMessageLongClick = ::onMessageLongClick, onUserDataClick = { hidingKeyboard { - state.eventSink(MessagesEvents.OnUserClicked(it)) + state.eventSink(MessagesEvent.OnUserClicked(it)) } }, onLinkClick = { link, customTab -> @@ -251,19 +252,19 @@ fun MessagesView( onLinkClick(link.url, true) // Do not check those links, they are internal link only } else { - state.linkState.eventSink(LinkEvents.OnLinkClick(link)) + state.linkState.eventSink(LinkEvent.OnLinkClick(link)) } }, onReactionClick = ::onEmojiReactionClick, onReactionLongClick = ::onEmojiReactionLongClick, onMoreReactionsClick = ::onMoreReactionsClick, onReadReceiptClick = { event -> - state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(event)) + state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvent.EventSelected(event)) }, onSendLocationClick = onSendLocationClick, onCreatePollClick = onCreatePollClick, onSwipeToReply = { targetEvent -> - state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent)) + state.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, targetEvent)) }, forceJumpToBottomVisibility = forceJumpToBottomVisibility, onJoinCallClick = onJoinCallClick, @@ -300,7 +301,7 @@ fun MessagesView( state = state, onLinkClick = { url, customTab -> onLinkClick(url, customTab) }, onRoomSuccessorClick = { roomId -> - state.timelineState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId = roomId)) + state.timelineState.eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(roomId = roomId)) }, ) }, @@ -341,22 +342,43 @@ fun MessagesView( maxBottomSheetContentHeight = maxComposerHeightPx.toDp(), ) + var endPollConfirmingEvent: TimelineItem.Event? by remember { mutableStateOf(null) } + + if (endPollConfirmingEvent != null) { + ConfirmationDialog( + content = stringResource(id = CommonStrings.common_poll_end_confirmation), + onSubmitClick = { + endPollConfirmingEvent?.let { event -> + onActionSelected(TimelineItemAction.EndPoll, event) + } + endPollConfirmingEvent = null + }, + onDismiss = { endPollConfirmingEvent = null }, + ) + } + ActionListView( state = state.actionListState, - onSelectAction = ::onActionSelected, + onSelectAction = { action: TimelineItemAction, event: TimelineItem.Event -> + if (action == TimelineItemAction.EndPoll) { + endPollConfirmingEvent = event + } else { + onActionSelected(action, event) + } + }, onCustomReactionClick = { event -> - state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) + state.customReactionState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event)) }, onEmojiReactionClick = ::onEmojiReactionClick, onVerifiedUserSendFailureClick = { event -> - state.timelineState.eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event)) + state.timelineState.eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event)) }, ) CustomReactionBottomSheet( state = state.customReactionState, onSelectEmoji = { uniqueId, emoji -> - state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, uniqueId)) + state.eventSink(MessagesEvent.ToggleReaction(emoji.unicode, uniqueId)) } ) @@ -382,8 +404,8 @@ private fun ReinviteDialog(state: MessagesState) { content = stringResource(id = R.string.screen_room_invite_again_alert_message), cancelText = stringResource(id = CommonStrings.action_cancel), submitText = stringResource(id = CommonStrings.action_invite), - onSubmitClick = { state.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) }, - onDismiss = { state.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) } + onSubmitClick = { state.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite)) }, + onDismiss = { state.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Cancel)) } ) } } @@ -467,7 +489,7 @@ private fun MessagesViewContent( ) { fun focusOnPinnedEvent(eventId: EventId) { state.timelineState.eventSink( - TimelineEvents.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds) + TimelineEvent.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds) ) } PinnedMessagesBannerView( @@ -497,17 +519,10 @@ private fun MessagesViewComposerBottomSheetContents( // Do not show the identity change if user is composing a Rich message or is seeing suggestion(s). if (state.composerState.suggestions.isEmpty() && state.composerState.textEditorState is TextEditorState.Markdown) { - if (state.identityChangeState.roomMemberIdentityStateChanges.isNotEmpty()) { - IdentityChangeStateView( - state = state.identityChangeState, - onLinkClick = onLinkClick, - ) - } else { - HistoryVisibleStateView( - state = state.historyVisibleState, - onLinkClick = onLinkClick, - ) - } + IdentityChangeStateView( + state = state.identityChangeState, + onLinkClick = onLinkClick, + ) } val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull { it.identityState == IdentityState.VerificationViolation diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvent.kt similarity index 85% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvent.kt index 415a80a48b..df6b1486b8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvent.kt @@ -11,10 +11,10 @@ package io.element.android.features.messages.impl.actionlist import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.timeline.model.TimelineItem -sealed interface ActionListEvents { - data object Clear : ActionListEvents +sealed interface ActionListEvent { + data object Clear : ActionListEvent data class ComputeForMessage( val event: TimelineItem.Event, val userEventPermissions: UserEventPermissions, - ) : ActionListEvents + ) : ActionListEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 36166409b9..f9d97f6d9d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -107,10 +107,10 @@ class DefaultActionListPresenter( val isThreadsEnabled = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false) - fun handleEvent(event: ActionListEvents) { + fun handleEvent(event: ActionListEvent) { when (event) { - ActionListEvents.Clear -> target.value = ActionListState.Target.None - is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage( + ActionListEvent.Clear -> target.value = ActionListState.Target.None + is ActionListEvent.ComputeForMessage -> localCoroutineScope.computeForMessage( timelineItem = event.event, usersEventPermissions = event.userEventPermissions, isDeveloperModeEnabled = isDeveloperModeEnabled, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt index c0554aaf0c..179c5e4fa0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt @@ -16,7 +16,7 @@ import kotlinx.collections.immutable.ImmutableList data class ActionListState( val target: Target, - val eventSink: (ActionListEvents) -> Unit, + val eventSink: (ActionListEvent) -> Unit, ) { @Immutable sealed interface Target { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index e57e5bdc1e..a69c6d7612 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -192,7 +192,7 @@ open class ActionListStateProvider : PreviewParameterProvider { fun anActionListState( target: ActionListState.Target = ActionListState.Target.None, - eventSink: (ActionListEvents) -> Unit = {}, + eventSink: (ActionListEvent) -> Unit = {}, ) = ActionListState( target = target, eventSink = eventSink diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index 704f79a4f5..53f15066b6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -118,7 +118,7 @@ fun ActionListView( ) { if (targetItem == null) return sheetState.hide(coroutineScope) { - state.eventSink(ActionListEvents.Clear) + state.eventSink(ActionListEvent.Clear) onSelectAction(itemAction, targetItem) } } @@ -126,7 +126,7 @@ fun ActionListView( fun onEmojiReactionClick(emoji: String) { if (targetItem == null) return sheetState.hide(coroutineScope) { - state.eventSink(ActionListEvents.Clear) + state.eventSink(ActionListEvent.Clear) onEmojiReactionClick(emoji, targetItem) } } @@ -134,19 +134,19 @@ fun ActionListView( fun onCustomReactionClick() { if (targetItem == null) return sheetState.hide(coroutineScope) { - state.eventSink(ActionListEvents.Clear) + state.eventSink(ActionListEvent.Clear) onCustomReactionClick(targetItem) } } fun onDismiss() { - state.eventSink(ActionListEvents.Clear) + state.eventSink(ActionListEvent.Clear) } fun onVerifiedUserSendFailureClick() { if (targetItem == null) return sheetState.hide(coroutineScope) { - state.eventSink(ActionListEvents.Clear) + state.eventSink(ActionListEvent.Clear) onVerifiedUserSendFailureClick(targetItem) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvent.kt similarity index 69% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvent.kt index d8e29de92e..d473d4c3f4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvent.kt @@ -8,8 +8,8 @@ package io.element.android.features.messages.impl.attachments.preview -sealed interface AttachmentsPreviewEvents { - data object SendAttachment : AttachmentsPreviewEvents - data object CancelAndDismiss : AttachmentsPreviewEvents - data object CancelAndClearSendState : AttachmentsPreviewEvents +sealed interface AttachmentsPreviewEvent { + data object SendAttachment : AttachmentsPreviewEvent + data object CancelAndDismiss : AttachmentsPreviewEvent + data object CancelAndClearSendState : AttachmentsPreviewEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index f7641ec21b..8e92e53f6a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -98,7 +98,7 @@ class AttachmentsPreviewPresenter( val mediaOptimizationSelectorPresenter = remember { mediaOptimizationSelectorPresenterFactory.create(mediaAttachment.localMedia) } - val mediaOptimizationSelectorState = mediaOptimizationSelectorPresenter.present() + val mediaOptimizationSelectorState by rememberUpdatedState(mediaOptimizationSelectorPresenter.present()) val observableSendState = snapshotFlow { sendActionState.value } @@ -140,9 +140,9 @@ class AttachmentsPreviewPresenter( } } - fun handleEvent(event: AttachmentsPreviewEvents) { + fun handleEvent(event: AttachmentsPreviewEvent) { when (event) { - is AttachmentsPreviewEvents.SendAttachment -> { + is AttachmentsPreviewEvent.SendAttachment -> { ongoingSendAttachmentJob.value = coroutineScope.launch { // If the media optimization selector is displayed, we need to wait for the user to select the options // before we can pre-process the media. @@ -191,7 +191,7 @@ class AttachmentsPreviewPresenter( } } } - AttachmentsPreviewEvents.CancelAndDismiss -> { + AttachmentsPreviewEvent.CancelAndDismiss -> { displayFileTooLargeError = false // Cancel media preprocessing and sending @@ -206,7 +206,7 @@ class AttachmentsPreviewPresenter( sendActionState, ) } - AttachmentsPreviewEvents.CancelAndClearSendState -> { + AttachmentsPreviewEvent.CancelAndClearSendState -> { // Cancel media sending ongoingSendAttachmentJob.value?.let { it.cancel() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt index 42d01a4f21..97ca230d77 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt @@ -20,7 +20,7 @@ data class AttachmentsPreviewState( val textEditorState: TextEditorState, val mediaOptimizationSelectorState: MediaOptimizationSelectorState, val displayFileTooLargeError: Boolean, - val eventSink: (AttachmentsPreviewEvents) -> Unit + val eventSink: (AttachmentsPreviewEvent) -> Unit, ) @Immutable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt index 8f55957e32..bb3a07c2ff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt @@ -82,15 +82,15 @@ fun AttachmentsPreviewView( modifier: Modifier = Modifier, ) { fun postSendAttachment() { - state.eventSink(AttachmentsPreviewEvents.SendAttachment) + state.eventSink(AttachmentsPreviewEvent.SendAttachment) } fun postCancel() { - state.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) + state.eventSink(AttachmentsPreviewEvent.CancelAndDismiss) } fun postClearSendState() { - state.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) + state.eventSink(AttachmentsPreviewEvent.CancelAndClearSendState) } BackHandler(enabled = state.sendActionState !is SendActionState.Sending.Uploading && state.sendActionState !is SendActionState.Done) { @@ -199,7 +199,7 @@ private fun AttachmentPreviewContent( AlertDialog( title = stringResource(CommonStrings.dialog_file_too_large_to_upload_title), content = content, - onDismiss = { state.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) }, + onDismiss = { state.eventSink(AttachmentsPreviewEvent.CancelAndDismiss) }, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleAcknowledgementRepository.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleAcknowledgementRepository.kt deleted file mode 100644 index 1fa992fc3e..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleAcknowledgementRepository.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.edit -import dev.zacsweers.metro.ContributesBinding -import io.element.android.libraries.androidutils.hash.hash -import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map - -interface HistoryVisibleAcknowledgementRepository { - fun hasAcknowledged(roomId: RoomId): Flow - suspend fun setAcknowledged(roomId: RoomId, value: Boolean) -} - -@ContributesBinding(SessionScope::class) -class DefaultHistoryVisibleAcknowledgementRepository( - sessionId: SessionId, - preferenceDataStoreFactory: PreferenceDataStoreFactory, -) : HistoryVisibleAcknowledgementRepository { - val store = - sessionId.value.hash().take(16).let { hash -> - preferenceDataStoreFactory.create("elementx_historyvisible_$hash") - } - - override fun hasAcknowledged(roomId: RoomId): Flow { - return store.data.map { prefs -> - val acknowledged = prefs[booleanPreferencesKey(roomId.value)] ?: false - acknowledged - } - } - - override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) { - store.edit { prefs -> - prefs[booleanPreferencesKey(roomId.value)] = value - } - } -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleEvent.kt deleted file mode 100644 index 775d9c00d4..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleEvent.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -sealed interface HistoryVisibleEvent { - data object Acknowledge : HistoryVisibleEvent -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleState.kt deleted file mode 100644 index 3f980eb086..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleState.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -data class HistoryVisibleState( - val showAlert: Boolean, - val eventSink: (HistoryVisibleEvent) -> Unit, -) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenter.kt deleted file mode 100644 index e79681cd5c..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenter.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import dev.zacsweers.metro.Inject -import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -@Inject -class HistoryVisibleStatePresenter( - private val featureFlagService: FeatureFlagService, - private val repository: HistoryVisibleAcknowledgementRepository, - private val room: JoinedRoom, -) : Presenter { - @Composable - override fun present(): HistoryVisibleState { - val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false) - val roomInfo by room.roomInfoFlow.collectAsState() - // Implicitly assume the alert is initially acknowledged to avoid flashes in UI. - val acknowledged by repository.hasAcknowledged(room.roomId).collectAsState(initial = true) - val isHistoryVisible = roomInfo.historyVisibility == RoomHistoryVisibility.Shared || roomInfo.historyVisibility == RoomHistoryVisibility.WorldReadable - - val coroutineScope = rememberCoroutineScope() - - LaunchedEffect(isHistoryVisible, acknowledged) { - if (!isHistoryVisible && acknowledged) { - // Clear the dismissed flag, if it is set to ensure that if a room is changed public -> private -> public, - // we show the banner again when it is set back to public. - repository.setAcknowledged(room.roomId, false) - } - } - - fun handleEvent(event: HistoryVisibleEvent) { - when (event) { - is HistoryVisibleEvent.Acknowledge -> coroutineScope.setAcknowledged(room.roomId, true) - } - } - - return HistoryVisibleState( - showAlert = isFeatureEnabled && isHistoryVisible && roomInfo.isEncrypted == true && !acknowledged, - eventSink = ::handleEvent, - ) - } - - private fun CoroutineScope.setAcknowledged(roomId: RoomId, value: Boolean) = launch { - repository.setAcknowledged(roomId, value) - } -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateProvider.kt deleted file mode 100644 index 752abdc76b..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider - -class HistoryVisibleStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aHistoryVisibleState(showAlert = true), - ) -} - -internal fun aHistoryVisibleState( - showAlert: Boolean = false, - eventSink: (HistoryVisibleEvent) -> Unit = {}, -) = HistoryVisibleState( - showAlert, - eventSink = eventSink, -) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateView.kt deleted file mode 100644 index d0655f695d..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateView.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.appconfig.LearnMoreConfig -import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertLevel -import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.text.stringWithLink -import io.element.android.libraries.ui.strings.CommonStrings - -@Composable -fun HistoryVisibleStateView( - state: HistoryVisibleState, - onLinkClick: (String, Boolean) -> Unit, - modifier: Modifier = Modifier, -) { - if (!state.showAlert) { - return - } - ComposerAlertMolecule( - modifier = modifier, - avatar = null, - showIcon = true, - level = ComposerAlertLevel.Info, - content = stringWithLink( - textRes = CommonStrings.crypto_history_visible, - url = LearnMoreConfig.HISTORY_VISIBLE_URL, - onLinkClick = { url -> onLinkClick(url, true) }, - ), - submitText = stringResource(CommonStrings.action_dismiss), - onSubmitClick = { state.eventSink(HistoryVisibleEvent.Acknowledge) }, - ) -} - -@PreviewsDayNight -@Composable -internal fun HistoryVisibleStateViewPreview( - @PreviewParameter(HistoryVisibleStateProvider::class) state: HistoryVisibleState, -) = ElementPreview { - HistoryVisibleStateView( - state = state, - onLinkClick = { _, _ -> }, - ) -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/MessagesViewWithHistoryVisiblePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/MessagesViewWithHistoryVisiblePreview.kt deleted file mode 100644 index 07cf5170d3..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/MessagesViewWithHistoryVisiblePreview.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -import androidx.compose.runtime.Composable -import io.element.android.features.messages.impl.MessagesView -import io.element.android.features.messages.impl.aMessagesState -import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown - -@PreviewsDayNight -@Composable -internal fun MessagesViewWithHistoryVisiblePreview() = ElementPreview { - MessagesView( - state = aMessagesState( - composerState = aMessageComposerState( - textEditorState = aTextEditorStateMarkdown( - initialText = "", - initialFocus = false, - ) - ), - historyVisibleState = aHistoryVisibleState(showAlert = true), - ), - onBackClick = {}, - onRoomDetailsClick = {}, - onEventContentClick = { _, _ -> false }, - onUserDataClick = {}, - onLinkClick = { _, _ -> }, - onSendLocationClick = {}, - onCreatePollClick = {}, - onJoinCallClick = {}, - onViewAllPinnedMessagesClick = {}, - knockRequestsBannerView = {} - ) -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvent.kt similarity index 70% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvent.kt index 242dc58453..607d7b423b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvent.kt @@ -10,12 +10,12 @@ package io.element.android.features.messages.impl.crypto.sendfailure.resolve import io.element.android.features.messages.impl.timeline.model.TimelineItem -sealed interface ResolveVerifiedUserSendFailureEvents { +sealed interface ResolveVerifiedUserSendFailureEvent { data class ComputeForMessage( val messageEvent: TimelineItem.Event, - ) : ResolveVerifiedUserSendFailureEvents + ) : ResolveVerifiedUserSendFailureEvent - data object ResolveAndResend : ResolveVerifiedUserSendFailureEvents - data object Retry : ResolveVerifiedUserSendFailureEvents - data object Dismiss : ResolveVerifiedUserSendFailureEvents + data object ResolveAndResend : ResolveVerifiedUserSendFailureEvent + data object Retry : ResolveVerifiedUserSendFailureEvent + data object Dismiss : ResolveVerifiedUserSendFailureEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt index 720e6762f7..e66bb34275 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt @@ -48,9 +48,9 @@ class ResolveVerifiedUserSendFailurePresenter( } val coroutineScope = rememberCoroutineScope() - fun handleEvent(event: ResolveVerifiedUserSendFailureEvents) { + fun handleEvent(event: ResolveVerifiedUserSendFailureEvent) { when (event) { - is ResolveVerifiedUserSendFailureEvents.ComputeForMessage -> { + is ResolveVerifiedUserSendFailureEvent.ComputeForMessage -> { val sendState = event.messageEvent.localSendState as? LocalEventSendState.Failed.VerifiedUser val transactionId = event.messageEvent.transactionId val sendHandle = event.messageEvent.sendhandle @@ -65,10 +65,10 @@ class ResolveVerifiedUserSendFailurePresenter( null } } - ResolveVerifiedUserSendFailureEvents.Dismiss -> { + ResolveVerifiedUserSendFailureEvent.Dismiss -> { resolver = null } - ResolveVerifiedUserSendFailureEvents.Retry -> { + ResolveVerifiedUserSendFailureEvent.Retry -> { coroutineScope.launch { resolver?.run { runUpdatingState(retryAction) { @@ -77,7 +77,7 @@ class ResolveVerifiedUserSendFailurePresenter( } } } - ResolveVerifiedUserSendFailureEvents.ResolveAndResend -> { + ResolveVerifiedUserSendFailureEvent.ResolveAndResend -> { coroutineScope.launch { resolver?.run { runUpdatingState(resolveAction) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt index dfe9ac36de..7a42a06a5b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt @@ -15,5 +15,5 @@ data class ResolveVerifiedUserSendFailureState( val verifiedUserSendFailure: VerifiedUserSendFailure, val resolveAction: AsyncAction, val retryAction: AsyncAction, - val eventSink: (ResolveVerifiedUserSendFailureEvents) -> Unit + val eventSink: (ResolveVerifiedUserSendFailureEvent) -> Unit ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt index 251f0e6564..e167ac3221 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt @@ -29,7 +29,7 @@ fun aResolveVerifiedUserSendFailureState( verifiedUserSendFailure: VerifiedUserSendFailure = VerifiedUserSendFailure.None, resolveAction: AsyncAction = AsyncAction.Uninitialized, retryAction: AsyncAction = AsyncAction.Uninitialized, - eventSink: (ResolveVerifiedUserSendFailureEvents) -> Unit = {} + eventSink: (ResolveVerifiedUserSendFailureEvent) -> Unit = {} ) = ResolveVerifiedUserSendFailureState( verifiedUserSendFailure = verifiedUserSendFailure, resolveAction = resolveAction, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt index 3f881e7bb9..98e2bba3be 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt @@ -47,15 +47,15 @@ fun ResolveVerifiedUserSendFailureView( var showSheet by remember { mutableStateOf(false) } fun dismiss() { - state.eventSink(ResolveVerifiedUserSendFailureEvents.Dismiss) + state.eventSink(ResolveVerifiedUserSendFailureEvent.Dismiss) } fun onRetryClick() { - state.eventSink(ResolveVerifiedUserSendFailureEvents.Retry) + state.eventSink(ResolveVerifiedUserSendFailureEvent.Retry) } fun onResolveAndResendClick() { - state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } LaunchedEffect(state.verifiedUserSendFailure) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt index a88dbb1b49..a345e09fa2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt @@ -11,8 +11,6 @@ package io.element.android.features.messages.impl.di import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.Binds import dev.zacsweers.metro.ContributesTo -import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState -import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStatePresenter import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter @@ -63,7 +61,4 @@ interface MessagesBindsModule { @Binds fun bindIdentityChangeStatePresenter(presenter: IdentityChangeStatePresenter): Presenter - - @Binds - fun bindHistoryVisibleStatePresenter(presenter: HistoryVisibleStatePresenter): Presenter } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvent.kt similarity index 68% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvent.kt index ce817bf642..fb94eaf420 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvent.kt @@ -10,8 +10,8 @@ package io.element.android.features.messages.impl.link import io.element.android.wysiwyg.link.Link -sealed interface LinkEvents { - data class OnLinkClick(val link: Link) : LinkEvents - data object Confirm : LinkEvents - data object Cancel : LinkEvents +sealed interface LinkEvent { + data class OnLinkClick(val link: Link) : LinkEvent + data object Confirm : LinkEvent + data object Cancel : LinkEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt index b6886652d5..572209f463 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt @@ -25,9 +25,9 @@ class LinkPresenter( override fun present(): LinkState { val linkClick: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvent(event: LinkEvents) { + fun handleEvent(event: LinkEvent) { when (event) { - is LinkEvents.OnLinkClick -> { + is LinkEvent.OnLinkClick -> { linkClick.value = AsyncAction.Loading val result = linkChecker.isSafe(event.link) if (result) { @@ -37,12 +37,12 @@ class LinkPresenter( linkClick.value = ConfirmingLinkClick(event.link) } } - LinkEvents.Confirm -> { + LinkEvent.Confirm -> { linkClick.value = (linkClick.value as? ConfirmingLinkClick) ?.let { AsyncAction.Success(it.link) } ?: AsyncAction.Uninitialized } - LinkEvents.Cancel -> { + LinkEvent.Cancel -> { linkClick.value = AsyncAction.Uninitialized } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt index c06d23ef9a..20b5cc7fe3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt @@ -13,5 +13,5 @@ import io.element.android.wysiwyg.link.Link data class LinkState( val linkClick: AsyncAction, - val eventSink: (LinkEvents) -> Unit, + val eventSink: (LinkEvent) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt index 8388cbea52..2e021ec276 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt @@ -29,7 +29,7 @@ open class LinkStateProvider : PreviewParameterProvider { fun aLinkState( linkClick: AsyncAction = AsyncAction.Uninitialized, - eventSink: (LinkEvents) -> Unit = {}, + eventSink: (LinkEvent) -> Unit = {}, ) = LinkState( linkClick = linkClick, eventSink = eventSink, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt index 1a7558de05..69e6ee8673 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt @@ -46,10 +46,10 @@ fun LinkView( ), submitText = stringResource(CommonStrings.action_continue), onSubmitClick = { - state.eventSink(LinkEvents.Confirm) + state.eventSink(LinkEvent.Confirm) }, onDismiss = { - state.eventSink(LinkEvents.Cancel) + state.eventSink(LinkEvent.Cancel) }, ) } @@ -58,7 +58,7 @@ fun LinkView( val latestOnLinkValid by rememberUpdatedState(onLinkValid) LaunchedEffect(state.linkClick.data) { latestOnLinkValid(state.linkClick.data) - state.eventSink(LinkEvents.Cancel) + state.eventSink(LinkEvent.Cancel) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvent.kt similarity index 74% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvent.kt index d4926404fe..42363c952d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvent.kt @@ -8,6 +8,6 @@ package io.element.android.features.messages.impl.pinned.banner -sealed interface PinnedMessagesBannerEvents { - data object MoveToNextPinned : PinnedMessagesBannerEvents +sealed interface PinnedMessagesBannerEvent { + data object MoveToNextPinned : PinnedMessagesBannerEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt index eada4b06d7..088959ea18 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt @@ -58,9 +58,9 @@ class PinnedMessagesBannerPresenter( }, ) - fun handleEvent(event: PinnedMessagesBannerEvents) { + fun handleEvent(event: PinnedMessagesBannerEvent) { when (event) { - is PinnedMessagesBannerEvents.MoveToNextPinned -> { + is PinnedMessagesBannerEvent.MoveToNextPinned -> { val loadedCount = pinnedItems.value.dataOrNull().orEmpty().size currentPinnedMessageIndex = (currentPinnedMessageIndex - 1).mod(loadedCount) } @@ -80,7 +80,7 @@ class PinnedMessagesBannerPresenter( expectedPinnedMessagesCount: Int, pinnedItems: AsyncData>, currentPinnedMessageIndex: Int, - eventSink: (PinnedMessagesBannerEvents) -> Unit + eventSink: (PinnedMessagesBannerEvent) -> Unit ): PinnedMessagesBannerState { return when (pinnedItems) { is AsyncData.Failure, is AsyncData.Uninitialized -> PinnedMessagesBannerState.Hidden diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt index 0ed43376ff..b6deb2edb4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt @@ -41,6 +41,6 @@ sealed interface PinnedMessagesBannerState { val currentPinnedMessage: PinnedMessagesBannerItem, val currentPinnedMessageIndex: Int, val loadedPinnedMessagesCount: Int, - val eventSink: (PinnedMessagesBannerEvents) -> Unit + val eventSink: (PinnedMessagesBannerEvent) -> Unit ) : Visible } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt index ad7713e6f3..2a2c30bf6b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt @@ -50,7 +50,7 @@ internal fun aLoadedPinnedMessagesBannerState( eventId = EventId("\$" + Random.nextInt().toString()), formatted = AnnotatedString(message) ), - eventSink: (PinnedMessagesBannerEvents) -> Unit = {} + eventSink: (PinnedMessagesBannerEvent) -> Unit = {} ) = PinnedMessagesBannerState.Loaded( currentPinnedMessage = currentPinnedMessage, currentPinnedMessageIndex = currentPinnedMessageIndex, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt index ee44f7b95d..21b89da5d1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt @@ -96,7 +96,7 @@ private fun PinnedMessagesBannerRow( if (state is PinnedMessagesBannerState.Loaded) { analyticsService.captureInteraction(Interaction.Name.PinnedMessageBannerClick) onClick(state.currentPinnedMessage.eventId) - state.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) + state.eventSink(PinnedMessagesBannerEvent.MoveToNextPinned) } }, verticalAlignment = Alignment.CenterVertically, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvent.kt similarity index 84% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvent.kt index 4a3dcf6146..6ad4fbefe6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvent.kt @@ -11,6 +11,6 @@ package io.element.android.features.messages.impl.pinned.list import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.model.TimelineItem -sealed interface PinnedMessagesListEvents { - data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : PinnedMessagesListEvents +sealed interface PinnedMessagesListEvent { + data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : PinnedMessagesListEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index b2d2caa7f9..f884cdac84 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -134,9 +134,9 @@ class PinnedMessagesListPresenter( } ) - fun handleEvent(event: PinnedMessagesListEvents) { + fun handleEvent(event: PinnedMessagesListEvent) { when (event) { - is PinnedMessagesListEvents.HandleAction -> sessionCoroutineScope.handleTimelineAction(event.action, event.event) + is PinnedMessagesListEvent.HandleAction -> sessionCoroutineScope.handleTimelineAction(event.action, event.event) } } @@ -232,7 +232,7 @@ class PinnedMessagesListPresenter( linkState: LinkState, userEventPermissions: UserEventPermissions, timelineItems: AsyncData>, - eventSink: (PinnedMessagesListEvents) -> Unit + eventSink: (PinnedMessagesListEvent) -> Unit ): PinnedMessagesListState { return when (timelineItems) { AsyncData.Uninitialized, is AsyncData.Loading -> PinnedMessagesListState.Loading diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt index c62d293ce8..bf37029eff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt @@ -35,7 +35,7 @@ sealed interface PinnedMessagesListState { val actionListState: ActionListState, val linkState: LinkState, val displayThreadSummaries: Boolean, - val eventSink: (PinnedMessagesListEvents) -> Unit, + val eventSink: (PinnedMessagesListEvent) -> Unit, ) : PinnedMessagesListState { val loadedPinnedMessagesCount = timelineItems.count { timelineItem -> timelineItem is TimelineItem.Event } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt index a3ed06c53f..bfa9820a44 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt @@ -94,7 +94,7 @@ fun aLoadedPinnedMessagesListState( actionListState: ActionListState = anActionListState(), aUserEventPermissions: UserEventPermissions = UserEventPermissions.DEFAULT, displayThreadSummaries: Boolean = false, - eventSink: (PinnedMessagesListEvents) -> Unit = {} + eventSink: (PinnedMessagesListEvent) -> Unit = {} ) = PinnedMessagesListState.Filled( timelineRoomInfo = timelineRoomInfo, timelineProtectionState = timelineProtectionState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index 18175f12c9..b125bdcf6f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -25,10 +25,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import im.vector.app.features.analytics.plan.Interaction import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.messages.impl.actionlist.ActionListEvents +import io.element.android.features.messages.impl.actionlist.ActionListEvent import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction -import io.element.android.features.messages.impl.link.LinkEvents +import io.element.android.features.messages.impl.link.LinkEvent import io.element.android.features.messages.impl.link.LinkView import io.element.android.features.messages.impl.timeline.components.TimelineItemRow import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView @@ -175,10 +175,10 @@ private fun PinnedMessagesListLoaded( ) { fun onActionSelected(timelineItemAction: TimelineItemAction, event: TimelineItem.Event) { state.actionListState.eventSink( - ActionListEvents.Clear + ActionListEvent.Clear ) state.eventSink( - PinnedMessagesListEvents.HandleAction( + PinnedMessagesListEvent.HandleAction( action = timelineItemAction, event = event, ) @@ -187,7 +187,7 @@ private fun PinnedMessagesListLoaded( fun onMessageLongClick(event: TimelineItem.Event) { state.actionListState.eventSink( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = event, userEventPermissions = state.userEventPermissions, ) @@ -222,7 +222,7 @@ private fun PinnedMessagesListLoaded( focusedEventId = null, onUserDataClick = onUserDataClick, onLinkClick = { link -> - state.linkState.eventSink(LinkEvents.OnLinkClick(link)) + state.linkState.eventSink(LinkEvent.OnLinkClick(link)) }, onLinkLongClick = onLinkLongClick, onContentClick = onEventClick, @@ -243,7 +243,7 @@ private fun PinnedMessagesListLoaded( onContentClick = { onEventClick(event) }, onLongClick = { onMessageLongClick(event) }, onLinkClick = { link -> - state.linkState.eventSink(LinkEvents.OnLinkClick(link)) + state.linkState.eventSink(LinkEvent.OnLinkClick(link)) }, onLinkLongClick = onLinkLongClick, modifier = contentModifier, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvent.kt similarity index 65% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvent.kt index 966b83d61b..d60029aed6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvent.kt @@ -8,9 +8,9 @@ package io.element.android.features.messages.impl.report -sealed interface ReportMessageEvents { - data class UpdateReason(val reason: String) : ReportMessageEvents - data object ToggleBlockUser : ReportMessageEvents - data object Report : ReportMessageEvents - data object ClearError : ReportMessageEvents +sealed interface ReportMessageEvent { + data class UpdateReason(val reason: String) : ReportMessageEvent + data object ToggleBlockUser : ReportMessageEvent + data object Report : ReportMessageEvent + data object ClearError : ReportMessageEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt index 5dee4f5b79..25a167f2eb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt @@ -54,12 +54,12 @@ class ReportMessagePresenter( var blockUser by rememberSaveable { mutableStateOf(false) } var result: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvent(event: ReportMessageEvents) { + fun handleEvent(event: ReportMessageEvent) { when (event) { - is ReportMessageEvents.UpdateReason -> reason = event.reason - ReportMessageEvents.ToggleBlockUser -> blockUser = !blockUser - ReportMessageEvents.Report -> coroutineScope.report(inputs.eventId, inputs.senderId, reason, blockUser, result) - ReportMessageEvents.ClearError -> result.value = AsyncAction.Uninitialized + is ReportMessageEvent.UpdateReason -> reason = event.reason + ReportMessageEvent.ToggleBlockUser -> blockUser = !blockUser + ReportMessageEvent.Report -> coroutineScope.report(inputs.eventId, inputs.senderId, reason, blockUser, result) + ReportMessageEvent.ClearError -> result.value = AsyncAction.Uninitialized } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt index 38eee9d746..efec4ef998 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt @@ -14,5 +14,5 @@ data class ReportMessageState( val reason: String, val blockUser: Boolean, val result: AsyncAction, - val eventSink: (ReportMessageEvents) -> Unit + val eventSink: (ReportMessageEvent) -> Unit ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt index a2c398776f..dde3f13558 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt @@ -57,7 +57,7 @@ fun ReportMessageView( progressDialog = {}, onSuccess = { onBackClick() }, errorMessage = { stringResource(CommonStrings.error_unknown) }, - onErrorDismiss = { state.eventSink(ReportMessageEvents.ClearError) } + onErrorDismiss = { state.eventSink(ReportMessageEvent.ClearError) } ) Scaffold( @@ -84,7 +84,7 @@ fun ReportMessageView( TextField( value = state.reason, - onValueChange = { state.eventSink(ReportMessageEvents.UpdateReason(it)) }, + onValueChange = { state.eventSink(ReportMessageEvent.UpdateReason(it)) }, placeholder = stringResource(R.string.screen_report_content_hint), minLines = 3, enabled = !isSending, @@ -113,7 +113,7 @@ fun ReportMessageView( Switch( enabled = !isSending, checked = state.blockUser, - onCheckedChange = { state.eventSink(ReportMessageEvents.ToggleBlockUser) }, + onCheckedChange = { state.eventSink(ReportMessageEvent.ToggleBlockUser) }, ) } @@ -125,7 +125,7 @@ fun ReportMessageView( showProgress = isSending, onClick = { focusManager.clearFocus(force = true) - state.eventSink(ReportMessageEvents.Report) + state.eventSink(ReportMessageEvent.Report) }, modifier = Modifier .fillMaxWidth() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt index 10922ca5e7..03953e0e29 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt @@ -36,7 +36,7 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelineController -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories @@ -148,7 +148,7 @@ class ThreadedMessagesNode( activity: Activity, darkTheme: Boolean, url: String, - eventSink: (TimelineEvents) -> Unit, + eventSink: (TimelineEvent) -> Unit, customTab: Boolean ) { when (val permalink = permalinkParser.parse(url)) { @@ -175,12 +175,12 @@ class ThreadedMessagesNode( private fun handleRoomLinkClick( roomLink: PermalinkData.RoomLink, - eventSink: (TimelineEvents) -> Unit, + eventSink: (TimelineEvent) -> Unit, ) { if (room.matches(roomLink.roomIdOrAlias)) { val eventId = roomLink.eventId if (eventId != null) { - eventSink(TimelineEvents.FocusOnEvent(eventId)) + eventSink(TimelineEvent.FocusOnEvent(eventId)) } else { // Click on the same room, navigate up // Note that it can not be enough to go back to the room if the thread has been opened @@ -277,7 +277,7 @@ class ThreadedMessagesNode( } LaunchedEffect(Unit) { focusedEventId?.also { eventId -> - state.timelineState.eventSink(TimelineEvents.FocusOnEvent(eventId)) + state.timelineState.eventSink(TimelineEvent.FocusOnEvent(eventId)) } // Reset the focused event id to null to avoid refocusing when restoring node. focusedEventId = null diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvent.kt similarity index 64% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvent.kt index 262c4f9522..1591cbf6cc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvent.kt @@ -8,53 +8,53 @@ package io.element.android.features.messages.impl.timeline +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import kotlin.time.Duration -sealed interface TimelineEvents { - data class OnScrollFinished(val firstIndex: Int) : TimelineEvents - data class FocusOnEvent(val eventId: EventId, val debounce: Duration = Duration.ZERO) : TimelineEvents - data object ClearFocusRequestState : TimelineEvents - data object OnFocusEventRender : TimelineEvents - data object JumpToLive : TimelineEvents +sealed interface TimelineEvent { + data class OnScrollFinished(val firstIndex: Int) : TimelineEvent + data class FocusOnEvent(val eventId: EventId, val debounce: Duration = Duration.ZERO) : TimelineEvent + data object ClearFocusRequestState : TimelineEvent + data object OnFocusEventRender : TimelineEvent + data object JumpToLive : TimelineEvent - data object HideShieldDialog : TimelineEvents + data object HideShieldDialog : TimelineEvent /** * Events coming from a timeline item. */ - sealed interface EventFromTimelineItem : TimelineEvents + sealed interface TimelineItemEvent : TimelineEvent - data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem - data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem - data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem - data class OpenThread(val threadRootEventId: ThreadId, val focusedEvent: EventId?) : EventFromTimelineItem + data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : TimelineItemEvent + data class ShowShieldDialog(val messageShieldData: MessageShieldData) : TimelineItemEvent + data class LoadMore(val direction: Timeline.PaginationDirection) : TimelineItemEvent + data class OpenThread(val threadRootEventId: ThreadId, val focusedEvent: EventId?) : TimelineItemEvent /** * Navigate to the predecessor or successor room of the current room. */ - data class NavigateToPredecessorOrSuccessorRoom(val roomId: RoomId) : EventFromTimelineItem + data class NavigateToPredecessorOrSuccessorRoom(val roomId: RoomId) : TimelineItemEvent /** * Events coming from a poll item. */ - sealed interface TimelineItemPollEvents : EventFromTimelineItem + sealed interface TimelineItemPollEvent : TimelineItemEvent data class SelectPollAnswer( val pollStartId: EventId, val answerId: String - ) : TimelineItemPollEvents + ) : TimelineItemPollEvent data class EndPoll( val pollStartId: EventId, - ) : TimelineItemPollEvents + ) : TimelineItemPollEvent data class EditPoll( val pollStartId: EventId, - ) : TimelineItemPollEvents + ) : TimelineItemPollEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index fd7d200a52..12e4e0b1d1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -25,8 +25,9 @@ import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.UserEventPermissions -import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents +import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvent import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.model.NewEventState @@ -52,7 +53,6 @@ import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsSta import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.DisplayFirstTimelineItems @@ -133,7 +133,7 @@ class TimelinePresenter( val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val newEventState = remember { mutableStateOf(NewEventState.None) } - val messageShield: MutableState = remember { mutableStateOf(null) } + val messageShieldDialogData: MutableState = remember { mutableStateOf(null) } val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present() val isSendPublicReadReceiptsEnabled by remember { @@ -150,9 +150,9 @@ class TimelinePresenter( value = featureFlagService.isFeatureEnabled(FeatureFlags.Threads) } - fun handleEvent(event: TimelineEvents) { + fun handleEvent(event: TimelineEvent) { when (event) { - is TimelineEvents.LoadMore -> { + is TimelineEvent.LoadMore -> { if (event.direction == Timeline.PaginationDirection.FORWARDS && timelineMode is Timeline.Mode.Thread) { // Do not paginate forwards in thread mode, as it's not supported return @@ -161,7 +161,7 @@ class TimelinePresenter( timelineController.paginate(direction = event.direction) } } - is TimelineEvents.OnScrollFinished -> { + is TimelineEvent.OnScrollFinished -> { if (isLive) { if (event.firstIndex == 0) { newEventState.value = NewEventState.None @@ -177,7 +177,7 @@ class TimelinePresenter( newEventState.value = NewEventState.None } } - is TimelineEvents.SelectPollAnswer -> sessionCoroutineScope.launch { + is TimelineEvent.SelectPollAnswer -> sessionCoroutineScope.launch { timelineController.invokeOnCurrentTimeline { sendPollResponseAction.execute( timeline = this, @@ -186,7 +186,7 @@ class TimelinePresenter( ) } } - is TimelineEvents.EndPoll -> sessionCoroutineScope.launch { + is TimelineEvent.EndPoll -> sessionCoroutineScope.launch { timelineController.invokeOnCurrentTimeline { endPollAction.execute( timeline = this, @@ -194,38 +194,38 @@ class TimelinePresenter( ) } } - is TimelineEvents.EditPoll -> { + is TimelineEvent.EditPoll -> { navigator.navigateToEditPoll(event.pollStartId) } - is TimelineEvents.FocusOnEvent -> sessionCoroutineScope.launch { + is TimelineEvent.FocusOnEvent -> sessionCoroutineScope.launch { focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce) delay(event.debounce) Timber.tag(tag).d("Started focus on ${event.eventId}") focusOnEvent(event.eventId, focusRequestState) }.start() - is TimelineEvents.OnFocusEventRender -> { + is TimelineEvent.OnFocusEventRender -> { // If there was a pending 'notification tap opens timeline' transaction, finish it now we're focused in the required event analyticsService.finishLongRunningTransaction(NotificationToMessage) focusRequestState.value = focusRequestState.value.onFocusEventRender() } - is TimelineEvents.ClearFocusRequestState -> { + is TimelineEvent.ClearFocusRequestState -> { focusRequestState.value = FocusRequestState.None } - is TimelineEvents.JumpToLive -> { + is TimelineEvent.JumpToLive -> { timelineController.focusOnLive() } - TimelineEvents.HideShieldDialog -> messageShield.value = null - is TimelineEvents.ShowShieldDialog -> messageShield.value = event.messageShield - is TimelineEvents.ComputeVerifiedUserSendFailure -> { - resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event)) + TimelineEvent.HideShieldDialog -> messageShieldDialogData.value = null + is TimelineEvent.ShowShieldDialog -> messageShieldDialogData.value = event.messageShieldData + is TimelineEvent.ComputeVerifiedUserSendFailure -> { + resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(event.event)) } - is TimelineEvents.NavigateToPredecessorOrSuccessorRoom -> { + is TimelineEvent.NavigateToPredecessorOrSuccessorRoom -> { // Navigate to the predecessor or successor room val serverNames = calculateServerNamesForRoom(room) navigator.navigateToRoom(event.roomId, null, serverNames) } - is TimelineEvents.OpenThread -> { + is TimelineEvent.OpenThread -> { navigator.navigateToThread( threadRootId = event.threadRootEventId, focusedEventId = event.focusedEvent, @@ -312,7 +312,7 @@ class TimelinePresenter( newEventState = newEventState.value, isLive = isLive, focusRequestState = focusRequestState.value, - messageShield = messageShield.value, + messageShieldDialogData = messageShieldDialogData.value, resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState, displayThreadSummaries = displayThreadSummaries, eventSink = ::handleEvent, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index 3ec168fd47..03f0083856 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.typing.TypingNotificationState @@ -18,7 +19,6 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import kotlinx.collections.immutable.ImmutableList import kotlin.time.Duration @@ -31,10 +31,10 @@ data class TimelineState( val isLive: Boolean, val focusRequestState: FocusRequestState, // If not null, info will be rendered in a dialog - val messageShield: MessageShield?, + val messageShieldDialogData: MessageShieldData?, val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState, val displayThreadSummaries: Boolean, - val eventSink: (TimelineEvents) -> Unit, + val eventSink: (TimelineEvent) -> Unit, ) { private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event val hasAnyEvent = lastTimelineEvent != null diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index b5f9e328dc..adfaa93cce 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.ReadReceiptData @@ -55,7 +56,7 @@ fun aTimelineState( messageShield: MessageShield? = null, resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(), displayThreadSummaries: Boolean = false, - eventSink: (TimelineEvents) -> Unit = {}, + eventSink: (TimelineEvent) -> Unit = {}, ): TimelineState { val focusedEventId = timelineItems.filterIsInstance().getOrNull(focusedEventIndex)?.eventId val focusRequestState = if (focusedEventId != null) { @@ -71,7 +72,7 @@ fun aTimelineState( newEventState = NewEventState.None, isLive = isLive, focusRequestState = focusRequestState, - messageShield = messageShield, + messageShieldDialogData = messageShield?.let { MessageShieldData(it) }, resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState, displayThreadSummaries = displayThreadSummaries, eventSink = eventSink, @@ -176,7 +177,9 @@ internal fun aTimelineItemEvent( origin = null, timelineItemDebugInfoProvider = { debugInfo }, messageShieldProvider = { messageShield }, - sendHandleProvider = { null } + sendHandleProvider = { null }, + forwarder = null, + forwarderProfile = null, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 73a15b797f..e44a6e170c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -107,19 +107,19 @@ fun TimelineView( nestedScrollConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(), ) { fun clearFocusRequestState() { - state.eventSink(TimelineEvents.ClearFocusRequestState) + state.eventSink(TimelineEvent.ClearFocusRequestState) } fun onScrollFinishAt(firstVisibleIndex: Int) { - state.eventSink(TimelineEvents.OnScrollFinished(firstVisibleIndex)) + state.eventSink(TimelineEvent.OnScrollFinished(firstVisibleIndex)) } fun onFocusEventRender() { - state.eventSink(TimelineEvents.OnFocusEventRender) + state.eventSink(TimelineEvent.OnFocusEventRender) } fun onJumpToLive() { - state.eventSink(TimelineEvents.JumpToLive) + state.eventSink(TimelineEvent.JumpToLive) } val context = LocalContext.current @@ -129,7 +129,7 @@ fun TimelineView( val useReverseLayout = !isTalkbackActive() fun inReplyToClick(eventId: EventId) { - state.eventSink(TimelineEvents.FocusOnEvent(eventId)) + state.eventSink(TimelineEvent.FocusOnEvent(eventId)) } fun onLinkLongClick(link: Link) { @@ -143,7 +143,7 @@ fun TimelineView( } fun prefetchMoreItems() { - state.eventSink(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS)) + state.eventSink(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS)) } // Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms @@ -220,10 +220,10 @@ fun TimelineView( @Composable private fun MessageShieldDialog(state: TimelineState) { - val messageShield = state.messageShield ?: return + val messageShield = state.messageShieldDialogData ?: return AlertDialog( content = messageShield.toText(), - onDismiss = { state.eventSink.invoke(TimelineEvents.HideShieldDialog) }, + onDismiss = { state.eventSink.invoke(TimelineEvent.HideShieldDialog) }, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt index 401fd7eec8..096ab018e2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt @@ -23,18 +23,22 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.messages.impl.R import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails +import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName import io.element.android.libraries.matrix.api.timeline.item.event.isCritical import io.element.android.libraries.ui.strings.CommonStrings @Composable internal fun MessageShieldView( - shield: MessageShield, - modifier: Modifier = Modifier + shield: MessageShieldData, + modifier: Modifier = Modifier, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -55,8 +59,24 @@ internal fun MessageShieldView( } } +data class MessageShieldData( + /** + * The message shield that the rust layer thinks we should show. + */ + val shield: MessageShield, + /** + * If the keys to this message were forwarded by another user via history sharing (MSC4268), the ID of that user. + */ + val forwarder: UserId? = null, + /** If [forwarder] is set, the profile of the forwarding user, if it was cached at the time the `EventTimelineItem` was created. */ + val forwarderProfile: ProfileDetails? = null, +) + +val MessageShieldData.isCritical: Boolean + get() = shield.isCritical + @Composable -internal fun MessageShield.toIconColor(): Color { +internal fun MessageShieldData.toIconColor(): Color { return when (isCritical) { true -> ElementTheme.colors.iconCriticalPrimary false -> ElementTheme.colors.iconSecondary @@ -64,7 +84,7 @@ internal fun MessageShield.toIconColor(): Color { } @Composable -private fun MessageShield.toTextColor(): Color { +private fun MessageShieldData.toTextColor(): Color { return when (isCritical) { true -> ElementTheme.colors.textCriticalPrimary false -> ElementTheme.colors.textSecondary @@ -72,23 +92,38 @@ private fun MessageShield.toTextColor(): Color { } @Composable -internal fun MessageShield.toText(): String { +internal fun MessageShieldData.toText(): String { + if (shield is MessageShield.AuthenticityNotGuaranteed && forwarder != null) { + var displayName = forwarderProfile?.getDisplayName() + return if (displayName == null) { + stringResource( + CommonStrings.crypto_event_key_forwarded_unknown_profile_dialog_content, + forwarder.toString(), + ) + } else { + stringResource( + CommonStrings.crypto_event_key_forwarded_known_profile_dialog_content, + displayName, + forwarder.toString(), + ) + } + } return stringResource( - id = when (this) { - is MessageShield.AuthenticityNotGuaranteed -> CommonStrings.event_shield_reason_authenticity_not_guaranteed - is MessageShield.UnknownDevice -> CommonStrings.event_shield_reason_unknown_device - is MessageShield.UnsignedDevice -> CommonStrings.event_shield_reason_unsigned_device - is MessageShield.UnverifiedIdentity -> CommonStrings.event_shield_reason_unverified_identity - is MessageShield.SentInClear -> CommonStrings.event_shield_reason_sent_in_clear - is MessageShield.VerificationViolation -> CommonStrings.event_shield_reason_previously_verified - is MessageShield.MismatchedSender -> CommonStrings.event_shield_mismatched_sender + id = when (shield) { + is MessageShield.AuthenticityNotGuaranteed -> R.string.crypto_event_authenticity_not_guaranteed + is MessageShield.UnknownDevice -> R.string.crypto_event_authenticity_unknown_device + is MessageShield.UnsignedDevice -> R.string.crypto_event_authenticity_unsigned_device + is MessageShield.UnverifiedIdentity -> R.string.crypto_event_authenticity_unverified_identity + is MessageShield.SentInClear -> R.string.crypto_event_authenticity_sent_in_clear + is MessageShield.VerificationViolation -> R.string.crypto_event_authenticity_previously_verified + is MessageShield.MismatchedSender -> R.string.crypto_event_authenticity_mismatched_sender } ) } @Composable -internal fun MessageShield.toIcon(): ImageVector { - return when (this) { +internal fun MessageShieldData.toIcon(): ImageVector { + return when (shield) { is MessageShield.AuthenticityNotGuaranteed -> CompoundIcons.Info() is MessageShield.UnknownDevice, is MessageShield.UnsignedDevice, @@ -108,25 +143,42 @@ internal fun MessageShieldViewPreview() { verticalArrangement = Arrangement.spacedBy(16.dp), ) { MessageShieldView( - shield = MessageShield.UnknownDevice(true) + shield = MessageShieldData(MessageShield.UnknownDevice(true)) ) MessageShieldView( - shield = MessageShield.UnverifiedIdentity(true) + shield = MessageShieldData(MessageShield.UnverifiedIdentity(true)) ) MessageShieldView( - shield = MessageShield.AuthenticityNotGuaranteed(false) + shield = MessageShieldData(MessageShield.AuthenticityNotGuaranteed(false)) ) MessageShieldView( - shield = MessageShield.UnsignedDevice(false) + shield = MessageShieldData( + MessageShield.AuthenticityNotGuaranteed(false), + forwarder = UserId("@alice:example.com"), + ) ) MessageShieldView( - shield = MessageShield.SentInClear(false) + shield = MessageShieldData( + MessageShield.AuthenticityNotGuaranteed(false), + forwarder = UserId("@alice:example.com"), + forwarderProfile = ProfileDetails.Ready( + displayName = "Alice", + displayNameAmbiguous = false, + avatarUrl = null, + ), + ) ) MessageShieldView( - shield = MessageShield.VerificationViolation(false) + shield = MessageShieldData(MessageShield.UnsignedDevice(false)) ) MessageShieldView( - shield = MessageShield.MismatchedSender(false) + shield = MessageShieldData(MessageShield.SentInClear(false)) + ) + MessageShieldView( + shield = MessageShieldData(MessageShield.VerificationViolation(false)) + ) + MessageShieldView( + shield = MessageShieldData(MessageShield.MismatchedSender(false)) ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt index 40369595ea..21ef7c5b09 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.isEdited import io.element.android.features.messages.impl.timeline.model.event.isRedacted @@ -33,13 +33,12 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState -import io.element.android.libraries.matrix.api.timeline.item.event.isCritical import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineEventTimestampView( event: TimelineItem.Event, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, ) { val formattedTime = event.sentTime @@ -80,7 +79,7 @@ fun TimelineEventTimestampView( enabled = isVerifiedUserSendFailure, onClickLabel = stringResource(CommonStrings.action_open_context_menu), ) { - eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event)) + eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event)) } ) } @@ -96,7 +95,7 @@ fun TimelineEventTimestampView( .clickable( onClickLabel = stringResource(CommonStrings.a11y_view_details), ) { - eventSink(TimelineEvents.ShowShieldDialog(shield)) + eventSink(TimelineEvent.ShowShieldDialog(shield)) }, tint = shield.toIconColor(), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 1f40e94a9b..b253f45937 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -57,7 +57,7 @@ import androidx.constraintlayout.compose.ConstrainScope import androidx.constraintlayout.compose.ConstraintLayout import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView @@ -153,7 +153,7 @@ fun TimelineItemEventRow( onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, onReadReceiptClick: (event: TimelineItem.Event) -> Unit, onSwipeToReply: () -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { contentModifier, onContentLayoutChange -> // Only pass down a custom clickable lambda if the content can be clicked separately @@ -278,7 +278,7 @@ fun TimelineItemEventRow( isOutgoing = event.isMine, onClick = { event.eventId?.let { - eventSink(TimelineEvents.OpenThread(it.toThreadId(), null)) + eventSink(TimelineEvent.OpenThread(it.toThreadId(), null)) } } ) @@ -410,7 +410,7 @@ private fun TimelineItemEventRowContent( onReactionClick: (emoji: String) -> Unit, onReactionLongClick: (emoji: String) -> Unit, onMoreReactionsClick: (event: TimelineItem.Event) -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit, ) { @@ -585,7 +585,7 @@ private fun MessageEventBubbleContent( timelineProtectionState: TimelineProtectionState, onMessageLongClick: () -> Unit, inReplyToClick: () -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, @SuppressLint("ModifierParameter") // need to rename this modifier to prevent linter false positives @Suppress("ModifierNaming") @@ -623,7 +623,7 @@ private fun MessageEventBubbleContent( @Composable fun WithTimestampLayout( timestampPosition: TimestampPosition, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, canShrinkContent: Boolean = false, content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index 66f2459491..505d76b24b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -16,7 +16,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import io.element.android.features.messages.impl.R -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.aGroupedEvents import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo @@ -57,7 +57,7 @@ fun TimelineItemGroupedEventsRow( onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { event, contentModifier, onContentLayoutChange -> @@ -130,7 +130,7 @@ private fun TimelineItemGroupedEventsRowContent( onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { event, contentModifier, onContentLayoutChange -> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 4a0a6f70b8..15e47e5269 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData @@ -73,7 +73,7 @@ internal fun TimelineItemRow( onReadReceiptClick: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, onJoinCallClick: () -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { event, contentModifier, onContentLayoutChange -> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index e796e93311..6e409f2b8d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState @@ -44,7 +44,7 @@ fun TimelineItemStateEventRow( onClick: () -> Unit, onLongClick: () -> Unit, onReadReceiptsClick: (event: TimelineItem.Event) -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier ) { val interactionSource = remember { MutableInteractionSource() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt index be94512c2c..3e8e3c919e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt @@ -15,7 +15,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemReadMarkerView @@ -35,7 +35,7 @@ import timber.log.Timber fun TimelineItemVirtualRow( virtual: TimelineItem.Virtual, timelineRoomInfo: TimelineRoomInfo, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier ) { Box(modifier = modifier) { @@ -48,7 +48,7 @@ fun TimelineItemVirtualRow( roomName = timelineRoomInfo.name, isDm = timelineRoomInfo.isDm, onPredecessorRoomClick = { roomId -> - eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId)) + eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(roomId)) }, ) } @@ -57,7 +57,7 @@ fun TimelineItemVirtualRow( val latestEventSink by rememberUpdatedState(eventSink) LaunchedEffect(virtual.model.timestamp) { Timber.d("Pagination triggered by load more indicator") - latestEventSink(TimelineEvents.LoadMore(virtual.model.direction)) + latestEventSink(TimelineEvent.LoadMore(virtual.model.direction)) } } // Empty model trick to avoid timeline jumping during forward pagination. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt index 0226eea7b9..f42a26cf65 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt @@ -35,13 +35,13 @@ fun CustomReactionBottomSheet( val target = state.target as? CustomReactionState.Target.Success fun onDismiss() { - state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) + state.eventSink(CustomReactionEvent.DismissCustomReactionSheet) } fun onEmojiSelectedDismiss(emoji: Emoji) { if (target?.event == null) return sheetState.hide(coroutineScope) { - state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) + state.eventSink(CustomReactionEvent.DismissCustomReactionSheet) onSelectEmoji(target.event.eventOrTransactionId, emoji) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvent.kt similarity index 88% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvent.kt index b73d1295c4..deaf27739e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvent.kt @@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.customreac import io.element.android.features.messages.impl.timeline.model.TimelineItem -sealed interface CustomReactionEvents { - data class ShowCustomReactionSheet(val event: TimelineItem.Event) : CustomReactionEvents - data object DismissCustomReactionSheet : CustomReactionEvents +sealed interface CustomReactionEvent { + data class ShowCustomReactionSheet(val event: TimelineItem.Event) : CustomReactionEvent + data object DismissCustomReactionSheet : CustomReactionEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt index 4ccfc3f883..a1ebb66ea7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt @@ -54,10 +54,10 @@ class CustomReactionPresenter( target.value = CustomReactionState.Target.None } - fun handleEvent(event: CustomReactionEvents) { + fun handleEvent(event: CustomReactionEvent) { when (event) { - is CustomReactionEvents.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event) - is CustomReactionEvents.DismissCustomReactionSheet -> handleDismissCustomReactionSheet() + is CustomReactionEvent.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event) + is CustomReactionEvent.DismissCustomReactionSheet -> handleDismissCustomReactionSheet() } } val event = (target.value as? CustomReactionState.Target.Success)?.event diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt index 1d3e3e820e..1897d4e5a7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt @@ -17,7 +17,7 @@ data class CustomReactionState( val target: Target, val selectedEmoji: ImmutableSet, val recentEmojis: ImmutableList, - val eventSink: (CustomReactionEvents) -> Unit, + val eventSink: (CustomReactionEvent) -> Unit, ) { sealed interface Target { data object None : Target diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt index 70d962ff49..0575341d3d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.emojibasebindings.Emoji import io.element.android.features.messages.impl.timeline.components.customreaction.EmojiItem -import io.element.android.features.messages.impl.timeline.components.customreaction.icon import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toSp @@ -58,11 +57,10 @@ fun EmojiPicker( Column(modifier) { SearchBar( modifier = Modifier.padding(bottom = 10.dp), - query = state.searchQuery, - onQueryChange = { state.eventSink(EmojiPickerEvents.UpdateSearchQuery(it)) }, + queryState = state.searchQuery, resultState = state.searchResults, active = state.isSearchActive, - onActiveChange = { state.eventSink(EmojiPickerEvents.ToggleSearchActive(it)) }, + onActiveChange = { state.eventSink(EmojiPickerEvent.ToggleSearchActive(it)) }, windowInsets = WindowInsets(0, 0, 0, 0), placeHolderTitle = stringResource(CommonStrings.emoji_picker_search_placeholder), ) { emojis -> diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvent.kt similarity index 76% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvent.kt index d0c4907b82..6ea5eea0f6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvent.kt @@ -8,7 +8,6 @@ package io.element.android.features.messages.impl.timeline.components.customreaction.picker -sealed interface EmojiPickerEvents { - data class ToggleSearchActive(val isActive: Boolean) : EmojiPickerEvents - data class UpdateSearchQuery(val query: String) : EmojiPickerEvents +sealed interface EmojiPickerEvent { + data class ToggleSearchActive(val isActive: Boolean) : EmojiPickerEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt index aed568420a..3a4cf6c9ce 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt @@ -8,6 +8,7 @@ package io.element.android.features.messages.impl.timeline.components.customreaction.picker +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -38,7 +39,7 @@ class EmojiPickerPresenter( ) : Presenter { @Composable override fun present(): EmojiPickerState { - var searchQuery by remember { mutableStateOf("") } + val queryState = rememberTextFieldState() var isSearchActive by remember { mutableStateOf(false) } var emojiResults by remember { mutableStateOf>>(SearchBarResultState.Initial()) } @@ -67,6 +68,7 @@ class EmojiPickerPresenter( } } + val searchQuery = queryState.text.toString() LaunchedEffect(searchQuery) { emojiResults = if (searchQuery.isEmpty()) { SearchBarResultState.Initial() @@ -91,20 +93,19 @@ class EmojiPickerPresenter( } val isInPreview = LocalInspectionMode.current - fun handleEvent(event: EmojiPickerEvents) { + fun handleEvent(event: EmojiPickerEvent) { when (event) { // For some reason, in preview mode the SearchBar emits this event with an `isActive = true` value automatically - is EmojiPickerEvents.ToggleSearchActive -> if (!isInPreview) { + is EmojiPickerEvent.ToggleSearchActive -> if (!isInPreview) { isSearchActive = event.isActive } - is EmojiPickerEvents.UpdateSearchQuery -> searchQuery = event.query } } return EmojiPickerState( categories = categories, allEmojis = emojibaseStore.allEmojis, - searchQuery = searchQuery, + searchQuery = queryState, isSearchActive = isSearchActive, searchResults = emojiResults, eventSink = ::handleEvent, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt index b050c346a5..c1d306dece 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt @@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline.components.customreaction.picker import androidx.annotation.StringRes +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.runtime.Immutable import io.element.android.emojibasebindings.Emoji import io.element.android.libraries.designsystem.theme.components.IconSource @@ -20,10 +21,10 @@ import kotlinx.collections.immutable.ImmutableList data class EmojiPickerState( val categories: ImmutableList, val allEmojis: ImmutableList, - val searchQuery: String, + val searchQuery: TextFieldState, val isSearchActive: Boolean, val searchResults: SearchBarResultState>, - val eventSink: (EmojiPickerEvents) -> Unit, + val eventSink: (EmojiPickerEvent) -> Unit, ) /** diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt index e26941e172..958b3849f5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.messages.impl.timeline.components.customreaction.picker +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.emojibasebindings.Emoji import io.element.android.emojibasebindings.EmojibaseCategory @@ -72,11 +73,11 @@ internal fun anEmojiPickerState( searchQuery: String = "", isSearchActive: Boolean = false, searchResults: SearchBarResultState> = SearchBarResultState.Initial(), - eventSink: (EmojiPickerEvents) -> Unit = {}, + eventSink: (EmojiPickerEvent) -> Unit = {}, ) = EmojiPickerState( categories = categories, allEmojis = allEmojis, - searchQuery = searchQuery, + searchQuery = TextFieldState(initialText = searchQuery), isSearchActive = isSearchActive, searchResults = searchResults, eventSink = eventSink, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt index 73cd1ad8b8..4fc243864c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt @@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.rememberPresenter @@ -43,7 +43,7 @@ fun TimelineItemEventContentView( onShowContentClick: () -> Unit, onLinkClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit, - eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, + eventSink: (TimelineEvent.TimelineItemEvent) -> Unit, modifier: Modifier = Modifier, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit = {}, ) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt index ba72c067f0..e956a99b45 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt @@ -11,7 +11,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider import io.element.android.features.poll.api.pollcontent.PollContentView @@ -23,19 +23,19 @@ import kotlinx.collections.immutable.toImmutableList @Composable fun TimelineItemPollView( content: TimelineItemPollContent, - eventSink: (TimelineEvents.TimelineItemPollEvents) -> Unit, + eventSink: (TimelineEvent.TimelineItemPollEvent) -> Unit, modifier: Modifier = Modifier, ) { fun onSelectAnswer(pollStartId: EventId, answerId: String) { - eventSink(TimelineEvents.SelectPollAnswer(pollStartId, answerId)) + eventSink(TimelineEvent.SelectPollAnswer(pollStartId, answerId)) } fun onEndPoll(pollStartId: EventId) { - eventSink(TimelineEvents.EndPoll(pollStartId)) + eventSink(TimelineEvent.EndPoll(pollStartId)) } fun onEditPoll(pollStartId: EventId) { - eventSink(TimelineEvents.EditPoll(pollStartId)) + eventSink(TimelineEvent.EditPoll(pollStartId)) } PollContentView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvent.kt similarity index 85% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvent.kt index 987b61e505..0203eb71b0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvent.kt @@ -11,7 +11,7 @@ package io.element.android.features.messages.impl.timeline.components.reactionsu import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.libraries.matrix.api.core.EventId -sealed interface ReactionSummaryEvents { - data object Clear : ReactionSummaryEvents - data class ShowReactionSummary(val eventId: EventId, val reactions: List, val selectedKey: String) : ReactionSummaryEvents +sealed interface ReactionSummaryEvent { + data object Clear : ReactionSummaryEvent + data class ShowReactionSummary(val eventId: EventId, val reactions: List, val selectedKey: String) : ReactionSummaryEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt index a95fe57bc7..d212aa6ff3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt @@ -37,14 +37,14 @@ class ReactionSummaryPresenter( } val targetWithAvatars = populateSenderAvatars(members = membersState.roomMembers().orEmpty().toImmutableList(), summary = target.value) - fun handleEvent(event: ReactionSummaryEvents) { + fun handleEvent(event: ReactionSummaryEvent) { when (event) { - is ReactionSummaryEvents.ShowReactionSummary -> target.value = ReactionSummaryState.Summary( + is ReactionSummaryEvent.ShowReactionSummary -> target.value = ReactionSummaryState.Summary( reactions = event.reactions.toImmutableList(), selectedKey = event.selectedKey, selectedEventId = event.eventId ) - ReactionSummaryEvents.Clear -> target.value = null + ReactionSummaryEvent.Clear -> target.value = null } } return ReactionSummaryState( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt index cf5342f853..36ba50a237 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt @@ -14,7 +14,7 @@ import kotlinx.collections.immutable.ImmutableList data class ReactionSummaryState( val target: Summary?, - val eventSink: (ReactionSummaryEvents) -> Unit + val eventSink: (ReactionSummaryEvent) -> Unit ) { data class Summary( val reactions: ImmutableList, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt index 944fd9a195..c138378a3a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt @@ -84,7 +84,7 @@ fun ReactionSummaryView( modifier: Modifier = Modifier, ) { fun onDismiss() { - state.eventSink(ReactionSummaryEvents.Clear) + state.eventSink(ReactionSummaryEvent.Clear) } if (state.target != null) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt index 57c46a6326..b65e326045 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt @@ -55,7 +55,7 @@ internal fun ReadReceiptBottomSheet( onDismissRequest = { coroutineScope.launch { sheetState.hide() - state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + state.eventSink(ReadReceiptBottomSheetEvent.Dismiss) } } ) { @@ -64,7 +64,7 @@ internal fun ReadReceiptBottomSheet( onUserDataClick = { coroutineScope.launch { sheetState.hide() - state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + state.eventSink(ReadReceiptBottomSheetEvent.Dismiss) onUserDataClick.invoke(it) } }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvent.kt similarity index 78% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvent.kt index 04723d0214..9c605c42df 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvent.kt @@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.receipt.bo import io.element.android.features.messages.impl.timeline.model.TimelineItem -sealed interface ReadReceiptBottomSheetEvents { - data class EventSelected(val event: TimelineItem.Event) : ReadReceiptBottomSheetEvents - data object Dismiss : ReadReceiptBottomSheetEvents +sealed interface ReadReceiptBottomSheetEvent { + data class EventSelected(val event: TimelineItem.Event) : ReadReceiptBottomSheetEvent + data object Dismiss : ReadReceiptBottomSheetEvent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt index e1264b77e7..b5b4168a28 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt @@ -23,13 +23,13 @@ class ReadReceiptBottomSheetPresenter : Presenter { override fun present(): ReadReceiptBottomSheetState { var selectedEvent: TimelineItem.Event? by remember { mutableStateOf(null) } - fun handleEvent(event: ReadReceiptBottomSheetEvents) { + fun handleEvent(event: ReadReceiptBottomSheetEvent) { @Suppress("LiftReturnOrAssignment") when (event) { - is ReadReceiptBottomSheetEvents.EventSelected -> { + is ReadReceiptBottomSheetEvent.EventSelected -> { selectedEvent = event.event } - ReadReceiptBottomSheetEvents.Dismiss -> { + ReadReceiptBottomSheetEvent.Dismiss -> { selectedEvent = null } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt index 7ec4107f8f..d708399f86 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt @@ -12,5 +12,5 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem data class ReadReceiptBottomSheetState( val selectedEvent: TimelineItem.Event?, - val eventSink: (ReadReceiptBottomSheetEvents) -> Unit, + val eventSink: (ReadReceiptBottomSheetEvent) -> Unit, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 77f1f96332..366c88157e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -118,6 +118,8 @@ class TimelineItemEventFactory( timelineItemDebugInfoProvider = currentTimelineItem.event.timelineItemDebugInfoProvider, messageShieldProvider = currentTimelineItem.event.messageShieldProvider, sendHandleProvider = currentTimelineItem.event.sendHandleProvider, + forwarder = currentTimelineItem.event.forwarder, + forwarderProfile = currentTimelineItem.event.forwarderProfile, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 1f01b16012..e169b10403 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline.model import androidx.compose.runtime.Immutable +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent @@ -87,6 +88,13 @@ sealed interface TimelineItem { val timelineItemDebugInfoProvider: TimelineItemDebugInfoProvider, val messageShieldProvider: MessageShieldProvider, val sendHandleProvider: SendHandleProvider, + /** + * If the keys to this message were forwarded by another user via history sharing (MSC4268), the ID of that user. + * If this is non-null, then [messageShieldProvider] will also return [MessageShield.AuthenticityNotGuaranteed]. + */ + val forwarder: UserId?, + /** If [forwarder] is set, the profile of the forwarding user, if it was cached at the time the `EventTimelineItem` was created. */ + val forwarderProfile: ProfileDetails?, ) : TimelineItem { val showSenderInformation = groupPosition.isNew() && !isMine @@ -115,7 +123,9 @@ sealed interface TimelineItem { get() = EventOrTransactionId.from(eventId = eventId, transactionId = transactionId) // No need to be lazy here? - val messageShield: MessageShield? = messageShieldProvider(strict = false) + val messageShield: MessageShieldData? = messageShieldProvider(strict = false)?.let { + MessageShieldData(it, forwarder, forwarderProfile) + } val debugInfo: TimelineItemDebugInfo get() = timelineItemDebugInfoProvider() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt index 7727b28398..eb756a7369 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt @@ -63,6 +63,7 @@ internal fun MessagesViewTopBar( heroes: ImmutableList, roomCallState: RoomCallState, dmUserIdentityState: IdentityState?, + showSharedHistoryIcon: Boolean, onRoomDetailsClick: () -> Unit, onJoinCallClick: () -> Unit, onBackClick: () -> Unit, @@ -108,6 +109,14 @@ internal fun MessagesViewTopBar( } else -> Unit } + + if (showSharedHistoryIcon) { + Icon( + imageVector = CompoundIcons.History(), + tint = ElementTheme.colors.iconInfoPrimary, + contentDescription = stringResource(CommonStrings.common_shared_history), + ) + } } }, actions = { @@ -169,6 +178,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview { heroes: ImmutableList = persistentListOf(), roomCallState: RoomCallState = RoomCallState.Unavailable, dmUserIdentityState: IdentityState? = null, + showSharedHistoryIcon: Boolean = false, ) = MessagesViewTopBar( roomName = roomName, roomAvatar = roomAvatar, @@ -176,6 +186,7 @@ internal fun MessagesViewTopBarPreview() = ElementPreview { heroes = heroes, roomCallState = roomCallState, dmUserIdentityState = dmUserIdentityState, + showSharedHistoryIcon = showSharedHistoryIcon, onRoomDetailsClick = {}, onJoinCallClick = {}, onBackClick = {}, @@ -208,5 +219,11 @@ internal fun MessagesViewTopBarPreview() = ElementPreview { isTombstoned = true, dmUserIdentityState = IdentityState.VerificationViolation ) + HorizontalDivider() + AMessagesViewTopBar( + roomName = "A DM with shared history", + dmUserIdentityState = IdentityState.Verified, + showSharedHistoryIcon = true, + ) } } diff --git a/features/messages/impl/src/main/res/values-be/translations.xml b/features/messages/impl/src/main/res/values-be/translations.xml index ff8fd2efad..80a5df8d6b 100644 --- a/features/messages/impl/src/main/res/values-be/translations.xml +++ b/features/messages/impl/src/main/res/values-be/translations.xml @@ -1,5 +1,11 @@ + "Сапраўднасць гэтага зашыфраванага паведамлення не можа быць гарантаваная на гэтай прыладзе." + "Зашыфравана раней правераным карыстальнікам." + "Не зашыфраваны." + "Зашыфравана невядомай ці выдаленай прыладай." + "Зашыфравана прыладай, не пацверджанай яе ўладальнікам." + "Зашыфравана неправераным карыстальнікам." "Актыўнасці" "Сцягі" "Ежа & Напоі" diff --git a/features/messages/impl/src/main/res/values-bg/translations.xml b/features/messages/impl/src/main/res/values-bg/translations.xml index 57a9fed4db..1e675f52ae 100644 --- a/features/messages/impl/src/main/res/values-bg/translations.xml +++ b/features/messages/impl/src/main/res/values-bg/translations.xml @@ -1,5 +1,6 @@ + "Без шифроване" "Дейности" "Знамена" "Храна & Напитки" diff --git a/features/messages/impl/src/main/res/values-cs/translations.xml b/features/messages/impl/src/main/res/values-cs/translations.xml index 3b9ffb42a3..d610ebf937 100644 --- a/features/messages/impl/src/main/res/values-cs/translations.xml +++ b/features/messages/impl/src/main/res/values-cs/translations.xml @@ -1,5 +1,12 @@ + "Odesílatel události se neshoduje s vlastníkem zařízení, které ji odeslalo." + "Autenticitu této zašifrované zprávy nelze na tomto zařízení zaručit." + "Zašifrováno dříve ověřeným uživatelem." + "Není zašifrováno." + "Šifrováno neznámým nebo smazaným zařízením." + "Šifrováno zařízením, které nebylo ověřeno jeho vlastníkem." + "Šifrováno neověřeným uživatelem." "Aktivity" "Vlajky" "Jídlo a nápoje" diff --git a/features/messages/impl/src/main/res/values-cy/translations.xml b/features/messages/impl/src/main/res/values-cy/translations.xml index 2d2c368fa3..736a9a34f9 100644 --- a/features/messages/impl/src/main/res/values-cy/translations.xml +++ b/features/messages/impl/src/main/res/values-cy/translations.xml @@ -1,5 +1,12 @@ + "Nid yw anfonwr y digwyddiad yn cyfateb i berchennog y ddyfais a\'i hanfonodd." + "Nid oes modd gwarantu dilysrwydd y neges hon sydd wedi\'i hamgryptio ar y ddyfais hon." + "Wedi\'i amgryptio gan ddefnyddiwr a wiriwyd gynt." + "Heb ei amgryptio." + "Wedi\'i amgryptio gan ddyfais anhysbys neu wedi\'i dileu." + "Wedi\'i amgryptio gan ddyfais nad yw wedi\'i wirio gan ei pherchennog." + "Wedi\'i amgryptio gan ddefnyddiwr heb ei wirio." "Gweithgareddau" "Baneri" "Bwyd a Diod" diff --git a/features/messages/impl/src/main/res/values-da/translations.xml b/features/messages/impl/src/main/res/values-da/translations.xml index af7b0f003e..ae0985dd96 100644 --- a/features/messages/impl/src/main/res/values-da/translations.xml +++ b/features/messages/impl/src/main/res/values-da/translations.xml @@ -1,5 +1,12 @@ + "Afsenderen af begivenheden matcher ikke ejeren af den enhed, der sendte den." + "Ægtheden af denne krypterede besked kan ikke garanteres på denne enhed." + "Krypteret af en tidligere verificeret bruger." + "Ikke krypteret." + "Krypteret af en ukendt eller slettet enhed." + "Krypteret af en enhed, der ikke er verificeret af sin ejer." + "Krypteret af en ikke-verificeret bruger." "Aktiviteter" "Flag" "Mad og drikke" diff --git a/features/messages/impl/src/main/res/values-de/translations.xml b/features/messages/impl/src/main/res/values-de/translations.xml index 458be8a3b9..5323e731e9 100644 --- a/features/messages/impl/src/main/res/values-de/translations.xml +++ b/features/messages/impl/src/main/res/values-de/translations.xml @@ -1,5 +1,12 @@ + "Der Absender des Ereignisses stimmt nicht mit dem Besitzer des Gerätes überein, das es gesendet hat." + "Die Authentizität dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden." + "Verschlüsselt von einem zuvor verifizierten Nutzer." + "Unverschlüsselt." + "Verschlüsselt von einem unbekannten oder gelöschten Gerät." + "Verschlüsselt durch ein Gerät, das nicht von seinem Besitzer verifiziert wurde." + "Verschlüsselt durch einen nicht verifizierten Nutzer." "Aktivitäten" "Flaggen" "Essen & Trinken" diff --git a/features/messages/impl/src/main/res/values-el/translations.xml b/features/messages/impl/src/main/res/values-el/translations.xml index 1bee722e63..7402055f15 100644 --- a/features/messages/impl/src/main/res/values-el/translations.xml +++ b/features/messages/impl/src/main/res/values-el/translations.xml @@ -1,5 +1,12 @@ + "Ο αποστολέας του συμβάντος δεν ταιριάζει με τον κάτοχο της συσκευής που το έστειλε." + "Η αυθεντικότητα αυτού του κρυπτογραφημένου μηνύματος δεν είναι εγγυημένη σε αυτήν τη συσκευή." + "Κρυπτογραφημένο από έναν προηγουμένως επαληθευμένο χρήστη." + "Μη κρυπτογραφημένο." + "Κρυπτογραφημένο από άγνωστη ή διαγεγραμμένη συσκευή." + "Κρυπτογραφημένο από μια συσκευή που δεν έχει επαληθευτεί από τον ιδιοκτήτη της." + "Κρυπτογραφημένο από μη επαληθευμένο χρήστη." "Δραστηριότητες" "Σημαίες" "Φαγητό & Ποτό" diff --git a/features/messages/impl/src/main/res/values-es/translations.xml b/features/messages/impl/src/main/res/values-es/translations.xml index e8f639fd21..d39cdcd5da 100644 --- a/features/messages/impl/src/main/res/values-es/translations.xml +++ b/features/messages/impl/src/main/res/values-es/translations.xml @@ -1,5 +1,11 @@ + "La autenticidad de este mensaje cifrado no puede ser garantizada en este dispositivo." + "Cifrado por un usuario verificado anteriormente." + "No cifrado." + "Cifrado por un dispositivo desconocido o eliminado." + "Cifrado por un dispositivo no verificado por su propietario." + "Cifrado por un usuario no verificado." "Actividades" "Banderas" "Comida y bebida" diff --git a/features/messages/impl/src/main/res/values-et/translations.xml b/features/messages/impl/src/main/res/values-et/translations.xml index b2129f4ffd..80b6307500 100644 --- a/features/messages/impl/src/main/res/values-et/translations.xml +++ b/features/messages/impl/src/main/res/values-et/translations.xml @@ -1,5 +1,12 @@ + "Sündmuse saatja ja seadme omanik pole vastavuses." + "Selle krüptitud sõnumi tõepärasus pole selles seadmes tagatud." + "Krüptitud varem verifitseeritud kasutaja poolt" + "Pole krüptitud." + "Krüptitud tundmatu või kustutatud seadme poolt." + "Krüptitud seadme poolt, mida tema omanik pole verifitseerinud." + "Krüptitud verifitseerimata kasutaja poolt." "Tegevused" "Lipud" "Toit ja jook" diff --git a/features/messages/impl/src/main/res/values-eu/translations.xml b/features/messages/impl/src/main/res/values-eu/translations.xml index fdd0b79ab2..6c76b4dfd9 100644 --- a/features/messages/impl/src/main/res/values-eu/translations.xml +++ b/features/messages/impl/src/main/res/values-eu/translations.xml @@ -1,5 +1,6 @@ + "Ez dago zifratuta." "Jarduerak" "Banderak" "Jana eta edana" diff --git a/features/messages/impl/src/main/res/values-fa/translations.xml b/features/messages/impl/src/main/res/values-fa/translations.xml index b102f82a98..446239c095 100644 --- a/features/messages/impl/src/main/res/values-fa/translations.xml +++ b/features/messages/impl/src/main/res/values-fa/translations.xml @@ -1,5 +1,11 @@ + "اعتبار این پیام رمز شده نمی‌تواند روی این افزاره تأیید شود." + "رمز شده به دست کاربری از پیش تأیید شده." + "رمز نشده." + "رمز شده به دست افزاره‌ای ناشناخته یا حذف شده." + "رمز شده به دست افزاره‌ای که از سوی مالکش تأیید نشده." + "رمز شده به دست کاربری تأیید نشده." "فعّالیت‌ها" "پرچم‌ها" "غذا و نوشیدنی" diff --git a/features/messages/impl/src/main/res/values-fi/translations.xml b/features/messages/impl/src/main/res/values-fi/translations.xml index 26c2c7fd65..b1f49ae6a4 100644 --- a/features/messages/impl/src/main/res/values-fi/translations.xml +++ b/features/messages/impl/src/main/res/values-fi/translations.xml @@ -1,5 +1,12 @@ + "Tapahtuman lähettäjä ei vastaa sitä lähettäneen laitteen omistajaa." + "Tämän salatun viestin aitoutta ei voida taata tällä laitteella." + "Aiemmin vahvistetun käyttäjän salaama." + "Ei salattu." + "Tuntemattoman tai poistetun laitteen salaama." + "Salattu laitteella, jota sen omistaja ei ole vahvistanut." + "Vahvistamattoman käyttäjän salaama." "Aktiviteetit" "Liput" "Ruoka ja juoma" diff --git a/features/messages/impl/src/main/res/values-fr/translations.xml b/features/messages/impl/src/main/res/values-fr/translations.xml index cb8e65fd01..f778bb26a6 100644 --- a/features/messages/impl/src/main/res/values-fr/translations.xml +++ b/features/messages/impl/src/main/res/values-fr/translations.xml @@ -1,5 +1,12 @@ + "L’expéditeur de ce message ne correspond pas au propriétaire de l’appareil qui l’a envoyé." + "L’authenticité de ce message chiffré ne peut être garantie sur cet appareil." + "Chiffré par un utilisateur précédemment vérifié." + "Non chiffré." + "Chiffré par un appareil inconnu ou supprimé." + "Chiffré par un appareil non vérifié par son propriétaire." + "Chiffré par un utilisateur non vérifié." "Activités" "Drapeaux" "Nourriture et boissons" diff --git a/features/messages/impl/src/main/res/values-hr/translations.xml b/features/messages/impl/src/main/res/values-hr/translations.xml index c3427df591..3da55fc4d7 100644 --- a/features/messages/impl/src/main/res/values-hr/translations.xml +++ b/features/messages/impl/src/main/res/values-hr/translations.xml @@ -1,5 +1,12 @@ + "Pošiljatelj događaja ne podudara se s vlasnikom uređaja koji ga je poslao." + "Autentičnost ove šifrirane poruke ne može se jamčiti na ovom uređaju" + "Šifrirao je prethodno provjereni korisnik." + "Nije šifrirano." + "Šifrirano nepoznatim ili izbrisanim uređajem." + "Šifrirano uređajem koji vlasnik nije potvrdio." + "Šifrirao neprovjereni korisnik." "Aktivnosti" "Zastave" "Hrana i piće" diff --git a/features/messages/impl/src/main/res/values-hu/translations.xml b/features/messages/impl/src/main/res/values-hu/translations.xml index f859aa516a..961b2cd7d1 100644 --- a/features/messages/impl/src/main/res/values-hu/translations.xml +++ b/features/messages/impl/src/main/res/values-hu/translations.xml @@ -1,5 +1,12 @@ + "Az esemény feladója nem egyezik az eseményt küldő eszköz tulajdonosával." + "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni." + "Egy korábban ellenőrzött felhasználó által titkosítva." + "Nincs titkosítva." + "Ismeretlen vagy törölt eszköz által titkosítva." + "A tulajdonos által nem ellenőrzött eszköz által titkosítva." + "Nem ellenőrzött felhasználó által titkosítva." "Tevékenységek" "Zászlók" "Étel és ital" diff --git a/features/messages/impl/src/main/res/values-in/translations.xml b/features/messages/impl/src/main/res/values-in/translations.xml index 1be1dba94c..b8cb35f76d 100644 --- a/features/messages/impl/src/main/res/values-in/translations.xml +++ b/features/messages/impl/src/main/res/values-in/translations.xml @@ -1,5 +1,12 @@ + "Pengirim peristiwa tidak cocok dengan pemilik perangkat yang mengirimnya." + "Keaslian pesan terenkripsi ini tidak dapat dijamin pada perangkat ini." + "Dienkripsi oleh pengguna yang telah diverifikasi sebelumnya." + "Tidak dienkripsi." + "Dienkripsi oleh perangkat yang tidak dikenal atau dihapus." + "Dienkripsi oleh perangkat yang tidak diverifikasi oleh pemiliknya." + "Dienkripsi oleh pengguna yang tidak terverifikasi." "Aktivitas" "Bendera" "Makanan & Minuman" @@ -21,7 +28,7 @@ "Lampiran" "Pustaka Foto & Video" "Lokasi" - "Pemungutan suara" + "Jajak pendapat" "Pemformatan Teks" "Riwayat pesan saat ini tidak tersedia di ruangan ini" "Riwayat pesan tidak tersedia di ruangan ini. Verifikasi perangkat ini untuk melihat riwayat pesan." diff --git a/features/messages/impl/src/main/res/values-it/translations.xml b/features/messages/impl/src/main/res/values-it/translations.xml index c5b98c8178..0d750e7ed5 100644 --- a/features/messages/impl/src/main/res/values-it/translations.xml +++ b/features/messages/impl/src/main/res/values-it/translations.xml @@ -1,5 +1,12 @@ + "Il mittente dell\'evento non corrisponde al proprietario del dispositivo che lo ha inviato." + "L\'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo." + "Cifrato da un utente precedentemente verificato." + "Non cifrato." + "Cifrato da un dispositivo sconosciuto o eliminato." + "Cifrato da un dispositivo non verificato dal proprietario." + "Cifrato da un utente non verificato." "Attività" "Bandiere" "Cibi & Bevande" diff --git a/features/messages/impl/src/main/res/values-ko/translations.xml b/features/messages/impl/src/main/res/values-ko/translations.xml index ddcecb58e8..6e79b9fb39 100644 --- a/features/messages/impl/src/main/res/values-ko/translations.xml +++ b/features/messages/impl/src/main/res/values-ko/translations.xml @@ -1,5 +1,12 @@ + "이벤트의 발신자가 이벤트를 보낸 장치의 소유자와 일치하지 않습니다." + "이 장치에서는 이 암호화된 메시지의 진위 여부를 보장할 수 없습니다." + "이전에 검증된 사용자에 의해 암호화되었습니다." + "암호화되지 않음." + "알 수 없거나 삭제된 장치에 의해 암호화됩니다." + "소유자가 확인하지 않은 장치에 의해 암호화되었습니다." + "검증되지 않은 사용자에 의해 암호화되었습니다." "활동" "깃발" "음식 & 음료" diff --git a/features/messages/impl/src/main/res/values-nb/translations.xml b/features/messages/impl/src/main/res/values-nb/translations.xml index 69b6586b4c..a21c2875c5 100644 --- a/features/messages/impl/src/main/res/values-nb/translations.xml +++ b/features/messages/impl/src/main/res/values-nb/translations.xml @@ -1,5 +1,12 @@ + "Avsenderen av hendelsen samsvarer ikke med eieren av enheten som sendte den." + "Ektheten til denne krypterte meldingen kan ikke garanteres på denne enheten." + "Kryptert av en tidligere verifisert bruker." + "Ikke kryptert." + "Kryptert av en ukjent eller slettet enhet." + "Kryptert med en enhet som ikke er verifisert av eieren." + "Kryptert av en ubekreftet bruker." "Aktiviteter" "Flagg" "Mat og drikke" diff --git a/features/messages/impl/src/main/res/values-nl/translations.xml b/features/messages/impl/src/main/res/values-nl/translations.xml index afc25d2a24..d2305229a7 100644 --- a/features/messages/impl/src/main/res/values-nl/translations.xml +++ b/features/messages/impl/src/main/res/values-nl/translations.xml @@ -1,5 +1,11 @@ + "De echtheid van dit versleutelde bericht kan op dit apparaat niet worden gegarandeerd." + "Versleuteld door een eerder geverifieerde gebruiker." + "Niet versleuteld." + "Versleuteld door een onbekend of verwijderd apparaat." + "Versleuteld door een apparaat dat niet is geverifieerd door de eigenaar." + "Versleuteld door een niet-geverifieerde gebruiker." "Activiteiten" "Vlaggen" "Eten & Drinken" diff --git a/features/messages/impl/src/main/res/values-pl/translations.xml b/features/messages/impl/src/main/res/values-pl/translations.xml index bd9f649522..18a8af6cb0 100644 --- a/features/messages/impl/src/main/res/values-pl/translations.xml +++ b/features/messages/impl/src/main/res/values-pl/translations.xml @@ -1,5 +1,12 @@ + "Nadawca zdarzenia nie pasuje do właściciela urządzenia, które je wysłał." + "Autentyczność tej wiadomości szyfrowanej nie jest gwarantowana na tym urządzeniu." + "Zaszyfrowane przez wcześniej zweryfikowanego użytkownika." + "Nieszyfrowany." + "Zaszyfrowana przez nieznane lub usunięte urządzenie." + "Zaszyfrowana przez urządzenie niezweryfikowane przez jego właściciela." + "Zaszyfrowana przez niezweryfikowanego użytkownika." "Aktywności" "Flagi" "Jedzenie i napoje" diff --git a/features/messages/impl/src/main/res/values-pt-rBR/translations.xml b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml index 6098fd0889..3e3fcf08d1 100644 --- a/features/messages/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml @@ -1,5 +1,12 @@ + "O remetente do evento não corresponde com o proprietário do dispositivo que o enviou." + "A autenticidade desta mensagem criptografada não pode ser garantida neste aparelho." + "Criptografado por um usuário verificado previamente." + "Não criptografado." + "Criptografado por um dispositivo desconhecido ou apagado." + "Criptografado por um dispositivo que não foi verificado pelo seu dono." + "Criptografado por um usuário não verificado." "Atividades" "Bandeiras" "Comida & Bebida" diff --git a/features/messages/impl/src/main/res/values-pt/translations.xml b/features/messages/impl/src/main/res/values-pt/translations.xml index 3848f04063..054d2a1759 100644 --- a/features/messages/impl/src/main/res/values-pt/translations.xml +++ b/features/messages/impl/src/main/res/values-pt/translations.xml @@ -1,5 +1,12 @@ + "O remetente deste evento não é o dono do dispositivo que o enviou." + "A autenticidade desta mensagem cifrada não pode ser garantida neste dispositivo." + "Criptografado por um usuário verificado anteriormente." + "Não cifrado." + "Cifragem com origem num dispositivo eliminado ou desconhecido." + "Cifragem com origem num dispositivo não verificado pelo seu dono." + "Cifragem com origem num utilizador não verificado." "Atividades" "Bandeiras" "Comidas e Bebidas" diff --git a/features/messages/impl/src/main/res/values-ro/translations.xml b/features/messages/impl/src/main/res/values-ro/translations.xml index a121bc7843..ff380874ea 100644 --- a/features/messages/impl/src/main/res/values-ro/translations.xml +++ b/features/messages/impl/src/main/res/values-ro/translations.xml @@ -1,5 +1,12 @@ + "Expeditorul evenimentului nu corespunde proprietarului dispozitivului care l-a trimis." + "Autenticitatea acestui mesaj criptat nu poate fi garantată pe acest dispozitiv." + "Criptat de un utilizator verificat anterior." + "Necriptat" + "Criptat de un dispozitiv necunoscut sau șters." + "Criptat de un dispozitiv care nu este verificat de proprietarul său." + "Criptat de un utilizator neverificat." "Activități" "Steaguri" "Mâncare & Băutură" diff --git a/features/messages/impl/src/main/res/values-ru/translations.xml b/features/messages/impl/src/main/res/values-ru/translations.xml index 4656a5978b..4cf7330439 100644 --- a/features/messages/impl/src/main/res/values-ru/translations.xml +++ b/features/messages/impl/src/main/res/values-ru/translations.xml @@ -1,5 +1,12 @@ + "Отправитель события и владелец устройства не совпадают." + "Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве." + "Зашифровано ранее проверенным пользователем." + "Не зашифровано." + "Зашифровано неизвестным или удаленным устройством." + "Зашифровано устройством, не проверенным его владельцем." + "Зашифровано непроверенным пользователем." "Деятельность" "Флаги" "Еда и напитки" diff --git a/features/messages/impl/src/main/res/values-sk/translations.xml b/features/messages/impl/src/main/res/values-sk/translations.xml index bca6ccfc89..4f267d8552 100644 --- a/features/messages/impl/src/main/res/values-sk/translations.xml +++ b/features/messages/impl/src/main/res/values-sk/translations.xml @@ -1,5 +1,12 @@ + "Odosielateľ udalosti sa nezhoduje s vlastníkom zariadenia, ktoré ju odoslalo." + "Pravosť tejto šifrovanej správy nie je možné zaručiť na tomto zariadení." + "Šifrované predtým overeným používateľom." + "Nie je šifrované." + "Zašifrované neznámym alebo odstráneným zariadením." + "Šifrované zariadením, ktoré nie je overené jeho majiteľom." + "Šifrované neovereným používateľom." "Aktivity" "Vlajky" "Jedlo a nápoje" diff --git a/features/messages/impl/src/main/res/values-sv/translations.xml b/features/messages/impl/src/main/res/values-sv/translations.xml index cd5423b64a..21d2cd5fe2 100644 --- a/features/messages/impl/src/main/res/values-sv/translations.xml +++ b/features/messages/impl/src/main/res/values-sv/translations.xml @@ -1,5 +1,12 @@ + "Avsändaren av händelsen matchar inte ägaren av den enhet som skickade den." + "Detta krypterade meddelandes äkthet kan inte garanteras på den här enheten." + "Krypterat av en tidigare verifierad användare." + "Inte krypterad." + "Krypterad av en okänd eller raderad enhet." + "Krypterad av en enhet som inte verifierats av ägaren." + "Krypterad av en overifierad användare." "Aktiviteter" "Flaggor" "Mat & dryck" diff --git a/features/messages/impl/src/main/res/values-tr/translations.xml b/features/messages/impl/src/main/res/values-tr/translations.xml index fbd425eee4..f630a5fdec 100644 --- a/features/messages/impl/src/main/res/values-tr/translations.xml +++ b/features/messages/impl/src/main/res/values-tr/translations.xml @@ -1,5 +1,11 @@ + "Bu şifrelenmiş mesajın doğruluğu bu cihazda garanti edilemez." + "Daha önce doğrulanmış bir kullanıcı tarafından şifrelenmiştir." + "Şifrelenmemiş." + "Bilinmeyen veya silinmiş bir cihaz tarafından şifrelenmiştir." + "Sahibi tarafından doğrulanmamış bir cihaz tarafından şifrelenmiştir." + "Doğrulanmamış bir kullanıcı tarafından şifrelenmiştir." "Aktiviteler" "Bayraklar" "Yiyecek ve İçecek" diff --git a/features/messages/impl/src/main/res/values-uk/translations.xml b/features/messages/impl/src/main/res/values-uk/translations.xml index 90ef76e0f6..cfab123dc9 100644 --- a/features/messages/impl/src/main/res/values-uk/translations.xml +++ b/features/messages/impl/src/main/res/values-uk/translations.xml @@ -1,5 +1,12 @@ + "Відправник події не збігається з власником пристрою, який його надіслав." + "Автентичність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої." + "Зашифровано попередньо перевіреним користувачем." + "Не зашифровано." + "Зашифровано невідомим або видаленим пристроєм." + "Зашифровано пристроєм, який не верифіковано його власником." + "Зашифровано неверифікованим користувачем." "Дії" "Прапори" "Їжа та напої" diff --git a/features/messages/impl/src/main/res/values-ur/translations.xml b/features/messages/impl/src/main/res/values-ur/translations.xml index 3b743a69e8..cb110ef5c6 100644 --- a/features/messages/impl/src/main/res/values-ur/translations.xml +++ b/features/messages/impl/src/main/res/values-ur/translations.xml @@ -1,5 +1,11 @@ + "اس آلہ پر اس مرموز کردہ پیغام کی صداقت کی ضمانت نہیں دی جا سکتی۔" + "پہلے سے تصدیق شدہ صارف کے ذریعہ خفیہ کردہ۔" + "مرموز کردہ نہیں۔" + "کسی نامعلوم یا حذف شدہ آلے کے ذریعے مرموز کردہ۔" + "کسی ایسے آلے کے ذریعے مرموز کردہ جس کی توثیق اسکے مالک سے نہیں ہوئی۔" + "ایک غیر توثیق شدہ صارف کے ذریعے مرموز کردہ۔" "سرگرمیاں" "جھنڈے" "طعم و مشروب" diff --git a/features/messages/impl/src/main/res/values-uz/translations.xml b/features/messages/impl/src/main/res/values-uz/translations.xml index 8f75212a31..4e5d83a649 100644 --- a/features/messages/impl/src/main/res/values-uz/translations.xml +++ b/features/messages/impl/src/main/res/values-uz/translations.xml @@ -1,5 +1,12 @@ + "Hodisani yuborgan shaxs uni yuborgan qurilmaning egasi bilan mos kelmaydi." + "Bu qurilmada shifrlangan xabarning haqiqiyligini kafolatlash imkonsiz." + "Avval tasdiqlangan foydalanuvchi tomonidan shifrlangan." + "Shifrlanmagan" + "Nomaʼlum yoki oʻchirib tashlangan qurilma tomonidan shifrlangan." + "Egasi tasdiqlamagan qurilma tomonidan shifrlangan." + "Tasdiqlanmagan foydalanuvchi tomonidan shifrlangan." "Faoliyatlar" "Bayroqlar" "Oziq-ovqat va ichimliklar" diff --git a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml index eb6ea81365..321c381359 100644 --- a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,5 +1,12 @@ + "事件的傳送者與傳送該事件的裝置擁有者不相符。" + "無法在此裝置上保證此加密訊息的真實性。" + "由先前驗證的使用者加密。" + "未加密。" + "由未知或已刪除的裝置加密。" + "由未經其擁有者驗證的裝置加密。" + "由未經驗證的使用者加密。" "活動" "旗幟" "食物與飲料" diff --git a/features/messages/impl/src/main/res/values-zh/translations.xml b/features/messages/impl/src/main/res/values-zh/translations.xml index 8d43d73632..5247193b84 100644 --- a/features/messages/impl/src/main/res/values-zh/translations.xml +++ b/features/messages/impl/src/main/res/values-zh/translations.xml @@ -1,5 +1,12 @@ + "事件发送者与发送设备的所有者不匹配。" + "此加密消息的真实性无法在此设备上保证。" + "由先前验证过的用户加密。" + "未加密。" + "由未知或已删除的设备加密。" + "由未经其所有者验证的设备加密。" + "由未经验证的用户加密。" "活动" "旗帜" "食物和饮料" diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index 7b0827d792..e8f4d4e6e5 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -1,5 +1,12 @@ + "The sender of the event does not match the owner of the device that sent it." + "The authenticity of this encrypted message can\'t be guaranteed on this device." + "Encrypted by a previously-verified user." + "Not encrypted." + "Encrypted by an unknown or deleted device." + "Encrypted by a device not verified by its owner." + "Encrypted by an unverified user." "Activities" "Flags" "Food & Drink" diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index bdf84e9eec..8fe20cbebe 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -13,11 +13,10 @@ package io.element.android.features.messages.impl import androidx.lifecycle.Lifecycle import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.PinUnpinAction -import io.element.android.features.messages.impl.actionlist.ActionListEvents +import io.element.android.features.messages.impl.actionlist.ActionListEvent import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction -import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.link.aLinkState @@ -28,7 +27,7 @@ import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMess import io.element.android.features.messages.impl.timeline.FakeMarkAsFullyRead import io.element.android.features.messages.impl.timeline.MarkAsFullyRead import io.element.android.features.messages.impl.timeline.TimelineController -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent @@ -65,6 +64,7 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo @@ -185,14 +185,14 @@ class MessagesPresenterTest { presenter.testWithLifecycleOwner { skipItems(1) val initialState = awaitItem() - initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) + initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) advanceUntilIdle() assert(toggleReactionSuccess) .isCalledOnce() .with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())) // No crashes when sending a reaction failed timeline.toggleReactionLambda = toggleReactionFailure - initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) + initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) advanceUntilIdle() assert(toggleReactionFailure) .isCalledOnce() @@ -230,8 +230,8 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) - initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) + initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) + initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) advanceUntilIdle() assert(toggleReactionSuccess) .isCalledExactly(2) @@ -253,7 +253,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(navigator = navigator) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent())) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Forward, aMessageEvent())) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) onForwardEventClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) } @@ -266,7 +266,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(clipboardHelper = clipboardHelper) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyText, event)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.CopyText, event)) skipItems(2) assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body) } @@ -289,7 +289,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.CopyLink, event)) skipItems(2) assertThat(clipboardHelper.clipboardContents).isEqualTo("a link") } @@ -303,7 +303,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent())) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, aMessageEvent())) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -321,7 +321,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter() presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null))) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null))) skipItems(1) } } @@ -354,7 +354,7 @@ class MessagesPresenterTest { formattedFileSize = "4MB" ) ) - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -396,7 +396,7 @@ class MessagesPresenterTest { formattedFileSize = "50MB" ) ) - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -431,7 +431,7 @@ class MessagesPresenterTest { fileExtension = "pdf", ) ) - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -452,7 +452,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent())) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Edit, aMessageEvent())) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -474,7 +474,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(navigator = navigator) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditPoll, aMessageEvent(content = aTimelineItemPollContent()))) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EditPoll, aMessageEvent(content = aTimelineItemPollContent()))) awaitItem() onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) } @@ -482,13 +482,13 @@ class MessagesPresenterTest { @Test fun `present - handle action end poll`() = runTest { - val timelineEventSink = EventsRecorder() + val timelineEventSink = EventsRecorder() val presenter = createMessagesPresenter(timelineEventSink = timelineEventSink) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent()))) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent()))) delay(1) - timelineEventSink.assertSingle(TimelineEvents.EndPoll(AN_EVENT_ID)) + timelineEventSink.assertSingle(TimelineEvent.EndPoll(AN_EVENT_ID)) cancelAndIgnoreRemainingEvents() } } @@ -516,7 +516,7 @@ class MessagesPresenterTest { presenter.testWithLifecycleOwner { val initialState = awaitItem() val messageEvent = aMessageEvent() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Redact, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent)) awaitItem() assert(redactEventLambda) .isCalledOnce() @@ -533,22 +533,12 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(navigator = navigator) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent())) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ReportContent, aMessageEvent())) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) onReportContentClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID), value(A_USER_ID)) } } - @Test - fun `present - handle dismiss action`() = runTest { - val presenter = createMessagesPresenter() - presenter.testWithLifecycleOwner { - val initialState = awaitItem() - initialState.eventSink(MessagesEvents.Dismiss) - assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) - } - } - @Test fun `present - handle action show developer info`() = runTest { val onShowEventDebugInfoClickLambda = lambdaRecorder { _: EventId?, _: TimelineItemDebugInfo -> } @@ -558,7 +548,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(navigator = navigator) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ViewSource, aMessageEvent())) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ViewSource, aMessageEvent())) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) onShowEventDebugInfoClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID), value(aTimelineItemDebugInfo())) } @@ -586,7 +576,7 @@ class MessagesPresenterTest { val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isTrue() // If it's dismissed then we stop showing the alert - initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) + initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Cancel)) skipItems(1) val dismissedState = awaitItem() assertThat(dismissedState.showReinvitePrompt).isFalse() @@ -658,7 +648,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(joinedRoom = room) presenter.testWithLifecycleOwner { val initialState = consumeItemsUntilTimeout().last() - initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) + initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite)) skipItems(1) val loadingState = awaitItem() assertThat(loadingState.inviteProgress.isLoading()).isTrue() @@ -690,7 +680,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(joinedRoom = room) presenter.testWithLifecycleOwner { val initialState = consumeItemsUntilTimeout().last() - initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) + initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite)) skipItems(1) val loadingState = consumeItemsUntilPredicate { state -> state.inviteProgress.isLoading() @@ -714,7 +704,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(joinedRoom = room) presenter.testWithLifecycleOwner { val initialState = consumeItemsUntilTimeout().last() - initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) + initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite)) skipItems(1) val loadingState = awaitItem() assertThat(loadingState.inviteProgress.isLoading()).isTrue() @@ -743,7 +733,7 @@ class MessagesPresenterTest { val presenter = createMessagesPresenter(joinedRoom = room) presenter.testWithLifecycleOwner { val initialState = consumeItemsUntilTimeout().last() - initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) + initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite)) val loadingState = consumeItemsUntilPredicate { state -> state.inviteProgress.isLoading() @@ -840,7 +830,7 @@ class MessagesPresenterTest { val poll = aMessageEvent( content = aTimelineItemPollContent() ) - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, poll)) skipItems(1) composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -878,11 +868,11 @@ class MessagesPresenterTest { val initialState = awaitItem() timeline.pinEventLambda = successPinEventLambda - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Pin, messageEvent)) assert(successPinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) timeline.pinEventLambda = failurePinEventLambda - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Pin, messageEvent)) assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) skipItems(1) assertThat(awaitItem().snackbarMessage).isNotNull() @@ -918,11 +908,11 @@ class MessagesPresenterTest { val initialState = awaitItem() timeline.unpinEventLambda = successUnpinEventLambda - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Unpin, messageEvent)) assert(successUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) timeline.unpinEventLambda = failureUnpinEventLambda - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Unpin, messageEvent)) assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) skipItems(1) assertThat(awaitItem().snackbarMessage).isNotNull() @@ -946,7 +936,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditCaption, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EditCaption, messageEvent)) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -972,7 +962,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.AddCaption, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.AddCaption, messageEvent)) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -1010,7 +1000,7 @@ class MessagesPresenterTest { presenter.testWithLifecycleOwner { skipItems(1) val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.RemoveCaption, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.RemoveCaption, messageEvent)) editCaptionLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID.toEventOrTransactionId()), value(null), value(null)) } } @@ -1024,7 +1014,7 @@ class MessagesPresenterTest { presenter.testWithLifecycleOwner { skipItems(1) val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ViewInTimeline, messageEvent)) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ViewInTimeline, messageEvent)) // No op! } } @@ -1118,7 +1108,7 @@ class MessagesPresenterTest { presenter.testWithLifecycleOwner { val initialState = awaitItem() initialState.eventSink( - MessagesEvents.HandleAction( + MessagesEvent.HandleAction( action = TimelineItemAction.ReplyInThread, event = aMessageEvent(threadInfo = TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID)) ) @@ -1140,7 +1130,7 @@ class MessagesPresenterTest { presenter.testWithLifecycleOwner { val initialState = awaitItem() initialState.eventSink( - MessagesEvents.HandleAction( + MessagesEvent.HandleAction( action = TimelineItemAction.ReplyInThread, event = aMessageEvent( // The event id will be used as the thread id instead @@ -1165,7 +1155,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReplyInThread, aMessageEvent())) + initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ReplyInThread, aMessageEvent())) awaitItem() composerRecorder.assertSingle( MessageComposerEvent.SetMode( @@ -1192,7 +1182,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.MarkAsFullyReadAndExit) + initialState.eventSink(MessagesEvent.MarkAsFullyReadAndExit) runCurrent() @@ -1216,7 +1206,7 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.MarkAsFullyReadAndExit) + initialState.eventSink(MessagesEvent.MarkAsFullyReadAndExit) runCurrent() @@ -1226,6 +1216,27 @@ class MessagesPresenterTest { } } + @Test + fun `present - shows a "history" icon if the room is encrypted and history is shared`() = runTest { + val presenter = createMessagesPresenter( + joinedRoom = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = roomPermissions(), + initialRoomInfo = aRoomInfo(isEncrypted = true, historyVisibility = RoomHistoryVisibility.Shared), + ), + ), + featureFlagService = FakeFeatureFlagService( + initialState = mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true) + ) + ) + presenter.testWithLifecycleOwner { + awaitItem() + runCurrent() + val state = awaitItem() + assertThat(state.showSharedHistoryIcon).isTrue() + } + } + private fun roomPermissions( canStartCall: Boolean = true, canRedactOther: Boolean = true, @@ -1273,7 +1284,7 @@ class MessagesPresenterTest { navigator: FakeMessagesNavigator = FakeMessagesNavigator(), clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), - timelineEventSink: (TimelineEvents) -> Unit = {}, + timelineEventSink: (TimelineEvent) -> Unit = {}, permalinkParser: PermalinkParser = FakePermalinkParser(), messageComposerPresenter: Presenter = Presenter { aMessageComposerState( @@ -1286,7 +1297,7 @@ class MessagesPresenterTest { }, encryptionService: FakeEncryptionService = FakeEncryptionService(), featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), - actionListEventSink: (ActionListEvents) -> Unit = {}, + actionListEventSink: (ActionListEvent) -> Unit = {}, addRecentEmoji: AddRecentEmoji = AddRecentEmoji { _ -> lambdaError() }, markAsFullyRead: MarkAsFullyRead = FakeMarkAsFullyRead(), ): MessagesPresenter { @@ -1298,7 +1309,6 @@ class MessagesPresenterTest { timelinePresenter = { aTimelineState(eventSink = timelineEventSink) }, timelineProtectionPresenter = { aTimelineProtectionState() }, identityChangeStatePresenter = { anIdentityChangeState() }, - historyVisibleStatePresenter = { aHistoryVisibleState() }, linkPresenter = { aLinkState() }, actionListPresenter = { anActionListState(eventSink = actionListEventSink) }, customReactionPresenter = { aCustomReactionState() }, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 23665a043c..c6d974bae7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -30,7 +30,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.emojibasebindings.Emoji import io.element.android.emojibasebindings.EmojibaseCategory import io.element.android.emojibasebindings.EmojibaseStore -import io.element.android.features.messages.impl.actionlist.ActionListEvents +import io.element.android.features.messages.impl.actionlist.ActionListEvent import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction @@ -40,17 +40,17 @@ import io.element.android.features.messages.impl.messagecomposer.aMessageCompose import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerItem import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineState -import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents +import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState -import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents +import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData -import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents +import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvent import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.libraries.matrix.api.core.RoomId @@ -88,7 +88,7 @@ class MessagesViewTest { @Test fun `clicking on back invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder ) @@ -103,7 +103,7 @@ class MessagesViewTest { @Test fun `clicking on room name invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder ) @@ -118,7 +118,7 @@ class MessagesViewTest { @Test fun `clicking on join call invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( eventSink = eventsRecorder ) @@ -134,7 +134,7 @@ class MessagesViewTest { @Test fun `clicking on an Event invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( timelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), @@ -183,7 +183,7 @@ class MessagesViewTest { userHasPermissionToSendReaction: Boolean = false, userCanPinEvent: Boolean = false, ) { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( actionListState = anActionListState( eventSink = eventsRecorder @@ -206,7 +206,7 @@ class MessagesViewTest { // Cannot perform click on "Text", it's not detected. Use tag instead rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() } eventsRecorder.assertSingle( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = timelineItem, userEventPermissions = state.userEventPermissions, ) @@ -216,7 +216,7 @@ class MessagesViewTest { @Test @Config(qualifiers = "h1024dp") fun `clicking on a read receipt list emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( renderReadReceipts = true, @@ -239,7 +239,7 @@ class MessagesViewTest { state = state, ) rule.onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick() - eventsRecorder.assertSingle(ReadReceiptBottomSheetEvents.EventSelected(timelineItem)) + eventsRecorder.assertSingle(ReadReceiptBottomSheetEvent.EventSelected(timelineItem)) } @Test @@ -253,7 +253,7 @@ class MessagesViewTest { } private fun swipeTest(userHasPermissionToSendMessage: Boolean) { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true) val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false) val state = aMessagesState( @@ -273,7 +273,7 @@ class MessagesViewTest { onLast().performTouchInput { swipeRight(endX = 200f) } } if (userHasPermissionToSendMessage) { - eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, canBeRepliedEvent)) + eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Reply, canBeRepliedEvent)) } else { eventsRecorder.assertEmpty() } @@ -281,7 +281,7 @@ class MessagesViewTest { @Test fun `clicking on send location invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( composerState = aMessageComposerState( showAttachmentSourcePicker = true @@ -299,7 +299,7 @@ class MessagesViewTest { @Test fun `clicking on create poll invoke expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( composerState = aMessageComposerState( showAttachmentSourcePicker = true @@ -319,7 +319,7 @@ class MessagesViewTest { @Test @Config(qualifiers = "h1024dp") fun `clicking on the avatar of the sender of an Event emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( eventSink = eventsRecorder ) @@ -327,7 +327,7 @@ class MessagesViewTest { rule.setMessagesView(state = state) rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() eventsRecorder.assertSingle( - MessagesEvents.OnUserClicked( + MessagesEvent.OnUserClicked( MatrixUser( userId = timelineEvent.senderId, displayName = timelineEvent.senderProfile.getDisplayName(), @@ -340,13 +340,13 @@ class MessagesViewTest { @Test @Config(qualifiers = "h1024dp") fun `clicking on the display name of the sender of an Event emits expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState(eventSink = eventsRecorder) val timelineEvent = state.timelineState.timelineItems.filterIsInstance().first() rule.setMessagesView(state = state) rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() eventsRecorder.assertSingle( - MessagesEvents.OnUserClicked( + MessagesEvent.OnUserClicked( MatrixUser( userId = timelineEvent.senderId, displayName = timelineEvent.senderProfile.getDisplayName(), @@ -358,7 +358,7 @@ class MessagesViewTest { @Test fun `selecting a action on a message emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( eventSink = eventsRecorder ) @@ -381,12 +381,12 @@ class MessagesViewTest { rule.clickOn(CommonStrings.action_edit) // Give time for the close animation to complete rule.mainClock.advanceTimeBy(milliseconds = 1_000) - eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Edit, timelineItem)) + eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Edit, timelineItem)) } @Test fun `clicking on a reaction emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), @@ -401,12 +401,12 @@ class MessagesViewTest { text = "👍️", useUnmergedTree = true, ).onFirst().performClick() - eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.eventOrTransactionId)) + eventsRecorder.assertSingle(MessagesEvent.ToggleReaction("👍️", timelineItem.eventOrTransactionId)) } @Test fun `long clicking on a reaction emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), @@ -424,12 +424,12 @@ class MessagesViewTest { text = "👍️", useUnmergedTree = true, ).onFirst().performTouchInput { longClick() } - eventsRecorder.assertSingle(ReactionSummaryEvents.ShowReactionSummary(timelineItem.eventId!!, timelineItem.reactionsState.reactions, "👍️")) + eventsRecorder.assertSingle(ReactionSummaryEvent.ShowReactionSummary(timelineItem.eventId!!, timelineItem.reactionsState.reactions, "👍️")) } @Test fun `clicking on more reaction emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), @@ -444,12 +444,12 @@ class MessagesViewTest { ) val moreReactionContentDescription = rule.activity.getString(R.string.screen_room_timeline_add_reaction) rule.onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick() - eventsRecorder.assertSingle(CustomReactionEvents.ShowCustomReactionSheet(timelineItem)) + eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem)) } @Test fun `clicking on more reaction from action list emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState( timelineItems = aTimelineItemList(aTimelineItemTextContent()), @@ -478,12 +478,12 @@ class MessagesViewTest { rule.onNodeWithContentDescription(moreReactionContentDescription).performClick() // Give time for the close animation to complete rule.mainClock.advanceTimeBy(milliseconds = 1_000) - eventsRecorder.assertSingle(CustomReactionEvents.ShowCustomReactionSheet(timelineItem)) + eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem)) } @Test fun `clicking on verified user send failure from action list emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState() val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event val stateWithActionListState = state.copy( @@ -506,14 +506,14 @@ class MessagesViewTest { rule.onNodeWithText(verifiedUserSendFailure).performClick() // Give time for the close animation to complete rule.mainClock.advanceTimeBy(milliseconds = 1_000) - eventsRecorder.assertSingle(TimelineEvents.ComputeVerifiedUserSendFailure(timelineItem)) + eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem)) } @Test fun `clicking on a custom emoji emits the expected Events`() { val aUnicode = "🙈" - val customReactionStateEventsRecorder = EventsRecorder() - val eventsRecorder = EventsRecorder() + val customReactionStateEventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( eventSink = eventsRecorder, ) @@ -546,13 +546,13 @@ class MessagesViewTest { rule.onNodeWithText(aUnicode, useUnmergedTree = true).performClick() // Give time for the close animation to complete rule.mainClock.advanceTimeBy(milliseconds = 1_000) - customReactionStateEventsRecorder.assertSingle(CustomReactionEvents.DismissCustomReactionSheet) - eventsRecorder.assertSingle(MessagesEvents.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId)) + customReactionStateEventsRecorder.assertSingle(CustomReactionEvent.DismissCustomReactionSheet) + eventsRecorder.assertSingle(MessagesEvent.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId)) } @Test fun `clicking on pinned messages banner emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aMessagesState( timelineState = aTimelineState(eventSink = eventsRecorder), pinnedMessagesBannerState = aLoadedPinnedMessagesBannerState( @@ -566,12 +566,12 @@ class MessagesViewTest { ) rule.setMessagesView(state = state) rule.onNodeWithText("This is a pinned message").performClick() - eventsRecorder.assertSingle(TimelineEvents.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)) + eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)) } @Test fun `clicking on successor room button emits expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val successorRoomId = RoomId("!successor:server.org") val state = aMessagesState( successorRoom = SuccessorRoom( @@ -584,12 +584,12 @@ class MessagesViewTest { val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action) // The bottomsheet subcompose seems to make the node to appear twice rule.onAllNodesWithText(text).onFirst().performClick() - eventsRecorder.assertSingle(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(successorRoomId)) + eventsRecorder.assertSingle(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(successorRoomId)) } @Test fun `no banner shown when there is no successor room`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aMessagesState( successorRoom = null, eventSink = eventsRecorder diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index 7c61d2b8b2..7a99ad39aa 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -8,9 +8,6 @@ package io.element.android.features.messages.impl.actionlist -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.aUserEventPermissions import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction @@ -62,9 +59,7 @@ class ActionListPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.target).isEqualTo(ActionListState.Target.None) } @@ -73,13 +68,11 @@ class ActionListPresenterTest { @Test fun `present - compute for message from me redacted`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent(isMine = true, isEditable = false, content = TimelineItemRedactedContent) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -103,7 +96,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -111,9 +104,7 @@ class ActionListPresenterTest { @Test fun `present - compute for message from others redacted`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = false, @@ -121,7 +112,7 @@ class ActionListPresenterTest { content = TimelineItemRedactedContent ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -145,7 +136,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -153,9 +144,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = false, @@ -163,7 +152,7 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -193,7 +182,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -210,7 +199,7 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -240,7 +229,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -248,9 +237,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message cannot sent message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = false, @@ -258,7 +245,7 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -287,7 +274,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -295,9 +282,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message and can redact`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = false, @@ -305,7 +290,7 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -336,7 +321,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -344,9 +329,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message and cannot send reaction`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = false, @@ -354,7 +337,7 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -385,7 +368,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -393,16 +376,14 @@ class ActionListPresenterTest { @Test fun `present - compute for my message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -433,7 +414,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -449,7 +430,7 @@ class ActionListPresenterTest { content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -480,7 +461,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -488,16 +469,14 @@ class ActionListPresenterTest { @Test fun `present - compute for my message cannot redact`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -527,7 +506,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -535,16 +514,14 @@ class ActionListPresenterTest { @Test fun `present - compute for my message no permission`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -571,7 +548,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -579,9 +556,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a media item`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -589,7 +564,7 @@ class ActionListPresenterTest { content = aTimelineItemImageContent(), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -619,7 +594,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -627,9 +602,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a media with caption item`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -639,7 +612,7 @@ class ActionListPresenterTest { ), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -671,7 +644,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -679,9 +652,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a media with caption item - other user event`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = false, @@ -691,7 +662,7 @@ class ActionListPresenterTest { ), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -721,7 +692,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -729,16 +700,14 @@ class ActionListPresenterTest { @Test fun `present - compute for a state item in debug build`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val stateEvent = aTimelineItemEvent( isMine = true, content = aTimelineItemStateEventContent(), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = stateEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -762,7 +731,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -770,16 +739,14 @@ class ActionListPresenterTest { @Test fun `present - compute for a state item in non-debuggable build`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val stateEvent = aTimelineItemEvent( isMine = true, content = aTimelineItemStateEventContent(), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = stateEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -797,16 +764,14 @@ class ActionListPresenterTest { @Test fun `present - compute message in non-debuggable build`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -836,7 +801,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -844,16 +809,14 @@ class ActionListPresenterTest { @Test fun `present - compute message when user can't pin`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -883,7 +846,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -897,16 +860,14 @@ class ActionListPresenterTest { isDeveloperModeEnabled = true, room = room ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -937,7 +898,7 @@ class ActionListPresenterTest { recentEmojis = suggestedEmojis, ) ) - initialState.eventSink.invoke(ActionListEvents.Clear) + initialState.eventSink.invoke(ActionListEvent.Clear) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } @@ -945,9 +906,7 @@ class ActionListPresenterTest { @Test fun `present - compute message with no actions`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -959,7 +918,7 @@ class ActionListPresenterTest { ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -973,7 +932,7 @@ class ActionListPresenterTest { assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = redactedEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = false, @@ -992,9 +951,7 @@ class ActionListPresenterTest { @Test fun `present - compute not sent message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( // No event id, so it's not sent yet @@ -1005,7 +962,7 @@ class ActionListPresenterTest { ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1037,9 +994,7 @@ class ActionListPresenterTest { @Test fun `present - compute for editable poll message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -1047,7 +1002,7 @@ class ActionListPresenterTest { content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1082,9 +1037,7 @@ class ActionListPresenterTest { @Test fun `present - compute for non-editable poll message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -1092,7 +1045,7 @@ class ActionListPresenterTest { content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1126,9 +1079,7 @@ class ActionListPresenterTest { @Test fun `present - compute for ended poll message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -1136,7 +1087,7 @@ class ActionListPresenterTest { content = aTimelineItemPollContent(isEnded = true), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1169,9 +1120,7 @@ class ActionListPresenterTest { @Test fun `present - compute for voice message`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -1181,7 +1130,7 @@ class ActionListPresenterTest { ), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1215,16 +1164,14 @@ class ActionListPresenterTest { @Test fun `present - compute for call notify`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, content = TimelineItemRtcNotificationContent(), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1256,15 +1203,13 @@ class ActionListPresenterTest { userDisplayNameResult = { Result.success("Alice") } ) val presenter = createActionListPresenter(isDeveloperModeEnabled = false, room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( sendState = LocalEventSendState.Failed.VerifiedUserChangedIdentity(users = listOf(A_USER_ID)), ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions(), ) @@ -1283,9 +1228,7 @@ class ActionListPresenterTest { timelineMode = Timeline.Mode.Thread(A_THREAD_ID), featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( isMine = true, @@ -1296,7 +1239,7 @@ class ActionListPresenterTest { threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = A_THREAD_ID) ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1334,9 +1277,7 @@ class ActionListPresenterTest { isDeveloperModeEnabled = false, featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( eventId = AN_EVENT_ID, @@ -1350,7 +1291,7 @@ class ActionListPresenterTest { assertThat(messageEvent.isRemote).isTrue() initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1388,9 +1329,7 @@ class ActionListPresenterTest { isDeveloperModeEnabled = false, featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( eventId = AN_EVENT_ID, @@ -1405,7 +1344,7 @@ class ActionListPresenterTest { assertThat(messageEvent.isRemote).isTrue() initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1443,9 +1382,7 @@ class ActionListPresenterTest { isDeveloperModeEnabled = false, featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( eventId = null, @@ -1460,7 +1397,7 @@ class ActionListPresenterTest { assertThat(messageEvent.isRemote).isFalse() initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, @@ -1498,9 +1435,7 @@ class ActionListPresenterTest { isDeveloperModeEnabled = false, recentEmojis = GetRecentEmojis { Result.success((listOf("👍️", ":)", "❤️") + otherEmojis).toImmutableList()) }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val messageEvent = aMessageEvent( eventId = null, @@ -1513,7 +1448,7 @@ class ActionListPresenterTest { ) initialState.eventSink.invoke( - ActionListEvents.ComputeForMessage( + ActionListEvent.ComputeForMessage( event = messageEvent, userEventPermissions = aUserEventPermissions( canRedactOwn = true, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index fe462fc988..384b78471d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -11,11 +11,8 @@ package io.element.android.features.messages.impl.attachments import android.net.Uri -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents +import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvent import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter import io.element.android.features.messages.impl.attachments.preview.OnDoneListener import io.element.android.features.messages.impl.attachments.preview.SendActionState @@ -111,13 +108,11 @@ class AttachmentsPreviewPresenterTest { mediaPreProcessor = mediaPreProcessor, onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) @@ -147,16 +142,14 @@ class AttachmentsPreviewPresenterTest { mediaPreProcessor = mediaPreProcessor, onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) // Pre-processing finishes processLatch.complete(Unit) advanceUntilIdle() assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) @@ -185,13 +178,11 @@ class AttachmentsPreviewPresenterTest { mediaPreProcessor = mediaPreProcessor, onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) // Pre-processing finishes processLatch.complete(Unit) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true)) @@ -216,12 +207,10 @@ class AttachmentsPreviewPresenterTest { }, onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) // Pre-processing finishes processLatch.complete(Unit) @@ -241,15 +230,13 @@ class AttachmentsPreviewPresenterTest { }, onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) // Pre-processing finishes processLatch.complete(Unit) advanceUntilIdle() - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java) } @@ -265,12 +252,10 @@ class AttachmentsPreviewPresenterTest { temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback), onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) - initialState.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) + initialState.eventSink(AttachmentsPreviewEvent.CancelAndDismiss) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) deleteCallback.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce() @@ -298,13 +283,11 @@ class AttachmentsPreviewPresenterTest { mediaPreProcessor = mediaPreProcessor, onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.textEditorState.setMarkdown(A_CAPTION) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java) @@ -341,13 +324,11 @@ class AttachmentsPreviewPresenterTest { mediaPreProcessor = mediaPreProcessor, onDoneListener = { onDoneListener() }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.textEditorState.setMarkdown(A_CAPTION) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java) @@ -388,7 +369,7 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.textEditorState.setMarkdown(A_CAPTION) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java) @@ -418,12 +399,10 @@ class AttachmentsPreviewPresenterTest { }, ) val presenter = createAttachmentsPreviewPresenter(room = room, onDoneListener = onDoneListenerResult) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) @@ -434,7 +413,7 @@ class AttachmentsPreviewPresenterTest { val failureState = awaitItem() assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure, mediaUploadInfo)) sendFileResult.assertions().isCalledOnce() - failureState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) + failureState.eventSink(AttachmentsPreviewEvent.CancelAndClearSendState) val clearedState = awaitLastSequentialItem() assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) } @@ -453,16 +432,14 @@ class AttachmentsPreviewPresenterTest { ), onDoneListener = onDoneListenerResult, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) - initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) + initialState.eventSink(AttachmentsPreviewEvent.SendAttachment) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) - initialState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) + initialState.eventSink(AttachmentsPreviewEvent.CancelAndClearSendState) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) // The sending is cancelled and the state is kept at ReadyToUpload ensureAllEventsConsumed() @@ -503,9 +480,7 @@ class AttachmentsPreviewPresenterTest { } ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { assertThat(localMedia.info.fileSize).isGreaterThan(maxUploadSize) consumeItemsUntilPredicate { it.mediaOptimizationSelectorState.maxUploadSize.isSuccess() } @@ -563,9 +538,7 @@ class AttachmentsPreviewPresenterTest { } ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { consumeItemsUntilPredicate { it.mediaOptimizationSelectorState.maxUploadSize.isSuccess() && it.mediaOptimizationSelectorState.videoSizeEstimations.dataOrNull()?.isNotEmpty() == true diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt index 96cc93ea3d..106fff7375 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt @@ -11,9 +11,6 @@ package io.element.android.features.messages.impl.attachments.video import android.net.Uri import android.util.Size import androidx.test.ext.junit.runners.AndroidJUnit4 -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.test.attachments.video.FakeVideoMetadataExtractor import io.element.android.features.messages.test.attachments.video.FakeVideoMetadataExtractorFactory @@ -29,6 +26,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -46,9 +44,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createDefaultMediaOptimizationSelectorPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { awaitItem().run { // Loading assertThat(videoSizeEstimations).isInstanceOf(AsyncData.Loading::class.java) @@ -77,9 +73,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { val presenter = createDefaultMediaOptimizationSelectorPresenter( localMedia = aLocalMedia(mockMediaUrl, anImageMediaInfo()) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading state skipItems(1) @@ -94,9 +88,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { @Test fun `present - OpenVideoPresetSelectorDialog displays it, DismissVideoPresetSelectorDialog hides it`() = runTest { val presenter = createDefaultMediaOptimizationSelectorPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading state val eventSink = awaitItem().eventSink @@ -115,9 +107,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { @Test fun `present - SelectVideoPreset sets it and dismisses the dialog`() = runTest { val presenter = createDefaultMediaOptimizationSelectorPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading state val eventSink = awaitItem().eventSink @@ -139,9 +129,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { val presenter = createDefaultMediaOptimizationSelectorPresenter( mediaExtractorFactory = FakeVideoMetadataExtractorFactory(FakeVideoMetadataExtractor(sizeResult = Result.failure(AN_EXCEPTION))), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading state val eventSink = awaitItem().eventSink @@ -163,9 +151,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { ) ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading and loaded states val eventSink = awaitItem().eventSink skipItems(1) @@ -188,9 +174,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { val presenter = createDefaultMediaOptimizationSelectorPresenter( localMedia = aLocalMedia(mockMediaUrl, anImageMediaInfo()), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading state val eventSink = awaitItem().eventSink @@ -207,9 +191,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { val presenter = createDefaultMediaOptimizationSelectorPresenter( maxUploadSizeProvider = MaxUploadSizeProvider { Result.failure(AN_EXCEPTION) } ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading and loaded state skipItems(1) assertThat(awaitItem().maxUploadSize.dataOrNull()).isEqualTo(1024 * 1024 * 100) @@ -221,9 +203,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { val presenter = createDefaultMediaOptimizationSelectorPresenter( featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to false)), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Skip loading and loaded state skipItems(1) assertThat(awaitItem().displayMediaSelectorViews).isFalse() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/FakeHistoryVisibleAcknowledgementRepository.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/FakeHistoryVisibleAcknowledgementRepository.kt deleted file mode 100644 index faf21720fa..0000000000 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/FakeHistoryVisibleAcknowledgementRepository.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -import io.element.android.libraries.matrix.api.core.RoomId -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow - -class FakeHistoryVisibleAcknowledgementRepository( - private val acknowledgements: MutableMap> = mutableMapOf() -) : HistoryVisibleAcknowledgementRepository { - override fun hasAcknowledged(roomId: RoomId): Flow { - return acknowledgements.getOrPut(roomId) { - MutableStateFlow(false) - } - } - - override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) { - val flow = acknowledgements.getOrPut(roomId) { - MutableStateFlow(value) - } - flow.emit(value) - } - - companion object { - /** - * Create the repository with a pre-existing entry. - */ - fun withRoom(roomId: RoomId, acknowledged: Boolean = false): FakeHistoryVisibleAcknowledgementRepository { - return FakeHistoryVisibleAcknowledgementRepository( - mutableMapOf( - roomId to MutableStateFlow(acknowledged) - ) - ) - } - } -} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenterTest.kt deleted file mode 100644 index afa1992cac..0000000000 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenterTest.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2025 Element Creations Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.crypto.historyvisible - -import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService -import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility -import io.element.android.libraries.matrix.test.room.FakeJoinedRoom -import io.element.android.libraries.matrix.test.room.aRoomInfo -import io.element.android.tests.testutils.WarmUpRule -import io.element.android.tests.testutils.awaitLastSequentialItem -import io.element.android.tests.testutils.test -import kotlinx.coroutines.test.runTest -import org.junit.Rule -import org.junit.Test - -class HistoryVisibleStatePresenterTest { - @get:Rule - val warmUpRule = WarmUpRule() - - @Test - fun `present - not visible if feature disabled`() = runTest { - val room = FakeJoinedRoom() - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) - val presenter = createHistoryVisibleStatePresenter(room, enabled = false, acknowledged = false) - presenter.test { - assertThat(awaitLastSequentialItem().showAlert).isFalse() - } - } - - @Test - fun `present - initial with room shared, unencrypted`() = runTest { - val room = FakeJoinedRoom() - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = false)) - val presenter = createHistoryVisibleStatePresenter(room) - presenter.test { - assertThat(awaitLastSequentialItem().showAlert).isFalse() - } - } - - @Test - fun `present - initial with room joined, encrypted`() = runTest { - val room = FakeJoinedRoom() - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true)) - val presenter = createHistoryVisibleStatePresenter(room) - presenter.test { - assertThat(awaitLastSequentialItem().showAlert).isFalse() - } - } - - @Test - fun `present - initial with room invited, encrypted`() = runTest { - val room = FakeJoinedRoom() - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Invited, isEncrypted = true)) - val presenter = createHistoryVisibleStatePresenter(room) - presenter.test { - assertThat(awaitLastSequentialItem().showAlert).isFalse() - } - } - - @Test - fun `present - initial with room shared, encrypted, unacknowledged`() = runTest { - val room = FakeJoinedRoom() - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) - val presenter = createHistoryVisibleStatePresenter(room, acknowledged = false) - presenter.test { - val initialState = awaitItem() - assertThat(initialState.showAlert).isFalse() - val nextState = awaitItem() - assertThat(nextState.showAlert).isTrue() - } - } - - @Test - fun `present - initial with room shared, encrypted, acknowledged`() = runTest { - val room = FakeJoinedRoom() - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) - val presenter = createHistoryVisibleStatePresenter(room, acknowledged = true) - presenter.test { - assertThat(awaitLastSequentialItem().showAlert).isFalse() - } - } - - @Test - fun `present - transition from joined + unencrypted, to shared + encrypted`() = runTest { - val room = FakeJoinedRoom() - val featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true)) - val repository = FakeHistoryVisibleAcknowledgementRepository() - - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = false)) - - val presenter = HistoryVisibleStatePresenter( - featureFlagService, - repository, - room, - ) - - presenter.test { - // emitted by the feature flag service(?) - assertThat(awaitItem().showAlert).isFalse() - - // emitted state from room info assignment - assertThat(awaitItem().showAlert).isFalse() - - // room is marked as encrypted - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true)) - assertThat(awaitItem().showAlert).isFalse() - - // room history visibility is changed to shared - room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) - assertThat(awaitItem().showAlert).isTrue() - - // alert is acknowledged - repository.setAcknowledged(room.roomId, true) - assertThat(awaitItem().showAlert).isFalse() - } - } - - private fun createHistoryVisibleStatePresenter( - room: JoinedRoom = FakeJoinedRoom(), - enabled: Boolean = true, - acknowledged: Boolean = false - ): HistoryVisibleStatePresenter { - return HistoryVisibleStatePresenter( - room = room, - featureFlagService = FakeFeatureFlagService(mapOf("feature.enableKeyShareOnInvite" to enabled)), - repository = FakeHistoryVisibleAcknowledgementRepository.withRoom(room.roomId, acknowledged) - ) - } -} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt index 7f22de9159..963ae16e76 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt @@ -47,7 +47,7 @@ class ResolveVerifiedUserSendFailurePresenterTest { val sentMessage = aMessageEvent() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(sentMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(sentMessage)) ensureAllEventsConsumed() } } @@ -61,7 +61,7 @@ class ResolveVerifiedUserSendFailurePresenterTest { ) val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(sentMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(sentMessage)) ensureAllEventsConsumed() } } @@ -75,7 +75,7 @@ class ResolveVerifiedUserSendFailurePresenterTest { ) val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) ensureAllEventsConsumed() } } @@ -94,11 +94,11 @@ class ResolveVerifiedUserSendFailurePresenterTest { val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) skipItems(1) awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) - state.eventSink(ResolveVerifiedUserSendFailureEvents.Dismiss) + state.eventSink(ResolveVerifiedUserSendFailureEvent.Dismiss) } skipItems(1) awaitItem().also { state -> @@ -122,12 +122,12 @@ class ResolveVerifiedUserSendFailurePresenterTest { val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) skipItems(1) awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) - state.eventSink(ResolveVerifiedUserSendFailureEvents.Retry) + state.eventSink(ResolveVerifiedUserSendFailureEvent.Retry) } awaitItem().also { state -> assertThat(state.retryAction).isEqualTo(AsyncAction.Loading) @@ -158,12 +158,12 @@ class ResolveVerifiedUserSendFailurePresenterTest { val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) skipItems(1) awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) - state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } awaitItem().also { state -> assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) @@ -173,7 +173,7 @@ class ResolveVerifiedUserSendFailurePresenterTest { awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromOther(A_USER_ID_2.value)) assertThat(state.resolveAction).isEqualTo(AsyncAction.Success(Unit)) - state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } skipItems(3) @@ -201,12 +201,12 @@ class ResolveVerifiedUserSendFailurePresenterTest { val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) skipItems(1) awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) - state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } awaitItem().also { state -> assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) @@ -233,12 +233,12 @@ class ResolveVerifiedUserSendFailurePresenterTest { val failedMessage = aVerifiedUserChangedIdentityMessage() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) skipItems(1) awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value)) - state.eventSink(ResolveVerifiedUserSendFailureEvents.Retry) + state.eventSink(ResolveVerifiedUserSendFailureEvent.Retry) } awaitItem().also { state -> assertThat(state.retryAction).isEqualTo(AsyncAction.Loading) @@ -269,12 +269,12 @@ class ResolveVerifiedUserSendFailurePresenterTest { val failedMessage = aVerifiedUserChangedIdentityMessage() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) skipItems(1) awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value)) - state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } awaitItem().also { state -> assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) @@ -284,7 +284,7 @@ class ResolveVerifiedUserSendFailurePresenterTest { awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID_2.value)) assertThat(state.resolveAction).isEqualTo(AsyncAction.Success(Unit)) - state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } skipItems(3) @@ -312,12 +312,12 @@ class ResolveVerifiedUserSendFailurePresenterTest { val failedMessage = aVerifiedUserChangedIdentityMessage() val initialState = awaitItem() assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) - initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) + initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage)) skipItems(1) awaitItem().also { state -> assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value)) - state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } awaitItem().also { state -> assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt index df1311b6d4..02767fbeb9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt @@ -27,7 +27,7 @@ class ResolveVerifiedUserSendFailureViewTest { @Test fun `clicking on resolve and resend emit the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setResolveVerifiedUserSendFailureView( state = aResolveVerifiedUserSendFailureState( verifiedUserSendFailure = aChangedIdentitySendFailure(), @@ -36,12 +36,12 @@ class ResolveVerifiedUserSendFailureViewTest { ) rule.clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title) - eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) + eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.ResolveAndResend) } @Test fun `clicking on retry emit the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setResolveVerifiedUserSendFailureView( state = aResolveVerifiedUserSendFailureState( verifiedUserSendFailure = aChangedIdentitySendFailure(), @@ -50,7 +50,7 @@ class ResolveVerifiedUserSendFailureViewTest { ) rule.clickOn(res = CommonStrings.action_retry) - eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvents.Retry) + eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.Retry) } private fun AndroidComposeTestRule.setResolveVerifiedUserSendFailureView( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt index 00babf4218..516cd9ea77 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt @@ -68,4 +68,6 @@ internal fun aMessageEvent( timelineItemDebugInfoProvider = debugInfoProvider, messageShieldProvider = messageShieldProvider, sendHandleProvider = sendHandleProvider, + forwarder = null, + forwarderProfile = null, ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt index 02292cf0ce..30548022e7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt @@ -45,7 +45,7 @@ class LinkPresenterTest { presenter.test { val initialState = awaitItem() assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized) - initialState.eventSink(LinkEvents.OnLinkClick(aLink)) + initialState.eventSink(LinkEvent.OnLinkClick(aLink)) assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading) val state = awaitItem() assertThat(state.linkClick).isEqualTo(AsyncAction.Success(aLink)) @@ -61,11 +61,11 @@ class LinkPresenterTest { presenter.test { val initialState = awaitItem() assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized) - initialState.eventSink(LinkEvents.OnLinkClick(aLink)) + initialState.eventSink(LinkEvent.OnLinkClick(aLink)) assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading) val state = awaitItem() assertThat(state.linkClick).isEqualTo(ConfirmingLinkClick(aLink)) - state.eventSink(LinkEvents.Cancel) + state.eventSink(LinkEvent.Cancel) val finalState = awaitItem() assertThat(finalState.linkClick).isEqualTo(AsyncAction.Uninitialized) } @@ -79,11 +79,11 @@ class LinkPresenterTest { presenter.test { val initialState = awaitItem() assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized) - initialState.eventSink(LinkEvents.OnLinkClick(aLink)) + initialState.eventSink(LinkEvent.OnLinkClick(aLink)) assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading) val state = awaitItem() assertThat(state.linkClick).isEqualTo(ConfirmingLinkClick(aLink)) - state.eventSink(LinkEvents.Confirm) + state.eventSink(LinkEvent.Confirm) val finalState = awaitItem() assertThat(finalState.linkClick).isEqualTo(AsyncAction.Success(aLink)) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt index 54c95d83c7..e198ea9043 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt @@ -31,7 +31,7 @@ class LinkViewTest { @Test fun `clicking on cancel emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setLinkView( aLinkState( linkClick = ConfirmingLinkClick(aLink), @@ -40,13 +40,13 @@ class LinkViewTest { ) rule.clickOn(CommonStrings.action_cancel) eventsRecorder.assertSingle( - LinkEvents.Cancel + LinkEvent.Cancel ) } @Test fun `clicking on continue emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setLinkView( aLinkState( linkClick = ConfirmingLinkClick(aLink), @@ -55,13 +55,13 @@ class LinkViewTest { ) rule.clickOn(CommonStrings.action_continue) eventsRecorder.assertSingle( - LinkEvents.Confirm + LinkEvent.Confirm ) } @Test fun `success state invokes the callback and emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(aLink) { callback -> rule.setLinkView( aLinkState( @@ -72,7 +72,7 @@ class LinkViewTest { ) } eventsRecorder.assertSingle( - LinkEvents.Cancel + LinkEvent.Cancel ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index 2feafbc9e5..e16236f109 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -137,9 +137,7 @@ class MessageComposerPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.isFullScreen).isFalse() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") @@ -152,9 +150,7 @@ class MessageComposerPresenterTest { @Test fun `present - toggle fullscreen`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(MessageComposerEvent.ToggleFullScreenState) val fullscreenState = awaitItem() @@ -168,9 +164,7 @@ class MessageComposerPresenterTest { @Test fun `present - change message`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.textEditorState.setHtml(A_MESSAGE) assertThat(initialState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) @@ -347,9 +341,7 @@ class MessageComposerPresenterTest { @Test fun `present - change mode to reply`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { var state = awaitFirstItem() val mode = aReplyMode() state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) @@ -363,9 +355,7 @@ class MessageComposerPresenterTest { @Test fun `present - cancel reply`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { var state = awaitFirstItem() val mode = aReplyMode() state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) @@ -625,9 +615,7 @@ class MessageComposerPresenterTest { val presenter = createPresenter( joinedRoom, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = aReplyMode() @@ -661,9 +649,7 @@ class MessageComposerPresenterTest { @Test fun `present - Open attachments menu`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.showAttachmentSourcePicker).isFalse() initialState.eventSink(MessageComposerEvent.AddAttachment) @@ -674,9 +660,7 @@ class MessageComposerPresenterTest { @Test fun `present - Dismiss attachments menu`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.AddAttachment) skipItems(1) @@ -717,9 +701,7 @@ class MessageComposerPresenterTest { ) ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) onPreviewAttachmentLambda.assertions().isCalledOnce() @@ -758,9 +740,7 @@ class MessageComposerPresenterTest { ) ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) onPreviewAttachmentLambda.assertions().isCalledOnce() @@ -774,9 +754,7 @@ class MessageComposerPresenterTest { givenResult(null) // Simulate a user canceling the flow givenMimeType(MimeTypes.Images) } - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) // No crashes here, otherwise it fails @@ -796,9 +774,7 @@ class MessageComposerPresenterTest { room = room, navigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromFiles) onPreviewAttachmentLambda.assertions().isCalledOnce() @@ -811,9 +787,7 @@ class MessageComposerPresenterTest { typingNoticeResult = { Result.success(Unit) } ) val presenter = createPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.AddAttachment) val attachmentOpenState = awaitItem() @@ -830,9 +804,7 @@ class MessageComposerPresenterTest { typingNoticeResult = { Result.success(Unit) } ) val presenter = createPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.AddAttachment) val attachmentOpenState = awaitItem() @@ -858,9 +830,7 @@ class MessageComposerPresenterTest { permissionPresenter = permissionPresenter, navigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) onPreviewAttachmentLambda.assertions().isCalledOnce() @@ -882,9 +852,7 @@ class MessageComposerPresenterTest { permissionPresenter = permissionPresenter, navigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) permissionPresenter.setPermissionGranted() @@ -908,9 +876,7 @@ class MessageComposerPresenterTest { permissionPresenter = permissionPresenter, navigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) onPreviewAttachmentLambda.assertions().isCalledOnce() @@ -932,9 +898,7 @@ class MessageComposerPresenterTest { permissionPresenter = permissionPresenter, navigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) val permissionState = awaitItem() @@ -949,9 +913,7 @@ class MessageComposerPresenterTest { fun `present - errors are tracked`() = runTest { val testException = Exception("Test error") val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(MessageComposerEvent.Error(testException)) assertThat(analyticsService.trackedErrors).containsExactly(testException) @@ -961,9 +923,7 @@ class MessageComposerPresenterTest { @Test fun `present - ToggleTextFormatting toggles text formatting`() = runTest { val presenter = createPresenter(isRichTextEditorEnabled = false) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.showTextFormatting).isFalse() initialState.eventSink(MessageComposerEvent.AddAttachment) @@ -1008,9 +968,7 @@ class MessageComposerPresenterTest { givenRoomInfo(aRoomInfo(isDirect = false)) } val presenter = createPresenter(room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() // A null suggestion (no suggestion was received) returns nothing @@ -1062,9 +1020,7 @@ class MessageComposerPresenterTest { givenRoomInfo(aRoomInfo(isDirect = false)) } val presenter = createPresenter(room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() // An empty suggestion returns the joined members that are not the current user, but not the room initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) @@ -1099,9 +1055,7 @@ class MessageComposerPresenterTest { ) } val presenter = createPresenter(room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() // An empty suggestion returns the joined members that are not the current user, but not the room initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) @@ -1119,9 +1073,7 @@ class MessageComposerPresenterTest { } ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.textEditorState.setHtml("Hey @bo") initialState.eventSink(MessageComposerEvent.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2)))) @@ -1153,9 +1105,7 @@ class MessageComposerPresenterTest { typingNoticeResult = { Result.success(Unit) } ) val presenter = createPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() // Check intentional mentions on message sent @@ -1242,9 +1192,7 @@ class MessageComposerPresenterTest { typingNoticeResult = typingNoticeResult, ) val presenter = createPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() typingNoticeResult.assertions().isNeverCalled() initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) @@ -1268,9 +1216,7 @@ class MessageComposerPresenterTest { isSendTypingNotificationsEnabled = false ) val presenter = createPresenter(room = room, sessionPreferencesStore = store) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() typingNoticeResult.assertions().isNeverCalled() initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) @@ -1286,9 +1232,7 @@ class MessageComposerPresenterTest { this.loadDraftLambda = loadDraftLambda } val presenter = createPresenter(draftService = composerDraftService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { awaitFirstItem() assert(loadDraftLambda) .isCalledOnce() @@ -1311,9 +1255,7 @@ class MessageComposerPresenterTest { draftService = composerDraftService, permalinkBuilder = permalinkBuilder, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(2) awaitItem().also { state -> assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) @@ -1344,9 +1286,7 @@ class MessageComposerPresenterTest { draftService = composerDraftService, permalinkBuilder = permalinkBuilder, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) awaitItem().also { state -> assertThat(state.showTextFormatting).isTrue() @@ -1377,9 +1317,7 @@ class MessageComposerPresenterTest { draftService = composerDraftService, permalinkBuilder = permalinkBuilder, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(2) awaitItem().also { state -> assertThat(state.showTextFormatting).isFalse() @@ -1423,9 +1361,7 @@ class MessageComposerPresenterTest { draftService = composerDraftService, permalinkBuilder = permalinkBuilder, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(2) awaitItem().also { state -> assertThat(state.showTextFormatting).isFalse() @@ -1452,9 +1388,7 @@ class MessageComposerPresenterTest { this.saveDraftLambda = saveDraftLambda } val presenter = createPresenter(draftService = composerDraftService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(MessageComposerEvent.SaveDraft) advanceUntilIdle() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt index d8269f85d8..1f95baccc9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt @@ -127,7 +127,7 @@ class PinnedMessagesBannerPresenterTest { assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(1) assertThat(loadedState.loadedPinnedMessagesCount).isEqualTo(2) assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent2.toString()) - loadedState.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) + loadedState.eventSink(PinnedMessagesBannerEvent.MoveToNextPinned) } awaitItem().also { loadedState -> @@ -135,7 +135,7 @@ class PinnedMessagesBannerPresenterTest { assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(0) assertThat(loadedState.loadedPinnedMessagesCount).isEqualTo(2) assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent1.toString()) - loadedState.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) + loadedState.eventSink(PinnedMessagesBannerEvent.MoveToNextPinned) } awaitItem().also { loadedState -> diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt index a7bbdf999a..2c33e348c0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt @@ -33,7 +33,7 @@ class PinnedMessagesBannerViewTest { @Test fun `clicking on the banner invoke expected callback`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val state = aLoadedPinnedMessagesBannerState( eventSink = eventsRecorder ) @@ -44,13 +44,13 @@ class PinnedMessagesBannerViewTest { onClick = callback ) rule.onRoot().performClick() - eventsRecorder.assertSingle(PinnedMessagesBannerEvents.MoveToNextPinned) + eventsRecorder.assertSingle(PinnedMessagesBannerEvent.MoveToNextPinned) } } @Test fun `clicking on view all emit the expected event`() { - val eventsRecorder = EventsRecorder(expectEvents = true) + val eventsRecorder = EventsRecorder(expectEvents = true) val state = aLoadedPinnedMessagesBannerState( eventSink = eventsRecorder ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt index 351f841fcf..8b2da2cefd 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt @@ -152,11 +152,11 @@ class PinnedMessagesListPresenterTest { val eventItem = filledState.timelineItems.first() as TimelineItem.Event pinnedEventsTimeline.unpinEventLambda = successUnpinEventLambda - filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem)) + filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.Unpin, eventItem)) advanceUntilIdle() pinnedEventsTimeline.unpinEventLambda = failureUnpinEventLambda - filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem)) + filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.Unpin, eventItem)) advanceUntilIdle() cancelAndIgnoreRemainingEvents() @@ -196,7 +196,7 @@ class PinnedMessagesListPresenterTest { skipItems(3) val filledState = awaitItem() as PinnedMessagesListState.Filled val eventItem = filledState.timelineItems.first() as TimelineItem.Event - filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewInTimeline, eventItem)) + filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.ViewInTimeline, eventItem)) advanceUntilIdle() cancelAndIgnoreRemainingEvents() assert(onViewInTimelineClickLambda) @@ -225,7 +225,7 @@ class PinnedMessagesListPresenterTest { skipItems(3) val filledState = awaitItem() as PinnedMessagesListState.Filled val eventItem = filledState.timelineItems.first() as TimelineItem.Event - filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewSource, eventItem)) + filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.ViewSource, eventItem)) advanceUntilIdle() cancelAndIgnoreRemainingEvents() assert(onShowEventDebugInfoClickLambda) @@ -254,7 +254,7 @@ class PinnedMessagesListPresenterTest { skipItems(3) val filledState = awaitItem() as PinnedMessagesListState.Filled val eventItem = filledState.timelineItems.first() as TimelineItem.Event - filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Forward, eventItem)) + filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.Forward, eventItem)) advanceUntilIdle() cancelAndIgnoreRemainingEvents() assert(onForwardEventClickLambda) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt index 3f96f1dfca..41671b71c1 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTouchInput import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.element.android.features.messages.impl.actionlist.ActionListEvents +import io.element.android.features.messages.impl.actionlist.ActionListEvent import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -43,7 +43,7 @@ class PinnedMessagesListViewTest { @Test fun `clicking on back calls the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val state = aLoadedPinnedMessagesListState( eventSink = eventsRecorder ) @@ -58,7 +58,7 @@ class PinnedMessagesListViewTest { @Test fun `click on an event calls the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) val content = aTimelineItemFileContent() val state = aLoadedPinnedMessagesListState( timelineItems = aTimelineItemList(content), @@ -77,7 +77,7 @@ class PinnedMessagesListViewTest { @Test fun `long click on an event emits the expected event`() { - val eventsRecorder = EventsRecorder(expectEvents = true) + val eventsRecorder = EventsRecorder(expectEvents = true) val content = aTimelineItemFileContent() val state = aLoadedPinnedMessagesListState( timelineItems = aTimelineItemList(content), @@ -92,7 +92,7 @@ class PinnedMessagesListViewTest { longClick() } val event = state.timelineItems.first() as TimelineItem.Event - eventsRecorder.assertSingle(ActionListEvents.ComputeForMessage(event, state.userEventPermissions)) + eventsRecorder.assertSingle(ActionListEvent.ComputeForMessage(event, state.userEventPermissions)) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt index fd8d33d1b6..69cf3b04cd 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt @@ -8,9 +8,6 @@ package io.element.android.features.messages.impl.report -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -22,6 +19,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -33,9 +31,7 @@ class ReportMessagePresenterTest { @Test fun `presenter - initial state`() = runTest { val presenter = createReportMessagePresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.reason).isEmpty() assertThat(initialState.blockUser).isFalse() @@ -46,12 +42,10 @@ class ReportMessagePresenterTest { @Test fun `presenter - update reason`() = runTest { val presenter = createReportMessagePresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val reason = "This user is making the chat very toxic." - initialState.eventSink(ReportMessageEvents.UpdateReason(reason)) + initialState.eventSink(ReportMessageEvent.UpdateReason(reason)) assertThat(awaitItem().reason).isEqualTo(reason) } @@ -60,15 +54,13 @@ class ReportMessagePresenterTest { @Test fun `presenter - toggle block user`() = runTest { val presenter = createReportMessagePresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(ReportMessageEvents.ToggleBlockUser) + initialState.eventSink(ReportMessageEvent.ToggleBlockUser) assertThat(awaitItem().blockUser).isTrue() - initialState.eventSink(ReportMessageEvents.ToggleBlockUser) + initialState.eventSink(ReportMessageEvent.ToggleBlockUser) assertThat(awaitItem().blockUser).isFalse() } @@ -83,13 +75,11 @@ class ReportMessagePresenterTest { reportContentResult = reportContentResult ) val presenter = createReportMessagePresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(ReportMessageEvents.ToggleBlockUser) + initialState.eventSink(ReportMessageEvent.ToggleBlockUser) skipItems(1) - initialState.eventSink(ReportMessageEvents.Report) + initialState.eventSink(ReportMessageEvent.Report) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) reportContentResult.assertions().isCalledOnce() @@ -105,11 +95,9 @@ class ReportMessagePresenterTest { reportContentResult = reportContentResult ) val presenter = createReportMessagePresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(ReportMessageEvents.Report) + initialState.eventSink(ReportMessageEvent.Report) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) reportContentResult.assertions().isCalledOnce() @@ -125,17 +113,15 @@ class ReportMessagePresenterTest { reportContentResult = reportContentResult ) val presenter = createReportMessagePresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(ReportMessageEvents.Report) + initialState.eventSink(ReportMessageEvent.Report) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) val resultState = awaitItem() assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java) reportContentResult.assertions().isCalledOnce() - resultState.eventSink(ReportMessageEvents.ClearError) + resultState.eventSink(ReportMessageEvent.ClearError) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index b84975c6a5..194694714b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -9,12 +9,12 @@ package io.element.android.features.messages.impl.timeline import app.cash.turbine.ReceiveTurbine -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.FakeMessagesNavigator import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.aCriticalShield import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem @@ -119,8 +119,8 @@ class TimelinePresenterTest { val presenter = createTimelinePresenter(timeline = timeline) presenter.test { val initialState = awaitItem() - initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS)) - initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.FORWARDS)) + initialState.eventSink.invoke(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS)) + initialState.eventSink.invoke(TimelineEvent.LoadMore(Timeline.PaginationDirection.FORWARDS)) assert(paginateLambda) .isCalledExactly(2) .withSequence( @@ -172,7 +172,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) + initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0)) runCurrent() assert(markAsReadResult) .isCalledOnce() @@ -206,7 +206,7 @@ class TimelinePresenterTest { presenter.test { skipItems(1) awaitItem().run { - eventSink.invoke(TimelineEvents.OnScrollFinished(1)) + eventSink.invoke(TimelineEvent.OnScrollFinished(1)) } advanceUntilIdle() assert(sendReadReceiptsLambda) @@ -245,8 +245,8 @@ class TimelinePresenterTest { presenter.test { skipItems(1) awaitItem().run { - eventSink.invoke(TimelineEvents.OnScrollFinished(0)) - eventSink.invoke(TimelineEvents.OnScrollFinished(1)) + eventSink.invoke(TimelineEvent.OnScrollFinished(0)) + eventSink.invoke(TimelineEvent.OnScrollFinished(1)) } advanceUntilIdle() assert(sendReadReceiptsLambda) @@ -281,8 +281,8 @@ class TimelinePresenterTest { presenter.test { skipItems(1) awaitItem().run { - eventSink.invoke(TimelineEvents.OnScrollFinished(1)) - eventSink.invoke(TimelineEvents.OnScrollFinished(1)) + eventSink.invoke(TimelineEvent.OnScrollFinished(1)) + eventSink.invoke(TimelineEvent.OnScrollFinished(1)) } advanceUntilIdle() cancelAndIgnoreRemainingEvents() @@ -309,7 +309,7 @@ class TimelinePresenterTest { presenter.test { skipItems(1) val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) + initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(1)) cancelAndIgnoreRemainingEvents() assert(sendReadReceiptsLambda).isNeverCalled() } @@ -348,7 +348,7 @@ class TimelinePresenterTest { consumeItemsUntilPredicate { it.timelineItems.size == 3 } // Scroll to bottom to clear previous FromMe - initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) + initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0)) awaitLastSequentialItem().also { state -> assertThat(state.newEventState).isEqualTo(NewEventState.None) } @@ -428,7 +428,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.SelectPollAnswer(AN_EVENT_ID, "anAnswerId")) + initialState.eventSink.invoke(TimelineEvent.SelectPollAnswer(AN_EVENT_ID, "anAnswerId")) } delay(1) sendPollResponseAction.verifyExecutionCount(1) @@ -442,7 +442,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.EndPoll(AN_EVENT_ID)) + initialState.eventSink.invoke(TimelineEvent.EndPoll(AN_EVENT_ID)) } delay(1) endPollAction.verifyExecutionCount(1) @@ -458,7 +458,7 @@ class TimelinePresenterTest { messagesNavigator = navigator, ) presenter.test { - awaitFirstItem().eventSink(TimelineEvents.EditPoll(AN_EVENT_ID)) + awaitFirstItem().eventSink(TimelineEvent.EditPoll(AN_EVENT_ID)) onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) } } @@ -509,7 +509,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO)) @@ -523,7 +523,7 @@ class TimelinePresenterTest { assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Success(AN_EVENT_ID)) assertThat(state.timelineItems).isNotEmpty() } - initialState.eventSink.invoke(TimelineEvents.JumpToLive) + initialState.eventSink.invoke(TimelineEvent.JumpToLive) skipItems(2) awaitItem().also { state -> // Event stays focused @@ -563,7 +563,7 @@ class TimelinePresenterTest { // Pre-populate the indexer after the first items have been retrieved timelineItemIndexer.process(listOf(aMessageEvent(eventId = AN_EVENT_ID))) - initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) advanceUntilIdle() @@ -594,7 +594,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + initialState.eventSink(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO)) @@ -605,7 +605,7 @@ class TimelinePresenterTest { } awaitItem().also { state -> assertThat(state.focusRequestState).isInstanceOf(FocusRequestState.Failure::class.java) - state.eventSink(TimelineEvents.ClearFocusRequestState) + state.eventSink(TimelineEvent.ClearFocusRequestState) } awaitItem().also { state -> assertThat(state.focusRequestState).isEqualTo(FocusRequestState.None) @@ -647,7 +647,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) @@ -706,7 +706,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) @@ -761,7 +761,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) @@ -820,7 +820,7 @@ class TimelinePresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) @@ -851,14 +851,15 @@ class TimelinePresenterTest { val shield = aCriticalShield() presenter.test { val initialState = awaitFirstItem() - assertThat(initialState.messageShield).isNull() - initialState.eventSink(TimelineEvents.ShowShieldDialog(shield)) + assertThat(initialState.messageShieldDialogData).isNull() + val shieldData = MessageShieldData(shield, null, null) + initialState.eventSink(TimelineEvent.ShowShieldDialog(shieldData)) awaitItem().also { state -> - assertThat(state.messageShield).isEqualTo(shield) - state.eventSink(TimelineEvents.HideShieldDialog) + assertThat(state.messageShieldDialogData).isEqualTo(shieldData) + state.eventSink(TimelineEvent.HideShieldDialog) } awaitItem().also { state -> - assertThat(state.messageShield).isNull() + assertThat(state.messageShieldDialogData).isNull() } } } @@ -961,7 +962,7 @@ class TimelinePresenterTest { val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID)) + initialState.eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID)) assert(onNavigateToRoomLambda) .isCalledOnce() .with( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index cdc0a6c9d0..c56641a048 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToIndex import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.aCriticalShield import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent @@ -49,7 +50,7 @@ class TimelineViewTest { @Test fun `reaching the end of the timeline with more events to load emits a LoadMore event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setTimelineView( state = aTimelineState( timelineItems = persistentListOf( @@ -61,12 +62,12 @@ class TimelineViewTest { eventSink = eventsRecorder, ), ) - eventsRecorder.assertSingle(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS)) + eventsRecorder.assertSingle(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS)) } @Test fun `reaching the end of the timeline does not send a LoadMore event`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setTimelineView( state = aTimelineState( eventSink = eventsRecorder, @@ -76,7 +77,7 @@ class TimelineViewTest { @Test fun `scroll to bottom on live timeline does not emit the Event`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setTimelineView( state = aTimelineState( isLive = true, @@ -90,7 +91,7 @@ class TimelineViewTest { @Test fun `scroll to bottom on detached timeline emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setTimelineView( state = aTimelineState( isLive = false, @@ -99,12 +100,12 @@ class TimelineViewTest { ) val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom) rule.onNodeWithContentDescription(contentDescription).performClick() - eventsRecorder.assertSingle(TimelineEvents.JumpToLive) + eventsRecorder.assertSingle(TimelineEvent.JumpToLive) } @Test fun `show shield dialog`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setTimelineView( state = aTimelineState( timelineItems = persistentListOf( @@ -121,15 +122,15 @@ class TimelineViewTest { rule.onNodeWithContentDescription(contentDescription).performClick() eventsRecorder.assertList( listOf( - TimelineEvents.OnScrollFinished(0), - TimelineEvents.ShowShieldDialog(MessageShield.UnverifiedIdentity(true)), + TimelineEvent.OnScrollFinished(0), + TimelineEvent.ShowShieldDialog(MessageShieldData(MessageShield.UnverifiedIdentity(true))), ) ) } @Test fun `hide shield dialog`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setTimelineView( state = aTimelineState( isLive = false, @@ -138,12 +139,12 @@ class TimelineViewTest { ), ) rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(TimelineEvents.HideShieldDialog) + eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog) } @Test fun `scrolling near to the start of the loaded items triggers a pre-fetch`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val items = List(200) { aTimelineItemEvent( eventId = EventId("\$event_$it"), @@ -166,8 +167,8 @@ class TimelineViewTest { eventsRecorder.assertList( listOf( - TimelineEvents.OnScrollFinished(firstIndex = 0), - TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS), + TimelineEvent.OnScrollFinished(firstIndex = 0), + TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS), ) ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt index a43b4121bb..e3317b3d42 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt @@ -8,15 +8,13 @@ package io.element.android.features.messages.impl.timeline.components.customreaction -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -33,37 +31,33 @@ class CustomReactionPresenterTest { @Test fun `present - handle selecting and de-selecting an event`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val event = aTimelineItemEvent(eventId = AN_EVENT_ID) val initialState = awaitItem() assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) - initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) + initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event)) assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId assertThat(eventId).isEqualTo(AN_EVENT_ID) - initialState.eventSink(CustomReactionEvents.DismissCustomReactionSheet) + initialState.eventSink(CustomReactionEvent.DismissCustomReactionSheet) assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.None) } } @Test fun `present - handle selected emojis`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val reactions = aTimelineItemReactions(count = 1, isHighlighted = true) val event = aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions) val initialState = awaitItem() assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) val key = reactions.reactions.first().key - initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) + initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event)) assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt index aa177b07e2..541b16b546 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt @@ -8,6 +8,7 @@ package io.element.android.features.messages.impl.timeline.components.customreaction.picker +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.runtime.InternalComposeApi import androidx.compose.runtime.currentComposer import androidx.compose.ui.platform.LocalConfiguration @@ -40,19 +41,19 @@ class EmojiPickerPresenterTest { val warmUpRule = WarmUpRule() @Test - fun `UpdateSearchQuery loads new results`() = runTest { + fun `updating search query loads new results`() = runTest { testPresenter { skipItems(1) val initialState = awaitItem() - assertThat(initialState.searchQuery).isEmpty() + assertThat(initialState.searchQuery.text.toString()).isEmpty() assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) - initialState.eventSink(EmojiPickerEvents.UpdateSearchQuery("smile")) - assertThat(awaitItem().searchQuery).isEqualTo("smile") + initialState.searchQuery.setTextAndPlaceCursorAtEnd("smile") + assertThat(awaitItem().searchQuery.text.toString()).isEqualTo("smile") val stateWithResults = awaitItem() - assertThat(stateWithResults.searchQuery).isEqualTo("smile") + assertThat(stateWithResults.searchQuery.text.toString()).isEqualTo("smile") assertThat(stateWithResults.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) } } @@ -65,10 +66,10 @@ class EmojiPickerPresenterTest { val initialState = awaitItem() assertThat(initialState.isSearchActive).isFalse() - initialState.eventSink(EmojiPickerEvents.ToggleSearchActive(true)) + initialState.eventSink(EmojiPickerEvent.ToggleSearchActive(true)) assertThat(awaitItem().isSearchActive).isTrue() - initialState.eventSink(EmojiPickerEvents.ToggleSearchActive(false)) + initialState.eventSink(EmojiPickerEvent.ToggleSearchActive(false)) assertThat(awaitItem().isSearchActive).isFalse() } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt index f8b416a302..64b5216d2e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.element.android.features.messages.impl.timeline.TimelineEvents +import io.element.android.features.messages.impl.timeline.TimelineEvent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings @@ -39,7 +39,7 @@ class TimelineItemPollViewTest { } private fun testAnswer(answerIndex: Int) { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val content = aTimelineItemPollContent() rule.setContent { TimelineItemPollView( @@ -52,12 +52,12 @@ class TimelineItemPollViewTest { matcher = hasText(answer.text), useUnmergedTree = true, ).performClick() - eventsRecorder.assertSingle(TimelineEvents.SelectPollAnswer(content.eventId!!, answer.id)) + eventsRecorder.assertSingle(TimelineEvent.SelectPollAnswer(content.eventId!!, answer.id)) } @Test fun `editing a poll should emit a PollEditClicked event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val content = aTimelineItemPollContent( isMine = true, isEditable = true, @@ -69,12 +69,12 @@ class TimelineItemPollViewTest { ) } rule.clickOn(CommonStrings.action_edit_poll) - eventsRecorder.assertSingle(TimelineEvents.EditPoll(content.eventId!!)) + eventsRecorder.assertSingle(TimelineEvent.EditPoll(content.eventId!!)) } @Test fun `closing a poll should emit a PollEndClicked event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val content = aTimelineItemPollContent( isMine = true, ) @@ -88,6 +88,6 @@ class TimelineItemPollViewTest { // A confirmation dialog should be shown eventsRecorder.assertEmpty() rule.pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(TimelineEvents.EndPoll(content.eventId!!)) + eventsRecorder.assertSingle(TimelineEvent.EndPoll(content.eventId!!)) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt index a05b7da5f5..f42f47adc3 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt @@ -8,9 +8,6 @@ package io.element.android.features.messages.impl.timeline.components.reactionsummary -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.timeline.model.anAggregatedReaction import io.element.android.libraries.matrix.api.room.RoomMembersState @@ -21,6 +18,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -32,7 +30,7 @@ class ReactionSummaryPresenterTest { private val aggregatedReaction = anAggregatedReaction(userId = A_USER_ID, key = "👍", isHighlighted = true) private val roomMember = aRoomMember(userId = A_USER_ID, avatarUrl = AN_AVATAR_URL, displayName = A_USER_NAME) - private val summaryEvent = ReactionSummaryEvents.ShowReactionSummary(AN_EVENT_ID, listOf(aggregatedReaction), aggregatedReaction.key) + private val summaryEvent = ReactionSummaryEvent.ShowReactionSummary(AN_EVENT_ID, listOf(aggregatedReaction), aggregatedReaction.key) private val room = FakeBaseRoom().apply { givenRoomMembersState(RoomMembersState.Ready(persistentListOf(roomMember))) } @@ -40,25 +38,21 @@ class ReactionSummaryPresenterTest { @Test fun `present - handle showing and hiding the reaction summary`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.target).isNull() initialState.eventSink(summaryEvent) assertThat(awaitItem().target).isNotNull() - initialState.eventSink(ReactionSummaryEvents.Clear) + initialState.eventSink(ReactionSummaryEvent.Clear) assertThat(awaitItem().target).isNull() } } @Test fun `present - handle reaction summary content and avatars populated`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.target).isNull() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt index d39500fec2..91c89ec89e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt @@ -8,12 +8,10 @@ package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -25,12 +23,10 @@ class ReadReceiptBottomSheetPresenterTest { @Test fun `present - handle event selected`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val selectedEvent = aTimelineItemEvent() - initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) + initialState.eventSink(ReadReceiptBottomSheetEvent.EventSelected(selectedEvent)) assertThat(awaitItem().selectedEvent).isSameInstanceAs(selectedEvent) } } @@ -38,14 +34,12 @@ class ReadReceiptBottomSheetPresenterTest { @Test fun `present - handle dismiss`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() val selectedEvent = aTimelineItemEvent() - initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) + initialState.eventSink(ReadReceiptBottomSheetEvent.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(ReadReceiptBottomSheetEvents.Dismiss) + initialState.eventSink(ReadReceiptBottomSheetEvent.Dismiss) assertThat(awaitItem().selectedEvent).isNull() } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt index 2a31d9063e..7a7d4cdfd4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt @@ -47,6 +47,8 @@ class TimelineItemGrouperTest { timelineItemDebugInfoProvider = { aTimelineItemDebugInfo() }, messageShieldProvider = { null }, sendHandleProvider = { FakeSendHandle() }, + forwarder = null, + forwarderProfile = null, ) private val aNonGroupableItem = aMessageEvent() private val aNonGroupableItemNoEvent = TimelineItem.Virtual(UniqueId("virtual"), aTimelineItemDaySeparatorModel("Today")) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt index 896c529ed1..62e596bc21 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt @@ -8,10 +8,7 @@ package io.element.android.features.messages.impl.typing -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow import app.cash.turbine.Event -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -26,6 +23,7 @@ import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @@ -40,9 +38,7 @@ class TypingNotificationPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.renderTypingNotifications).isTrue() assertThat(initialState.typingMembers).isEmpty() @@ -61,9 +57,7 @@ class TypingNotificationPresenterTest { joinedRoom = room, sessionPreferencesStore = sessionPreferencesStore, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.renderTypingNotifications).isFalse() @@ -95,9 +89,7 @@ class TypingNotificationPresenterTest { val typingMembersFlow = MutableStateFlow>(emptyList()) val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow) val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.typingMembers).isEmpty() typingMembersFlow.emit(listOf(A_USER_ID_2)) @@ -133,9 +125,7 @@ class TypingNotificationPresenterTest { ) } val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.typingMembers).isEmpty() typingMembersFlow.emit(listOf(A_USER_ID_2)) @@ -160,9 +150,7 @@ class TypingNotificationPresenterTest { val typingMembersFlow = MutableStateFlow>(emptyList()) val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow) val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.typingMembers).isEmpty() typingMembersFlow.emit(listOf(A_USER_ID_2)) @@ -194,9 +182,7 @@ class TypingNotificationPresenterTest { val typingMembersFlow = MutableStateFlow>(emptyList()) val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow) val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.typingMembers).isEmpty() typingMembersFlow.emit(listOf(A_USER_ID_2)) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt index 8bced93023..a1ffd1f02b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -99,6 +99,8 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf( }, messageShieldProvider = { null }, sendHandleProvider = { FakeSendHandle() }, + forwarder = null, + forwarderProfile = null, ), ) ) diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt index 60969dcb1d..338c3e900f 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt @@ -10,14 +10,12 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.logs.LogFilesRemover /** * Remove existing logs from the device to remove any leaks of sensitive data. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration01( private val logFilesRemover: LogFilesRemover, ) : AppMigration { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt index 64682749d8..d89c29c787 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt @@ -10,7 +10,6 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.sessionstorage.api.SessionStore @@ -21,7 +20,6 @@ import kotlinx.coroutines.coroutineScope * This way we don't force existing users to verify their session again. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration02( private val sessionStore: SessionStore, private val sessionPreferenceStoreFactory: SessionPreferencesStoreFactory, diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt index 62674cf583..ac773a36f8 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt @@ -10,13 +10,11 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject /** * This performs the same operation as [AppMigration01], since we need to clear the local logs again. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration03( private val migration01: AppMigration01, ) : AppMigration { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt index 8ac4146640..36b85e49a2 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt @@ -11,7 +11,6 @@ package io.element.android.features.migration.impl.migrations import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.annotations.ApplicationContext @@ -19,13 +18,13 @@ import io.element.android.libraries.di.annotations.ApplicationContext * Remove notifications.bin file, used to store notification data locally. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration04( @ApplicationContext private val context: Context, ) : AppMigration { companion object { internal const val NOTIFICATION_FILE_NAME = "notifications.bin" } + override val order: Int = 4 override suspend fun migrate(isFreshInstall: Boolean) { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt index 094248f5a0..c831a3e494 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt @@ -10,13 +10,11 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.BaseDirectory import io.element.android.libraries.sessionstorage.api.SessionStore import java.io.File @ContributesIntoSet(AppScope::class) -@Inject class AppMigration05( private val sessionStore: SessionStore, @BaseDirectory private val baseDirectory: File, diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt index 0fbda43207..ebd6e46af3 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt @@ -10,7 +10,6 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.sessionstorage.api.SessionStore import java.io.File @@ -19,7 +18,6 @@ import java.io.File * Create the cache directory for the existing sessions. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration06( private val sessionStore: SessionStore, @CacheDirectory private val cacheDirectory: File, diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt index d562be793b..2a632e3934 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt @@ -10,14 +10,12 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.logs.LogFilesRemover /** * Delete the previous log files. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration07( private val logFilesRemover: LogFilesRemover, ) : AppMigration { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt index 0f3b33a66d..9669d6f7cb 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt @@ -10,7 +10,6 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.features.announcement.api.Announcement import io.element.android.features.announcement.api.AnnouncementService @@ -18,7 +17,6 @@ import io.element.android.features.announcement.api.AnnouncementService * Ensure the new notification sound banner is displayed, but only on application upgrade. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration08( private val announcementService: AnnouncementService, ) : AppMigration { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration09.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration09.kt index a420ac8e8f..16fc7007d0 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration09.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration09.kt @@ -9,7 +9,6 @@ package io.element.android.features.migration.impl.migrations import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.sessionstorage.api.SessionStore @@ -18,7 +17,6 @@ import io.element.android.libraries.sessionstorage.api.SessionStore * Ensure we clear the well-known cached config, since it could be invalid due to an SDK issue. */ @ContributesIntoSet(AppScope::class) -@Inject class AppMigration09( private val sessionStore: SessionStore, private val matrixClientProvider: MatrixClientProvider, diff --git a/features/poll/impl/src/main/res/values-in/translations.xml b/features/poll/impl/src/main/res/values-in/translations.xml index 99250c4bef..f7ddd0976d 100644 --- a/features/poll/impl/src/main/res/values-in/translations.xml +++ b/features/poll/impl/src/main/res/values-in/translations.xml @@ -1,20 +1,20 @@ "Tambahkan opsi" - "Tampilkan hasil hanya setelah pemungutan suara berakhir" - "Pemungutan suara anonim" + "Tampilkan hasil hanya setelah jajak pendapat berakhir" + "Jajak pendapat anonim" "Opsi %1$d" "Perubahan Anda belum disimpan. Apakah Anda yakin ingin kembali?" "Hapus opsi %1$s" "Pertanyaan atau topik" - "Tentang apa pemungutan suara ini?" - "Buat pemungutan suara" - "Apakah Anda yakin ingin menghapus pemungutan suara ini?" - "Hapus pemungutan suara" - "Sunting pemungutan suara" - "Tidak dapat menemukan pemungutan suara yang sedang berlangsung." - "Tidak dapat menemukan pemungutan suara sebelumnya." + "Tentang apa jajak pendapat ini?" + "Buat jajak pendapat" + "Apakah Anda yakin ingin menghapus jajak pendapat ini?" + "Hapus jajak pendapat" + "Edit jajak pendapat" + "Tidak dapat menemukan jajak pendapat yang sedang berlangsung." + "Tidak dapat menemukan jajak pendapat sebelumnya." "Sedang berlangsung" "Masa lalu" - "Pemungutan suara" + "Jajak pendapat" diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index 1e056c0bf0..59aa6ece8e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -139,7 +139,7 @@ class PreferencesRootPresenter( return PreferencesRootState( myUser = matrixUser.value, - version = versionFormatter.get(), + version = remember { versionFormatter.get() }, deviceId = matrixClient.deviceId, isMultiAccountEnabled = isMultiAccountEnabled, otherSessions = otherSessions, diff --git a/features/preferences/impl/src/main/res/values-in/translations.xml b/features/preferences/impl/src/main/res/values-in/translations.xml index ffc00d38ed..1c310a1212 100644 --- a/features/preferences/impl/src/main/res/values-in/translations.xml +++ b/features/preferences/impl/src/main/res/values-in/translations.xml @@ -34,7 +34,7 @@ "Nama tampilan Anda" "Terjadi kesalahan yang tidak diketahui dan informasi tidak dapat diubah." "Tidak dapat memperbarui profil" - "Sunting profil" + "Edit profil" "Memperbarui profil…" "Pengaturan tambahan" "Panggilan audio dan video" diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvent.kt similarity index 65% rename from features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt rename to features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvent.kt index 89c49338d1..9a95910c13 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.rageshake.api.crash -sealed interface CrashDetectionEvents { - data object ResetAllCrashData : CrashDetectionEvents - data object ResetAppHasCrashed : CrashDetectionEvents +sealed interface CrashDetectionEvent { + data object ResetAllCrashData : CrashDetectionEvent + data object ResetAppHasCrashed : CrashDetectionEvent } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt index dd08cbf86c..9c29dc4e7b 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt @@ -11,5 +11,5 @@ package io.element.android.features.rageshake.api.crash data class CrashDetectionState( val appName: String, val crashDetected: Boolean, - val eventSink: (CrashDetectionEvents) -> Unit + val eventSink: (CrashDetectionEvent) -> Unit ) diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt index 57d28527ca..5db1d4f076 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt @@ -22,7 +22,7 @@ fun CrashDetectionView( onOpenBugReport: () -> Unit = { }, ) { fun onPopupDismissed() { - state.eventSink(CrashDetectionEvents.ResetAllCrashData) + state.eventSink(CrashDetectionEvent.ResetAllCrashData) } if (state.crashDetected) { diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvent.kt similarity index 60% rename from features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt rename to features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvent.kt index 921ae861f2..83ae545d95 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvent.kt @@ -10,10 +10,10 @@ package io.element.android.features.rageshake.api.detection import io.element.android.features.rageshake.api.screenshot.ImageResult -sealed interface RageshakeDetectionEvents { - data object Dismiss : RageshakeDetectionEvents - data object Disable : RageshakeDetectionEvents - data object StartDetection : RageshakeDetectionEvents - data object StopDetection : RageshakeDetectionEvents - data class ProcessScreenshot(val imageResult: ImageResult) : RageshakeDetectionEvents +sealed interface RageshakeDetectionEvent { + data object Dismiss : RageshakeDetectionEvent + data object Disable : RageshakeDetectionEvent + data object StartDetection : RageshakeDetectionEvent + data object StopDetection : RageshakeDetectionEvent + data class ProcessScreenshot(val imageResult: ImageResult) : RageshakeDetectionEvent } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt index a240fed687..2e458e983a 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt @@ -15,5 +15,5 @@ data class RageshakeDetectionState( val showDialog: Boolean, val isStarted: Boolean, val preferenceState: RageshakePreferencesState, - val eventSink: (RageshakeDetectionEvents) -> Unit + val eventSink: (RageshakeDetectionEvent) -> Unit ) diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt index dff6d9b716..745a362637 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt @@ -35,22 +35,22 @@ fun RageshakeDetectionView( val context = LocalContext.current OnLifecycleEvent { _, event -> when (event) { - Lifecycle.Event.ON_RESUME -> eventSink(RageshakeDetectionEvents.StartDetection) - Lifecycle.Event.ON_PAUSE -> eventSink(RageshakeDetectionEvents.StopDetection) + Lifecycle.Event.ON_RESUME -> eventSink(RageshakeDetectionEvent.StartDetection) + Lifecycle.Event.ON_PAUSE -> eventSink(RageshakeDetectionEvent.StopDetection) else -> Unit } } when { state.takeScreenshot -> TakeScreenshot( - onScreenshot = { eventSink(RageshakeDetectionEvents.ProcessScreenshot(it)) } + onScreenshot = { eventSink(RageshakeDetectionEvent.ProcessScreenshot(it)) } ) state.showDialog -> { LaunchedEffect(Unit) { context.vibrate() } RageshakeDialogContent( - onNoClick = { eventSink(RageshakeDetectionEvents.Dismiss) }, - onDisableClick = { eventSink(RageshakeDetectionEvents.Disable) }, + onNoClick = { eventSink(RageshakeDetectionEvent.Dismiss) }, + onDisableClick = { eventSink(RageshakeDetectionEvent.Disable) }, onYesClick = onOpenBugReport ) } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvent.kt similarity index 84% rename from features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvents.kt rename to features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvent.kt index 49458c2e34..8b1540c0d5 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvent.kt @@ -8,7 +8,7 @@ package io.element.android.features.rageshake.api.preferences -sealed interface RageshakePreferencesEvents { - data class SetSensitivity(val sensitivity: Float) : RageshakePreferencesEvents - data class SetIsEnabled(val isEnabled: Boolean) : RageshakePreferencesEvents +sealed interface RageshakePreferencesEvent { + data class SetSensitivity(val sensitivity: Float) : RageshakePreferencesEvent + data class SetIsEnabled(val isEnabled: Boolean) : RageshakePreferencesEvent } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt index faba80a3c5..00e65d3e13 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt @@ -13,5 +13,5 @@ data class RageshakePreferencesState( val isEnabled: Boolean, val isSupported: Boolean, val sensitivity: Float, - val eventSink: (RageshakePreferencesEvents) -> Unit, + val eventSink: (RageshakePreferencesEvent) -> Unit, ) diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt index d1a863745f..1de65c1a69 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt @@ -23,7 +23,7 @@ fun aRageshakePreferencesState( isEnabled: Boolean = false, isSupported: Boolean = true, sensitivity: Float = 0.3f, - eventSink: (RageshakePreferencesEvents) -> Unit = {} + eventSink: (RageshakePreferencesEvent) -> Unit = {} ) = RageshakePreferencesState( isFeatureEnabled = isFeatureEnabled, isEnabled = isEnabled, diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt index e4f32982f9..7ea35308e9 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt @@ -29,11 +29,11 @@ fun RageshakePreferencesView( modifier: Modifier = Modifier, ) { fun onSensitivityChanged(sensitivity: Float) { - state.eventSink(RageshakePreferencesEvents.SetSensitivity(sensitivity = sensitivity)) + state.eventSink(RageshakePreferencesEvent.SetSensitivity(sensitivity = sensitivity)) } fun onEnabledChanged(isEnabled: Boolean) { - state.eventSink(RageshakePreferencesEvents.SetIsEnabled(isEnabled = isEnabled)) + state.eventSink(RageshakePreferencesEvent.SetIsEnabled(isEnabled = isEnabled)) } Column(modifier = modifier) { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index 612d3aa68c..c2c34835c9 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import coil3.request.CachePolicy import coil3.request.ImageRequest import io.element.android.features.rageshake.impl.R import io.element.android.libraries.architecture.AsyncAction @@ -135,6 +136,9 @@ fun BugReportView( val context = LocalContext.current val model = ImageRequest.Builder(context) .data(state.screenshotUri) + // Since `screenshotUri` always has the same value, we need to disable memory cache to + // ensure the image is reloaded when the URI content changes + .memoryCachePolicy(CachePolicy.DISABLED) .build() AsyncImage( modifier = Modifier.fillMaxWidth(fraction = 0.5f), diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt index 25afadec57..b75d36f21f 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt @@ -18,7 +18,7 @@ import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import io.element.android.features.rageshake.api.RageshakeFeatureAvailability -import io.element.android.features.rageshake.api.crash.CrashDetectionEvents +import io.element.android.features.rageshake.api.crash.CrashDetectionEvent import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter import io.element.android.features.rageshake.api.crash.CrashDetectionState import io.element.android.libraries.core.meta.BuildMeta @@ -48,10 +48,10 @@ class DefaultCrashDetectionPresenter( } }.collectAsState(false) - fun handleEvent(event: CrashDetectionEvents) { + fun handleEvent(event: CrashDetectionEvent) { when (event) { - CrashDetectionEvents.ResetAllCrashData -> localCoroutineScope.resetAll() - CrashDetectionEvents.ResetAppHasCrashed -> localCoroutineScope.resetAppHasCrashed() + CrashDetectionEvent.ResetAllCrashData -> localCoroutineScope.resetAll() + CrashDetectionEvent.ResetAppHasCrashed -> localCoroutineScope.resetAppHasCrashed() } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt index a7813ed6fb..49014d72a7 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt @@ -12,15 +12,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvents +import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvent import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter import io.element.android.features.rageshake.api.detection.RageshakeDetectionState -import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvents +import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvent import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter import io.element.android.features.rageshake.api.screenshot.ImageResult import io.element.android.features.rageshake.impl.rageshake.RageShake @@ -49,29 +48,19 @@ class DefaultRageshakeDetectionPresenter( mutableStateOf(false) } - fun handleEvent(event: RageshakeDetectionEvents) { + fun handleEvent(event: RageshakeDetectionEvent) { when (event) { - RageshakeDetectionEvents.Disable -> { - preferencesState.eventSink(RageshakePreferencesEvents.SetIsEnabled(false)) + RageshakeDetectionEvent.Disable -> { + preferencesState.eventSink(RageshakePreferencesEvent.SetIsEnabled(false)) showDialog.value = false } - RageshakeDetectionEvents.StartDetection -> isStarted.value = true - RageshakeDetectionEvents.StopDetection -> isStarted.value = false - is RageshakeDetectionEvents.ProcessScreenshot -> localCoroutineScope.processScreenshot(takeScreenshot, showDialog, event.imageResult) - RageshakeDetectionEvents.Dismiss -> showDialog.value = false + RageshakeDetectionEvent.StartDetection -> isStarted.value = true + RageshakeDetectionEvent.StopDetection -> isStarted.value = false + is RageshakeDetectionEvent.ProcessScreenshot -> localCoroutineScope.processScreenshot(takeScreenshot, showDialog, event.imageResult) + RageshakeDetectionEvent.Dismiss -> showDialog.value = false } } - val state = remember(preferencesState, isStarted.value, takeScreenshot.value, showDialog.value) { - RageshakeDetectionState( - isStarted = isStarted.value, - takeScreenshot = takeScreenshot.value, - showDialog = showDialog.value, - preferenceState = preferencesState, - eventSink = ::handleEvent, - ) - } - LaunchedEffect(preferencesState.sensitivity) { rageShake.setSensitivity(preferencesState.sensitivity) } @@ -83,14 +72,25 @@ class DefaultRageshakeDetectionPresenter( !showDialog.value LaunchedEffect(shouldStart) { - handleRageShake(shouldStart, state, takeScreenshot) + handleRageShake( + start = shouldStart, + sensitivity = preferencesState.sensitivity, + takeScreenshot = takeScreenshot, + ) } - return state + + return RageshakeDetectionState( + isStarted = isStarted.value, + takeScreenshot = takeScreenshot.value, + showDialog = showDialog.value, + preferenceState = preferencesState, + eventSink = ::handleEvent, + ) } - private fun handleRageShake(start: Boolean, state: RageshakeDetectionState, takeScreenshot: MutableState) { + private fun handleRageShake(start: Boolean, sensitivity: Float, takeScreenshot: MutableState) { if (start) { - rageShake.start(state.preferenceState.sensitivity) + rageShake.start(sensitivity) rageShake.setInterceptor { takeScreenshot.value = true } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt index f16d99c634..7f463135f9 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt @@ -19,7 +19,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding import io.element.android.features.rageshake.api.RageshakeFeatureAvailability -import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvents +import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvent import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState import io.element.android.features.rageshake.impl.rageshake.RageShake @@ -48,10 +48,10 @@ class DefaultRageshakePreferencesPresenter( rageshakeDataStore.sensitivity() }.collectAsState(initial = 0f) - fun handleEvent(event: RageshakePreferencesEvents) { + fun handleEvent(event: RageshakePreferencesEvent) { when (event) { - is RageshakePreferencesEvents.SetIsEnabled -> localCoroutineScope.setIsEnabled(event.isEnabled) - is RageshakePreferencesEvents.SetSensitivity -> localCoroutineScope.setSensitivity(event.sensitivity) + is RageshakePreferencesEvent.SetIsEnabled -> localCoroutineScope.setIsEnabled(event.isEnabled) + is RageshakePreferencesEvent.SetSensitivity -> localCoroutineScope.setSensitivity(event.sensitivity) } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index f238f9d6ff..b4e7faa01e 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -135,6 +135,10 @@ class DefaultBugReporter( // enumerate files to delete val bugReportFiles: MutableList = ArrayList() var response: Response? = null + + // Start at something like 1000 lines to have some 'buffer' in case unexpected lines were added + var totalLogLines = 1000L + try { var serverError: String? = null withContext(coroutineDispatchers.io) { @@ -147,18 +151,12 @@ class DefaultBugReporter( } } val gzippedFiles = mutableListOf() - if (withDevicesLogs) { - val files = getLogFiles().sortedByDescending { it.lastModified() } - files.mapNotNullTo(gzippedFiles) { file -> - when { - file.extension == "gz" -> file - else -> compressFile(file) - } - } - } + var filesTooBig = emptyList() + if (withCrashLogs || withDevicesLogs) { saveLogCat() ?.takeIf { it.length() < RageshakeConfig.MAX_LOG_CONTENT_SIZE } + ?.takeIf { countLogLines(it) + totalLogLines < RageshakeConfig.MAX_LOG_LINES_SIZE } ?.let { logCatFile -> compressFile(logCatFile).also { logCatFile.safeDelete() @@ -203,15 +201,57 @@ class DefaultBugReporter( if (sendPushRules) { client.notificationSettingsService.getRawPushRules().getOrNull()?.let { pushRules -> - builder.addFormDataPart( - name = "file", - filename = "push_rules.json", - body = pushRules.toByteArray().toRequestBody(MimeTypes.Json.toMediaTypeOrNull()) - ) + val logLines = pushRules.lineSequence().count() + + if (totalLogLines + logLines < RageshakeConfig.MAX_LOG_LINES_SIZE) { + builder.addFormDataPart( + name = "file", + filename = "push_rules.json", + body = pushRules.toByteArray().toRequestBody(MimeTypes.Json.toMediaTypeOrNull()) + ) + } else { + Timber.w("Could not upload push rules because it would exceed the max log lines size") + } } } } } + + if (withDevicesLogs) { + val files = getLogFiles().sortedByDescending { it.lastModified() } + val filesBySize = files.groupBy { + it.length() < RageshakeConfig.MAX_LOG_CONTENT_SIZE + }.toMutableMap() + + filesBySize[true].orEmpty().mapNotNullTo(gzippedFiles) { file -> + val logLines = countLogLines(file) + totalLogLines += logLines + + when { + totalLogLines > RageshakeConfig.MAX_LOG_LINES_SIZE -> { + // Add it to the list of omitted files too + (filesBySize.getOrPut(false) { mutableListOf() } as MutableList).add(file) + + Timber.e( + "Could not upload file ${file.name} because it would exceed the max log lines size " + + "($totalLogLines/${RageshakeConfig.MAX_LOG_LINES_SIZE}" + ) + + totalLogLines -= logLines + + null + } + file.extension == "gz" -> file + else -> compressFile(file) + } + } + filesTooBig = filesBySize[false].orEmpty().map { it.name } + } + + if (filesTooBig.isNotEmpty()) { + builder.addFormDataPart("omitted_logs", filesTooBig.toString()) + } + if (crashCallStack.isNotEmpty() && withCrashLogs) { builder.addFormDataPart("label", "crash") } @@ -394,8 +434,7 @@ class DefaultBugReporter( logDirectory.listFiles() ?.filter { it.isFile && - !it.name.endsWith(LOG_CAT_FILENAME) && - it.length() < RageshakeConfig.MAX_LOG_CONTENT_SIZE + !it.name.endsWith(LOG_CAT_FILENAME) } }.orEmpty() } @@ -447,4 +486,8 @@ class DefaultBugReporter( Timber.e(e, "getLogCatContent fails") } } + + private fun countLogLines(file: File): Int { + return file.reader().useLines { it.count() } + } } diff --git a/features/rageshake/impl/src/main/res/values-in/translations.xml b/features/rageshake/impl/src/main/res/values-in/translations.xml index dd80d0aa01..0140bfee7a 100644 --- a/features/rageshake/impl/src/main/res/values-in/translations.xml +++ b/features/rageshake/impl/src/main/res/values-in/translations.xml @@ -3,7 +3,7 @@ "Lampirkan tangkapan layar" "Anda dapat menghubungi saya jika Anda memiliki pertanyaan lebih lanjut." "Hubungi saya" - "Sunting tangkapan layar" + "Edit tangkapan layar" "Silakan jelaskan masalah tersebut. Apa yang Anda lakukan? Apa yang Anda harapkan untuk terjadi? Apa yang sebenarnya terjadi? Jelaskan sedetail mungkin." "Jelaskan masalah tersebut…" "Jika memungkinkan, silakan tulis deskripsi dalam bahasa Inggris." diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt index f5b0508063..148c375e25 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt @@ -8,9 +8,6 @@ package io.element.android.features.rageshake.impl.bugreport -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.rageshake.impl.crash.A_CRASH_DATA @@ -22,6 +19,7 @@ import io.element.android.features.rageshake.impl.screenshot.ScreenshotHolder import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -37,9 +35,7 @@ class BugReportPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.hasCrashLogs).isFalse() assertThat(initialState.formState).isEqualTo(BugReportFormState.Default) @@ -53,9 +49,7 @@ class BugReportPresenterTest { @Test fun `present - set description`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetDescription(A_SHORT_DESCRIPTION)) assertThat(awaitItem().submitEnabled).isTrue() @@ -67,9 +61,7 @@ class BugReportPresenterTest { @Test fun `present - can contact`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetCanContact(true)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(canContact = true)) @@ -81,9 +73,7 @@ class BugReportPresenterTest { @Test fun `present - send logs`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() // Since this is true by default, start by disabling initialState.eventSink.invoke(BugReportEvents.SetSendLog(false)) @@ -96,9 +86,7 @@ class BugReportPresenterTest { @Test fun `present - send screenshot`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetSendScreenshot(true)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendScreenshot = true)) @@ -110,9 +98,7 @@ class BugReportPresenterTest { @Test fun `present - send notification settings`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetSendPushRules(true)) assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendPushRules = true)) @@ -127,9 +113,7 @@ class BugReportPresenterTest { crashDataStore = FakeCrashDataStore(crashData = A_CRASH_DATA, appHasCrashed = true), screenshotHolder = FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.hasCrashLogs).isTrue() @@ -148,9 +132,7 @@ class BugReportPresenterTest { FakeCrashDataStore(crashData = A_CRASH_DATA, appHasCrashed = true), FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetDescription(A_LONG_DESCRIPTION)) skipItems(1) @@ -174,9 +156,7 @@ class BugReportPresenterTest { FakeCrashDataStore(crashData = A_CRASH_DATA, appHasCrashed = true), FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetDescription(A_LONG_DESCRIPTION)) skipItems(1) @@ -200,9 +180,7 @@ class BugReportPresenterTest { @Test fun `present - send failure description too short`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetDescription(A_SHORT_DESCRIPTION)) skipItems(1) @@ -223,9 +201,7 @@ class BugReportPresenterTest { FakeCrashDataStore(crashData = A_CRASH_DATA, appHasCrashed = true), FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(BugReportEvents.SetDescription(A_LONG_DESCRIPTION)) skipItems(1) diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt index dcaaa20a69..137f13bdc7 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt @@ -8,17 +8,15 @@ package io.element.android.features.rageshake.impl.crash.ui -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.rageshake.api.crash.CrashDetectionEvents +import io.element.android.features.rageshake.api.crash.CrashDetectionEvent import io.element.android.features.rageshake.impl.crash.A_CRASH_DATA import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter import io.element.android.features.rageshake.impl.crash.FakeCrashDataStore import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -33,9 +31,7 @@ class CrashDetectionPresenterTest { @Test fun `present - initial state no crash`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.crashDetected).isFalse() } @@ -46,9 +42,7 @@ class CrashDetectionPresenterTest { val presenter = createPresenter( FakeCrashDataStore(appHasCrashed = true) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.crashDetected).isTrue() @@ -61,9 +55,7 @@ class CrashDetectionPresenterTest { FakeCrashDataStore(appHasCrashed = true), isFeatureAvailableFlow = flowOf(false), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.crashDetected).isFalse() } @@ -74,13 +66,11 @@ class CrashDetectionPresenterTest { val presenter = createPresenter( FakeCrashDataStore(appHasCrashed = true) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.crashDetected).isTrue() - initialState.eventSink.invoke(CrashDetectionEvents.ResetAppHasCrashed) + initialState.eventSink.invoke(CrashDetectionEvent.ResetAppHasCrashed) assertThat(awaitItem().crashDetected).isFalse() } } @@ -90,13 +80,11 @@ class CrashDetectionPresenterTest { val presenter = createPresenter( FakeCrashDataStore(appHasCrashed = true, crashData = A_CRASH_DATA) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.crashDetected).isTrue() - initialState.eventSink.invoke(CrashDetectionEvents.ResetAllCrashData) + initialState.eventSink.invoke(CrashDetectionEvent.ResetAllCrashData) assertThat(awaitItem().crashDetected).isFalse() } } @@ -109,9 +97,7 @@ class CrashDetectionPresenterTest { crashDataStore = crashDataStore, isFeatureAvailableFlow = isFeatureAvailableFlow, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.crashDetected).isFalse() crashDataStore.setCrashData("Some crash data") diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt index d76ab73aa8..1e2a556dff 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt @@ -9,11 +9,8 @@ package io.element.android.features.rageshake.impl.detection import android.graphics.Bitmap -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvents +import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvent import io.element.android.features.rageshake.api.screenshot.ImageResult import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter import io.element.android.features.rageshake.impl.rageshake.FakeRageShake @@ -21,6 +18,7 @@ import io.element.android.features.rageshake.impl.rageshake.FakeRageshakeDataSto import io.element.android.features.rageshake.impl.screenshot.FakeScreenshotHolder import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import io.mockk.mockk import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf @@ -57,9 +55,7 @@ class RageshakeDetectionPresenterTest { rageshakeFeatureAvailability = { flowOf(true) }, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.takeScreenshot).isFalse() @@ -82,14 +78,12 @@ class RageshakeDetectionPresenterTest { rageshakeFeatureAvailability = { flowOf(true) }, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink.invoke(RageshakeDetectionEvents.StartDetection) + initialState.eventSink.invoke(RageshakeDetectionEvent.StartDetection) assertThat(awaitItem().isStarted).isTrue() - initialState.eventSink.invoke(RageshakeDetectionEvents.StopDetection) + initialState.eventSink.invoke(RageshakeDetectionEvent.StopDetection) assertThat(awaitItem().isStarted).isFalse() } } @@ -108,21 +102,19 @@ class RageshakeDetectionPresenterTest { rageshakeFeatureAvailability = { flowOf(true) }, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.isStarted).isFalse() - initialState.eventSink.invoke(RageshakeDetectionEvents.StartDetection) + initialState.eventSink.invoke(RageshakeDetectionEvent.StartDetection) assertThat(awaitItem().isStarted).isTrue() rageshake.triggerPhoneRageshake() assertThat(awaitItem().takeScreenshot).isTrue() initialState.eventSink.invoke( - RageshakeDetectionEvents.ProcessScreenshot(ImageResult.Success(aBitmap)) + RageshakeDetectionEvent.ProcessScreenshot(ImageResult.Success(aBitmap)) ) assertThat(awaitItem().showDialog).isTrue() - initialState.eventSink.invoke(RageshakeDetectionEvents.Dismiss) + initialState.eventSink.invoke(RageshakeDetectionEvent.Dismiss) val finalState = awaitItem() assertThat(finalState.showDialog).isFalse() assertThat(rageshakeDataStore.isEnabled().first()).isTrue() @@ -143,21 +135,19 @@ class RageshakeDetectionPresenterTest { rageshakeFeatureAvailability = { flowOf(true) }, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.isStarted).isFalse() - initialState.eventSink.invoke(RageshakeDetectionEvents.StartDetection) + initialState.eventSink.invoke(RageshakeDetectionEvent.StartDetection) assertThat(awaitItem().isStarted).isTrue() rageshake.triggerPhoneRageshake() assertThat(awaitItem().takeScreenshot).isTrue() initialState.eventSink.invoke( - RageshakeDetectionEvents.ProcessScreenshot(ImageResult.Error(AN_EXCEPTION)) + RageshakeDetectionEvent.ProcessScreenshot(ImageResult.Error(AN_EXCEPTION)) ) assertThat(awaitItem().showDialog).isTrue() - initialState.eventSink.invoke(RageshakeDetectionEvents.Dismiss) + initialState.eventSink.invoke(RageshakeDetectionEvent.Dismiss) val finalState = awaitItem() assertThat(finalState.showDialog).isFalse() assertThat(rageshakeDataStore.isEnabled().first()).isTrue() @@ -178,21 +168,19 @@ class RageshakeDetectionPresenterTest { rageshakeFeatureAvailability = { flowOf(true) }, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.isStarted).isFalse() - initialState.eventSink.invoke(RageshakeDetectionEvents.StartDetection) + initialState.eventSink.invoke(RageshakeDetectionEvent.StartDetection) assertThat(awaitItem().isStarted).isTrue() rageshake.triggerPhoneRageshake() assertThat(awaitItem().takeScreenshot).isTrue() initialState.eventSink.invoke( - RageshakeDetectionEvents.ProcessScreenshot(ImageResult.Success(aBitmap)) + RageshakeDetectionEvent.ProcessScreenshot(ImageResult.Success(aBitmap)) ) assertThat(awaitItem().showDialog).isTrue() - initialState.eventSink.invoke(RageshakeDetectionEvents.Disable) + initialState.eventSink.invoke(RageshakeDetectionEvent.Disable) skipItems(1) assertThat(awaitItem().showDialog).isFalse() assertThat(rageshakeDataStore.isEnabled().first()).isFalse() diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt index 8fda5c3d97..ac491fb860 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt @@ -8,15 +8,13 @@ package io.element.android.features.rageshake.impl.preferences -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvents +import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvent import io.element.android.features.rageshake.impl.rageshake.A_SENSITIVITY import io.element.android.features.rageshake.impl.rageshake.FakeRageShake import io.element.android.features.rageshake.impl.rageshake.FakeRageshakeDataStore import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -33,9 +31,7 @@ class RageshakePreferencesPresenterTest { FakeRageshakeDataStore(isEnabled = true), rageshakeFeatureAvailability = { flowOf(true) }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.isSupported).isTrue() @@ -50,9 +46,7 @@ class RageshakePreferencesPresenterTest { FakeRageshakeDataStore(isEnabled = true), rageshakeFeatureAvailability = { flowOf(true) }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.isSupported).isFalse() @@ -67,15 +61,13 @@ class RageshakePreferencesPresenterTest { FakeRageshakeDataStore(isEnabled = true), rageshakeFeatureAvailability = { flowOf(true) }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.isEnabled).isTrue() - initialState.eventSink.invoke(RageshakePreferencesEvents.SetIsEnabled(false)) + initialState.eventSink.invoke(RageshakePreferencesEvent.SetIsEnabled(false)) assertThat(awaitItem().isEnabled).isFalse() - initialState.eventSink.invoke(RageshakePreferencesEvents.SetIsEnabled(true)) + initialState.eventSink.invoke(RageshakePreferencesEvent.SetIsEnabled(true)) assertThat(awaitItem().isEnabled).isTrue() } } @@ -87,13 +79,11 @@ class RageshakePreferencesPresenterTest { FakeRageshakeDataStore(isEnabled = true), rageshakeFeatureAvailability = { flowOf(true) }, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.sensitivity).isEqualTo(A_SENSITIVITY) - initialState.eventSink.invoke(RageshakePreferencesEvents.SetSensitivity(A_SENSITIVITY + 1f)) + initialState.eventSink.invoke(RageshakePreferencesEvent.SetSensitivity(A_SENSITIVITY + 1f)) assertThat(awaitItem().sensitivity).isEqualTo(A_SENSITIVITY + 1f) } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt index 41e136a53e..c503e497f7 100755 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt @@ -536,6 +536,6 @@ class DefaultBugReporterTest { } companion object { - private const val EXPECTED_NUMBER_OF_PROGRESS_VALUE = 18 + private const val EXPECTED_NUMBER_OF_PROGRESS_VALUE = 17 } } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt index 2867273e80..49aa7373bd 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt @@ -12,7 +12,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser sealed interface ChangeRolesEvent { data object ToggleSearchActive : ChangeRolesEvent - data class QueryChanged(val query: String?) : ChangeRolesEvent data class UserSelectionToggled(val matrixUser: MatrixUser) : ChangeRolesEvent data object Save : ChangeRolesEvent data object Exit : ChangeRolesEvent diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt index 3989a76df3..d2c9175f38 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt @@ -8,6 +8,7 @@ package io.element.android.features.rolesandpermissions.impl.roles +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState @@ -68,7 +69,7 @@ class ChangeRolesPresenter( @Composable override fun present(): ChangeRolesState { - var query by rememberSaveable { mutableStateOf(null) } + val queryState = rememberTextFieldState() var searchActive by rememberSaveable { mutableStateOf(false) } var searchResults by remember { mutableStateOf>(SearchBarResultState.Initial()) @@ -105,9 +106,10 @@ class ChangeRolesPresenter( val roomMemberState by room.membersStateFlow.collectAsState() // Update search results for every query change + val query = queryState.text.toString() LaunchedEffect(query, roomMemberState) { val results = dataSource - .search(query.orEmpty()) + .search(query) .groupedByRole() searchResults = if (results.isEmpty()) { @@ -136,9 +138,6 @@ class ChangeRolesPresenter( is ChangeRolesEvent.ToggleSearchActive -> { searchActive = !searchActive } - is ChangeRolesEvent.QueryChanged -> { - query = event.query - } is ChangeRolesEvent.UserSelectionToggled -> { val newList = selectedUsers.value.toMutableList() val index = newList.indexOfFirst { it.userId == event.matrixUser.userId } @@ -188,7 +187,7 @@ class ChangeRolesPresenter( } return ChangeRolesState( role = role, - query = query, + searchQuery = queryState, isSearchActive = searchActive, searchResults = searchResults, selectedUsers = selectedUsers.value, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt index 71fef01fa7..173db04524 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt @@ -8,6 +8,7 @@ package io.element.android.features.rolesandpermissions.impl.roles +import androidx.compose.foundation.text.input.TextFieldState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId @@ -19,7 +20,7 @@ import kotlinx.collections.immutable.toImmutableList data class ChangeRolesState( val role: RoomMember.Role, - val query: String?, + val searchQuery: TextFieldState, val isSearchActive: Boolean, val searchResults: SearchBarResultState, val selectedUsers: ImmutableList, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt index 654259c02a..ebaf619d4e 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.rolesandpermissions.impl.roles +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -34,8 +35,8 @@ class ChangeRolesStateProvider : PreviewParameterProvider { aChangeRolesStateWithSelectedUsers().copy( selectedUsers = aMatrixUserList().take(2).toImmutableList(), ), - aChangeRolesStateWithSelectedUsers().copy( - query = "Alice", + aChangeRolesState( + searchQuery = "Alice", isSearchActive = true, searchResults = SearchBarResultState.Results( MembersByRole( @@ -44,6 +45,8 @@ class ChangeRolesStateProvider : PreviewParameterProvider { ) ), selectedUsers = aMatrixUserList().take(1).toImmutableList(), + hasPendingChanges = true, + canRemoveMember = { it != UserId("@alice:server.org") }, ), aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.ConfirmingCancellation), aChangeRolesStateWithSelectedUsers().copy(savingState = ConfirmingModifyingAdmins), @@ -59,7 +62,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider { internal fun aChangeRolesState( role: RoomMember.Role = RoomMember.Role.Admin, - query: String? = null, + searchQuery: String = "", isSearchActive: Boolean = false, searchResults: SearchBarResultState = SearchBarResultState.NoResultsFound(), selectedUsers: ImmutableList = persistentListOf(), @@ -69,7 +72,7 @@ internal fun aChangeRolesState( eventSink: (ChangeRolesEvent) -> Unit = {}, ) = ChangeRolesState( role = role, - query = query, + searchQuery = TextFieldState(initialText = searchQuery), isSearchActive = isSearchActive, searchResults = searchResults, selectedUsers = selectedUsers, diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt index 20abc70bb7..b2c88efede 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt @@ -118,8 +118,7 @@ fun ChangeRolesView( .fillMaxWidth() .padding(bottom = 16.dp), placeHolderTitle = stringResource(CommonStrings.common_search_for_someone), - query = state.query.orEmpty(), - onQueryChange = { state.eventSink(ChangeRolesEvent.QueryChanged(it)) }, + queryState = state.searchQuery, active = state.isSearchActive, onActiveChange = { state.eventSink(ChangeRolesEvent.ToggleSearchActive) }, resultState = state.searchResults, diff --git a/features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml index 4160039a0a..e44a1dba21 100644 --- a/features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml @@ -38,6 +38,10 @@ "Mentetlen módosításai vannak." "Menti a módosításokat?" "Ebben a szobában nincsenek kitiltott felhasználók." + + "%1$d kitiltva" + "%1$d kitiltva" + "Ellenőrizze a helyesírást, vagy próbáljon meg egy új keresést" "Nincs találat a következőre: „%1$s\"" @@ -51,6 +55,10 @@ "Visszaengedés a szobába" "Kitiltva" "Tagok" + + "%1$d meghívott" + "%1$d meghívott" + "Függőben" "Adminisztrátor" "Moderátor" diff --git a/features/rolesandpermissions/impl/src/main/res/values-in/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-in/translations.xml index 2a1cd15ac0..2cf57a0f38 100644 --- a/features/rolesandpermissions/impl/src/main/res/values-in/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-in/translations.xml @@ -8,11 +8,11 @@ "Admin dan moderator" "Keluarkan orang-orang dan tolak permintaan untuk bergabung" "Ubah avatar ruangan" - "Sunting Ruangan" + "Edit detail" "Ubah nama ruangan" "Ubah topik ruangan" "Kirim pesan" - "Sunting Admin" + "Edit Admin" "Anda tidak akan dapat mengurungkan tindakan ini. Anda mempromosikan pengguna untuk memiliki tingkat daya yang sama seperti Anda." "Tambahkan Admin?" "Turunkan" @@ -21,7 +21,7 @@ "%1$s (Tertunda)" "(Tertunda)" "Admin secara otomatis memiliki hak moderator" - "Sunting Moderator" + "Edit Moderator" "Admin" "Moderator" "Anggota" diff --git a/features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml index 41f475012f..299dcc9cb9 100644 --- a/features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml @@ -2,9 +2,12 @@ "Admin" "Forby folk" + "Endre innstillinger" "Fjern meldinger" "Medlem" "Inviter folk" + "Administrer området" + "Administrer rom" "Administrer medlemmer" "Meldinger og innhold" "Moderator" @@ -14,6 +17,7 @@ "Endre romnavn" "Endre temaet til rommet" "Send meldinger" + "Tillatelser" "Rediger administratorer" "Du vil ikke kunne angre denne handlingen. Du forfremmer brukeren til å ha samme rettighetsnivå som deg." "Legg til administrator?" @@ -59,10 +63,12 @@ "Meldinger og innhold" "Moderatorer" "Eiere" + "Tillatelser" "Tilbakestill tillatelser" "Når du har tilbakestilt tillatelsene, mister du gjeldende innstillinger." "Vil du tilbakestille tillatelsene?" "Roller" "Romdetaljer" + "Detaljer om området" "Roller og tillatelser" diff --git a/features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml index 32db66ee12..4e8bed7f7f 100644 --- a/features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml @@ -1,14 +1,16 @@ - "Apenas administradores" + "Administrador" "Banir pessoas" "Remover mensagens" - "Convidar pessoas e aceitar pedidos de entrada" + "Membro" + "Convidar pessoas" + "Gerir membros" "Mensagens e conteúdo" - "Administradores e moderadores" - "Remover pessoas e rejeitar pedidos de entrada" + "Moderador" + "Remover pessoas" "Alterar o ícone da sala" - "Editar sala" + "Editar detalhes" "Altera o nome da sala" "Alterar a descrição da sala" "Enviar mensagens" @@ -31,7 +33,7 @@ "Participantes" "Tens alterações por guardar." "Guardar alterações?" - "Não há nenhum utilizador banido desta sala." + "Não há nenhum utilizador banido." "%1$d pessoa" "%1$d pessoas" @@ -43,8 +45,8 @@ "Desbanir da sala" "Banidos" "Participantes" - "Apenas administradores" - "Administradores e moderadores" + "Administrador" + "Moderador" "Dono / Dona" "Participantes" "A anular banimento de %1$s" diff --git a/features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml index d922e58550..7289041017 100644 --- a/features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml @@ -38,6 +38,11 @@ "У вас есть несохраненные изменения." "Сохранить изменения?" "В этой комнате нет заблокированных пользователей." + + "%1$d заблокирован" + "%1$d заблокированы" + "%1$d заблокированы" + "Проверьте правописание или попробуйте новый поиск" "Отсутствует результат по запросу “%1$s”" @@ -52,6 +57,11 @@ "Разблокировать в комнате" "Заблокированные" "Участники" + + "%1$d приглашён" + "%1$d приглашены" + "%1$d приглашены" + "В ожидании" "Только администраторы" "Модератор" diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt index 8747585354..4cde70b3bc 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt @@ -8,6 +8,7 @@ package io.element.android.features.rolesandpermissions.impl.roles +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.features.rolesandpermissions.impl.RoomMemberListDataSource @@ -49,7 +50,7 @@ class ChangeRolesPresenterTest { presenter.test { with(awaitItem()) { assertThat(role).isEqualTo(RoomMember.Role.Admin) - assertThat(query).isNull() + assertThat(searchQuery.text.toString()).isEmpty() assertThat(isSearchActive).isFalse() assertThat(searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(selectedUsers).isEmpty() @@ -206,7 +207,7 @@ class ChangeRolesPresenterTest { } @Test - fun `present - QueryChanged produces new results`() = runTest { + fun `present - updating query produces new results`() = runTest { val room = FakeJoinedRoom().apply { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) } @@ -219,7 +220,7 @@ class ChangeRolesPresenterTest { assertThat(initialResults?.moderators).hasSize(1) assertThat(initialResults?.admins).hasSize(1) - initialState.eventSink(ChangeRolesEvent.QueryChanged("Alice")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("Alice") skipItems(1) val searchResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt index 39967f9160..af14d5be55 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt @@ -76,7 +76,7 @@ class ChangeRolesViewTest { ), ) rule.pressBackKey() - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit)) + eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test @@ -89,7 +89,7 @@ class ChangeRolesViewTest { ), ) rule.pressBack() - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit)) + eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test @@ -102,7 +102,7 @@ class ChangeRolesViewTest { ), ) rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Save)) + eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test @@ -115,7 +115,7 @@ class ChangeRolesViewTest { ), ) rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""))) + eventsRecorder.assertEmpty() } @Test @@ -238,12 +238,7 @@ class ChangeRolesViewTest { label = contentDescription, useUnmergedTree = true, ).performClick() - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.UserSelectionToggled(userToDeselect), - ) - ) + eventsRecorder.assertSingle(ChangeRolesEvent.UserSelectionToggled(userToDeselect)) } @Test @@ -262,12 +257,7 @@ class ChangeRolesViewTest { ) // Select the user from the user list rule.onNodeWithText("Carol").performClick() - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.UserSelectionToggled(userToSelect), - ) - ) + eventsRecorder.assertSingle(ChangeRolesEvent.UserSelectionToggled(userToSelect)) } @Test @@ -288,12 +278,7 @@ class ChangeRolesViewTest { text = "Bob", useUnmergedTree = true, )[1].performClick() - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.UserSelectionToggled(userToSelect), - ) - ) + eventsRecorder.assertSingle(ChangeRolesEvent.UserSelectionToggled(userToSelect)) } private fun AndroidComposeTestRule.setChangeRolesContent( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt index ec1b130b3e..f33bb2df00 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt @@ -12,6 +12,5 @@ import io.element.android.libraries.matrix.api.room.RoomMember sealed interface RoomMemberListEvents { data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvents - data class UpdateSearchQuery(val query: String) : RoomMemberListEvents data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 6917057a06..7ad98a373d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -8,6 +8,7 @@ package io.element.android.features.roomdetails.impl.members +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect @@ -16,7 +17,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents.ShowActionsForUser @@ -56,7 +56,7 @@ class RoomMemberListPresenter( @Composable override fun present(): RoomMemberListState { - var searchQuery by rememberSaveable { mutableStateOf("") } + val searchQuery = rememberTextFieldState() val membersState by room.membersStateFlow.collectAsState() val canInvite by room.permissionsAsState(false) { perms -> perms.canOwnUserInvite() } val roomModerationState = roomMembersModerationPresenter.present() @@ -117,17 +117,16 @@ class RoomMemberListPresenter( } } - LaunchedEffect(searchQuery, roomMembers) { + LaunchedEffect(searchQuery.text, roomMembers) { filteredRoomMembers = roomMembers.map { members -> withContext(coroutineDispatchers.io) { - members.filter(searchQuery) + members.filter(searchQuery.text.toString()) } } } fun handleEvent(event: RoomMemberListEvents) { when (event) { - is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query is RoomMemberListEvents.RoomMemberSelected -> roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser())) is RoomMemberListEvents.ChangeSelectedSection -> selectedSection = event.section diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index 7c928fb27a..3cc795a8db 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -8,6 +8,7 @@ package io.element.android.features.roomdetails.impl.members +import androidx.compose.foundation.text.input.TextFieldState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.bool.orFalse @@ -20,7 +21,7 @@ data class RoomMemberListState( // Only used to know if we can show the banned section private val roomMembers: AsyncData, val filteredRoomMembers: AsyncData, - val searchQuery: String, + val searchQuery: TextFieldState, val canInvite: Boolean, val selectedSection: SelectedSection, val moderationState: RoomMemberModerationState, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index b37738c30f..63dfec56e5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.roomdetails.impl.members +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions @@ -88,7 +89,7 @@ internal fun aRoomMemberListState( ) = RoomMemberListState( roomMembers = roomMembers, filteredRoomMembers = roomMembers.map { it.filter(searchQuery) }, - searchQuery = searchQuery, + searchQuery = TextFieldState(searchQuery), canInvite = canInvite, moderationState = moderationState, selectedSection = selectedSection, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 7c83f74f00..bc37bbbe74 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -42,7 +42,6 @@ import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubti import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon @@ -89,13 +88,8 @@ fun RoomMemberListView( .consumeWindowInsets(padding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - var searchQuery by textFieldState(state.searchQuery) SearchField( - value = searchQuery, - onValueChange = { newQuery -> - searchQuery = newQuery - state.eventSink(RoomMemberListEvents.UpdateSearchQuery(newQuery)) - }, + state = state.searchQuery, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), @@ -105,7 +99,7 @@ fun RoomMemberListView( roomMembersData = state.filteredRoomMembers, selectedSection = state.selectedSection, showBannedSection = state.showBannedSection, - searchQuery = state.searchQuery, + searchQuery = state.searchQuery.text.toString(), onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) }, onSelectUser = ::onSelectUser, ) diff --git a/features/roomdetails/impl/src/main/res/values-da/translations.xml b/features/roomdetails/impl/src/main/res/values-da/translations.xml index d646b03acb..bbf500850e 100644 --- a/features/roomdetails/impl/src/main/res/values-da/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-da/translations.xml @@ -63,6 +63,7 @@ "Profil" "Anmodninger om at deltage" "Roller og tilladelser" + "Navn" "Sikkerhed og privatliv" "Sikkerhed" "Del rum" @@ -128,7 +129,10 @@ "Detaljer om rummet" "Roller og tilladelser" "Tilføj adresse" + "Alle i autoriserede grupper kan deltage, men alle andre skal anmode om adgang." "Alle skal anmode om adgang." + "Bed om at deltage" + "Alle i %1$s kan tilmelde sig, men alle andre skal anmode om adgang." "Ja, aktivér kryptering" "Når det først er aktiveret, kan kryptering for et rum ikke deaktiveres igen. Beskedhistorik vil kun være synlig for rummedlemmer, siden de blev inviteret, eller siden de blev medlem af rummet. Ingen udover medlemmer af rummet vil være i stand til at læse beskeder. Dette kan forhindre bots og broer i at fungere korrekt. @@ -138,9 +142,14 @@ Vi anbefaler ikke at aktivere kryptering for rum, som alle kan finde og deltage "Kryptering" "Aktivér end-to-end-kryptering" "Alle kan være med." + "Alle" + "Vælg, hvilke gruppers medlemmer der kan deltage i dette rum uden en invitation.%1$s" + "Administrer grupper" "Kun inviterede personer kan deltage i dette rum." "Kun inviterede" "Adgang" + "Alle i autoriserede grupper kan deltage." + "Alle i %1$s kan deltage." "Medlemmer af gruppen" "Grupper understøttes ikke i øjeblikket" "Du skal bruge en adresse for at gøre det synligt i det offentlige register." @@ -148,12 +157,15 @@ Vi anbefaler ikke at aktivere kryptering for rum, som alle kan finde og deltage "Tillad, at dette rum kan findes ved at søge i %1$s fortegnelse over offentlige rum" "Gør det muligt at blive fundet ved søgninger i det offentlige register." "Synlig i det offentlige register" + "Alle (historikken er offentlig)" + "Ændringer vil ikke påvirke tidligere beskeder, kun nye. %1$s" "Hvem kan læse historikken?" - "Kun medlemmer, efter de blev inviteret" - "Kun medlemmer siden valg af denne mulighed" + "Medlemmer siden de blev inviteret" + "Medlemmer (fuld historik)" "Rum-adresser er en måde at finde og få adgang til værelser på. Dette sikrer også, at du nemt kan dele dit rum med andre. Du kan vælge at offentliggøre dit rum i din hjemmeservers offentlige katalog over rum." "Udgivelse af rum" + "Adresser er en måde at finde og få adgang til rum og grupper. Dette sikrer også, at du nemt kan dele dem med andre." "Synlighed" "Sikkerhed og privatliv" diff --git a/features/roomdetails/impl/src/main/res/values-hu/translations.xml b/features/roomdetails/impl/src/main/res/values-hu/translations.xml index a549ef437c..f7e743d011 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -63,6 +63,7 @@ "Profil" "Csatlakozási kérelem" "Szerepkörök és jogosultságok" + "Név" "Biztonság és adatvédelem" "Biztonság" "Szoba megosztása" @@ -70,6 +71,10 @@ "Téma" "Szoba frissítése…" "Ebben a szobában nincsenek kitiltott felhasználók." + + "%1$d kitiltva" + "%1$d kitiltva" + "Ellenőrizze a helyesírást, vagy próbáljon meg egy új keresést" "Nincs találat a következőre: „%1$s\"" @@ -83,6 +88,10 @@ "Visszaengedés a szobába" "Kitiltva" "Tagok" + + "%1$d meghívott" + "%1$d meghívott" + "Függőben" "Adminisztrátor" "Moderátor" @@ -120,8 +129,10 @@ "Szoba részletei" "Szerepkörök és jogosultságok" "Cím hozzáadása" + "Bárki csatlakozhat, az engedélyezett terekből, és mindenki másnak hozzáférést kell kérnie." "Mindenkinek hozzáférést kell kérnie." "Csatlakozás kérése" + "Bárki csatlakozhat innen: %1$s, és mindenki másnak hozzáférést kell kérnie." "Igen, engedélyezze a titkosítást" "Az engedélyezés után a szoba titkosítása nem tiltható le. Az üzenetek előzményei csak a szobatagok számára láthatók, amikor meghívást kaptak, vagy mióta csatlakoztak a szobához. A szobatagokon kívül senki sem tudja olvasni az üzeneteket. Ez megakadályozhatja a botok és a hidak megfelelő működését. @@ -131,10 +142,14 @@ Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket b "Titkosítás" "Végpontok közötti titkosítás engedélyezése" "Bárki csatlakozhat." - "Nyilvános" + "Bárki" + "Válassza ki, hogy mely terek tagjai csatlakozhatnak ehhez a szobához meghívás nélkül.%1$s" + "Terek kezelése" "Csak a meghívott emberek léphetnek be." "Csak meghívásos" "Hozzáférés" + "Bárki csatlakozhat, az engedélyezett terekből." + "Bárki csatlakozhatnak innen: %1$s." "A tér tagjai" "A terek jelenleg nem támogatottak" "Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban." @@ -142,13 +157,15 @@ Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket b "A szoba megtalálhatóvá tétele a(z) %1$s nyilvános szobakatalógusában való kereséssel." "Lehetővé teszi, hogy a nyilvános szobakatalógusban megtalálható legyen." "Látható a nyilvános szobakatalógusban" - "Bárki" + "Bárki (az előzmények nyilvánosak)" + "A változások nem érintik a korábbi üzeneteket, csak az újakat. %1$s" "Ki olvashatja az előzményeket" - "Csak a tagok, a meghívásuktól kezdődően" - "Csak a tagok, a beállítás választásától kezdődően" + "Tagok a meghívás óta" + "Tagok (teljes történet)" "A szobacímek a szobák megtalálásának és elérésnek módjai. Ez azt is biztosítja, hogy könnyen megoszthatja a szobáját másokkal. Kiválaszthatja, hogy szobáját közzéteszi-e a Matrix-kiszolgáló nyilvános szobakatalógusában." "Szoba közzététele" + "A címek segítségével megtalálhatja és elérheti a szobákat és a tereket. Ez biztosítja azt is, hogy könnyen megoszthassa azokat másokkal." "Láthatóság" "Biztonság és adatvédelem" diff --git a/features/roomdetails/impl/src/main/res/values-in/translations.xml b/features/roomdetails/impl/src/main/res/values-in/translations.xml index 27a1a5745e..85dbf4b901 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -4,7 +4,7 @@ "Alamat ruangan" "Terjadi kesalahan saat memperbarui pengaturan pemberitahuan." "Homeserver Anda tidak mendukung opsi ini dalam ruangan terenkripsi, Anda mungkin tidak diberi tahu dalam beberapa ruangan." - "Pemungutan suara" + "Jajak pendapat" "Hanya admin" "Cekal orang-orang" "Hilangkan pesan" @@ -13,11 +13,11 @@ "Admin dan moderator" "Keluarkan orang-orang dan tolak permintaan untuk bergabung" "Ubah avatar ruangan" - "Sunting Ruangan" + "Edit detail" "Ubah nama ruangan" "Ubah topik ruangan" "Kirim pesan" - "Sunting Admin" + "Edit Admin" "Anda tidak akan dapat mengurungkan tindakan ini. Anda mempromosikan pengguna untuk memiliki tingkat daya yang sama seperti Anda." "Tambahkan Admin?" "Turunkan" @@ -26,7 +26,7 @@ "%1$s (Tertunda)" "(Tertunda)" "Admin secara otomatis memiliki hak moderator" - "Sunting Moderator" + "Edit Moderator" "Admin" "Moderator" "Anggota" @@ -36,7 +36,7 @@ "Terenkripsi" "Tidak terenkripsi" "Ruangan publik" - "Sunting Ruangan" + "Edit detail" "Terjadi kesalahan yang tidak diketahui dan informasinya tidak dapat diubah." "Tidak dapat memperbarui ruangan" "Pesan diamankan dengan kunci. Hanya Anda dan penerima yang memiliki kunci unik untuk membukanya." diff --git a/features/roomdetails/impl/src/main/res/values-nb/translations.xml b/features/roomdetails/impl/src/main/res/values-nb/translations.xml index d28c16f0ca..b5ef934c26 100644 --- a/features/roomdetails/impl/src/main/res/values-nb/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-nb/translations.xml @@ -109,6 +109,7 @@ "Meldinger og innhold" "Moderatorer" "Eiere" + "Tillatelser" "Tilbakestill tillatelser" "Når du har tilbakestilt tillatelsene, mister du gjeldende innstillinger." "Vil du tilbakestille tillatelsene?" @@ -117,6 +118,7 @@ "Roller og tillatelser" "Legg til adresse" "Alle må be om tilgang." + "Be om å få bli med" "Ja, aktiver kryptering" "Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet. Ingen andre enn rommedlemmene vil kunne lese meldingene. Dette kan føre til at bots og broer ikke fungerer som de skal. @@ -126,6 +128,7 @@ Vi anbefaler ikke å aktivere kryptering for rom som hvem som helst kan finne og "Kryptering" "Aktiver ende-til-ende-kryptering" "Alle kan bli med." + "Hvem som helst" "Bare inviterte personer kan bli med." "Kun for inviterte" "Tilgang" diff --git a/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml index b24f4e5716..80e2b2378c 100644 --- a/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml @@ -131,6 +131,7 @@ "Adicionar endereço" "Qualquer um nos espaços autorizados podem entrar, mas todos os outros devem pedir acesso." "Qualquer um pode pedir acesso, mas um administrador terá que aceitar o pedido." + "Pedir para entrar" "Qualquer um em %1$s pode entrar, mas todos os outros devem pedir acesso." "Sim, ativar a criptografia" "Uma vez ativada, a criptografia de uma sala não pode ser desativada. O histórico de mensagens só será visível para os membros da sala desde que foram convidados ou desde que entraram na sala. diff --git a/features/roomdetails/impl/src/main/res/values-pt/translations.xml b/features/roomdetails/impl/src/main/res/values-pt/translations.xml index 6e75c110ee..3b39811c7e 100644 --- a/features/roomdetails/impl/src/main/res/values-pt/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-pt/translations.xml @@ -1,19 +1,21 @@ - "É necessário um endereço para tornar a sala visível no diretório." - "Endereço da sala" + "É necessário um endereço para tornar a sala visível no diretório público." + "Editar endereço" "Erro ao atualizar a configuração de notificação." "O teu servidor não suporta esta opção em salas cifradas, pelo que poderás não ser notificado em algumas salas." "Sondagens" - "Apenas administradores" + "Administrador" "Banir pessoas" "Remover mensagens" - "Convidar pessoas e aceitar pedidos de entrada" + "Membro" + "Convidar pessoas" + "Gerir membros" "Mensagens e conteúdo" - "Administradores e moderadores" - "Remover pessoas e rejeitar pedidos de entrada" + "Moderador" + "Remover pessoas" "Alterar o ícone da sala" - "Editar sala" + "Editar detalhes" "Altera o nome da sala" "Alterar a descrição da sala" "Enviar mensagens" @@ -40,7 +42,7 @@ "Cifrada" "Não cifrada" "Sala pública" - "Editar sala" + "Editar detalhes" "Ocorreu um erro desconhecido e não foi possível alterar a informação." "Não foi possível atualizar a sala" "As mensagens são protegidas por cadeados. Apenas tu e os destinatários têm as chaves únicas para os desbloquear." @@ -67,7 +69,7 @@ "Informação da sala" "Descrição" "A atualizar sala…" - "Não há nenhum utilizador banido desta sala." + "Não há nenhum utilizador banido." "%1$d pessoa" "%1$d pessoas" @@ -79,8 +81,8 @@ "Desbanir da sala" "Banidos" "Participantes" - "Apenas administradores" - "Administradores e moderadores" + "Administrador" + "Moderador" "Dono / Dona" "Participantes" "A anular banimento de %1$s" @@ -113,8 +115,8 @@ "Cargos" "Detalhes da sala" "Cargos e permissões" - "Adicionar endereço de sala" - "Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou moderador tem que aceitar o pedido." + "Adicionar endereço" + "Todos precisam de pedir acesso." "Pedir para entrar" "Sim, ativar cifragem" "Uma vez ativada, a cifragem não pode ser desativada. O histórico de mensagens só será visível a membros a partir do momento em que foram convidados ou que entraram na sala. @@ -124,24 +126,24 @@ Não recomendamos ativar a cifragem em salas que qualquer pessoa possa encontrar "Uma vez ativada, a cifragem não pode ser desativada." "Cifragem" "Ativar cifragem ponta-a-ponta" - "Qualquer pessoa pode encontrar a sala e entrar" + "Qualquer pessoa pode entrar." "Qualquer pessoa" - "Só é possível entrar tendo um convite" + "Apenas pessoas convidadas podem entrar." "Apenas por convite" - "Acesso à sala" + "Acesso" "Membros do espaço" "Os espaços ainda não estão implementados" - "É necessário um endereço para tornar a sala visível no diretório." - "Endereço da sala" + "É necessário um endereço para tornar a sala visível no diretório público." + "Endereço" "Permite que esta sala seja encontrada através do diretório público do %1$s." - "Visível no diretório público de salas" - "Qualquer pessoa" + "Visível no diretório público" + "Qualquer pessoa (histórico público)" "Quem pode ler o histórico de mensagens" - "Apenas membros, desde o momento em que forem convidados" - "Apenas membros, desde o memento em que esta opção for selecionada" + "Membros, desde que foram convidados" + "Membros (histórico completo)" "Estes endereços permitem encontrar e aceder a sala, bem como a sua fácil partilha com outros. Podes escolher publicar a sala no diretório público do teu servidor." "Publicar sala" - "Visibilidade da sala" + "Visibilidade" "Segurança e privacidade" diff --git a/features/roomdetails/impl/src/main/res/values-ru/translations.xml b/features/roomdetails/impl/src/main/res/values-ru/translations.xml index e618bd8dc1..8bf017ba87 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -63,6 +63,7 @@ "Профиль" "Запросы на вступление" "Роли и разрешения" + "Имя" "Безопасность и конфиденциальность" "Безопасность" "Поделиться комнатой" @@ -70,6 +71,11 @@ "Тема" "Обновление комнаты…" "В этой комнате нет заблокированных пользователей." + + "%1$d заблокирован" + "%1$d заблокированы" + "%1$d заблокированы" + "Проверьте правописание или попробуйте новый поиск" "Отсутствует результат по запросу “%1$s”" @@ -84,6 +90,11 @@ "Разблокировать в комнате" "Заблокированные" "Участники" + + "%1$d приглашён" + "%1$d приглашены" + "%1$d приглашены" + "В ожидании" "Только администраторы" "Модератор" @@ -121,8 +132,10 @@ "Информация о комнате" "Роли и разрешения" "Добавить адрес" + "Любой, кто находится в авторизованных пространствах, может присоединиться, а все остальным необходимо запрашивать доступ." "Каждый должен запросить доступ." "Присоединиться" + "Любой человек из %1$s может присоединиться, а всем остальным нужно запросить доступ." "Да, включить шифрование" "Шифрование комнаты нельзя будет отключить, история сообщений будет видна только участникам комнаты с момента их приглашения или с момента присоединения к комнате. Никто, кроме членов комнаты, не сможет читать сообщения. Это может помешать ботам и мостам работать корректно. @@ -132,10 +145,13 @@ "Шифрование" "Включить сквозное шифрование" "Любой желающий может найти и присоединиться" - "Публичный" + "Любой" + "Выберите, участники каких пространств могут присоединиться к этой комнате без приглашения.%1$s" + "Управление пространством" "Присоединиться могут только приглашенные люди." "Только по приглашению" "Доступ" + "Любой человек в авторизованных пространствах может присоединиться." "Участники пространства" "Пространства в настоящее время не поддерживаются." "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." @@ -143,13 +159,15 @@ "Опубликовать %1$s в каталоге публичных комнат" "Разрешить поиск в публичном каталоге." "Доступна в списке публичных комнат" - "Любой" + "Любой (история общедоступна)" + "Изменения не затронут старые сообщения, только новые. %1$s" "Кто может читать историю" - "Участники только с тех пор, как они были приглашены" - "Только для участников с момента выбора этой опции" + "Участники с момента приглашения" + "Участники (полная история)" "Адреса комнат — это способ найти комнату и получить к ней доступ. Это также гарантирует, что вы сможете легко поделиться своей комнатой с другими. Вы можете опубликовать свою комнату в каталоге общедоступных комнат на домашнем сервере." "Публикация комнат" + "Адреса позволяют находить и получать доступ к комнатам и пространствам. Это также гарантирует, что вы сможете легко поделиться ими с другими." "Видимость" "Безопасность и конфиденциальность" diff --git a/features/roomdetails/impl/src/main/res/values-sk/translations.xml b/features/roomdetails/impl/src/main/res/values-sk/translations.xml index 7cd14bc7a5..b81d9e8f78 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -63,6 +63,7 @@ "Profil" "Žiadosti o vstup" "Roly a povolenia" + "Meno" "Bezpečnosť a súkromie" "Bezpečnosť" "Zdieľať miestnosť" @@ -131,8 +132,10 @@ "Podrobnosti o miestnosti" "Roly a povolenia" "Pridať adresu" + "Pripojiť sa môže ktokoľvek z autorizovaných priestorov, ale všetci ostatní musia o prístup požiadať." "Všetci musia požiadať o prístup." "Požiadať o pripojenie" + "Ktokoľvek v %1$s sa môže pripojiť, ale všetci ostatní musia požiadať o prístup." "Áno, povoliť šifrovanie" "Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti. Nikto okrem členov miestnosti nebude môcť čítať správy. @@ -143,7 +146,7 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p "Povoliť end-to-end šifrovanie" "Pripojiť sa môže ktokoľvek." "Ktokoľvek" - "Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s" + "Vyberte, členovia ktorých priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s" "Spravovať priestory" "Pripojiť sa môžu iba pozvaní ľudia." "Iba na pozvánku" @@ -157,10 +160,11 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p "Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s" "Umožniť nájdenie vyhľadávaním vo verejnom adresári." "Viditeľné vo verejnom adresári" - "Ktokoľvek" + "Ktokoľvek (história je verejná)" + "Zmeny neovplyvnia predchádzajúce správy, iba nové. %1$s" "Kto môže čítať históriu" - "Len pre členov, odkedy boli pozvaní" - "Len členovia od zvolenia tejto možnosti" + "Členovia, odkedy boli pozvaní" + "Členovia (úplná história)" "Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými. Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera." "Zverejnenie miestnosti" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt index f1cab1524a..028d4b2781 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt @@ -8,6 +8,7 @@ package io.element.android.features.roomdetails.impl.members +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import com.google.common.truth.Truth.assertThat import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.Presenter @@ -42,7 +43,7 @@ class RoomMemberListPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.filteredRoomMembers.isLoading()).isTrue() - assertThat(initialState.searchQuery).isEmpty() + assertThat(initialState.searchQuery.text.toString()).isEmpty() assertThat(initialState.selectedSection).isEqualTo(SelectedSection.MEMBERS) } } @@ -87,7 +88,7 @@ class RoomMemberListPresenterTest { skipItems(1) val initialState = awaitItem() assertThat(initialState.filteredRoomMembers.isLoading()).isTrue() - assertThat(initialState.searchQuery).isEmpty() + assertThat(initialState.searchQuery.text.toString()).isEmpty() room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) // Skip items while the new members state is processed skipItems(2) @@ -116,9 +117,9 @@ class RoomMemberListPresenterTest { assertThat(loadedRoomMembers.invited).isNotEmpty() assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() - loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) + loadedState.searchQuery.setTextAndPlaceCursorAtEnd("something") val searchQueryUpdatedState = awaitItem() - assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something") + assertThat(searchQueryUpdatedState.searchQuery.text).isEqualTo("something") val searchSearchResultDelivered = awaitItem() val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!! assertThat(emptyRoomMembers.joined).isEmpty() @@ -144,9 +145,9 @@ class RoomMemberListPresenterTest { assertThat(loadedRoomMembers.invited).isNotEmpty() assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() - loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("alice")) + loadedState.searchQuery.setTextAndPlaceCursorAtEnd("alice") val searchQueryUpdatedState = awaitItem() - assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("alice") + assertThat(searchQueryUpdatedState.searchQuery.text).isEqualTo("alice") val searchSearchResultDelivered = awaitItem() val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!! assertThat(emptyRoomMembers.joined).isNotEmpty() diff --git a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt index d2de2ba260..3fd9daef3e 100644 --- a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt @@ -101,9 +101,9 @@ fun RoomDetailsEditView( .verticalScroll(rememberScrollState()) ) { Spacer(modifier = Modifier.height(24.dp)) - val avatarPickerState = remember(state.roomAvatarUrl) { + val avatarPickerState = remember(state.roomAvatarUrl, state.roomRawName) { val size = AvatarSize.EditRoomDetails - val type = AvatarType.Room() + val type = if (state.isSpace) AvatarType.Space() else AvatarType.Room() AvatarPickerState.Selected( avatarData = AvatarData(id = state.roomId.value, name = state.roomRawName, size = size, url = state.roomAvatarUrl), type = type @@ -112,6 +112,7 @@ fun RoomDetailsEditView( AvatarPickerView( state = avatarPickerState, onClick = ::onAvatarClick, + enabled = state.canChangeAvatar, modifier = Modifier.align(Alignment.CenterHorizontally), ) Spacer(modifier = Modifier.height(32.dp)) diff --git a/features/roomdetailsedit/impl/src/main/res/values-in/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-in/translations.xml index 14b3d68c74..4dcc480c1f 100644 --- a/features/roomdetailsedit/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetailsedit/impl/src/main/res/values-in/translations.xml @@ -1,6 +1,6 @@ - "Sunting Ruangan" + "Edit detail" "Terjadi kesalahan yang tidak diketahui dan informasinya tidak dapat diubah." "Tidak dapat memperbarui ruangan" "Memperbarui ruangan…" diff --git a/features/roomdetailsedit/impl/src/main/res/values-pt/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-pt/translations.xml index 25069318a5..d8f42c933b 100644 --- a/features/roomdetailsedit/impl/src/main/res/values-pt/translations.xml +++ b/features/roomdetailsedit/impl/src/main/res/values-pt/translations.xml @@ -1,6 +1,6 @@ - "Editar sala" + "Editar detalhes" "Ocorreu um erro desconhecido e não foi possível alterar a informação." "Não foi possível atualizar a sala" "A atualizar sala…" diff --git a/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml index 392b271a1a..93760c8edd 100644 --- a/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml @@ -4,10 +4,12 @@ "Utesteng" "De vil ikke kunne bli med i dette rommet igjen hvis de blir invitert." "Er du sikker på at du vil utestenge dette medlemmet?" + "De vil ikke kunne bli med i dette området igjen hvis de blir invitert, men de vil fortsatt beholde medlemskapet sitt i alle rom eller underrom." "Utestenger %1$s" "Fjern" "De vil kunne bli med i dette rommet igjen hvis de blir invitert." "Er du sikker på at du vil fjerne dette medlemmet?" + "De vil kunne bli med i dette området igjen hvis de blir invitert, og de vil fortsatt beholde medlemskapet sitt i alle rom eller underrom." "Vis profil" "Fjern bruker" "Fjerne medlem og utestenge fra å bli med i fremtiden?" diff --git a/features/securityandprivacy/impl/src/main/res/values-da/translations.xml b/features/securityandprivacy/impl/src/main/res/values-da/translations.xml index 2758e944b6..bcd85f69de 100644 --- a/features/securityandprivacy/impl/src/main/res/values-da/translations.xml +++ b/features/securityandprivacy/impl/src/main/res/values-da/translations.xml @@ -2,8 +2,16 @@ "Du skal bruge en adresse for at gøre det synligt i det offentlige register." "Redigér adresse" + "Grupper, hvor medlemmer kan deltage i rummet uden en invitation." + "Administrer grupper" + "(Ukendt gruppe)" + "Andre grupper, du ikke er medlem af" + "Dine grupper" "Tilføj adresse" + "Alle i autoriserede grupper kan deltage, men alle andre skal anmode om adgang." "Alle skal anmode om adgang." + "Bed om at deltage" + "Alle i %1$s kan tilmelde sig, men alle andre skal anmode om adgang." "Ja, aktivér kryptering" "Når det først er aktiveret, kan kryptering for et rum ikke deaktiveres igen. Beskedhistorik vil kun være synlig for rummedlemmer, siden de blev inviteret, eller siden de blev medlem af rummet. Ingen udover medlemmer af rummet vil være i stand til at læse beskeder. Dette kan forhindre bots og broer i at fungere korrekt. @@ -13,9 +21,14 @@ Vi anbefaler ikke at aktivere kryptering for rum, som alle kan finde og deltage "Kryptering" "Aktivér end-to-end-kryptering" "Alle kan være med." + "Alle" + "Vælg, hvilke gruppers medlemmer der kan deltage i dette rum uden en invitation.%1$s" + "Administrer grupper" "Kun inviterede personer kan deltage i dette rum." "Kun inviterede" "Adgang" + "Alle i autoriserede grupper kan deltage." + "Alle i %1$s kan deltage." "Medlemmer af gruppen" "Grupper understøttes ikke i øjeblikket" "Du skal bruge en adresse for at gøre det synligt i det offentlige register." @@ -23,12 +36,15 @@ Vi anbefaler ikke at aktivere kryptering for rum, som alle kan finde og deltage "Tillad, at dette rum kan findes ved at søge i %1$s fortegnelse over offentlige rum" "Gør det muligt at blive fundet ved søgninger i det offentlige register." "Synlig i det offentlige register" + "Alle (historikken er offentlig)" + "Ændringer vil ikke påvirke tidligere beskeder, kun nye. %1$s" "Hvem kan læse historikken?" - "Kun medlemmer, efter de blev inviteret" - "Kun medlemmer siden valg af denne mulighed" + "Medlemmer siden de blev inviteret" + "Medlemmer (fuld historik)" "Rum-adresser er en måde at finde og få adgang til værelser på. Dette sikrer også, at du nemt kan dele dit rum med andre. Du kan vælge at offentliggøre dit rum i din hjemmeservers offentlige katalog over rum." "Udgivelse af rum" + "Adresser er en måde at finde og få adgang til rum og grupper. Dette sikrer også, at du nemt kan dele dem med andre." "Synlighed" "Sikkerhed og privatliv" diff --git a/features/securityandprivacy/impl/src/main/res/values-hu/translations.xml b/features/securityandprivacy/impl/src/main/res/values-hu/translations.xml index 980b55d52f..60e2190e84 100644 --- a/features/securityandprivacy/impl/src/main/res/values-hu/translations.xml +++ b/features/securityandprivacy/impl/src/main/res/values-hu/translations.xml @@ -2,9 +2,16 @@ "Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban." "Cím szerkesztése" + "Olyan terek, ahol a tagok meghívás nélkül csatlakozhatnak a szobákhoz." + "Terek kezelése" + "(Ismeretlen tér)" + "Egyéb terek, amelyeknek nem tagja" + "Saját terek" "Cím hozzáadása" + "Bárki csatlakozhat, az engedélyezett terekből, és mindenki másnak hozzáférést kell kérnie." "Mindenkinek hozzáférést kell kérnie." "Csatlakozás kérése" + "Bárki csatlakozhat innen: %1$s, és mindenki másnak hozzáférést kell kérnie." "Igen, engedélyezze a titkosítást" "Az engedélyezés után a szoba titkosítása nem tiltható le. Az üzenetek előzményei csak a szobatagok számára láthatók, amikor meghívást kaptak, vagy mióta csatlakoztak a szobához. A szobatagokon kívül senki sem tudja olvasni az üzeneteket. Ez megakadályozhatja a botok és a hidak megfelelő működését. @@ -14,10 +21,14 @@ Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket b "Titkosítás" "Végpontok közötti titkosítás engedélyezése" "Bárki csatlakozhat." - "Nyilvános" + "Bárki" + "Válassza ki, hogy mely terek tagjai csatlakozhatnak ehhez a szobához meghívás nélkül.%1$s" + "Terek kezelése" "Csak a meghívott emberek léphetnek be." "Csak meghívásos" "Hozzáférés" + "Bárki csatlakozhat, az engedélyezett terekből." + "Bárki csatlakozhatnak innen: %1$s." "A tér tagjai" "A terek jelenleg nem támogatottak" "Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban." @@ -25,13 +36,15 @@ Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket b "A szoba megtalálhatóvá tétele a(z) %1$s nyilvános szobakatalógusában való kereséssel." "Lehetővé teszi, hogy a nyilvános szobakatalógusban megtalálható legyen." "Látható a nyilvános szobakatalógusban" - "Bárki" + "Bárki (az előzmények nyilvánosak)" + "A változások nem érintik a korábbi üzeneteket, csak az újakat. %1$s" "Ki olvashatja az előzményeket" - "Csak a tagok, a meghívásuktól kezdődően" - "Csak a tagok, a beállítás választásától kezdődően" + "Tagok a meghívás óta" + "Tagok (teljes történet)" "A szobacímek a szobák megtalálásának és elérésnek módjai. Ez azt is biztosítja, hogy könnyen megoszthatja a szobáját másokkal. Kiválaszthatja, hogy szobáját közzéteszi-e a Matrix-kiszolgáló nyilvános szobakatalógusában." "Szoba közzététele" + "A címek segítségével megtalálhatja és elérheti a szobákat és a tereket. Ez biztosítja azt is, hogy könnyen megoszthassa azokat másokkal." "Láthatóság" "Biztonság és adatvédelem" diff --git a/features/securityandprivacy/impl/src/main/res/values-nb/translations.xml b/features/securityandprivacy/impl/src/main/res/values-nb/translations.xml index 8dd3220565..035f99cfb5 100644 --- a/features/securityandprivacy/impl/src/main/res/values-nb/translations.xml +++ b/features/securityandprivacy/impl/src/main/res/values-nb/translations.xml @@ -4,6 +4,7 @@ "Rediger adresse" "Legg til adresse" "Alle må be om tilgang." + "Be om å få bli med" "Ja, aktiver kryptering" "Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet. Ingen andre enn rommedlemmene vil kunne lese meldingene. Dette kan føre til at bots og broer ikke fungerer som de skal. @@ -13,6 +14,7 @@ Vi anbefaler ikke å aktivere kryptering for rom som hvem som helst kan finne og "Kryptering" "Aktiver ende-til-ende-kryptering" "Alle kan bli med." + "Hvem som helst" "Bare inviterte personer kan bli med." "Kun for inviterte" "Tilgang" diff --git a/features/securityandprivacy/impl/src/main/res/values-pt-rBR/translations.xml b/features/securityandprivacy/impl/src/main/res/values-pt-rBR/translations.xml index 26d8660180..aec4488a4a 100644 --- a/features/securityandprivacy/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/securityandprivacy/impl/src/main/res/values-pt-rBR/translations.xml @@ -10,6 +10,7 @@ "Adicionar endereço" "Qualquer um nos espaços autorizados podem entrar, mas todos os outros devem pedir acesso." "Qualquer um pode pedir acesso, mas um administrador terá que aceitar o pedido." + "Pedir para entrar" "Qualquer um em %1$s pode entrar, mas todos os outros devem pedir acesso." "Sim, ativar a criptografia" "Uma vez ativada, a criptografia de uma sala não pode ser desativada. O histórico de mensagens só será visível para os membros da sala desde que foram convidados ou desde que entraram na sala. diff --git a/features/securityandprivacy/impl/src/main/res/values-pt/translations.xml b/features/securityandprivacy/impl/src/main/res/values-pt/translations.xml index f06e7d8945..dd4cdf76ca 100644 --- a/features/securityandprivacy/impl/src/main/res/values-pt/translations.xml +++ b/features/securityandprivacy/impl/src/main/res/values-pt/translations.xml @@ -1,9 +1,9 @@ - "É necessário um endereço para tornar a sala visível no diretório." - "Endereço da sala" - "Adicionar endereço de sala" - "Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou moderador tem que aceitar o pedido." + "É necessário um endereço para tornar a sala visível no diretório público." + "Editar endereço" + "Adicionar endereço" + "Todos precisam de pedir acesso." "Pedir para entrar" "Sim, ativar cifragem" "Uma vez ativada, a cifragem não pode ser desativada. O histórico de mensagens só será visível a membros a partir do momento em que foram convidados ou que entraram na sala. @@ -13,24 +13,24 @@ Não recomendamos ativar a cifragem em salas que qualquer pessoa possa encontrar "Uma vez ativada, a cifragem não pode ser desativada." "Cifragem" "Ativar cifragem ponta-a-ponta" - "Qualquer pessoa pode encontrar a sala e entrar" + "Qualquer pessoa pode entrar." "Qualquer pessoa" - "Só é possível entrar tendo um convite" + "Apenas pessoas convidadas podem entrar." "Apenas por convite" - "Acesso à sala" + "Acesso" "Membros do espaço" "Os espaços ainda não estão implementados" - "É necessário um endereço para tornar a sala visível no diretório." - "Endereço da sala" + "É necessário um endereço para tornar a sala visível no diretório público." + "Endereço" "Permite que esta sala seja encontrada através do diretório público do %1$s." - "Visível no diretório público de salas" - "Qualquer pessoa" + "Visível no diretório público" + "Qualquer pessoa (histórico público)" "Quem pode ler o histórico de mensagens" - "Apenas membros, desde o momento em que forem convidados" - "Apenas membros, desde o memento em que esta opção for selecionada" + "Membros, desde que foram convidados" + "Membros (histórico completo)" "Estes endereços permitem encontrar e aceder a sala, bem como a sua fácil partilha com outros. Podes escolher publicar a sala no diretório público do teu servidor." "Publicar sala" - "Visibilidade da sala" + "Visibilidade" "Segurança e privacidade" diff --git a/features/securityandprivacy/impl/src/main/res/values-ru/translations.xml b/features/securityandprivacy/impl/src/main/res/values-ru/translations.xml index 2fc0101a0c..03c672ece2 100644 --- a/features/securityandprivacy/impl/src/main/res/values-ru/translations.xml +++ b/features/securityandprivacy/impl/src/main/res/values-ru/translations.xml @@ -2,9 +2,16 @@ "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." "Редактировать адрес комнаты" + "Пространства, где участники могут присоединиться к комнате без приглашения." + "Управление пространством" + "(Неизвестное пространство)" + "Другие пространства, в которых вы не состоите" + "Ваши пространства" "Добавить адрес" + "Любой, кто находится в авторизованных пространствах, может присоединиться, а все остальным необходимо запрашивать доступ." "Каждый должен запросить доступ." "Присоединиться" + "Любой человек из %1$s может присоединиться, а всем остальным нужно запросить доступ." "Да, включить шифрование" "Шифрование комнаты нельзя будет отключить, история сообщений будет видна только участникам комнаты с момента их приглашения или с момента присоединения к комнате. Никто, кроме членов комнаты, не сможет читать сообщения. Это может помешать ботам и мостам работать корректно. @@ -14,10 +21,13 @@ "Шифрование" "Включить сквозное шифрование" "Любой желающий может найти и присоединиться" - "Публичный" + "Любой" + "Выберите, участники каких пространств могут присоединиться к этой комнате без приглашения.%1$s" + "Управление пространством" "Присоединиться могут только приглашенные люди." "Только по приглашению" "Доступ" + "Любой человек в авторизованных пространствах может присоединиться." "Участники пространства" "Пространства в настоящее время не поддерживаются." "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." @@ -25,13 +35,15 @@ "Опубликовать %1$s в каталоге публичных комнат" "Разрешить поиск в публичном каталоге." "Доступна в списке публичных комнат" - "Любой" + "Любой (история общедоступна)" + "Изменения не затронут старые сообщения, только новые. %1$s" "Кто может читать историю" - "Участники только с тех пор, как они были приглашены" - "Только для участников с момента выбора этой опции" + "Участники с момента приглашения" + "Участники (полная история)" "Адреса комнат — это способ найти комнату и получить к ней доступ. Это также гарантирует, что вы сможете легко поделиться своей комнатой с другими. Вы можете опубликовать свою комнату в каталоге общедоступных комнат на домашнем сервере." "Публикация комнат" + "Адреса позволяют находить и получать доступ к комнатам и пространствам. Это также гарантирует, что вы сможете легко поделиться ими с другими." "Видимость" "Безопасность и конфиденциальность" diff --git a/features/securityandprivacy/impl/src/main/res/values-sk/translations.xml b/features/securityandprivacy/impl/src/main/res/values-sk/translations.xml index 3853fc5f8a..0256a9b36c 100644 --- a/features/securityandprivacy/impl/src/main/res/values-sk/translations.xml +++ b/features/securityandprivacy/impl/src/main/res/values-sk/translations.xml @@ -8,8 +8,10 @@ "Iné priestory, ktorých nie ste členom" "Vaše priestory" "Pridať adresu" + "Pripojiť sa môže ktokoľvek z autorizovaných priestorov, ale všetci ostatní musia o prístup požiadať." "Všetci musia požiadať o prístup." "Požiadať o pripojenie" + "Ktokoľvek v %1$s sa môže pripojiť, ale všetci ostatní musia požiadať o prístup." "Áno, povoliť šifrovanie" "Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti. Nikto okrem členov miestnosti nebude môcť čítať správy. @@ -20,7 +22,7 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p "Povoliť end-to-end šifrovanie" "Pripojiť sa môže ktokoľvek." "Ktokoľvek" - "Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s" + "Vyberte, členovia ktorých priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s" "Spravovať priestory" "Pripojiť sa môžu iba pozvaní ľudia." "Iba na pozvánku" @@ -34,10 +36,11 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p "Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s" "Umožniť nájdenie vyhľadávaním vo verejnom adresári." "Viditeľné vo verejnom adresári" - "Ktokoľvek" + "Ktokoľvek (história je verejná)" + "Zmeny neovplyvnia predchádzajúce správy, iba nové. %1$s" "Kto môže čítať históriu" - "Len pre členov, odkedy boli pozvaní" - "Len členovia od zvolenia tejto možnosti" + "Členovia, odkedy boli pozvaní" + "Členovia (úplná história)" "Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými. Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera." "Zverejnenie miestnosti" diff --git a/features/space/impl/build.gradle.kts b/features/space/impl/build.gradle.kts index 03c8cda4ac..a29d388587 100644 --- a/features/space/impl/build.gradle.kts +++ b/features/space/impl/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.services.analytics.api) implementation(libs.coil.compose) implementation(projects.libraries.featureflag.api) + implementation(projects.features.createroom.api) implementation(projects.features.invite.api) implementation(projects.libraries.previewutils) implementation(projects.features.securityandprivacy.api) @@ -49,5 +50,6 @@ dependencies { testImplementation(projects.services.analytics.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.features.createroom.test) testImplementation(projects.features.invite.test) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index 4c91da0301..666a538a29 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -24,7 +24,9 @@ import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.space.api.SpaceEntryPoint +import io.element.android.features.space.impl.addroom.AddRoomToSpaceNode import io.element.android.features.space.impl.di.SpaceFlowGraph import io.element.android.features.space.impl.leave.LeaveSpaceNode import io.element.android.features.space.impl.root.SpaceNode @@ -48,6 +50,7 @@ class SpaceFlowNode( room: JoinedRoom, spaceService: SpaceService, graphFactory: SpaceFlowGraph.Factory, + private val createRoomEntryPoint: CreateRoomEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Root, @@ -69,6 +72,12 @@ class SpaceFlowNode( @Parcelize data object Leave : NavTarget + + @Parcelize + data object CreateRoom : NavTarget + + @Parcelize + data object AddRoom : NavTarget } override fun onBuilt() { @@ -111,6 +120,14 @@ class SpaceFlowNode( override fun startLeaveSpaceFlow() { backstack.push(NavTarget.Leave) } + + override fun onCreateRoom() { + backstack.push(NavTarget.CreateRoom) + } + + override fun navigateToAddRoom() { + backstack.push(NavTarget.AddRoom) + } } createNode(buildContext, listOf(callback)) } @@ -132,6 +149,29 @@ class SpaceFlowNode( } createNode(buildContext, listOf(callback)) } + is NavTarget.CreateRoom -> { + val callback = object : CreateRoomEntryPoint.Callback { + override fun onRoomCreated(roomId: RoomId) { + callback.navigateToRoom(roomId, emptyList()) + } + } + createRoomEntryPoint + .builder( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) + .setParentSpace(spaceRoomList.roomId) + .build() + } + NavTarget.AddRoom -> { + val callback = object : AddRoomToSpaceNode.Callback { + override fun onFinish() { + backstack.pop() + } + } + createNode(buildContext, listOf(callback)) + } } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceEvent.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceEvent.kt new file mode 100644 index 0000000000..ee4dc12ba4 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceEvent.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import io.element.android.libraries.matrix.ui.model.SelectRoomInfo + +sealed interface AddRoomToSpaceEvent { + data class ToggleRoom(val room: SelectRoomInfo) : AddRoomToSpaceEvent + data class OnSearchActiveChanged(val active: Boolean) : AddRoomToSpaceEvent + data object Save : AddRoomToSpaceEvent + data object ResetSaveAction : AddRoomToSpaceEvent +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceNode.kt new file mode 100644 index 0000000000..71e7665844 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceNode.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.space.impl.di.SpaceFlowScope +import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.architecture.callback + +@ContributesNode(SpaceFlowScope::class) +@AssistedInject +class AddRoomToSpaceNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: AddRoomToSpacePresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onFinish() + } + + private val callback: Callback = callback() + private val stateFlow = launchMolecule { presenter.present() } + + @Composable + override fun View(modifier: Modifier) { + val state by stateFlow.collectAsState() + AddRoomToSpaceView( + state = state, + onBackClick = ::navigateUp, + onRoomsAdded = callback::onFinish, + modifier = modifier + ) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpacePresenter.kt new file mode 100644 index 0000000000..657d0702f8 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpacePresenter.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import androidx.compose.foundation.text.input.clearText +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import dev.zacsweers.metro.Inject +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.runUpdatingState +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.matrix.api.spaces.SpaceRoomList +import io.element.android.libraries.matrix.api.spaces.SpaceService +import io.element.android.libraries.matrix.ui.model.SelectRoomInfo +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch + +@Inject +class AddRoomToSpacePresenter( + private val spaceRoomList: SpaceRoomList, + private val spaceService: SpaceService, + private val dataSourceFactory: AddRoomToSpaceSearchDataSource.Factory, +) : Presenter { + @Composable + override fun present(): AddRoomToSpaceState { + var selectedRooms: ImmutableList by remember { mutableStateOf(persistentListOf()) } + var searchQuery = rememberTextFieldState() + var isSearchActive by remember { mutableStateOf(false) } + val saveAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } + + val coroutineScope = rememberCoroutineScope() + val dataSource = remember { dataSourceFactory.create(coroutineScope) } + + // Update search query in data source + LaunchedEffect(searchQuery.text) { + dataSource.setSearchQuery(searchQuery.text.toString()) + } + LaunchedEffect(isSearchActive) { + dataSource.setIsActive(isSearchActive) + } + + val suggestions by dataSource.suggestions.collectAsState(initial = persistentListOf()) + + val filteredRooms by dataSource.roomInfoList.collectAsState(initial = persistentListOf()) + val searchResults by remember>>> { + derivedStateOf { + when { + filteredRooms.isNotEmpty() -> SearchBarResultState.Results(filteredRooms) + isSearchActive && searchQuery.text.isNotEmpty() -> SearchBarResultState.NoResultsFound() + else -> SearchBarResultState.Initial() + } + } + } + + fun handleEvent(event: AddRoomToSpaceEvent) { + when (event) { + is AddRoomToSpaceEvent.ToggleRoom -> { + selectedRooms = if (selectedRooms.any { it.roomId == event.room.roomId }) { + selectedRooms.filterNot { it.roomId == event.room.roomId }.toImmutableList() + } else { + (selectedRooms + event.room).toImmutableList() + } + } + is AddRoomToSpaceEvent.OnSearchActiveChanged -> { + isSearchActive = event.active + if (!event.active) { + searchQuery.clearText() + } + } + AddRoomToSpaceEvent.Save -> { + coroutineScope.addRoomsToSpace( + selectedRooms = selectedRooms, + addAction = saveAction, + ) + } + AddRoomToSpaceEvent.ResetSaveAction -> { + saveAction.value = AsyncAction.Uninitialized + } + } + } + + return AddRoomToSpaceState( + searchQuery = searchQuery, + isSearchActive = isSearchActive, + searchResults = searchResults, + selectedRooms = selectedRooms, + suggestions = suggestions, + saveAction = saveAction.value, + eventSink = ::handleEvent, + ) + } + + private fun CoroutineScope.addRoomsToSpace( + selectedRooms: ImmutableList, + addAction: MutableState>, + ) = launch { + addAction.runUpdatingState { + val results = selectedRooms.map { selectedRoom -> + async { + spaceService.addChildToSpace( + spaceId = spaceRoomList.roomId, + childId = selectedRoom.roomId, + ) + } + }.awaitAll() + val anyFailure = results.any { it.isFailure } + if (anyFailure) { + Result.failure(Exception("Failed to add some rooms")) + } else { + Result.success(Unit) + } + } + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceSearchDataSource.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceSearchDataSource.kt new file mode 100644 index 0000000000..61f08d53ae --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceSearchDataSource.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.CurrentUserMembership +import io.element.android.libraries.matrix.api.room.RoomInfo +import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.room.recent.getRecentlyVisitedRoomInfoFlow +import io.element.android.libraries.matrix.api.roomlist.RoomList +import io.element.android.libraries.matrix.api.roomlist.RoomListFilter +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.roomlist.loadAllIncrementally +import io.element.android.libraries.matrix.api.spaces.SpaceRoomList +import io.element.android.libraries.matrix.ui.model.SelectRoomInfo +import io.element.android.libraries.matrix.ui.model.toSelectRoomInfo +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toList + +private const val PAGE_SIZE = 30 +private const val MAX_SUGGESTIONS_COUNT = 5 + +/** + * DataSource for rooms that can be added to a space. + * Filters out DMs, spaces, rooms already in the space, and only includes rooms the user has joined. + */ +@AssistedInject +class AddRoomToSpaceSearchDataSource( + @Assisted coroutineScope: CoroutineScope, + roomListService: RoomListService, + spaceRoomList: SpaceRoomList, + private val matrixClient: MatrixClient, + private val coroutineDispatchers: CoroutineDispatchers, +) { + @AssistedFactory + interface Factory { + fun create(coroutineScope: CoroutineScope): AddRoomToSpaceSearchDataSource + } + + private val roomList = roomListService.createRoomList( + pageSize = PAGE_SIZE, + initialFilter = RoomListFilter.all(), + source = RoomList.Source.All, + coroutineScope = coroutineScope, + ) + + private val spaceChildrenFlow = spaceRoomList.spaceRoomsFlow.map { spaceChildren -> + spaceChildren.map { it.roomId }.toSet() + } + + private val filterRoomPredicate: (RoomInfo, Set) -> Boolean = { info, childIds -> + !info.isSpace && + !info.isDm && + info.currentUserMembership == CurrentUserMembership.JOINED && + info.id !in childIds + } + + val roomInfoList: Flow> = combine( + roomList.filteredSummaries, + spaceChildrenFlow, + ) { roomSummaries, childIds -> + roomSummaries + .filter { filterRoomPredicate(it.info, childIds) } + .map { it.toSelectRoomInfo() } + .toImmutableList() + }.flowOn(coroutineDispatchers.computation) + + val suggestions: Flow> = spaceChildrenFlow.map { childIds -> + matrixClient + .getRecentlyVisitedRoomInfoFlow { filterRoomPredicate(it, childIds) } + .take(MAX_SUGGESTIONS_COUNT) + .map { it.toSelectRoomInfo() } + .toList() + .toImmutableList() + }.flowOn(coroutineDispatchers.computation) + + suspend fun setIsActive(isActive: Boolean) = coroutineScope { + if (isActive) { + roomList.loadAllIncrementally(this) + } else { + roomList.reset() + } + } + + suspend fun setSearchQuery(searchQuery: String) { + val filter = if (searchQuery.isBlank()) { + RoomListFilter.None + } else { + RoomListFilter.NormalizedMatchRoomName(searchQuery) + } + roomList.updateFilter(filter) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceState.kt new file mode 100644 index 0000000000..99526be25f --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import androidx.compose.foundation.text.input.TextFieldState +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.matrix.ui.model.SelectRoomInfo +import kotlinx.collections.immutable.ImmutableList + +data class AddRoomToSpaceState( + val searchQuery: TextFieldState, + val isSearchActive: Boolean, + val searchResults: SearchBarResultState>, + val selectedRooms: ImmutableList, + val suggestions: ImmutableList, + val saveAction: AsyncAction, + val eventSink: (AddRoomToSpaceEvent) -> Unit, +) { + val canSave: Boolean = selectedRooms.isNotEmpty() && !saveAction.isLoading() +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceStateProvider.kt new file mode 100644 index 0000000000..870b72ecb7 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceStateProvider.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.ui.model.SelectRoomInfo +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +internal class AddRoomToSpaceStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + // Initial state with suggestions + anAddRoomToSpaceState( + suggestions = aSelectRoomInfoList(), + ), + // Search active, empty query + anAddRoomToSpaceState( + isSearchActive = true, + searchQuery = "", + suggestions = aSelectRoomInfoList(), + ), + // Search active with query and results + anAddRoomToSpaceState( + isSearchActive = true, + searchQuery = "general", + searchResults = SearchBarResultState.Results(aSelectRoomInfoList()), + ), + // Search active with query and no results + anAddRoomToSpaceState( + isSearchActive = true, + searchQuery = "unknown", + searchResults = SearchBarResultState.NoResultsFound(), + ), + // With selected rooms + anAddRoomToSpaceState( + suggestions = aSelectRoomInfoList(), + selectedRooms = aSelectRoomInfoList().take(1).toImmutableList(), + ), + // Loading state + anAddRoomToSpaceState( + selectedRooms = aSelectRoomInfoList().take(1).toImmutableList(), + saveAction = AsyncAction.Loading, + ), + // Error state + anAddRoomToSpaceState( + selectedRooms = aSelectRoomInfoList().take(1).toImmutableList(), + saveAction = AsyncAction.Failure(Exception("Failed to add rooms")), + ), + ) +} + +internal fun anAddRoomToSpaceState( + searchQuery: String = "", + searchResults: SearchBarResultState> = SearchBarResultState.Initial(), + selectedRooms: ImmutableList = persistentListOf(), + isSearchActive: Boolean = false, + saveAction: AsyncAction = AsyncAction.Uninitialized, + suggestions: ImmutableList = persistentListOf(), + eventSink: (AddRoomToSpaceEvent) -> Unit = {}, +): AddRoomToSpaceState { + return AddRoomToSpaceState( + searchQuery = TextFieldState(searchQuery), + searchResults = searchResults, + selectedRooms = selectedRooms, + isSearchActive = isSearchActive, + saveAction = saveAction, + suggestions = suggestions, + eventSink = eventSink, + ) +} + +internal fun aSelectRoomInfoList(): ImmutableList = listOf( + SelectRoomInfo( + roomId = RoomId("!room1:server.org"), + name = "General", + canonicalAlias = null, + avatarUrl = null, + heroes = persistentListOf(), + isTombstoned = false, + ), + SelectRoomInfo( + roomId = RoomId("!room2:server.org"), + name = "Engineering", + canonicalAlias = null, + avatarUrl = null, + heroes = persistentListOf(), + isTombstoned = false, + ), + SelectRoomInfo( + roomId = RoomId("!room3:server.org"), + name = "Design", + canonicalAlias = null, + avatarUrl = null, + heroes = persistentListOf(), + isTombstoned = false, + ), +).toImmutableList() diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceView.kt new file mode 100644 index 0000000000..854924041b --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceView.kt @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.space.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.list.AvatarListItem +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.ListSectionHeader +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.SearchBar +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.ui.components.SelectedRoom +import io.element.android.libraries.matrix.ui.model.SelectRoomInfo +import io.element.android.libraries.matrix.ui.model.getAvatarData +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddRoomToSpaceView( + state: AddRoomToSpaceState, + onBackClick: () -> Unit, + onRoomsAdded: () -> Unit, + modifier: Modifier = Modifier, +) { + fun onRoomRemoved(roomInfo: SelectRoomInfo) { + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(roomInfo)) + } + + fun onBack() { + if (state.isSearchActive) { + state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(false)) + } else { + onBackClick() + } + } + + BackHandler(onBack = ::onBack) + + // Navigate back on success + LaunchedEffect(state.saveAction) { + if (state.saveAction is AsyncAction.Success) { + onRoomsAdded() + } + } + + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + titleStr = stringResource(CommonStrings.action_add_existing_rooms), + navigationIcon = { + BackButton(onClick = ::onBack) + }, + actions = { + TextButton( + text = stringResource(CommonStrings.action_save), + enabled = state.canSave, + onClick = { state.eventSink(AddRoomToSpaceEvent.Save) } + ) + } + ) + } + ) { paddingValues -> + Column( + Modifier + .padding(paddingValues) + .consumeWindowInsets(paddingValues) + ) { + SearchBar( + modifier = Modifier.fillMaxWidth(), + placeHolderTitle = stringResource(CommonStrings.action_search), + queryState = state.searchQuery, + active = state.isSearchActive, + onActiveChange = { state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(it)) }, + showBackButton = false, + resultState = state.searchResults, + contentPrefix = { + if (state.selectedRooms.isNotEmpty()) { + SelectedRoomsRow( + selectedRooms = state.selectedRooms, + onRemoveRoom = ::onRoomRemoved, + modifier = Modifier.padding(vertical = 16.dp) + ) + } + }, + ) { rooms -> + LazyColumn { + items(rooms, key = { it.roomId.value }) { roomInfo -> + RoomListItem( + roomInfo = roomInfo, + isSelected = state.selectedRooms.any { it.roomId == roomInfo.roomId }, + onToggle = { state.eventSink(AddRoomToSpaceEvent.ToggleRoom(it)) } + ) + } + } + } + + if (!state.isSearchActive) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = stringResource(R.string.screen_space_add_rooms_room_access_description), + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodySmRegular, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + if (state.selectedRooms.isNotEmpty()) { + SelectedRoomsRow( + selectedRooms = state.selectedRooms, + onRemoveRoom = ::onRoomRemoved, + modifier = Modifier.padding(vertical = 16.dp) + ) + } + + if (state.suggestions.isNotEmpty()) { + LazyColumn { + item { + ListSectionHeader( + title = stringResource(id = CommonStrings.common_suggestions), + hasDivider = true, + ) + } + items(state.suggestions, key = { it.roomId.value }) { roomInfo -> + RoomListItem( + roomInfo = roomInfo, + isSelected = state.selectedRooms.any { it.roomId == roomInfo.roomId }, + onToggle = { state.eventSink(AddRoomToSpaceEvent.ToggleRoom(it)) } + ) + } + } + } + } + } + } + SaveActionView( + saveAction = state.saveAction, + onRetry = { state.eventSink(AddRoomToSpaceEvent.Save) }, + onDismiss = { state.eventSink(AddRoomToSpaceEvent.ResetSaveAction) } + ) +} + +@Composable +private fun SaveActionView( + saveAction: AsyncAction, + onRetry: () -> Unit, + onDismiss: () -> Unit, +) { + AsyncActionView( + async = saveAction, + onRetry = onRetry, + errorTitle = { + stringResource(CommonStrings.common_something_went_wrong) + }, + errorMessage = { + stringResource(CommonStrings.error_network_or_server_issue) + }, + onSuccess = { onDismiss() }, + onErrorDismiss = onDismiss, + ) +} + +@Composable +private fun SelectedRoomsRow( + selectedRooms: ImmutableList, + onRemoveRoom: (SelectRoomInfo) -> Unit, + modifier: Modifier = Modifier, +) { + LazyRow( + modifier = modifier, + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(32.dp) + ) { + items(selectedRooms, key = { it.roomId.value }) { roomInfo -> + SelectedRoom(roomInfo = roomInfo, onRemoveRoom = onRemoveRoom) + } + } +} + +@Composable +private fun RoomListItem( + roomInfo: SelectRoomInfo, + isSelected: Boolean, + onToggle: (SelectRoomInfo) -> Unit, +) { + AvatarListItem( + avatarData = roomInfo.getAvatarData(size = AvatarSize.RoomSelectRoomListItem), + avatarType = AvatarType.Room( + heroes = roomInfo.heroes.map { user -> + user.getAvatarData(size = AvatarSize.RoomSelectRoomListItem) + }.toImmutableList(), + isTombstoned = roomInfo.isTombstoned, + ), + headline = roomInfo.name ?: stringResource(id = CommonStrings.common_no_room_name), + supportingText = roomInfo.canonicalAlias?.value, + trailingContent = ListItemContent.Checkbox(checked = isSelected), + onClick = { onToggle(roomInfo) }, + ) +} + +@PreviewsDayNight +@Composable +internal fun AddRoomToSpaceViewPreview( + @PreviewParameter(AddRoomToSpaceStateProvider::class) state: AddRoomToSpaceState +) = ElementPreview { + AddRoomToSpaceView( + state = state, + onBackClick = {}, + onRoomsAdded = {}, + ) +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt index a331aefccd..2d30bc8617 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt @@ -69,7 +69,7 @@ class LeaveSpacePresenter( // By default select all rooms that can be left val otherRoomsExcludingDm = otherRooms.filter { it.spaceRoom.isDirect != true } selectedRoomIds = otherRoomsExcludingDm - .filter { it.isLastAdmin.not() } + .filter { it.isLastOwner.not() } .map { it.spaceRoom.roomId } leaveSpaceRooms = rooms.fold( onSuccess = { @@ -91,7 +91,7 @@ class LeaveSpacePresenter( it.others.map { room -> SelectableSpaceRoom( spaceRoom = room.spaceRoom, - isLastAdmin = room.isLastAdmin, + isLastOwner = room.isLastOwner, isSelected = selectedRoomIds.contains(room.spaceRoom.roomId), ) }.toImmutableList() @@ -110,7 +110,7 @@ class LeaveSpacePresenter( LeaveSpaceEvents.SelectAllRooms -> { selectedRoomIds = selectableSpaceRooms.dataOrNull() .orEmpty() - .filter { it.isLastAdmin.not() } + .filter { it.isLastOwner.not() } .map { it.spaceRoom.roomId } } is LeaveSpaceEvents.ToggleRoomSelection -> { @@ -132,7 +132,7 @@ class LeaveSpacePresenter( return LeaveSpaceState( spaceName = leaveSpaceRooms.dataOrNull()?.current?.spaceRoom?.displayName, - isLastAdmin = leaveSpaceRooms.dataOrNull()?.current?.isLastAdmin == true, + isLastOwner = leaveSpaceRooms.dataOrNull()?.current?.isLastOwner == true, selectableSpaceRooms = selectableSpaceRooms, leaveSpaceAction = leaveSpaceAction.value, eventSink = ::handleEvent, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt index 1e85014c84..2ff98ea583 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt @@ -15,7 +15,7 @@ import kotlinx.collections.immutable.toImmutableList data class LeaveSpaceState( val spaceName: String?, - val isLastAdmin: Boolean, + val isLastOwner: Boolean, val selectableSpaceRooms: AsyncData>, val leaveSpaceAction: AsyncAction, val eventSink: (LeaveSpaceEvents) -> Unit, @@ -25,7 +25,7 @@ data class LeaveSpaceState( private val selectableRooms: ImmutableList init { - val partition = rooms.partition { it.isLastAdmin } + val partition = rooms.partition { it.isLastOwner } lastAdminRooms = partition.first.toImmutableList() selectableRooms = partition.second.toImmutableList() } @@ -33,12 +33,12 @@ data class LeaveSpaceState( /** * True if we should show the quick action to select/deselect all rooms. */ - val showQuickAction = isLastAdmin.not() && selectableRooms.isNotEmpty() + val showQuickAction = isLastOwner.not() && selectableRooms.isNotEmpty() /** * True if we should show the leave button. */ - val showLeaveButton = isLastAdmin.not() && selectableSpaceRooms is AsyncData.Success + val showLeaveButton = isLastOwner.not() && selectableSpaceRooms is AsyncData.Success /** * True if there all the selectable rooms are selected. diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt index 46d3e53b2b..362e987fc4 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt @@ -34,7 +34,7 @@ class LeaveSpaceStateProvider : PreviewParameterProvider { displayName = "A long space name that should be truncated", worldReadable = true, ), - isLastAdmin = true, + isLastOwner = true, ), aSelectableSpaceRoom( spaceRoom = aSpaceRoom( @@ -52,7 +52,7 @@ class LeaveSpaceStateProvider : PreviewParameterProvider { spaceRoom = aSpaceRoom( worldReadable = true, ), - isLastAdmin = true, + isLastOwner = true, ), aSelectableSpaceRoom( spaceRoom = aSpaceRoom( @@ -70,7 +70,7 @@ class LeaveSpaceStateProvider : PreviewParameterProvider { spaceRoom = aSpaceRoom( worldReadable = true, ), - isLastAdmin = true, + isLastOwner = true, ), ) ), @@ -82,11 +82,11 @@ class LeaveSpaceStateProvider : PreviewParameterProvider { spaceRoom = aSpaceRoom( worldReadable = true, ), - isLastAdmin = true, + isLastOwner = true, ), aSelectableSpaceRoom( spaceRoom = aSpaceRoom(), - isLastAdmin = true, + isLastOwner = true, ), ) ), @@ -107,19 +107,19 @@ class LeaveSpaceStateProvider : PreviewParameterProvider { selectableSpaceRooms = AsyncData.Failure(Exception("An error")), ), aLeaveSpaceState( - isLastAdmin = true, + isLastOwner = true, ), ) } fun aLeaveSpaceState( spaceName: String? = "Space name", - isLastAdmin: Boolean = false, + isLastOwner: Boolean = false, selectableSpaceRooms: AsyncData> = AsyncData.Uninitialized, leaveSpaceAction: AsyncAction = AsyncAction.Uninitialized, ) = LeaveSpaceState( spaceName = spaceName, - isLastAdmin = isLastAdmin, + isLastOwner = isLastOwner, selectableSpaceRooms = selectableSpaceRooms, leaveSpaceAction = leaveSpaceAction, eventSink = { } @@ -127,10 +127,10 @@ fun aLeaveSpaceState( fun aSelectableSpaceRoom( spaceRoom: SpaceRoom = aSpaceRoom(), - isLastAdmin: Boolean = false, + isLastOwner: Boolean = false, isSelected: Boolean = false, ) = SelectableSpaceRoom( spaceRoom = spaceRoom, - isLastAdmin = isLastAdmin, + isLastOwner = isLastOwner, isSelected = isSelected, ) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt index d405162b88..52d52a103e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt @@ -94,7 +94,7 @@ fun LeaveSpaceView( modifier = Modifier .weight(1f), ) { - if (state.isLastAdmin.not()) { + if (state.isLastOwner.not()) { when (state.selectableSpaceRooms) { is AsyncData.Success -> { // List rooms where the user is the only admin @@ -132,7 +132,7 @@ fun LeaveSpaceView( state.eventSink(LeaveSpaceEvents.LeaveSpace) }, onCancel = onCancel, - showRolesAndPermissionsButton = state.isLastAdmin, + showRolesAndPermissionsButton = state.isLastOwner, onRolesAndPermissionsClick = onRolesAndPermissionsClick, ) } @@ -162,11 +162,11 @@ private fun LeaveSpaceHeader( modifier = Modifier.padding(top = 0.dp, bottom = 8.dp, start = 24.dp, end = 24.dp), iconStyle = BigIcon.Style.AlertSolid, title = stringResource( - if (state.isLastAdmin) R.string.screen_leave_space_title_last_admin else R.string.screen_leave_space_title, + if (state.isLastOwner) R.string.screen_leave_space_title_last_admin else R.string.screen_leave_space_title, state.spaceName ?: stringResource(CommonStrings.common_space) ), subTitle = - if (state.isLastAdmin) { + if (state.isLastOwner) { stringResource(R.string.screen_leave_space_subtitle_last_admin) } else if (state.selectableSpaceRooms is AsyncData.Success && state.selectableSpaceRooms.data.isNotEmpty()) { if (state.hasOnlyLastAdminRoom) { @@ -265,11 +265,11 @@ private fun SpaceItem( .toggleable( value = selectableSpaceRoom.isSelected, role = Role.Checkbox, - enabled = selectableSpaceRoom.isLastAdmin.not(), + enabled = selectableSpaceRoom.isLastOwner.not(), onValueChange = { onClick() } ) .clickable( - enabled = selectableSpaceRoom.isLastAdmin.not(), + enabled = selectableSpaceRoom.isLastOwner.not(), // TODO onClickLabel = null, role = Role.Checkbox, @@ -324,7 +324,7 @@ private fun SpaceItem( room.numJoinedMembers, room.numJoinedMembers ) - val subTitle = if (selectableSpaceRoom.isLastAdmin) { + val subTitle = if (selectableSpaceRoom.isLastOwner) { stringResource(R.string.screen_leave_space_last_admin_info, membersCount) } else { membersCount @@ -343,7 +343,7 @@ private fun SpaceItem( Checkbox( checked = selectableSpaceRoom.isSelected, onCheckedChange = null, - enabled = selectableSpaceRoom.isLastAdmin.not(), + enabled = selectableSpaceRoom.isLastOwner.not(), ) } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt index d7ed243af6..a6c42e16dc 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt @@ -12,6 +12,6 @@ import io.element.android.libraries.matrix.api.spaces.SpaceRoom data class SelectableSpaceRoom( val spaceRoom: SpaceRoom, - val isLastAdmin: Boolean, + val isLastOwner: Boolean, val isSelected: Boolean, ) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt index 240bc89990..5a5b10671c 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt @@ -46,6 +46,9 @@ class SpaceNode( fun navigateToSpaceSettings() fun navigateToRoomMemberList() fun startLeaveSpaceFlow() + fun navigateToAddRoom() + + fun onCreateRoom() } private val callback: Callback = callback() @@ -89,6 +92,9 @@ class SpaceNode( onViewMembersClick = { callback.navigateToRoomMemberList() }, + onAddRoomClick = { + callback.navigateToAddRoom() + }, acceptDeclineInviteView = { acceptDeclineInviteView.Render( state = state.acceptDeclineInviteState, @@ -101,6 +107,7 @@ class SpaceNode( modifier = Modifier ) }, + onCreateRoomClick = callback::onCreateRoom, modifier = modifier ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt index 9641e17625..5ad8feecc0 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt @@ -36,7 +36,7 @@ open class SpaceStateProvider : PreviewParameterProvider { spaceInfo = aSpaceInfo(), children = aListOfSpaceRooms(), joiningRooms = setOf(RoomId("!spaceId0:example.com")), - hasMoreToLoad = false + hasMoreToLoad = true, ), aSpaceState( topicViewerState = TopicViewerState.Shown(topic = "Space description goes here." + LoremIpsum(20).values.first()), @@ -71,7 +71,7 @@ fun aSpaceState( joiningRooms: Set = emptySet(), joinActions: Map> = joiningRooms.associateWith { AsyncAction.Loading }, hideInvitesAvatar: Boolean = false, - hasMoreToLoad: Boolean = true, + hasMoreToLoad: Boolean = false, acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(), topicViewerState: TopicViewerState = TopicViewerState.Hidden, canAccessSpaceSettings: Boolean = true, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt index 695b887496..c54bf0009e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt @@ -50,7 +50,9 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.space.impl.R import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.molecules.InviteButtonsRowMolecule +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.ClickableLinkText import io.element.android.libraries.designsystem.components.SimpleModalBottomSheet import io.element.android.libraries.designsystem.components.async.AsyncActionView @@ -65,6 +67,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Checkbox import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.DropdownMenu @@ -72,6 +75,7 @@ import io.element.android.libraries.designsystem.theme.components.DropdownMenuIt import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton @@ -98,6 +102,8 @@ fun SpaceView( onLeaveSpaceClick: () -> Unit, onSettingsClick: () -> Unit, onViewMembersClick: () -> Unit, + onCreateRoomClick: () -> Unit, + onAddRoomClick: () -> Unit, modifier: Modifier = Modifier, acceptDeclineInviteView: @Composable () -> Unit, ) { @@ -140,6 +146,8 @@ fun SpaceView( onShareSpace = onShareSpace, onViewMembersClick = onViewMembersClick, onManageRoomsClick = { state.eventSink(SpaceEvents.EnterManageMode) }, + onAddRoomClick = onAddRoomClick, + onCreateRoomClick = onCreateRoomClick, ) } } @@ -159,7 +167,8 @@ fun SpaceView( }, onTopicClick = { topic -> state.eventSink(SpaceEvents.ShowTopicViewer(topic)) - } + }, + onCreateRoomClick = onCreateRoomClick, ) JoinFailuresEffect( hasAnyFailure = state.hasAnyJoinFailures, @@ -232,6 +241,7 @@ private fun SpaceViewContent( state: SpaceState, onRoomClick: (spaceRoom: SpaceRoom) -> Unit, onTopicClick: (String) -> Unit, + onCreateRoomClick: () -> Unit, modifier: Modifier = Modifier, ) { LazyColumn(modifier.fillMaxSize()) { @@ -257,61 +267,90 @@ private fun SpaceViewContent( } } } - itemsIndexed( - items = state.children, - key = { _, spaceRoom -> spaceRoom.roomId } - ) { index, spaceRoom -> - val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED - val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) - val isSelected = state.isSelected(spaceRoom.roomId) - val showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites && !state.isManageMode - SpaceRoomItemView( - spaceRoom = spaceRoom, - showUnreadIndicator = showUnreadIndicator, - hideAvatars = isInvitation && state.hideInvitesAvatar, - onClick = { - onRoomClick(spaceRoom) - }, - onLongClick = { - // TODO - }, - trailingAction = if (state.isManageMode) { - { - Checkbox( - checked = isSelected, - onCheckedChange = null, + + if (state.children.isEmpty() && state.canEditSpaceGraph && !state.hasMoreToLoad) { + item { + EmptySpaceView(onCreateRoomClick = onCreateRoomClick) + } + } else { + itemsIndexed( + items = state.children, + key = { _, spaceRoom -> spaceRoom.roomId } + ) { index, spaceRoom -> + val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED + val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) + val isSelected = state.isSelected(spaceRoom.roomId) + val showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites && !state.isManageMode + SpaceRoomItemView( + spaceRoom = spaceRoom, + showUnreadIndicator = showUnreadIndicator, + hideAvatars = isInvitation && state.hideInvitesAvatar, + onClick = { + onRoomClick(spaceRoom) + }, + onLongClick = { + // TODO + }, + trailingAction = if (state.isManageMode) { + { + Checkbox( + checked = isSelected, + onCheckedChange = null, + ) + } + } else { + spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { + state.eventSink(SpaceEvents.Join(spaceRoom)) + } + }, + bottomAction = if (state.isManageMode) { + null + } else { + spaceRoom.inviteButtons( + onAcceptClick = { + state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) + }, + onDeclineClick = { + state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) + } ) } - } else { - spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { - state.eventSink(SpaceEvents.Join(spaceRoom)) - } - }, - bottomAction = if (state.isManageMode) { - null - } else { - spaceRoom.inviteButtons( - onAcceptClick = { - state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) - }, - onDeclineClick = { - state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) - } - ) + ) + if (index != state.children.lastIndex) { + HorizontalDivider() + } + } + + if (state.hasMoreToLoad) { + item { + LoadingMoreIndicator(eventSink = state.eventSink) } - ) - if (index != state.children.lastIndex) { - HorizontalDivider() - } - } - if (state.hasMoreToLoad) { - item { - LoadingMoreIndicator(eventSink = state.eventSink) } } } } +@Composable +private fun EmptySpaceView(onCreateRoomClick: () -> Unit) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(bottom = 24.dp), + ) { + IconTitleSubtitleMolecule( + title = stringResource(R.string.screen_space_empty_state_title), + subTitle = null, + iconStyle = BigIcon.Style.Default(CompoundIcons.Room()), + modifier = Modifier.fillMaxWidth() + .padding(top = 40.dp, start = 24.dp, end = 24.dp, bottom = 24.dp), + ) + Button( + text = stringResource(R.string.screen_space_add_room_action), + leadingIcon = IconSource.Vector(CompoundIcons.Plus()), + onClick = onCreateRoomClick, + ) + } +} + @Composable private fun LoadingMoreIndicator( eventSink: (SpaceEvents) -> Unit, @@ -344,6 +383,8 @@ private fun SpaceViewTopBar( onShareSpace: () -> Unit, onViewMembersClick: () -> Unit, onManageRoomsClick: () -> Unit, + onAddRoomClick: () -> Unit, + onCreateRoomClick: () -> Unit, modifier: Modifier = Modifier, ) { TopAppBar( @@ -376,6 +417,22 @@ private fun SpaceViewTopBar( onDismissRequest = { showMenu = false } ) { if (showManageRoomsAction) { + SpaceMenuItem( + titleRes = CommonStrings.action_create_room, + icon = CompoundIcons.Plus(), + onClick = { + showMenu = false + onCreateRoomClick() + } + ) + SpaceMenuItem( + titleRes = CommonStrings.action_add_existing_rooms, + icon = CompoundIcons.Room(), + onClick = { + showMenu = false + onAddRoomClick() + } + ) SpaceMenuItem( titleRes = CommonStrings.action_manage_rooms, icon = CompoundIcons.Edit(), @@ -600,6 +657,8 @@ internal fun SpaceViewPreview( acceptDeclineInviteView = {}, onSettingsClick = {}, onViewMembersClick = {}, + onCreateRoomClick = {}, + onAddRoomClick = {}, onBackClick = {}, ) } diff --git a/features/space/impl/src/main/res/values-da/translations.xml b/features/space/impl/src/main/res/values-da/translations.xml index 068712629d..8fa612ae43 100644 --- a/features/space/impl/src/main/res/values-da/translations.xml +++ b/features/space/impl/src/main/res/values-da/translations.xml @@ -10,7 +10,13 @@ "Du vil ikke blive fjernet fra følgende rum, fordi du er den eneste administrator:" "Forlad %1$s?" "Du er den eneste administrator for %1$s" + "Tilføjelse af et rum påvirker ikke adgangen til rummet. For at ændre adgangen, gå til Rumindstillinger > Sikkerhed og privatliv." "Vis medlemmer" + "Fjernelse af et rum påvirker ikke adgangen til rummet. For at ændre adgangen, gå til Rum-info > Privatliv og sikkerhed." + + "Fjern %1$d rum fra %2$s" + "Fjern %1$d rum fra %2$s" + "Forlad gruppe" "Roller og tilladelser" "Sikkerhed og privatliv" diff --git a/features/space/impl/src/main/res/values-de/translations.xml b/features/space/impl/src/main/res/values-de/translations.xml index 1d0238cf7f..e549e1c4bf 100644 --- a/features/space/impl/src/main/res/values-de/translations.xml +++ b/features/space/impl/src/main/res/values-de/translations.xml @@ -10,6 +10,7 @@ "Du wirst aus den folgenden Chats nicht entfernt, weil du der einzige Admin bist:" "%1$s verlassen?" "Du bist der einzige Administrator für %1$s" + "Das Hinzufügen eines Chats hat keinen Einfluss auf die Beitrittsregeln. Um die Regeln zu ändern, gehe zu \"Raum Info\" und dann zu \"Datenschutz und Sicherheit\"" "Mitglieder anzeigen" "Das Entfernen eines Chats hat keinen Einfluss auf die Beitrittsregeln. Um die Regeln zu ändern, gehe zu \"Raum Info\" und dann zu \"Datenschutz und Sicherheit\"" diff --git a/features/space/impl/src/main/res/values-fr/translations.xml b/features/space/impl/src/main/res/values-fr/translations.xml index 89cc3e619f..ef7c4182d2 100644 --- a/features/space/impl/src/main/res/values-fr/translations.xml +++ b/features/space/impl/src/main/res/values-fr/translations.xml @@ -10,7 +10,15 @@ "Vous ne quitterez pas le ou les salons suivants car vous y êtes le seul administrateur:" "Quitter %1$s?" "Vous êtes le seul administrateur de %1$s" + "Salon" + "Ajouter un salon ne changera pas l’accès au salon. Pour modifier l’accès, aller dans les paramètres du salon puis dans Sécurité & confidentialité." + "Ajoutez votre premier salon" "Voir les membres" + "Supprimer un salon n’affectera pas ses paramètres d’accès. Pour modifier l’accès, aller dans les settings du salon puis \"Sécurité & confidentialité\"." + + "Retirer %1$d salon de %2$s" + "Retirer %1$d salons de %2$s" + "Quitter l’espace" "Rôles & autorisations" "Sécurité & confidentialité" diff --git a/features/space/impl/src/main/res/values-hu/translations.xml b/features/space/impl/src/main/res/values-hu/translations.xml index 670e14cc3c..e50d95ac3b 100644 --- a/features/space/impl/src/main/res/values-hu/translations.xml +++ b/features/space/impl/src/main/res/values-hu/translations.xml @@ -10,7 +10,15 @@ "Nem lesz eltávolítva a következő szobá(k)ból, mert ön az egyetlen adminisztrátor:" "Kilép innen: %1$s?" "Ön az egyetlen adminisztrátor itt: %1$s" + "Szoba" + "A szoba hozzáadása nem befolyásolja a szobához való hozzáférést. A hozzáférés módosításához lépjen a szoba beállításainak Biztonság és adatvédelem menüpontjához." + "Első szoba hozzáadása" "Tagok megtekintése" + "A szoba eltávolítása nem befolyásolja a szoba hozzáférését. A hozzáférés módosításához lépjen a szoba beállításainak Biztonság és adatvédelem menüpontjához." + + "%1$d szoba eltávolítása innen: %2$s" + "%1$d szoba eltávolítása innen: %2$s" + "Tér elhagyása" "Szerepkörök és jogosultságok" "Biztonság és adatvédelem" diff --git a/features/space/impl/src/main/res/values-pt-rBR/translations.xml b/features/space/impl/src/main/res/values-pt-rBR/translations.xml index c509b8caf8..a75df4a4dd 100644 --- a/features/space/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/space/impl/src/main/res/values-pt-rBR/translations.xml @@ -10,7 +10,15 @@ "Você não será removido das seguintes salas porque você é o único administrador:" "Sair de %1$s?" "Você é o único administrador de %1$s" + "Sala" + "Adicionar uma sala não afetará o acesso dela. Para alterar o acesso, vá as Configurações da sala > Segurança e privacidade." + "Adicione sua primeira sala" "Ver membros" + "Remover uma sala não afetará o acesso dela. Para alterar o acesso, vá as Informações da sala > Privacidade e segurança." + + "Remover %1$d sala de %2$s" + "Remover %1$d salas de %2$s" + "Sair do espaço" "Cargos e permissões" "Segurança e privacidade" diff --git a/features/space/impl/src/main/res/values-ru/translations.xml b/features/space/impl/src/main/res/values-ru/translations.xml index 090c551fd5..f1b314dede 100644 --- a/features/space/impl/src/main/res/values-ru/translations.xml +++ b/features/space/impl/src/main/res/values-ru/translations.xml @@ -11,7 +11,14 @@ "Вы не будете удалены из следующих комнат, поскольку вы являетесь единственным администратором:" "Выйти из %1$s?" "Вы единственный администратор для %1$s" + "Добавление комнаты не повлияет на доступ к ней. Чтобы изменить доступ к комнате, перейдите в Настройки > Безопасность и конфиденциальность." "Просмотреть участников" + "Удаление комнаты не повлияет на доступ к ней. Чтобы изменить доступ, перейдите в раздел «Информация о комнате > Конфиденциальность и безопасность." + + "Удалить %1$d комнату из %2$s" + "Удалить %1$d комнаты из %2$s" + "Удалить %1$d комнат из %2$s" + "Покинуть пространство" "Роли и разрешения" "Безопасность и конфиденциальность" diff --git a/features/space/impl/src/main/res/values-sk/translations.xml b/features/space/impl/src/main/res/values-sk/translations.xml index 79b8fbfbb6..ff7ddc362b 100644 --- a/features/space/impl/src/main/res/values-sk/translations.xml +++ b/features/space/impl/src/main/res/values-sk/translations.xml @@ -11,7 +11,16 @@ "Z nasledujúcich miestností nebudete odstránený/á, pretože ste jediným správcom:" "Opustiť %1$s?" "Ste jediným administrátorom pre %1$s" + "Miestnosť" + "Pridanie miestnosti neovplyvní prístup k miestnosti. Ak chcete zmeniť prístup, prejdite do časti Nastavenia miestnosti > Zabezpečenie a súkromie." + "Pridajte svoju prvú miestnosť" "Zobraziť členov" + "Odstránenie miestnosti neovplyvní prístup k miestnosti. Ak chcete zmeniť prístup, prejdite do sekcie Informácie o miestnosti > Súkromie a zabezpečenie." + + "Odstrániť %1$d miestnosť z priestoru %2$s" + "Odstrániť %1$d miestnosti z priestoru %2$s" + "Odstrániť %1$d miestností z priestoru %2$s" + "Opustiť priestor" "Roly a povolenia" "Bezpečnosť a súkromie" diff --git a/features/space/impl/src/main/res/values/localazy.xml b/features/space/impl/src/main/res/values/localazy.xml index 10aa0fb28c..b5495b721a 100644 --- a/features/space/impl/src/main/res/values/localazy.xml +++ b/features/space/impl/src/main/res/values/localazy.xml @@ -10,6 +10,9 @@ "You will not be removed from the following room(s) because you\'re the only administrator:" "Leave %1$s?" "You are the only admin for %1$s" + "Room" + "Adding a room will not affect the room access. To change the access go to Room settings > Security & privacy." + "Add your first room" "View members" "Removing a room will not affect the room access. To change the access go to Room info > Privacy & security." diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt index 0e2717c055..0c3ae1b888 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt @@ -12,6 +12,7 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat +import io.element.android.features.createroom.api.FakeCreateRoomEntryPoint import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.FakeSpaceFlowGraph import io.element.android.libraries.matrix.api.core.RoomId @@ -43,7 +44,8 @@ class DefaultSpaceEntryPointTest { spaceRoomListResult = { _: RoomId -> FakeSpaceRoomList(A_ROOM_ID) } ), room = FakeJoinedRoom(), - graphFactory = FakeSpaceFlowGraph.Factory + graphFactory = FakeSpaceFlowGraph.Factory, + createRoomEntryPoint = FakeCreateRoomEntryPoint(), ) } val callback = object : SpaceEntryPoint.Callback { diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpacePresenterTest.kt new file mode 100644 index 0000000000..b9a67704c9 --- /dev/null +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpacePresenterTest.kt @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.features.space.impl.addroom + +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.CurrentUserMembership +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.aRoomSummary +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList +import io.element.android.libraries.matrix.test.spaces.FakeSpaceService +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class AddRoomToSpacePresenterTest { + @Test + fun `present - initial state has empty selection and no search`() = runTest { + val presenter = createAddRoomToSpacePresenter() + presenter.test { + val state = awaitItem() + assertThat(state.selectedRooms).isEmpty() + assertThat(state.searchQuery.text.toString()).isEmpty() + assertThat(state.isSearchActive).isFalse() + assertThat(state.saveAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(state.canSave).isFalse() + } + } + + @Test + fun `present - ToggleRoom adds room to selection`() = runTest { + val presenter = createAddRoomToSpacePresenter() + presenter.test { + val state = awaitItem() + val room = aSelectRoomInfoList().first() + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room)) + val updatedState = awaitItem() + assertThat(updatedState.selectedRooms).hasSize(1) + assertThat(updatedState.selectedRooms.first().roomId).isEqualTo(room.roomId) + assertThat(updatedState.canSave).isTrue() + } + } + + @Test + fun `present - ToggleRoom removes already selected room`() = runTest { + val presenter = createAddRoomToSpacePresenter() + presenter.test { + val state = awaitItem() + val room = aSelectRoomInfoList().first() + // Add room + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room)) + val stateWithRoom = awaitItem() + assertThat(stateWithRoom.selectedRooms).hasSize(1) + // Remove room + stateWithRoom.eventSink(AddRoomToSpaceEvent.ToggleRoom(room)) + val stateWithoutRoom = awaitItem() + assertThat(stateWithoutRoom.selectedRooms).isEmpty() + assertThat(stateWithoutRoom.canSave).isFalse() + } + } + + @Test + fun `present - OnSearchActiveChanged activates search`() = runTest { + val presenter = createAddRoomToSpacePresenter() + presenter.test { + val state = awaitItem() + state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(true)) + val updatedState = awaitItem() + assertThat(updatedState.isSearchActive).isTrue() + } + } + + @Test + fun `present - OnSearchActiveChanged deactivates search and clears query`() = runTest { + val presenter = createAddRoomToSpacePresenter() + presenter.test { + val state = awaitItem() + // Activate search and set query + state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(true)) + awaitItem() + state.searchQuery.setTextAndPlaceCursorAtEnd("test") + awaitItem() + // Deactivate search + state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(false)) + advanceUntilIdle() + val finalState = expectMostRecentItem() + assertThat(finalState.isSearchActive).isFalse() + assertThat(finalState.searchQuery.text.toString()).isEmpty() + } + } + + @Test + fun `present - searchResults shows Results when rooms available`() = runTest { + val roomListService = FakeRoomListService() + val presenter = createAddRoomToSpacePresenter(roomListService = roomListService) + presenter.test { + awaitItem() // Initial state + // Post rooms to the service + roomListService.postAllRooms( + listOf( + aRoomSummary( + roomId = A_ROOM_ID, + name = "Room 1", + isDirect = false, + isSpace = false, + currentUserMembership = CurrentUserMembership.JOINED, + ) + ) + ) + advanceUntilIdle() + val state = expectMostRecentItem() + assertThat(state.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) + } + } + + @Test + fun `present - searchResults shows NoResultsFound when search active with query but no results`() = runTest { + val presenter = createAddRoomToSpacePresenter() + presenter.test { + val state = awaitItem() + state.eventSink(AddRoomToSpaceEvent.OnSearchActiveChanged(true)) + awaitItem() + state.searchQuery.setTextAndPlaceCursorAtEnd("nonexistent") + advanceUntilIdle() + val finalState = expectMostRecentItem() + assertThat(finalState.isSearchActive).isTrue() + assertThat(finalState.searchQuery.text).isEqualTo("nonexistent") + assertThat(finalState.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) + } + } + + @Test + fun `present - Save triggers addChildToSpace for all selected rooms`() = runTest { + val addChildToSpaceResult = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val spaceService = FakeSpaceService( + addChildToSpaceResult = addChildToSpaceResult, + ) + val presenter = createAddRoomToSpacePresenter(spaceService = spaceService) + presenter.test { + val state = awaitItem() + // Select two rooms + val room1 = aSelectRoomInfoList()[0] + val room2 = aSelectRoomInfoList()[1] + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room1)) + awaitItem() + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room2)) + awaitItem() + // Save + state.eventSink(AddRoomToSpaceEvent.Save) + // Wait for loading and success states + skipItems(1) // Loading + advanceUntilIdle() + skipItems(1) // Success + // Verify service was called for both rooms + addChildToSpaceResult.assertions().isCalledExactly(2) + } + } + + @Test + fun `present - Save success updates saveAction to Success`() = runTest { + val spaceService = FakeSpaceService( + addChildToSpaceResult = { _, _ -> Result.success(Unit) }, + ) + val presenter = createAddRoomToSpacePresenter(spaceService = spaceService) + presenter.test { + val state = awaitItem() + val room = aSelectRoomInfoList().first() + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room)) + awaitItem() + state.eventSink(AddRoomToSpaceEvent.Save) + // Wait for loading state + val loadingState = awaitItem() + assertThat(loadingState.saveAction).isEqualTo(AsyncAction.Loading) + // Wait for success state + advanceUntilIdle() + val successState = awaitItem() + assertThat(successState.saveAction).isInstanceOf(AsyncAction.Success::class.java) + } + } + + @Test + fun `present - Save failure updates saveAction to Failure`() = runTest { + val spaceService = FakeSpaceService( + addChildToSpaceResult = { _, _ -> Result.failure(AN_EXCEPTION) }, + ) + val presenter = createAddRoomToSpacePresenter(spaceService = spaceService) + presenter.test { + val state = awaitItem() + val room = aSelectRoomInfoList().first() + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room)) + awaitItem() + state.eventSink(AddRoomToSpaceEvent.Save) + // Wait for loading state + val loadingState = awaitItem() + assertThat(loadingState.saveAction).isEqualTo(AsyncAction.Loading) + // Wait for failure state + advanceUntilIdle() + val failureState = awaitItem() + assertThat(failureState.saveAction).isInstanceOf(AsyncAction.Failure::class.java) + } + } + + @Test + fun `present - ResetSaveAction resets to Uninitialized`() = runTest { + val spaceService = FakeSpaceService( + addChildToSpaceResult = { _, _ -> Result.success(Unit) }, + ) + val presenter = createAddRoomToSpacePresenter(spaceService = spaceService) + presenter.test { + val state = awaitItem() + val room = aSelectRoomInfoList().first() + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room)) + awaitItem() + state.eventSink(AddRoomToSpaceEvent.Save) + skipItems(1) // Loading + advanceUntilIdle() + val successState = awaitItem() + assertThat(successState.saveAction).isInstanceOf(AsyncAction.Success::class.java) + // Reset + successState.eventSink(AddRoomToSpaceEvent.ResetSaveAction) + val resetState = awaitItem() + assertThat(resetState.saveAction).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `canSave is false when no rooms selected`() = runTest { + val presenter = createAddRoomToSpacePresenter() + presenter.test { + val state = awaitItem() + assertThat(state.selectedRooms).isEmpty() + assertThat(state.canSave).isFalse() + } + } + + @Test + fun `canSave is false when loading`() = runTest { + val spaceService = FakeSpaceService( + addChildToSpaceResult = { _, _ -> Result.success(Unit) }, + ) + val presenter = createAddRoomToSpacePresenter(spaceService = spaceService) + presenter.test { + val state = awaitItem() + val room = aSelectRoomInfoList().first() + state.eventSink(AddRoomToSpaceEvent.ToggleRoom(room)) + val stateWithRoom = awaitItem() + assertThat(stateWithRoom.canSave).isTrue() + stateWithRoom.eventSink(AddRoomToSpaceEvent.Save) + val loadingState = awaitItem() + assertThat(loadingState.saveAction).isEqualTo(AsyncAction.Loading) + assertThat(loadingState.canSave).isFalse() + } + } + + private fun TestScope.createAddRoomToSpacePresenter( + spaceRoomList: FakeSpaceRoomList = FakeSpaceRoomList( + paginateResult = { Result.success(Unit) }, + ), + spaceService: FakeSpaceService = FakeSpaceService( + addChildToSpaceResult = { _, _ -> Result.success(Unit) }, + ), + roomListService: FakeRoomListService = FakeRoomListService(), + matrixClient: FakeMatrixClient = FakeMatrixClient( + roomListService = roomListService, + ), + ): AddRoomToSpacePresenter { + val dataSourceFactory = object : AddRoomToSpaceSearchDataSource.Factory { + override fun create(coroutineScope: CoroutineScope) = AddRoomToSpaceSearchDataSource( + coroutineScope = coroutineScope, + roomListService = roomListService, + spaceRoomList = spaceRoomList, + matrixClient = matrixClient, + coroutineDispatchers = testCoroutineDispatchers(), + ) + } + return AddRoomToSpacePresenter( + spaceRoomList = spaceRoomList, + spaceService = spaceService, + dataSourceFactory = dataSourceFactory, + ) + } +} diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceViewTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceViewTest.kt new file mode 100644 index 0000000000..d6a9ff770a --- /dev/null +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/addroom/AddRoomToSpaceViewTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.addroom + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import kotlinx.collections.immutable.toImmutableList +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +class AddRoomToSpaceViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking back when search inactive invokes onBackClick`() { + ensureCalledOnce { + rule.setAddRoomToSpaceView( + anAddRoomToSpaceState( + isSearchActive = false, + ), + onBackClick = it, + ) + rule.pressBack() + } + } + + @Test + fun `clicking back when search active emits CloseSearch event`() { + val eventsRecorder = EventsRecorder() + rule.setAddRoomToSpaceView( + anAddRoomToSpaceState( + isSearchActive = true, + eventSink = eventsRecorder, + ), + ) + rule.pressBack() + eventsRecorder.assertSingle(AddRoomToSpaceEvent.OnSearchActiveChanged(false)) + } + + @Test + fun `clicking save emits Save event`() { + val eventsRecorder = EventsRecorder() + rule.setAddRoomToSpaceView( + anAddRoomToSpaceState( + selectedRooms = aSelectRoomInfoList().take(1).toImmutableList(), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_save) + eventsRecorder.assertSingle(AddRoomToSpaceEvent.Save) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking room in suggestions emits ToggleRoom event`() { + val suggestions = aSelectRoomInfoList() + val eventsRecorder = EventsRecorder() + rule.setAddRoomToSpaceView( + anAddRoomToSpaceState( + suggestions = suggestions, + eventSink = eventsRecorder, + ), + ) + rule.onNodeWithText(suggestions.first().name!!).performClick() + eventsRecorder.assertSingle(AddRoomToSpaceEvent.ToggleRoom(suggestions.first())) + } + + @Test + fun `onRoomsAdded called when saveAction is Success`() { + ensureCalledOnce { + rule.setAddRoomToSpaceView( + anAddRoomToSpaceState( + saveAction = AsyncAction.Success(Unit), + ), + onRoomsAdded = it, + ) + } + } +} + +private fun AndroidComposeTestRule.setAddRoomToSpaceView( + state: AddRoomToSpaceState, + onBackClick: () -> Unit = EnsureNeverCalled(), + onRoomsAdded: () -> Unit = EnsureNeverCalled(), +) { + setContent { + AddRoomToSpaceView( + state = state, + onBackClick = onBackClick, + onRoomsAdded = onRoomsAdded, + ) + } +} diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt index 495f0b38bd..e78a338bb7 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt @@ -44,7 +44,7 @@ class LeaveSpacePresenterTest { presenter.test { val state = awaitItem() assertThat(state.spaceName).isNull() - assertThat(state.isLastAdmin).isFalse() + assertThat(state.isLastOwner).isFalse() assertThat(state.selectableSpaceRooms.isLoading()).isTrue() assertThat(state.leaveSpaceAction).isEqualTo(AsyncAction.Uninitialized) cancelAndIgnoreRemainingEvents() @@ -78,7 +78,7 @@ class LeaveSpacePresenterTest { fun `present - current space name and is last admin`() = runTest { val presenter = createLeaveSpacePresenter( leaveSpaceHandle = FakeLeaveSpaceHandle( - roomsResult = { Result.success(listOf(aLeaveSpaceRoom(spaceRoom = aSpace, isLastAdmin = true))) }, + roomsResult = { Result.success(listOf(aLeaveSpaceRoom(spaceRoom = aSpace, isLastOwner = true))) }, ) ) presenter.test { @@ -87,7 +87,7 @@ class LeaveSpacePresenterTest { skipItems(2) val finalState = awaitItem() assertThat(finalState.spaceName).isEqualTo(A_SPACE_NAME) - assertThat(finalState.isLastAdmin).isTrue() + assertThat(finalState.isLastOwner).isTrue() // The current state is not in the sub room list assertThat(finalState.selectableSpaceRooms.dataOrNull()!!).isEmpty() } @@ -145,8 +145,8 @@ class LeaveSpacePresenterTest { roomsResult = { Result.success( listOf( - LeaveSpaceRoom(aSpaceRoom(roomId = A_ROOM_ID), isLastAdmin = false), - LeaveSpaceRoom(aSpaceRoom(roomId = A_ROOM_ID_2), isLastAdmin = true), + LeaveSpaceRoom(aSpaceRoom(roomId = A_ROOM_ID), isLastOwner = false), + LeaveSpaceRoom(aSpaceRoom(roomId = A_ROOM_ID_2), isLastOwner = true), ) ) }, @@ -157,18 +157,18 @@ class LeaveSpacePresenterTest { skipItems(3) val state = awaitItem() assertThat(state.spaceName).isNull() - assertThat(state.isLastAdmin).isFalse() + assertThat(state.isLastOwner).isFalse() val data = state.selectableSpaceRooms.dataOrNull()!! assertThat(data.size).isEqualTo(2) // Only one room is selectable as the user is the last admin in the other one val room1 = data[0] assertThat(room1.spaceRoom.roomId).isEqualTo(A_ROOM_ID) assertThat(room1.isSelected).isTrue() - assertThat(room1.isLastAdmin).isFalse() + assertThat(room1.isLastOwner).isFalse() val room2 = data[1] assertThat(room2.spaceRoom.roomId).isEqualTo(A_ROOM_ID_2) assertThat(room2.isSelected).isFalse() - assertThat(room2.isLastAdmin).isTrue() + assertThat(room2.isLastOwner).isTrue() // Deselect all state.eventSink(LeaveSpaceEvents.DeselectAllRooms) skipItems(1) @@ -246,8 +246,8 @@ private fun aLeaveSpaceRoom( roomId = A_SPACE_ID, displayName = A_SPACE_NAME, ), - isLastAdmin: Boolean = false, + isLastOwner: Boolean = false, ) = LeaveSpaceRoom( spaceRoom = spaceRoom, - isLastAdmin = isLastAdmin, + isLastOwner = isLastOwner, ) diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt index d3b3f44398..83eee70e5c 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt @@ -44,10 +44,10 @@ class LeaveSpaceStateTest { @Test fun `test last admin`() { val sut = aLeaveSpaceState( - isLastAdmin = true, + isLastOwner = true, selectableSpaceRooms = AsyncData.Success( persistentListOf( - aSelectableSpaceRoom(isLastAdmin = false, isSelected = false), + aSelectableSpaceRoom(isLastOwner = false, isSelected = false), ) ) ) @@ -63,8 +63,8 @@ class LeaveSpaceStateTest { val sut = aLeaveSpaceState( selectableSpaceRooms = AsyncData.Success( listOf( - aSelectableSpaceRoom(isLastAdmin = false, isSelected = true), - aSelectableSpaceRoom(isLastAdmin = false, isSelected = false), + aSelectableSpaceRoom(isLastOwner = false, isSelected = true), + aSelectableSpaceRoom(isLastOwner = false, isSelected = false), ).toImmutableList() ) ) @@ -80,8 +80,8 @@ class LeaveSpaceStateTest { val sut = aLeaveSpaceState( selectableSpaceRooms = AsyncData.Success( listOf( - aSelectableSpaceRoom(isLastAdmin = false, isSelected = true), - aSelectableSpaceRoom(isLastAdmin = false, isSelected = true), + aSelectableSpaceRoom(isLastOwner = false, isSelected = true), + aSelectableSpaceRoom(isLastOwner = false, isSelected = true), ).toImmutableList() ) ) @@ -97,9 +97,9 @@ class LeaveSpaceStateTest { val sut = aLeaveSpaceState( selectableSpaceRooms = AsyncData.Success( persistentListOf( - aSelectableSpaceRoom(isLastAdmin = true, isSelected = false), - aSelectableSpaceRoom(isLastAdmin = false, isSelected = true), - aSelectableSpaceRoom(isLastAdmin = false, isSelected = true), + aSelectableSpaceRoom(isLastOwner = true, isSelected = false), + aSelectableSpaceRoom(isLastOwner = false, isSelected = true), + aSelectableSpaceRoom(isLastOwner = false, isSelected = true), ) ) ) @@ -115,8 +115,8 @@ class LeaveSpaceStateTest { val sut = aLeaveSpaceState( selectableSpaceRooms = AsyncData.Success( listOf( - aSelectableSpaceRoom(isLastAdmin = true, isSelected = false), - aSelectableSpaceRoom(isLastAdmin = true, isSelected = false), + aSelectableSpaceRoom(isLastOwner = true, isSelected = false), + aSelectableSpaceRoom(isLastOwner = true, isSelected = false), ).toImmutableList() ) ) diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt index 27970e93f8..ec4d681a40 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.space.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.spaces.SpaceRoom @@ -30,6 +31,7 @@ import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.ensureCalledOnceWithParam +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey import org.junit.Rule @@ -200,6 +202,22 @@ class SpaceViewTest { rule.clickOn(CommonStrings.action_remove, inDialog = true) eventsRecorder.assertSingle(SpaceEvents.ConfirmRoomRemoval) } + + @Test + fun `clicking create room button calls the expected callback`() { + val onCreateRoomClick = lambdaRecorder { } + rule.setSpaceView( + aSpaceState( + children = emptyList(), + hasMoreToLoad = false, + isManageMode = true, + canManageRooms = true, + ), + onCreateRoomClick = onCreateRoomClick, + ) + rule.clickOn(R.string.screen_space_add_room_action) + onCreateRoomClick.assertions().isCalledOnce() + } } private fun AndroidComposeTestRule.setSpaceView( @@ -210,6 +228,8 @@ private fun AndroidComposeTestRule.setSpace onLeaveSpaceClick: () -> Unit = EnsureNeverCalled(), onSettingsClick: () -> Unit = EnsureNeverCalled(), onViewMembersClick: () -> Unit = EnsureNeverCalled(), + onCreateRoomClick: () -> Unit = EnsureNeverCalled(), + onAddRoomClick: () -> Unit = EnsureNeverCalled(), acceptDeclineInviteView: @Composable () -> Unit = {}, ) { setContent { @@ -221,7 +241,9 @@ private fun AndroidComposeTestRule.setSpace onLeaveSpaceClick = onLeaveSpaceClick, onSettingsClick = onSettingsClick, onViewMembersClick = onViewMembersClick, + onAddRoomClick = onAddRoomClick, acceptDeclineInviteView = acceptDeclineInviteView, + onCreateRoomClick = onCreateRoomClick, ) } } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt index c7d9aa4fd0..9cb8d48778 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt @@ -80,12 +80,14 @@ class StartChatFlowNode( navigator.onRoomCreated(roomId.toRoomIdOrAlias(), emptyList()) } } - createRoomEntryPoint.createNode( - isSpace = false, - parentNode = this, - buildContext = buildContext, - callback = callback, - ) + createRoomEntryPoint + .builder( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) + .setIsSpace(false) + .build() } NavTarget.JoinByAddress -> { createNode(buildContext = buildContext, plugins = listOf(navigator)) diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt index a664ad0912..c8284c6a4f 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.surfaceColorAtElevation @@ -40,14 +41,13 @@ import kotlinx.collections.immutable.ImmutableList @OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchUserBar( - query: String, - state: SearchBarResultState>, + queryState: TextFieldState, + resultState: SearchBarResultState>, showLoader: Boolean, selectedUsers: ImmutableList, active: Boolean, isMultiSelectionEnable: Boolean, onActiveChange: (Boolean) -> Unit, - onTextChange: (String) -> Unit, onUserSelect: (MatrixUser) -> Unit, onUserDeselect: (MatrixUser) -> Unit, modifier: Modifier = Modifier, @@ -57,8 +57,7 @@ fun SearchUserBar( val columnState = rememberLazyListState() SearchBar( - query = query, - onQueryChange = onTextChange, + queryState = queryState, active = active, onActiveChange = onActiveChange, modifier = modifier, @@ -98,7 +97,7 @@ fun SearchUserBar( AsyncLoading() } }, - resultState = state, + resultState = resultState, resultHandler = { users -> LazyColumn(state = columnState) { if (isMultiSelectionEnable) { diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt index f35c6282ed..cab1c57015 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt @@ -46,15 +46,14 @@ fun UserListView( ) { SearchUserBar( modifier = Modifier.fillMaxWidth(), - query = state.searchQuery, - state = state.searchResults, + queryState = state.searchQuery, + resultState = state.searchResults, selectedUsers = state.selectedUsers, active = state.isSearchActive, showLoader = state.showSearchLoader, isMultiSelectionEnable = state.isMultiSelectionEnabled, showBackButton = showBackButton, onActiveChange = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) }, - onTextChange = { state.eventSink(UserListEvents.UpdateSearchQuery(it)) }, onUserSelect = { state.eventSink(UserListEvents.AddToSelection(it)) onSelectUser(it) diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvents.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvent.kt similarity index 70% rename from features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvents.kt rename to features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvent.kt index 4a5c2de972..3f3627dd9f 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvents.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvent.kt @@ -8,8 +8,8 @@ package io.element.android.features.startchat.impl.joinbyaddress -sealed interface JoinRoomByAddressEvents { - data object Dismiss : JoinRoomByAddressEvents - data object Continue : JoinRoomByAddressEvents - data class UpdateAddress(val address: String) : JoinRoomByAddressEvents +sealed interface JoinRoomByAddressEvent { + data object Dismiss : JoinRoomByAddressEvent + data object Continue : JoinRoomByAddressEvent + data class UpdateAddress(val address: String) : JoinRoomByAddressEvent } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt index bda1e054e4..533f680674 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt @@ -49,16 +49,16 @@ class JoinRoomByAddressPresenter( var internalAddressState by remember { mutableStateOf(RoomAddressState.Unknown) } var validateAddress: Boolean by remember { mutableStateOf(false) } - fun handleEvent(event: JoinRoomByAddressEvents) { + fun handleEvent(event: JoinRoomByAddressEvent) { when (event) { - JoinRoomByAddressEvents.Continue -> { + JoinRoomByAddressEvent.Continue -> { when (val currentState = internalAddressState) { is RoomAddressState.RoomFound -> onRoomFound(currentState) else -> validateAddress = true } } - JoinRoomByAddressEvents.Dismiss -> navigator.onDismissJoinRoomByAddress() - is JoinRoomByAddressEvents.UpdateAddress -> { + JoinRoomByAddressEvent.Dismiss -> navigator.onDismissJoinRoomByAddress() + is JoinRoomByAddressEvent.UpdateAddress -> { validateAddress = false address = event.address.trim() } @@ -113,7 +113,7 @@ class JoinRoomByAddressPresenter( // debounce the room address resolution delay(300) val roomAlias = tryOrNull { RoomAlias(fullAddress) } - if (roomAlias != null && roomAliasHelper.isRoomAliasValid(roomAlias)) { + if (roomAlias != null) { onChange(RoomAddressState.Resolving) onChange(client.resolveRoomAddress(roomAlias)) } else { @@ -130,11 +130,21 @@ class JoinRoomByAddressPresenter( if (resolved.isPresent) { RoomAddressState.RoomFound(resolved.get()) } else { - RoomAddressState.RoomNotFound + roomAlias.toInvalidOrNotFound() } }, - onFailure = { _ -> RoomAddressState.RoomNotFound } + onFailure = { _ -> + roomAlias.toInvalidOrNotFound() + } ) } ?: RoomAddressState.RoomNotFound } + + private fun RoomAlias.toInvalidOrNotFound(): RoomAddressState { + return if (roomAliasHelper.isRoomAliasValid(this)) { + RoomAddressState.RoomNotFound + } else { + RoomAddressState.Invalid + } + } } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt index 4749b14b22..d74b36590f 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt @@ -14,7 +14,7 @@ import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias data class JoinRoomByAddressState( val address: String, val addressState: RoomAddressState, - val eventSink: (JoinRoomByAddressEvents) -> Unit + val eventSink: (JoinRoomByAddressEvent) -> Unit ) @Immutable diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt index eb1305eb80..5528a84196 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt @@ -30,7 +30,7 @@ open class JoinRoomByAddressStateProvider : PreviewParameterProvider Unit = {}, + eventSink: (JoinRoomByAddressEvent) -> Unit = {}, ) = JoinRoomByAddressState( address = address, addressState = addressState, diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt index 0627a78da0..e4f8a4faf2 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt @@ -50,7 +50,7 @@ fun JoinRoomByAddressView( modifier = modifier, sheetState = sheetState, onDismissRequest = { - state.eventSink(JoinRoomByAddressEvents.Dismiss) + state.eventSink(JoinRoomByAddressEvent.Dismiss) }, ) { Column( @@ -64,10 +64,10 @@ fun JoinRoomByAddressView( addressState = state.addressState, requestFocus = sheetState.isVisible, onAddressChange = { - state.eventSink(JoinRoomByAddressEvents.UpdateAddress(it)) + state.eventSink(JoinRoomByAddressEvent.UpdateAddress(it)) }, onContinue = { - state.eventSink(JoinRoomByAddressEvents.Continue) + state.eventSink(JoinRoomByAddressEvent.Continue) }, ) Spacer(modifier = Modifier.height(24.dp)) @@ -76,7 +76,7 @@ fun JoinRoomByAddressView( modifier = Modifier.fillMaxWidth(), showProgress = state.addressState is RoomAddressState.Resolving, onClick = { - state.eventSink(JoinRoomByAddressEvents.Continue) + state.eventSink(JoinRoomByAddressEvent.Continue) } ) } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt index 1c82ae373e..448ad1a80a 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt @@ -27,10 +27,10 @@ open class StartChatStateProvider : PreviewParameterProvider { aCreateRoomRootState( startDmAction = AsyncAction.Loading, userListState = aMatrixUser().let { - aUserListState().copy( + aUserListState( searchQuery = it.userId.value, searchResults = SearchBarResultState.Results(persistentListOf(UserSearchResult(it, false))), - selectedUsers = persistentListOf(it), + selectedUsers = listOf(it), isSearchActive = true, ) } @@ -38,10 +38,10 @@ open class StartChatStateProvider : PreviewParameterProvider { aCreateRoomRootState( startDmAction = AsyncAction.Failure(RuntimeException("error")), userListState = aMatrixUser().let { - aUserListState().copy( + aUserListState( searchQuery = it.userId.value, searchResults = SearchBarResultState.Results(persistentListOf(UserSearchResult(it, false))), - selectedUsers = persistentListOf(it), + selectedUsers = listOf(it), isSearchActive = true, ) } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt index 4e85dd0353..ce579c5b50 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt @@ -8,6 +8,7 @@ package io.element.android.features.startchat.impl.userlist +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -31,6 +32,10 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.toList + +private const val MAX_SUGGESTIONS_COUNT = 5 @AssistedInject class DefaultUserListPresenter( @@ -53,16 +58,20 @@ class DefaultUserListPresenter( override fun present(): UserListState { var recentDirectRooms by remember { mutableStateOf(emptyList()) } LaunchedEffect(Unit) { - recentDirectRooms = matrixClient.getRecentDirectRooms() + recentDirectRooms = matrixClient + .getRecentDirectRooms() + .take(MAX_SUGGESTIONS_COUNT) + .toList() } var isSearchActive by rememberSaveable { mutableStateOf(false) } val selectedUsers by userListDataStore.selectedUsers.collectAsState(emptyList()) - var searchQuery by rememberSaveable { mutableStateOf("") } + val queryState = rememberTextFieldState() var searchResults: SearchBarResultState> by remember { mutableStateOf(SearchBarResultState.Initial()) } var showSearchLoader by remember { mutableStateOf(false) } + val searchQuery = queryState.text.toString() LaunchedEffect(searchQuery) { searchResults = SearchBarResultState.Initial() showSearchLoader = false @@ -79,14 +88,13 @@ class DefaultUserListPresenter( fun handleEvent(event: UserListEvents) { when (event) { is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active - is UserListEvents.UpdateSearchQuery -> searchQuery = event.query is UserListEvents.AddToSelection -> userListDataStore.selectUser(event.matrixUser) is UserListEvents.RemoveFromSelection -> userListDataStore.removeUserFromSelection(event.matrixUser) } } return UserListState( - searchQuery = searchQuery, + searchQuery = queryState, searchResults = searchResults, selectedUsers = selectedUsers.toImmutableList(), isSearchActive = isSearchActive, diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt index 99e910b89f..f7601f6d36 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt @@ -11,7 +11,6 @@ package io.element.android.features.startchat.impl.userlist import io.element.android.libraries.matrix.api.user.MatrixUser sealed interface UserListEvents { - data class UpdateSearchQuery(val query: String) : UserListEvents data class AddToSelection(val matrixUser: MatrixUser) : UserListEvents data class RemoveFromSelection(val matrixUser: MatrixUser) : UserListEvents data class OnSearchActiveChanged(val active: Boolean) : UserListEvents diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt index 33b74d240c..6a507f8a75 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt @@ -8,6 +8,7 @@ package io.element.android.features.startchat.impl.userlist +import androidx.compose.foundation.text.input.TextFieldState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.room.recent.RecentDirectRoom import io.element.android.libraries.matrix.api.user.MatrixUser @@ -15,7 +16,7 @@ import io.element.android.libraries.usersearch.api.UserSearchResult import kotlinx.collections.immutable.ImmutableList data class UserListState( - val searchQuery: String, + val searchQuery: TextFieldState, val searchResults: SearchBarResultState>, val showSearchLoader: Boolean, val selectedUsers: ImmutableList, diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt index cd43f96688..3da8bfdabe 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.startchat.impl.userlist +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.RoomId @@ -69,7 +70,7 @@ fun aUserListState( recentDirectRooms: List = emptyList(), eventSink: (UserListEvents) -> Unit = {}, ) = UserListState( - searchQuery = searchQuery, + searchQuery = TextFieldState(initialText = searchQuery), isSearchActive = isSearchActive, searchResults = searchResults, selectedUsers = selectedUsers.toImmutableList(), diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt index 74c43d683e..393f51dd2d 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt @@ -21,6 +21,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Test +import java.util.Optional class JoinBaseRoomByAddressPresenterTest { @Test @@ -43,12 +44,12 @@ class JoinBaseRoomByAddressPresenterTest { ) presenter.test { with(awaitItem()) { - eventSink(JoinRoomByAddressEvents.UpdateAddress("invalid_address")) + eventSink(JoinRoomByAddressEvent.UpdateAddress("invalid_address")) } with(awaitItem()) { assertThat(address).isEqualTo("invalid_address") assertThat(addressState).isEqualTo(RoomAddressState.Unknown) - eventSink(JoinRoomByAddressEvents.Continue) + eventSink(JoinRoomByAddressEvent.Continue) } // The address should be marked as invalid only after the user tries to continue with(awaitItem()) { @@ -58,6 +59,107 @@ class JoinBaseRoomByAddressPresenterTest { } } + @Test + fun `present - invalid address - but room exists`() = runTest { + val presenter = createJoinRoomByAddressPresenter( + roomAliasHelper = FakeRoomAliasHelper( + isRoomAliasValidLambda = { + // The SDK still return false, but we have a room for this alias + false + } + ) + ) + presenter.test { + with(awaitItem()) { + eventSink(JoinRoomByAddressEvent.UpdateAddress("#ö:invalid.org")) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Unknown) + eventSink(JoinRoomByAddressEvent.Continue) + } + // The address should not be marked as valid + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Resolving) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isInstanceOf(RoomAddressState.RoomFound::class.java) + } + } + } + + @Test + fun `present - invalid address - room does not exist`() = runTest { + val presenter = createJoinRoomByAddressPresenter( + roomAliasHelper = FakeRoomAliasHelper( + isRoomAliasValidLambda = { + // The SDK return false + false + } + ), + matrixClient = FakeMatrixClient( + resolveRoomAliasResult = { + Result.success(Optional.empty()) + } + ) + ) + presenter.test { + with(awaitItem()) { + eventSink(JoinRoomByAddressEvent.UpdateAddress("#ö:invalid.org")) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Unknown) + eventSink(JoinRoomByAddressEvent.Continue) + } + // The address should not be marked as valid + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Resolving) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Invalid) + } + } + } + + @Test + fun `present - invalid address - failure to resolve the room`() = runTest { + val presenter = createJoinRoomByAddressPresenter( + roomAliasHelper = FakeRoomAliasHelper( + isRoomAliasValidLambda = { + // The SDK still return false, but we have a room for this alias + false + } + ), + matrixClient = FakeMatrixClient( + resolveRoomAliasResult = { Result.failure(RuntimeException()) } + ) + ) + presenter.test { + with(awaitItem()) { + eventSink(JoinRoomByAddressEvent.UpdateAddress("#ö:invalid.org")) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Unknown) + eventSink(JoinRoomByAddressEvent.Continue) + } + // The address should not be marked as valid + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Resolving) + } + with(awaitItem()) { + assertThat(address).isEqualTo("#ö:invalid.org") + assertThat(addressState).isEqualTo(RoomAddressState.Invalid) + } + } + } + @Test fun `present - room found`() = runTest { val openRoomLambda = lambdaRecorder, Unit> { _, _ -> } @@ -69,7 +171,7 @@ class JoinBaseRoomByAddressPresenterTest { val presenter = createJoinRoomByAddressPresenter(navigator = navigator) presenter.test { with(awaitItem()) { - eventSink(JoinRoomByAddressEvents.UpdateAddress("#room_found:matrix.org")) + eventSink(JoinRoomByAddressEvent.UpdateAddress("#room_found:matrix.org")) } with(awaitItem()) { assertThat(address).isEqualTo("#room_found:matrix.org") @@ -78,7 +180,7 @@ class JoinBaseRoomByAddressPresenterTest { with(awaitItem()) { assertThat(address).isEqualTo("#room_found:matrix.org") assertThat(addressState).isInstanceOf(RoomAddressState.RoomFound::class.java) - eventSink(JoinRoomByAddressEvents.Continue) + eventSink(JoinRoomByAddressEvent.Continue) } assert(openRoomLambda).isCalledOnce() assert(dismissJoinRoomByAddressLambda).isCalledOnce() @@ -94,12 +196,12 @@ class JoinBaseRoomByAddressPresenterTest { ) presenter.test { with(awaitItem()) { - eventSink(JoinRoomByAddressEvents.UpdateAddress("#room_not_found:matrix.org")) + eventSink(JoinRoomByAddressEvent.UpdateAddress("#room_not_found:matrix.org")) } with(awaitItem()) { assertThat(address).isEqualTo("#room_not_found:matrix.org") assertThat(addressState).isEqualTo(RoomAddressState.Unknown) - eventSink(JoinRoomByAddressEvents.Continue) + eventSink(JoinRoomByAddressEvent.Continue) } with(awaitItem()) { assertThat(address).isEqualTo("#room_not_found:matrix.org") @@ -121,7 +223,7 @@ class JoinBaseRoomByAddressPresenterTest { val presenter = createJoinRoomByAddressPresenter(navigator = navigator) presenter.test { with(awaitItem()) { - eventSink(JoinRoomByAddressEvents.Dismiss) + eventSink(JoinRoomByAddressEvent.Dismiss) } assert(dismissJoinRoomByAddressLambda).isCalledOnce() } diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt index eeea85cb63..92162ca82c 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt @@ -31,7 +31,7 @@ class JoinBaseRoomByAddressViewTest { @Test fun `entering text emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setJoinRoomByAddressView( aJoinRoomByAddressState( eventSink = eventsRecorder, @@ -39,19 +39,19 @@ class JoinBaseRoomByAddressViewTest { ) val text = rule.activity.getString(R.string.screen_start_chat_join_room_by_address_action) rule.onNodeWithText(text).performTextInput("#address:matrix.org") - eventsRecorder.assertSingle(JoinRoomByAddressEvents.UpdateAddress("#address:matrix.org")) + eventsRecorder.assertSingle(JoinRoomByAddressEvent.UpdateAddress("#address:matrix.org")) } @Test fun `clicking on continue emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setJoinRoomByAddressView( aJoinRoomByAddressState( eventSink = eventsRecorder, ) ) rule.clickOn(CommonStrings.action_continue) - eventsRecorder.assertSingle(JoinRoomByAddressEvents.Continue) + eventsRecorder.assertSingle(JoinRoomByAddressEvent.Continue) } } diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt index 7b34b8b52b..7c209d9052 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt @@ -9,9 +9,6 @@ package io.element.android.features.startchat.impl.root import androidx.compose.runtime.MutableState -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.invitepeople.test.FakeStartDMAction import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser @@ -33,6 +30,7 @@ import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -49,9 +47,7 @@ class StartChatPresenterTest { } val startDMAction = FakeStartDMAction(executeResult = executeResult) val presenter = createStartChatPresenter(startDMAction) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java) assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName) @@ -83,9 +79,7 @@ class StartChatPresenterTest { } val startDMAction = FakeStartDMAction(executeResult = executeResult) val presenter = createStartChatPresenter(startDMAction) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java) assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName) @@ -114,9 +108,7 @@ class StartChatPresenterTest { } val startDMAction = FakeStartDMAction(executeResult = executeResult) val presenter = createStartChatPresenter(startDMAction) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java) initialState.eventSink(StartChatEvents.StartDM(matrixUser)) @@ -144,9 +136,7 @@ class StartChatPresenterTest { } val startDMAction = FakeStartDMAction(executeResult = executeResult) val presenter = createStartChatPresenter(startDMAction) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java) initialState.eventSink(StartChatEvents.StartDM(matrixUser)) @@ -169,9 +159,7 @@ class StartChatPresenterTest { @Test fun `present - room directory search`() = runTest { val presenter = createStartChatPresenter(isRoomDirectorySearchEnabled = true) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) awaitItem().let { state -> assertThat(state.isRoomDirectorySearchEnabled).isTrue() diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt index c02b59f77a..4d958011a7 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt @@ -8,9 +8,7 @@ package io.element.android.features.startchat.impl.userlist -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import com.google.common.truth.Truth.assertThat import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -20,6 +18,7 @@ import io.element.android.libraries.usersearch.api.UserSearchResult import io.element.android.libraries.usersearch.api.UserSearchResultState import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -40,12 +39,10 @@ class DefaultUserListPresenterTest { UserListDataStore(), FakeMatrixClient(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.searchQuery).isEmpty() + assertThat(initialState.searchQuery.text.toString()).isEmpty() assertThat(initialState.isMultiSelectionEnabled).isFalse() assertThat(initialState.isSearchActive).isFalse() assertThat(initialState.selectedUsers).isEmpty() @@ -62,12 +59,10 @@ class DefaultUserListPresenterTest { UserListDataStore(), FakeMatrixClient(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.searchQuery).isEmpty() + assertThat(initialState.searchQuery.text.toString()).isEmpty() assertThat(initialState.isMultiSelectionEnabled).isTrue() assertThat(initialState.isSearchActive).isFalse() assertThat(initialState.selectedUsers).isEmpty() @@ -84,9 +79,7 @@ class DefaultUserListPresenterTest { UserListDataStore(), FakeMatrixClient(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() @@ -94,14 +87,14 @@ class DefaultUserListPresenterTest { assertThat(awaitItem().isSearchActive).isTrue() val matrixIdQuery = "@name:matrix.org" - initialState.eventSink(UserListEvents.UpdateSearchQuery(matrixIdQuery)) - assertThat(awaitItem().searchQuery).isEqualTo(matrixIdQuery) + initialState.searchQuery.setTextAndPlaceCursorAtEnd(matrixIdQuery) + assertThat(awaitItem().searchQuery.text.toString()).isEqualTo(matrixIdQuery) assertThat(userRepository.providedQuery).isEqualTo(matrixIdQuery) skipItems(1) val notMatrixIdQuery = "name" - initialState.eventSink(UserListEvents.UpdateSearchQuery(notMatrixIdQuery)) - assertThat(awaitItem().searchQuery).isEqualTo(notMatrixIdQuery) + initialState.searchQuery.setTextAndPlaceCursorAtEnd(notMatrixIdQuery) + assertThat(awaitItem().searchQuery.text.toString()).isEqualTo(notMatrixIdQuery) assertThat(userRepository.providedQuery).isEqualTo(notMatrixIdQuery) skipItems(1) @@ -121,13 +114,11 @@ class DefaultUserListPresenterTest { UserListDataStore(), FakeMatrixClient(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink(UserListEvents.UpdateSearchQuery("alice")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("alice") assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(userRepository.providedQuery).isEqualTo("alice") skipItems(2) @@ -174,13 +165,11 @@ class DefaultUserListPresenterTest { UserListDataStore(), FakeMatrixClient(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink(UserListEvents.UpdateSearchQuery("alice")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("alice") assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(userRepository.providedQuery).isEqualTo("alice") skipItems(2) @@ -200,9 +189,7 @@ class DefaultUserListPresenterTest { UserListDataStore(), FakeMatrixClient(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 216d9ecd75..24efae8251 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ constraintlayout = "2.2.1" constraintlayout_compose = "1.1.1" lifecycle = "2.10.0" activity = "1.12.2" -media3 = "1.9.0" +media3 = "1.9.1" camera = "1.5.2" work = "2.11.0" @@ -32,11 +32,11 @@ accompanist = "0.37.3" # Test test_core = "1.7.0" -roborazzi = "1.56.0" +roborazzi = "1.57.0" # Jetbrain datetime = "0.7.1" -serialization_json = "1.9.0" +serialization_json = "1.10.0" #other coil = "3.3.0" @@ -44,7 +44,7 @@ coil = "3.3.0" showkase = "1.0.5" appyx = "1.7.1" sqldelight = "2.2.1" -wysiwyg = "2.41.0" +wysiwyg = "2.41.1" telephoto = "0.18.0" haze = "1.7.1" @@ -52,7 +52,7 @@ haze = "1.7.1" dependencyAnalysis = "3.5.1" # DI -metro = "0.9.4" +metro = "0.10.1" # Auto service autoservice = "1.1.1" @@ -160,12 +160,12 @@ test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" } test_arch_core = "androidx.arch.core:core-testing:2.2.0" test_junit = "junit:junit:4.13.2" test_runner = "androidx.test:runner:1.7.0" -test_mockk = "io.mockk:mockk:1.14.7" +test_mockk = "io.mockk:mockk:1.14.9" test_konsist = "com.lemonappdev:konsist:0.17.3" test_turbine = "app.cash.turbine:turbine:1.2.1" test_truth = "com.google.truth:truth:1.4.5" -test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.20" -test_robolectric = "org.robolectric:robolectric:4.16" +test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.21" +test_robolectric = "org.robolectric:robolectric:4.16.1" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.8.1" test_detekt_api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" } @@ -177,7 +177,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version # https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt # All new features should not be implemented in the pull request that upgrades the version, developers should # only fix API breaks and may add some TODOs. -matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.1.12" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.1.27" # Others coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } @@ -199,7 +199,7 @@ matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } -sqlcipher = "net.zetetic:sqlcipher-android:4.12.0" +sqlcipher = "net.zetetic:sqlcipher-android:4.13.0" sqlite = "androidx.sqlite:sqlite-ktx:2.6.2" unifiedpush = "org.unifiedpush.android:connector:3.2.0" vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" @@ -218,8 +218,8 @@ haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = color_picker = "io.mhssn:colorpicker:1.0.0" # Analytics -posthog = "com.posthog:posthog-android:3.28.1" -sentry = "io.sentry:sentry-android:8.30.0" +posthog = "com.posthog:posthog-android:3.29.1" +sentry = "io.sentry:sentry-android:8.31.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.29.2" @@ -259,13 +259,13 @@ metro = { id = "dev.zacsweers.metro", version.ref = "metro" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } ktlint = "org.jlleitschuh.gradle.ktlint:14.0.1" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" -dependencycheck = "org.owasp.dependencycheck:12.1.9" +dependencycheck = "org.owasp.dependencycheck:12.2.0" dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:2.0.0-alpha02" roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } -knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" } +knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.1" } sonarqube = "org.sonarqube:7.2.2.6593" licensee = "app.cash.licensee:1.14.1" compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 4124d7a967..8407445394 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -74,6 +74,7 @@ enum class AvatarSize(val dp: Dp) { RoomPreviewInviter(56.dp), SpaceMember(24.dp), LeaveSpaceRoom(32.dp), + SelectParentSpace(32.dp), AccountItem(32.dp), } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/AvatarListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/AvatarListItem.kt new file mode 100644 index 0000000000..c6a85e9b85 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/AvatarListItem.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.components.list + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Text + +/** + * A list item with an Avatar as leading content. + * + * Figma link : https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=1979-1894&m=dev + * + * @param avatarData The data for the avatar. + * @param avatarType The type of avatar to display. + * @param headline The main text of the list item. + * @param modifier The modifier to apply to the list item. + * @param supportingText The supporting text displayed below the headline. + * @param trailingContent The trailing content of the list item. + * @param enabled Whether the list item is enabled. + * @param style The style of the list item. + * @param onClick The callback to invoke when the list item is clicked. + */ +@Composable +fun AvatarListItem( + avatarData: AvatarData, + avatarType: AvatarType, + headline: String, + modifier: Modifier = Modifier, + supportingText: String? = null, + trailingContent: ListItemContent? = null, + enabled: Boolean = true, + style: ListItemStyle = ListItemStyle.Default, + onClick: (() -> Unit)? = null, +) { + ListItem( + modifier = modifier, + headlineContent = { Text(headline) }, + supportingContent = supportingText?.let { @Composable { Text(it) } }, + leadingContent = ListItemContent.Custom { _ -> + Avatar( + avatarData = avatarData, + avatarType = avatarType, + ) + }, + trailingContent = trailingContent, + style = style, + enabled = enabled, + onClick = onClick, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt index c1fd7bc6f0..2e19fecced 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt @@ -17,6 +17,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.clearText +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarColors @@ -25,9 +28,7 @@ import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape @@ -51,8 +52,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchBar( - query: String, - onQueryChange: (String) -> Unit, + queryState: TextFieldState, active: Boolean, onActiveChange: (Boolean) -> Unit, placeHolderTitle: String, @@ -72,10 +72,9 @@ fun SearchBar( ) { val focusManager = LocalFocusManager.current val colors = if (active) activeBarColors else inactiveBarColors - val updatedOnQueryChange by rememberUpdatedState(onQueryChange) LaunchedEffect(active) { if (!active) { - updatedOnQueryChange("") + queryState.clearText() focusManager.clearFocus() } } @@ -83,8 +82,7 @@ fun SearchBar( SearchBar( inputField = { SearchBarDefaults.InputField( - query = query, - onQueryChange = updatedOnQueryChange, + state = queryState, onSearch = { focusManager.clearFocus() }, expanded = active, onExpandedChange = onActiveChange, @@ -98,9 +96,9 @@ fun SearchBar( null }, trailingIcon = when { - active && query.isNotEmpty() -> { + active && queryState.text.isNotEmpty() -> { { - IconButton(onClick = { onQueryChange("") }) { + IconButton(onClick = { queryState.clearText() }) { Icon( imageVector = CompoundIcons.Close(), contentDescription = stringResource(CommonStrings.action_clear), @@ -221,7 +219,7 @@ internal fun SearchBarInactivePreview() = ElementThemedPreview { ContentToPrevie @Composable internal fun SearchBarActiveNoneQueryPreview() = ElementThemedPreview { ContentToPreview( - query = "", + initialQuery = "", active = true, ) } @@ -230,7 +228,7 @@ internal fun SearchBarActiveNoneQueryPreview() = ElementThemedPreview { @Composable internal fun SearchBarActiveWithQueryPreview() = ElementThemedPreview { ContentToPreview( - query = "search term", + initialQuery = "search term", active = true, ) } @@ -239,7 +237,7 @@ internal fun SearchBarActiveWithQueryPreview() = ElementThemedPreview { @Composable internal fun SearchBarActiveWithQueryNoBackButtonPreview() = ElementThemedPreview { ContentToPreview( - query = "search term", + initialQuery = "search term", active = true, showBackButton = false, ) @@ -249,7 +247,7 @@ internal fun SearchBarActiveWithQueryNoBackButtonPreview() = ElementThemedPrevie @Composable internal fun SearchBarActiveWithNoResultsPreview() = ElementThemedPreview { ContentToPreview( - query = "search term", + initialQuery = "search term", active = true, resultState = SearchBarResultState.NoResultsFound(), ) @@ -259,7 +257,7 @@ internal fun SearchBarActiveWithNoResultsPreview() = ElementThemedPreview { @Composable internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview { ContentToPreview( - query = "search term", + initialQuery = "search term", active = true, resultState = SearchBarResultState.Results("result!"), contentPrefix = { @@ -292,7 +290,7 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview { @Composable @ExcludeFromCoverage private fun ContentToPreview( - query: String = "", + initialQuery: String = "", active: Boolean = false, showBackButton: Boolean = true, resultState: SearchBarResultState = SearchBarResultState.Initial(), @@ -300,13 +298,13 @@ private fun ContentToPreview( contentSuffix: @Composable ColumnScope.() -> Unit = {}, resultHandler: @Composable ColumnScope.(String) -> Unit = {}, ) { + val queryState = rememberTextFieldState(initialText = initialQuery) SearchBar( modifier = Modifier.heightIn(max = 200.dp), - query = query, + queryState = queryState, active = active, resultState = resultState, showBackButton = showBackButton, - onQueryChange = {}, onActiveChange = {}, placeHolderTitle = "Search for things", contentPrefix = contentPrefix, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt index fc84abe827..d677a89577 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt @@ -22,8 +22,10 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.clearText import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -34,7 +36,6 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme @@ -50,8 +51,7 @@ import io.element.android.libraries.ui.strings.CommonStrings */ @Composable fun SearchField( - value: String, - onValueChange: (String) -> Unit, + state: TextFieldState, modifier: Modifier = Modifier, placeholder: String? = null, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, @@ -59,67 +59,28 @@ fun SearchField( val focusManager = LocalFocusManager.current val isFocused by interactionSource.collectIsFocusedAsState() BasicTextField( - value = value, - onValueChange = onValueChange, + state = state, modifier = modifier, textStyle = textFieldStyle(), - singleLine = true, + lineLimits = TextFieldLineLimits.SingleLine, interactionSource = interactionSource, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Search, ), - keyboardActions = KeyboardActions( - onSearch = { - focusManager.clearFocus() - } - ), + onKeyboardAction = { + focusManager.clearFocus() + }, cursorBrush = SolidColor(ElementTheme.colors.textActionAccent), - ) { innerTextField -> - DecorationBox( - isFocused = isFocused, - placeholder = placeholder, - isTextEmpty = value.isEmpty(), - innerTextField = innerTextField, - onClear = { onValueChange("") }, - ) - } -} - -@Composable -fun SearchField( - value: TextFieldValue, - onValueChange: (TextFieldValue) -> Unit, - modifier: Modifier = Modifier, - placeholder: String? = null, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, -) { - val focusManager = LocalFocusManager.current - val isFocused by interactionSource.collectIsFocusedAsState() - BasicTextField( - value = value, - onValueChange = onValueChange, - modifier = modifier, - textStyle = textFieldStyle(), - singleLine = true, - interactionSource = interactionSource, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Search, - ), - keyboardActions = KeyboardActions( - onSearch = { - focusManager.clearFocus() - } - ), - cursorBrush = SolidColor(ElementTheme.colors.textActionAccent), - ) { innerTextField -> - DecorationBox( - isFocused = isFocused, - placeholder = placeholder, - isTextEmpty = value.text.isEmpty(), - innerTextField = innerTextField, - onClear = { TextFieldValue() } - ) - } + decorator = { innerTextField -> + DecorationBox( + isFocused = isFocused, + placeholder = placeholder, + isTextEmpty = state.text.isEmpty(), + innerTextField = innerTextField, + onClear = { state.clearText() }, + ) + } + ) } @Composable @@ -211,14 +172,12 @@ private fun ContentToPreview() { verticalArrangement = spacedBy(8.dp) ) { SearchField( - onValueChange = {}, placeholder = "Search", - value = "", + state = TextFieldState(""), ) SearchField( - onValueChange = {}, placeholder = "Search", - value = "Search term", + state = TextFieldState("Search term"), ) } } diff --git a/libraries/eventformatter/impl/src/main/res/values-bg/translations.xml b/libraries/eventformatter/impl/src/main/res/values-bg/translations.xml index 9bfcdfe6a6..6166741007 100644 --- a/libraries/eventformatter/impl/src/main/res/values-bg/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-bg/translations.xml @@ -57,5 +57,6 @@ "Вие променихте темата на: %1$s" "%1$s премахна темата на стаята" "Вие премахнахте темата на стаята" + "%1$s премахна забраната на %2$s" "%1$s направи неизвестна промяна в членството си" diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index aff3092549..02e40813b0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.timeline.item.event.MessageType data class NotificationData( @@ -36,6 +37,7 @@ data class NotificationData( val timestamp: Long, val content: NotificationContent, val hasMention: Boolean, + val roomJoinRule: JoinRule?, ) { fun getDisambiguatedDisplayName(userId: UserId): String = when { senderDisplayName.isNullOrBlank() -> userId.value diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt index 481171ace0..589f88e9fd 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt @@ -85,6 +85,13 @@ interface BaseRoom : Closeable { */ suspend fun getUpdatedMember(userId: UserId): Result + /** + * Gets the direct room member, if any. + * This is a convenience method for getting the other member in a direct message room. + * Returns null if the room is not a dm or if the member cannot be found. + */ + suspend fun getDirectRoomMember(): RoomMember? + /** * Adds the room to the sync subscription list. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt index 80cefc41e1..a164c9199d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt @@ -17,10 +17,7 @@ import kotlin.coroutines.CoroutineContext * It does filter through the already known members, it doesn't perform additional requests. */ suspend fun BaseRoom.filterMembers(query: String, coroutineContext: CoroutineContext): List = withContext(coroutineContext) { - val roomMembersState = membersStateFlow.value - val activeRoomMembers = roomMembersState.roomMembers() - ?.filter { it.membership.isActive() } - .orEmpty() + val activeRoomMembers = membersStateFlow.value.activeRoomMembers() val filteredMembers = if (query.isBlank()) { activeRoomMembers } else { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/SendQueueUpdate.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/SendQueueUpdate.kt index 41e1e5643b..7c5b3fd9fd 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/SendQueueUpdate.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/SendQueueUpdate.kt @@ -18,5 +18,5 @@ sealed interface SendQueueUpdate { data class SendError(val transactionId: TransactionId) : SendQueueUpdate data class RetrySendingEvent(val transactionId: TransactionId) : SendQueueUpdate data class SentEvent(val transactionId: TransactionId, val eventId: EventId) : SendQueueUpdate - data class MediaUpload(val relatedTo: EventId, val file: MediaSource?, val index: Long, val progress: Float) : SendQueueUpdate + data class MediaUpload(val relatedTo: TransactionId, val file: MediaSource?, val index: Long, val progress: Float) : SendQueueUpdate } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt index 8221674439..6db326c854 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt @@ -11,48 +11,35 @@ package io.element.android.libraries.matrix.api.room.recent import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser -import kotlinx.coroutines.flow.first - -private const val MAX_RECENT_DIRECT_ROOMS_TO_RETURN = 5 +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow data class RecentDirectRoom( val roomId: RoomId, val matrixUser: MatrixUser, ) -suspend fun MatrixClient.getRecentDirectRooms( - maxNumberOfResults: Int = MAX_RECENT_DIRECT_ROOMS_TO_RETURN, -): List { - val result = mutableListOf() +/** + * Returns a [Flow] of [RecentDirectRoom] from recently visited DM rooms. + * The flow emits items lazily, allowing callers to filter and take only what they need. + * Use [kotlinx.coroutines.flow.take] to limit results and stop iteration early. + */ +fun MatrixClient.getRecentDirectRooms(): Flow = flow { val foundUserIds = mutableSetOf() - getRecentlyVisitedRooms().getOrNull()?.let { roomIds -> - roomIds - .mapNotNull { roomId -> getRoom(roomId) } - .filter { it.isDm() && it.isJoined() } - .map { room -> - val otherUser = room.getMembers().getOrNull() - ?.firstOrNull { it.userId != sessionId } - ?.takeIf { foundUserIds.add(it.userId) } - ?.toMatrixUser() - if (otherUser != null) { - result.add( - RecentDirectRoom(room.roomId, otherUser) - ) - // Return early to avoid useless computation - if (result.size >= maxNumberOfResults) { - return@map - } + val recentlyVisitedRooms = getRecentlyVisitedRooms().getOrDefault(emptyList()) + for (roomId in recentlyVisitedRooms) { + getRoom(roomId)?.use { room -> + val info = room.info() + if (info.isDm && info.currentUserMembership == CurrentUserMembership.JOINED) { + val otherUser = room.getDirectRoomMember()?.toMatrixUser() + if (otherUser != null && foundUserIds.add(otherUser.userId)) { + emit(RecentDirectRoom(room.roomId, otherUser)) } } + } } - return result -} - -suspend fun BaseRoom.isJoined(): Boolean { - return roomInfoFlow.first().currentUserMembership == CurrentUserMembership.JOINED } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentRoom.kt new file mode 100644 index 0000000000..a03952194c --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentRoom.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.room.recent + +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.RoomInfo +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +/** + * Returns a [Flow] of [RoomInfo] from recently visited rooms. + * The flow emits items lazily, allowing callers to filter and take only what they need. + * Use [kotlinx.coroutines.flow.take] to limit results and stop iteration early. + * + */ +fun MatrixClient.getRecentlyVisitedRoomInfoFlow( + predicate: (RoomInfo) -> Boolean, +): Flow = flow { + val recentlyVisitedRooms = getRecentlyVisitedRooms().getOrDefault(emptyList()) + for (roomId in recentlyVisitedRooms) { + getRoom(roomId)?.use { room -> + val info = room.info() + if (predicate(info)) { + emit(info) + } + } + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index 4a531792df..c0cf7d57da 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.api.roomlist import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterIsInstance @@ -39,11 +40,13 @@ interface RoomListService { * @param pageSize the number of rooms to load at once. * @param initialFilter the initial filter to apply to the rooms. * @param source the source of the rooms, either all rooms or invites. + * @param coroutineScope the coroutine scope to use for the room list operations. */ fun createRoomList( pageSize: Int, initialFilter: RoomListFilter, source: RoomList.Source, + coroutineScope: CoroutineScope, ): DynamicRoomList /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt index 071d5693b8..2b1389ea6e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt @@ -10,5 +10,5 @@ package io.element.android.libraries.matrix.api.spaces data class LeaveSpaceRoom( val spaceRoom: SpaceRoom, - val isLastAdmin: Boolean, + val isLastOwner: Boolean, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt index d21c3764f3..6a72577760 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.api.spaces +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.CurrentUserMembership @@ -16,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList +@Immutable data class SpaceRoom( val rawName: String?, val displayName: String, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt index 927bec13e1..1122415d58 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt @@ -21,8 +21,21 @@ interface SpaceService { fun spaceRoomList(id: RoomId): SpaceRoomList + /** + * Get the list of spaces in which the current user can modify their rooms (adding or removing them). + */ + suspend fun editableSpaces(): Result> + fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle + /** + * Add a child room to a space. + * @param spaceId The space ID to which the child will be added. + * @param childId The room ID of the child to add. + * @return A result indicating success or failure. + */ + suspend fun addChildToSpace(spaceId: RoomId, childId: RoomId): Result + /** * Remove a child room from a space. * @param spaceId The space ID from which to remove the child. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 401240f927..f3982e83d2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -34,6 +34,13 @@ data class EventTimelineItem( val timelineItemDebugInfoProvider: TimelineItemDebugInfoProvider, val messageShieldProvider: MessageShieldProvider, val sendHandleProvider: SendHandleProvider, + /** + * If the keys to this message were forwarded by another user via history sharing (MSC4268), the ID of that user. + * If this is set, then [messageShieldProvider] will also return [MessageShield.AuthenticityNotGuaranteed]. + */ + val forwarder: UserId?, + /** If [forwarder] is set, the profile of the forwarding user, if it was cached at the time this `EventTimelineItem` was created. */ + val forwarderProfile: ProfileDetails?, ) { fun inReplyTo(): InReplyTo? { return (content as? MessageContent)?.inReplyTo diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index b24f4b90d8..065b0a506f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -192,7 +192,6 @@ class RustMatrixClient( sessionDispatcher = sessionDispatcher, roomListFactory = RoomListFactory( innerRoomListService = innerRoomListService, - sessionCoroutineScope = sessionCoroutineScope, analyticsService = analyticsService, ), roomSyncSubscriber = roomSyncSubscriber, @@ -370,6 +369,9 @@ class RustMatrixClient( override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result = withContext(sessionDispatcher) { runCatchingExceptions { + val hasPublicAccess = createRoomParams.preset == RoomPreset.PUBLIC_CHAT || createRoomParams.joinRuleOverride == JoinRule.Public + val powerLevels = defaultRoomCreationPowerLevels(isSpace = createRoomParams.isSpace, isPublic = hasPublicAccess) + val rustParams = RustCreateRoomParameters( name = createRoomParams.name, topic = createRoomParams.topic, @@ -383,12 +385,12 @@ class RustMatrixClient( }, invite = createRoomParams.invite?.map { it.value }, avatar = createRoomParams.avatar, - powerLevelContentOverride = defaultRoomCreationPowerLevels.copy( + powerLevelContentOverride = powerLevels.copy( invite = if (createRoomParams.joinRuleOverride == JoinRule.Knock) { // override the invite power level so it's the same as kick. RoomMember.Role.Moderator.powerLevel.toInt() } else { - null + powerLevels.invite } ), joinRuleOverride = createRoomParams.joinRuleOverride?.map(), @@ -833,18 +835,23 @@ class RustMatrixClient( } } -private val defaultRoomCreationPowerLevels = PowerLevels( +private fun defaultRoomCreationPowerLevels(isPublic: Boolean, isSpace: Boolean) = PowerLevels( usersDefault = null, - eventsDefault = null, + // Only admins should be able to send events in general + eventsDefault = if (isSpace) 100 else null, stateDefault = null, ban = null, kick = null, redact = null, - invite = null, + invite = if (isPublic) 0 else 50, notifications = null, users = mapOf(), - events = mapOf( - "m.call.member" to 0, - "org.matrix.msc3401.call.member" to 0, - ) + events = if (!isSpace) { + mapOf( + "m.call.member" to 0, + "org.matrix.msc3401.call.member" to 0, + ) + } else { + mapOf() + } ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt index 511c899fdb..f1996dd942 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt @@ -18,6 +18,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.impl.room.join.map import io.element.android.services.toolbox.api.systemclock.SystemClock import org.matrix.rustcomponents.sdk.NotificationEvent import org.matrix.rustcomponents.sdk.NotificationItem @@ -59,6 +60,7 @@ class NotificationMapper( timestamp = timestamp, content = notificationContentMapper.map(item.event).getOrThrow(), hasMention = item.hasMention.orFalse(), + roomJoinRule = item.roomInfo.joinRule?.map(), ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt index 8c4847a73f..e73bed084e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.draft.ComposerDraft +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom @@ -112,6 +113,20 @@ class RustBaseRoom( } } + override suspend fun getDirectRoomMember(): RoomMember? = withContext(roomDispatcher) { + runCatchingExceptions { + if (info().isDm) { + innerRoom.membersNoSync().use { members -> + members.nextChunk(members.len()) + ?.map(RoomMemberMapper::map) + ?.firstOrNull { roomMember -> roomMember.userId != sessionId && roomMember.membership.isActive() } + } + } else { + null + } + }.getOrNull() + } + override suspend fun getUpdatedMember(userId: UserId): Result = withContext(roomDispatcher) { runCatchingExceptions { RoomMemberMapper.map(innerRoom.member(userId.value)) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/SendQueueUpdatesExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/SendQueueUpdatesExt.kt index efc723e1df..667cbdb2bf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/SendQueueUpdatesExt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/SendQueueUpdatesExt.kt @@ -17,7 +17,7 @@ fun RoomSendQueueUpdate.map(): SendQueueUpdate = when (this) { is RoomSendQueueUpdate.NewLocalEvent -> SendQueueUpdate.NewLocalEvent(TransactionId(transactionId)) is RoomSendQueueUpdate.CancelledLocalEvent -> SendQueueUpdate.CancelledLocalEvent(TransactionId(transactionId)) is RoomSendQueueUpdate.MediaUpload -> SendQueueUpdate.MediaUpload( - relatedTo = EventId(relatedTo), + relatedTo = TransactionId(relatedTo), file = file?.map(), index = index.toLong(), progress = progress.current.toFloat() / progress.total.toFloat(), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt index fa3f4a8acf..19bd3b7573 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt @@ -38,7 +38,6 @@ private val ROOM_LIST_RUST_FILTERS = listOf( internal class RoomListFactory( private val innerRoomListService: RoomListService, - private val sessionCoroutineScope: CoroutineScope, private val analyticsService: AnalyticsService, ) { private val roomSummaryFactory: RoomSummaryFactory = RoomSummaryFactory() @@ -49,7 +48,7 @@ internal class RoomListFactory( fun createRoomList( pageSize: Int, coroutineContext: CoroutineContext, - coroutineScope: CoroutineScope = sessionCoroutineScope, + coroutineScope: CoroutineScope, initialFilter: RoomListFilter = RoomListFilter.all(), innerProvider: suspend () -> InnerRoomList ): DynamicRoomList { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt index 9c462ddaf5..69bddf328e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -35,17 +35,19 @@ internal class RustRoomListService( private val sessionDispatcher: CoroutineDispatcher, private val roomListFactory: RoomListFactory, private val roomSyncSubscriber: RoomSyncSubscriber, - sessionCoroutineScope: CoroutineScope, + private val sessionCoroutineScope: CoroutineScope, ) : RoomListService { override fun createRoomList( pageSize: Int, initialFilter: RoomListFilter, - source: RoomList.Source + source: RoomList.Source, + coroutineScope: CoroutineScope, ): DynamicRoomList { return roomListFactory.createRoomList( pageSize = pageSize, initialFilter = initialFilter, coroutineContext = sessionDispatcher, + coroutineScope = coroutineScope, ) { when (source) { RoomList.Source.All -> innerRoomListService.allRooms() @@ -60,6 +62,7 @@ internal class RustRoomListService( override val allRooms: DynamicRoomList = roomListFactory.createRoomList( pageSize = DEFAULT_PAGE_SIZE, coroutineContext = sessionDispatcher, + coroutineScope = sessionCoroutineScope, ) { innerRoomListService.allRooms() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt index 10e329e46f..16b5186c92 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt @@ -40,7 +40,7 @@ class RustLeaveSpaceHandle( inner.await().rooms().map { leaveSpaceRoom -> LeaveSpaceRoom( spaceRoom = spaceRoomMapper.map(leaveSpaceRoom.spaceRoom), - isLastAdmin = leaveSpaceRoom.isLastAdmin, + isLastOwner = leaveSpaceRoom.isLastOwner, ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt index 2ce184484c..4d9bc2fe61 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt @@ -87,6 +87,12 @@ class RustSpaceService( ) } + override suspend fun editableSpaces(): Result> = withContext(sessionDispatcher) { + runCatchingExceptions { + innerSpaceService.editableSpaces().map(spaceRoomMapper::map) + } + } + override fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle { return RustLeaveSpaceHandle( id = spaceId, @@ -98,6 +104,12 @@ class RustSpaceService( } } + override suspend fun addChildToSpace(spaceId: RoomId, childId: RoomId): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + innerSpaceService.addChildToSpace(childId = childId.value, spaceId = spaceId.value) + } + } + override suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result = withContext(sessionDispatcher) { runCatchingExceptions { innerSpaceService.removeChildFromSpace(childId = childId.value, spaceId = spaceId.value) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index a95b2acccc..297b0b31a4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -59,7 +59,9 @@ class EventTimelineItemMapper( origin = origin?.map(), timelineItemDebugInfoProvider = { lazyProvider.debugInfo().map() }, messageShieldProvider = { strict -> lazyProvider.getShields(strict).map() }, - sendHandleProvider = { lazyProvider.getSendHandle()?.let(::RustSendHandle) } + sendHandleProvider = { lazyProvider.getSendHandle()?.let(::RustSendHandle) }, + forwarder = forwarder?.let { UserId(it) }, + forwarderProfile = forwarderProfile?.map(), ) } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt index ba45b310ef..171c285697 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt @@ -20,12 +20,12 @@ class RoomListFactoryTest { fun `createRoomList should work`() = runTest { val sut = RoomListFactory( innerRoomListService = FakeFfiRoomListService(), - sessionCoroutineScope = backgroundScope, analyticsService = FakeAnalyticsService(), ) sut.createRoomList( pageSize = 10, coroutineContext = EmptyCoroutineContext, + coroutineScope = backgroundScope, ) { FakeFfiRoomList() } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt index 88b57035e8..27e7f15f8b 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt @@ -50,7 +50,6 @@ private fun TestScope.createRustRoomListService( sessionDispatcher = StandardTestDispatcher(testScheduler), roomListFactory = RoomListFactory( innerRoomListService = roomListService, - sessionCoroutineScope = backgroundScope, analyticsService = FakeAnalyticsService(), ), roomSyncSubscriber = RoomSyncSubscriber( diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt index b6b6cb66e9..803c9d8f42 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt @@ -11,6 +11,7 @@ package io.element.android.libraries.matrix.test.notification import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME @@ -27,7 +28,8 @@ fun aNotificationData( timestamp: Long = A_TIMESTAMP, senderDisplayName: String? = A_USER_NAME_2, senderIsNameAmbiguous: Boolean = false, - roomDisplayName: String? = A_ROOM_NAME + roomDisplayName: String? = A_ROOM_NAME, + roomJoinRule: JoinRule? = null, ): NotificationData { return NotificationData( sessionId = A_SESSION_ID, @@ -47,5 +49,6 @@ fun aNotificationData( timestamp = timestamp, content = content, hasMention = hasMention, + roomJoinRule = roomJoinRule, ) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt index 78765d1ec4..4ceddc414c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt @@ -55,6 +55,7 @@ class FakeBaseRoom( private val leaveRoomLambda: () -> Result = { lambdaError() }, private var updateMembersResult: () -> Unit = { lambdaError() }, private val getMembersResult: (Int) -> Result> = { lambdaError() }, + private val getDirectRoomMemberResult: () -> RoomMember? = { null }, private val saveComposerDraftLambda: (ComposerDraft) -> Result = { _: ComposerDraft -> Result.success(Unit) }, private val loadComposerDraftLambda: () -> Result = { Result.success(null) }, private val clearComposerDraftLambda: () -> Result = { Result.success(Unit) }, @@ -90,6 +91,10 @@ class FakeBaseRoom( return getMembersResult(limit) } + override suspend fun getDirectRoomMember(): RoomMember? { + return getDirectRoomMemberResult() + } + override suspend fun subscribeToSync() { subscribeToSyncLambda() } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index b184438c4e..96a72d1278 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -14,6 +14,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -44,7 +45,8 @@ class FakeRoomListService( override fun createRoomList( pageSize: Int, initialFilter: RoomListFilter, - source: RoomList.Source + source: RoomList.Source, + coroutineScope: CoroutineScope, ): DynamicRoomList { return when (source) { RoomList.Source.All -> allRooms diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt index 0cd9907db3..b7a40fdeef 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt @@ -26,6 +26,8 @@ class FakeSpaceService( private val removeChildFromSpaceResult: (RoomId, RoomId) -> Result = { _, _ -> lambdaError() }, private val joinedParentsResult: (RoomId) -> Result> = { lambdaError() }, private val getSpaceRoomResult: (RoomId) -> SpaceRoom? = { lambdaError() }, + private val editableSpacesResult: () -> Result> = { lambdaError() }, + private val addChildToSpaceResult: (RoomId, RoomId) -> Result = { _, _ -> lambdaError() }, ) : SpaceService { private val _spaceRoomsFlow = MutableSharedFlow>() override val spaceRoomsFlow: SharedFlow> @@ -51,10 +53,18 @@ class FakeSpaceService( return spaceRoomListResult(id) } + override suspend fun editableSpaces(): Result> { + return editableSpacesResult() + } + override fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle { return leaveSpaceHandleResult(spaceId) } + override suspend fun addChildToSpace(spaceId: RoomId, childId: RoomId): Result = simulateLongTask { + addChildToSpaceResult(spaceId, childId) + } + override suspend fun removeChildFromSpace(spaceId: RoomId, childId: RoomId): Result = simulateLongTask { removeChildFromSpaceResult(spaceId, childId) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt index 24c26a6734..2825e7c5be 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt @@ -77,6 +77,8 @@ fun anEventTimelineItem( timelineItemDebugInfoProvider = debugInfoProvider, messageShieldProvider = messageShieldProvider, sendHandleProvider = sendHandleProvider, + forwarder = null, + forwarderProfile = null, ) fun aProfileDetails( diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarPickerView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarPickerView.kt index 1c904e891b..68a346ab84 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarPickerView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarPickerView.kt @@ -78,18 +78,19 @@ fun AvatarPickerView( enabled: Boolean = true, ) { val a11yAvatar = stringResource(CommonStrings.a11y_avatar) - - val clickableModifier = Modifier.clickable( - enabled = enabled, - interactionSource = remember { MutableInteractionSource() }, - onClickLabel = onClickLabel, - onClick = onClick, - indication = ripple(bounded = false), - ) - .testTag(TestTags.editAvatar) - .clearAndSetSemantics { - contentDescription = a11yAvatar - } + val interactionSource = remember { MutableInteractionSource() } + val clickableModifier = Modifier + .clickable( + enabled = enabled, + interactionSource = interactionSource, + onClickLabel = onClickLabel, + onClick = onClick, + indication = ripple(bounded = false), + ) + .testTag(TestTags.editAvatar) + .clearAndSetSemantics { + contentDescription = a11yAvatar + } val layoutDirection = LocalLayoutDirection.current @@ -123,18 +124,30 @@ fun AvatarPickerView( buttonSize = state.buttonSize, iconSize = state.iconSize, iconId = state.iconId, - modifier = modifier.padding(state.externalPadding).then(clickableModifier), + modifier = modifier + .padding(state.externalPadding) + .then(clickableModifier), ) } is AvatarPickerState.Selected -> { Box(modifier = modifier) { + val backgroundModifier = if (enabled) { + eraseBackgroundModifier(state.avatarData.size.dp, state.avatarData.size.dp * 0.225f) + } else { + Modifier + } Avatar( avatarData = state.avatarData, avatarType = state.type, - modifier = clickableModifier.then(eraseBackgroundModifier(state.avatarData.size.dp, state.avatarData.size.dp * 0.225f)), + modifier = clickableModifier.then(backgroundModifier), ) - - OverlayEditButton(editButtonSize = state.avatarData.size.dp * 0.44f) + if (enabled) { + OverlayEditButton( + editButtonSize = state.avatarData.size.dp * 0.44f, + onClick = onClick, + interactionSource = interactionSource + ) + } } } } @@ -165,12 +178,18 @@ private fun PickButton( } @Composable -private fun BoxScope.OverlayEditButton(editButtonSize: Dp) { +private fun BoxScope.OverlayEditButton( + editButtonSize: Dp, + onClick: () -> Unit, + interactionSource: MutableInteractionSource +) { Box( - modifier = Modifier.align(Alignment.BottomEnd) + modifier = Modifier + .align(Alignment.BottomEnd) .size(editButtonSize) .offset(x = editButtonSize * 0.266f) .clip(CircleShape) + .clickable(interactionSource = interactionSource, onClick = onClick, indication = null) .background(ElementTheme.colors.bgCanvasDefault) .border(BorderStroke(1.dp, ElementTheme.colors.borderInteractiveSecondary), shape = CircleShape), contentAlignment = Alignment.Center, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt index 49c8415fc7..d6981f62fd 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt @@ -12,6 +12,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList @@ -32,11 +33,13 @@ data class SelectRoomInfo( ) } -fun RoomSummary.toSelectRoomInfo() = SelectRoomInfo( - roomId = roomId, - name = info.name, - avatarUrl = info.avatarUrl, - heroes = info.heroes, - canonicalAlias = info.canonicalAlias, - isTombstoned = info.successorRoom != null, +fun RoomSummary.toSelectRoomInfo() = info.toSelectRoomInfo() + +fun RoomInfo.toSelectRoomInfo() = SelectRoomInfo( + roomId = id, + name = name, + avatarUrl = avatarUrl, + heroes = heroes, + canonicalAlias = canonicalAlias, + isTombstoned = successorRoom != null, ) diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt index eff20fabec..6f29415968 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt @@ -12,7 +12,6 @@ import android.Manifest import android.os.Build import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.permissions.api.PermissionStateProvider import io.element.android.libraries.permissions.impl.R import io.element.android.libraries.permissions.impl.action.PermissionActions @@ -26,7 +25,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @ContributesIntoSet(AppScope::class) -@Inject class NotificationTroubleshootCheckPermissionTest( private val permissionStateProvider: PermissionStateProvider, private val sdkVersionProvider: BuildVersionSdkIntProvider, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index e3d0f21d03..c0eb07b091 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -31,11 +31,12 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.exception.NotificationResolverException -import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.media.getMediaPreviewValue +import io.element.android.libraries.matrix.api.media.isPreviewEnabled import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EventType @@ -140,10 +141,24 @@ class DefaultNotifiableEventResolver( ): Result = runCatchingExceptions { when (val content = this.content) { is NotificationContent.MessageLike.RoomMessage -> { - val showMediaPreview = client.mediaPreviewService.getMediaPreviewValue() == MediaPreviewValue.On + val showMediaPreview = client.mediaPreviewService.getMediaPreviewValue().isPreviewEnabled(roomJoinRule) val senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId) - val imageMimeType = if (showMediaPreview) content.getImageMimetype() else null - val imageUriString = imageMimeType?.let { content.fetchImageIfPresent(client, imageMimeType)?.toString() } + val imageMimeType = content.getImageMimetype() + val imageUriString = if (showMediaPreview && imageMimeType != null) { + content.fetchImageIfPresent(client, imageMimeType)?.toString() + } else { + null + } + + val isPublicRoom = roomJoinRule == null || roomJoinRule == JoinRule.Public + if (imageMimeType != null && !showMediaPreview && isPublicRoom) { + Timber.tag(loggerTag.value) + .d("We should only display media previews for private rooms and the current room is public. Ignoring image uri.") + } else if (showMediaPreview && content.messageType is ImageMessageType && imageUriString == null) { + Timber.tag(loggerTag.value) + .w("No image uri returned for message with an image and previews enabled. Something went wrong.") + } + val messageBody = descriptionFromMessageContent( content = content, senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt index 347ee3bee4..1d34315105 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.push.impl.troubleshoot import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.PushService @@ -22,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @ContributesIntoSet(SessionScope::class) -@Inject class CurrentPushProviderTest( private val pushService: PushService, private val sessionId: SessionId, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt index 59ba1eba0e..b2aa489f47 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.push.impl.troubleshoot import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.push.impl.R @@ -22,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @ContributesIntoSet(SessionScope::class) -@Inject class IgnoredUsersTest( private val matrixClient: MatrixClient, private val stringProvider: StringProvider, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt index 96860bf3e0..e845f803d9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.push.impl.troubleshoot import androidx.compose.ui.graphics.toArgb import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.di.SessionScope @@ -31,7 +30,6 @@ import timber.log.Timber import kotlin.time.Duration.Companion.seconds @ContributesIntoSet(SessionScope::class) -@Inject class NotificationTest( private val sessionId: SessionId, private val notificationCreator: NotificationCreator, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt index 91ac1ca0ee..bc89d031a8 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.push.impl.troubleshoot import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.PushService @@ -31,7 +30,6 @@ import timber.log.Timber import kotlin.time.Duration.Companion.seconds @ContributesIntoSet(SessionScope::class) -@Inject class PushLoopbackTest( private val sessionId: SessionId, private val pushService: PushService, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt index 1a6368b75f..dbd2c4a16b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.push.impl.troubleshoot import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.push.impl.R import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest @@ -21,7 +20,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @ContributesIntoSet(AppScope::class) -@Inject class PushProvidersTest( pushProviders: Set<@JvmSuppressWildcards PushProvider>, private val stringProvider: StringProvider, diff --git a/libraries/push/impl/src/main/res/values-da/translations.xml b/libraries/push/impl/src/main/res/values-da/translations.xml index 5f16a8cf8a..b48875c20e 100644 --- a/libraries/push/impl/src/main/res/values-da/translations.xml +++ b/libraries/push/impl/src/main/res/values-da/translations.xml @@ -38,6 +38,8 @@ "%1$s inviterede dig til at deltage i rummet" "Mig" "%1$s nævnt eller besvaret" + "Inviterede dig til at deltage i gruppen" + "%1$s inviterede dig til at deltage i gruppen" "Du ser notifikationen! Klik på mig!" "Tråd i %1$s" "%1$s: %2$s" diff --git a/libraries/push/impl/src/main/res/values-hu/translations.xml b/libraries/push/impl/src/main/res/values-hu/translations.xml index 69d4082871..d65c3bb4c8 100644 --- a/libraries/push/impl/src/main/res/values-hu/translations.xml +++ b/libraries/push/impl/src/main/res/values-hu/translations.xml @@ -38,6 +38,8 @@ "%1$s meghívta, hogy csatlakozzon a szobához" "Én" "%1$s megemlítette vagy válaszolt" + "Meghívták a térbe" + "%1$s meghívta ebbe a térbe" "Az értesítést nézi! Kattintson ide!" "Üzenetszál itt: %1$s" "%1$s: %2$s" diff --git a/libraries/push/impl/src/main/res/values-ru/translations.xml b/libraries/push/impl/src/main/res/values-ru/translations.xml index b2a79defce..0d42e0e52e 100644 --- a/libraries/push/impl/src/main/res/values-ru/translations.xml +++ b/libraries/push/impl/src/main/res/values-ru/translations.xml @@ -42,6 +42,8 @@ "%1$s пригласил вас присоединиться к комнате" "Я" "%1$s упомянул или ответил" + "Пригласил вас присоединиться к пространству" + "%1$s пригласил вас присоединиться к пространству" "Вы просматриваете уведомление! Нажмите на меня!" "Ветка обсуждения в %1$s" "%1$s: %2$s" diff --git a/libraries/push/impl/src/main/res/values-sk/translations.xml b/libraries/push/impl/src/main/res/values-sk/translations.xml index 764dbefa70..31b11df0eb 100644 --- a/libraries/push/impl/src/main/res/values-sk/translations.xml +++ b/libraries/push/impl/src/main/res/values-sk/translations.xml @@ -42,6 +42,8 @@ "%1$s vás pozval/a, aby ste sa pripojili k miestnosti" "Ja" "%1$s spomenul/a alebo odpovedal/a" + "Boli ste pozvaní, aby ste sa pripojili do priestoru" + "%1$s vás pozval/a, aby ste sa pripojili k priestoru" "Prezeráte si oznámenie! Kliknite na mňa!" "Vlákno v %1$s" "%1$s: %2$s" diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt index 0aa345769f..fdb886bd90 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId @@ -23,7 +22,6 @@ import timber.log.Timber private val loggerTag = LoggerTag("FirebasePushProvider", LoggerTag.PushLoggerTag) @ContributesIntoSet(AppScope::class) -@Inject class FirebasePushProvider( private val firebaseStore: FirebaseStore, private val pusherSubscriber: PusherSubscriber, diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt index 69ffd504fd..3f4eeca305 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable import io.element.android.libraries.pushproviders.firebase.R @@ -23,7 +22,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @ContributesIntoSet(AppScope::class) -@Inject class FirebaseAvailabilityTest( private val isPlayServiceAvailable: IsPlayServiceAvailable, private val stringProvider: StringProvider, diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt index 8bcd44911e..22fa999d08 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.FirebaseStore import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter @@ -28,7 +27,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @ContributesIntoSet(AppScope::class) -@Inject class FirebaseTokenTest( private val firebaseStore: FirebaseStore, private val firebaseTroubleshooter: FirebaseTroubleshooter, diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt index 7c65e8e2e4..7288fccab2 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.pushproviders.unifiedpush import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushproviders.api.Config @@ -19,7 +18,6 @@ import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret @ContributesIntoSet(AppScope::class) -@Inject class UnifiedPushProvider( private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider, private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt index 7a50077915..82c43ab581 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.SessionId @@ -25,7 +24,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @ContributesIntoSet(SessionScope::class) -@Inject class UnifiedPushMatrixGatewayTest( private val sessionId: SessionId, private val unifiedPushApiFactory: UnifiedPushApiFactory, diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt index 6d6d3b68af..328b5d8ac2 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt @@ -10,7 +10,6 @@ package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import io.element.android.libraries.pushproviders.unifiedpush.R import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider @@ -24,7 +23,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow @ContributesIntoSet(AppScope::class) -@Inject class UnifiedPushTest( private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider, private val openDistributorWebPageAction: OpenDistributorWebPageAction, diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt index b5ea0e0727..0dcfb51079 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt @@ -16,5 +16,4 @@ sealed interface RoomSelectEvents { // TODO remove to restore multi-selection data object RemoveSelectedRoom : RoomSelectEvents data object ToggleSearchActive : RoomSelectEvents - data class UpdateQuery(val query: String) : RoomSelectEvents } diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt index 93294c9753..943da643c6 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt @@ -9,6 +9,8 @@ package io.element.android.libraries.roomselect.impl import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -17,6 +19,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.appyx.launchMolecule import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope @@ -36,11 +39,12 @@ class RoomSelectNode( private val inputs: Inputs = inputs() private val presenter = presenterFactory.create(inputs.mode) + private val stateFlow = launchMolecule { presenter.present() } private val callback: RoomSelectEntryPoint.Callback = callback() @Composable override fun View(modifier: Modifier) { - val state = presenter.present() + val state by stateFlow.collectAsState() RoomSelectView( state = state, onDismiss = callback::onCancel, diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt index be66db77dc..d38d9bde2e 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.roomselect.impl +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State @@ -16,6 +17,7 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory @@ -31,7 +33,7 @@ import kotlinx.collections.immutable.toImmutableList @AssistedInject class RoomSelectPresenter( @Assisted private val mode: RoomSelectMode, - private val dataSource: RoomSelectSearchDataSource, + private val dataSourceFactory: RoomSelectSearchDataSource.Factory, ) : Presenter { @AssistedFactory fun interface Factory { @@ -41,13 +43,13 @@ class RoomSelectPresenter( @Composable override fun present(): RoomSelectState { var selectedRooms by remember { mutableStateOf(persistentListOf()) } - var searchQuery by remember { mutableStateOf("") } + val queryState = rememberTextFieldState() var isSearchActive by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - dataSource.load() - } + val coroutineScope = rememberCoroutineScope() + val dataSource = remember { dataSourceFactory.create(coroutineScope) } + val searchQuery = queryState.text.toString() LaunchedEffect(searchQuery) { dataSource.setSearchQuery(searchQuery) } @@ -77,7 +79,6 @@ class RoomSelectPresenter( // } } RoomSelectEvents.RemoveSelectedRoom -> selectedRooms = persistentListOf() - is RoomSelectEvents.UpdateQuery -> searchQuery = event.query RoomSelectEvents.ToggleSearchActive -> isSearchActive = !isSearchActive } } @@ -85,7 +86,7 @@ class RoomSelectPresenter( return RoomSelectState( mode = mode, resultState = searchResults, - query = searchQuery, + searchQuery = queryState, isSearchActive = isSearchActive, selectedRooms = selectedRooms, eventSink = ::handleEvent, diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt index 15148eb15b..3c5cc124c0 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt @@ -8,7 +8,9 @@ package io.element.android.libraries.roomselect.impl -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.roomlist.RoomList @@ -19,6 +21,7 @@ import io.element.android.libraries.matrix.ui.model.SelectRoomInfo import io.element.android.libraries.matrix.ui.model.toSelectRoomInfo import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn @@ -30,16 +33,25 @@ private const val PAGE_SIZE = 30 * DataSource for RoomSummaryDetails that can be filtered by a search query, * and which only includes rooms the user has joined. */ -@Inject +@AssistedInject class RoomSelectSearchDataSource( + @Assisted coroutineScope: CoroutineScope, roomListService: RoomListService, coroutineDispatchers: CoroutineDispatchers, ) { + @AssistedFactory + interface Factory { + fun create(coroutineScope: CoroutineScope): RoomSelectSearchDataSource + } + private val roomList = roomListService.createRoomList( pageSize = PAGE_SIZE, initialFilter = RoomListFilter.all(), source = RoomList.Source.All, - ) + coroutineScope = coroutineScope + ).apply { + loadAllIncrementally(coroutineScope) + } val roomInfoList: Flow> = roomList.filteredSummaries .map { roomSummaries -> @@ -51,10 +63,6 @@ class RoomSelectSearchDataSource( } .flowOn(coroutineDispatchers.computation) - suspend fun load() = coroutineScope { - roomList.loadAllIncrementally(this) - } - suspend fun setSearchQuery(searchQuery: String) = coroutineScope { val filter = if (searchQuery.isBlank()) { RoomListFilter.all() diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt index c1ccb0077d..927a1c02d6 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.roomselect.impl +import androidx.compose.foundation.text.input.TextFieldState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.ui.model.SelectRoomInfo import io.element.android.libraries.roomselect.api.RoomSelectMode @@ -16,7 +17,7 @@ import kotlinx.collections.immutable.ImmutableList data class RoomSelectState( val mode: RoomSelectMode, val resultState: SearchBarResultState>, - val query: String, + val searchQuery: TextFieldState, val isSearchActive: Boolean, val selectedRooms: ImmutableList, val eventSink: (RoomSelectEvents) -> Unit diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt index 5b63d6dbd7..6cd2bc6921 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.roomselect.impl +import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.RoomAlias @@ -22,16 +23,16 @@ open class RoomSelectStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomSelectState(), - aRoomSelectState(query = "Test", isSearchActive = true), + aRoomSelectState(searchQuery = "Test", isSearchActive = true), aRoomSelectState(resultState = SearchBarResultState.Results(aRoomSelectRoomList())), aRoomSelectState( resultState = SearchBarResultState.Results(aRoomSelectRoomList()), - query = "Test", + searchQuery = "Test", isSearchActive = true, ), aRoomSelectState( resultState = SearchBarResultState.Results(aRoomSelectRoomList()), - query = "Test", + searchQuery = "Test", isSearchActive = true, selectedRooms = aRoomSelectRoomList().subList(0, 1), ), @@ -45,13 +46,13 @@ open class RoomSelectStateProvider : PreviewParameterProvider { private fun aRoomSelectState( mode: RoomSelectMode = RoomSelectMode.Forward, resultState: SearchBarResultState> = SearchBarResultState.Initial(), - query: String = "", + searchQuery: String = "", isSearchActive: Boolean = false, selectedRooms: ImmutableList = persistentListOf(), ) = RoomSelectState( mode = mode, resultState = resultState, - query = query, + searchQuery = TextFieldState(initialText = searchQuery), isSearchActive = isSearchActive, selectedRooms = selectedRooms, eventSink = {} diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt index 4d02e1ba10..19be54d6c4 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt @@ -132,8 +132,7 @@ fun RoomSelectView( SearchBar( modifier = Modifier.fillMaxWidth(), placeHolderTitle = stringResource(CommonStrings.action_search), - query = state.query, - onQueryChange = { state.eventSink(RoomSelectEvents.UpdateQuery(it)) }, + queryState = state.searchQuery, active = state.isSearchActive, onActiveChange = { state.eventSink(RoomSelectEvents.ToggleSearchActive) }, resultState = state.resultState, diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt index 0d805904db..a99a0396c2 100644 --- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt +++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.roomselect.impl import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.RoomId @@ -16,14 +17,18 @@ import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint import io.element.android.libraries.roomselect.api.RoomSelectMode import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class DefaultRoomSelectEntryPointTest { @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `test node builder`() = runTest { val entryPoint = DefaultRoomSelectEntryPoint() diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt index f6c7ef516d..bd8ef59482 100644 --- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt +++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.roomselect.impl +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test @@ -22,6 +23,7 @@ import io.element.android.libraries.roomselect.api.RoomSelectMode import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -77,13 +79,13 @@ class RoomSelectPresenterTest { assertThat(result).isEqualTo(listOf(expectedRoomInfo)) initialState.eventSink(RoomSelectEvents.ToggleSearchActive) skipItems(1) - initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained")) + initialState.searchQuery.setTextAndPlaceCursorAtEnd("string not contained") assertThat( roomListService.allRooms.currentFilter.value ).isEqualTo( RoomListFilter.NormalizedMatchRoomName("string not contained") ) - assertThat(awaitItem().query).isEqualTo("string not contained") + assertThat(awaitItem().searchQuery.text.toString()).isEqualTo("string not contained") roomListService.postAllRooms( emptyList() ) @@ -119,8 +121,13 @@ internal fun TestScope.createRoomSelectPresenter( roomListService: RoomListService = FakeRoomListService(), ) = RoomSelectPresenter( mode = mode, - dataSource = RoomSelectSearchDataSource( - roomListService = roomListService, - coroutineDispatchers = testCoroutineDispatchers(), - ), + dataSourceFactory = object : RoomSelectSearchDataSource.Factory { + override fun create(coroutineScope: CoroutineScope): RoomSelectSearchDataSource { + return RoomSelectSearchDataSource( + coroutineScope = coroutineScope, + roomListService = roomListService, + coroutineDispatchers = testCoroutineDispatchers(), + ) + } + } ) diff --git a/libraries/textcomposer/impl/src/main/res/values-in/translations.xml b/libraries/textcomposer/impl/src/main/res/values-in/translations.xml index 64f032ca0a..9b98938b34 100644 --- a/libraries/textcomposer/impl/src/main/res/values-in/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-in/translations.xml @@ -9,7 +9,7 @@ "Kirim pesan…" "Pesan tidak terenkripsi…" "Buat tautan" - "Sunting tautan" + "Edit tautan" "%1$s, keadaan: %2$s" "Terapkan format tebal" "Terapkan format miring" diff --git a/libraries/ui-strings/src/main/res/values-be/translations.xml b/libraries/ui-strings/src/main/res/values-be/translations.xml index d2a1f4dea7..467fb05e25 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -53,7 +53,7 @@ "Скапіраваць спасылку" "Скапіраваць спасылку на паведамленне" "Стварыць" - "Стварыце пакой" + "Стварыце пакой" "Дэактываваць" "Дэактываваць уліковы запіс" "Адхіліць" @@ -289,12 +289,6 @@ "%1$s не мае дазволу на доступ да вашага мікрафона. Дазвольце доступ да запісу галасавога паведамлення." "Некаторыя паведамленні не былі адпраўлены" "Выбачце, адбылася памылка" - "Сапраўднасць гэтага зашыфраванага паведамлення не можа быць гарантаваная на гэтай прыладзе." - "Зашыфравана раней правераным карыстальнікам." - "Не зашыфраваны." - "Зашыфравана невядомай ці выдаленай прыладай." - "Зашыфравана прыладай, не пацверджанай яе ўладальнікам." - "Зашыфравана неправераным карыстальнікам." "🔐️ Далучайцеся да мяне %1$s" "Гэй, пагавары са мной у %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-bg/translations.xml b/libraries/ui-strings/src/main/res/values-bg/translations.xml index d6e314044b..ec7489b877 100644 --- a/libraries/ui-strings/src/main/res/values-bg/translations.xml +++ b/libraries/ui-strings/src/main/res/values-bg/translations.xml @@ -53,7 +53,7 @@ "Копиране на връзката към съобщението" "Копиране на текста" "Създаване" - "Създаване на стая" + "Създаване на стая" "Деактивиране" "Деактивиране на акаунта" "Отхвърляне" @@ -311,7 +311,6 @@ "%1$s няма разрешение за достъп до вашето местоположение. Активирайте достъпа по-долу." "Някои съобщения не са изпратени" "Съжаляваме, възникна грешка" - "Без шифроване" "🔐️ Присъединете се към мен в %1$s" "Хей, говорете с мен в %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index fafb59d4fb..45a8b7e284 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -76,7 +76,7 @@ "Kopírovat odkaz na zprávu" "Kopírovat text" "Vytvořit" - "Vytvořit místnost" + "Vytvořit místnost" "Deaktivovat" "Deaktivovat účet" "Odmítnout" @@ -426,13 +426,6 @@ Opravdu chcete pokračovat?" "Některé znaky nejsou povoleny. Podporovány jsou pouze písmena, číslice a následující symboly ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Některé zprávy nebyly odeslány" "Omlouváme se, došlo k chybě" - "Odesílatel události se neshoduje s vlastníkem zařízení, které ji odeslalo." - "Autenticitu této zašifrované zprávy nelze na tomto zařízení zaručit." - "Zašifrováno dříve ověřeným uživatelem." - "Není zašifrováno." - "Šifrováno neznámým nebo smazaným zařízením." - "Šifrováno zařízením, které nebylo ověřeno jeho vlastníkem." - "Šifrováno neověřeným uživatelem." "🔐️ Připojte se ke mně na %1$s" "Ahoj, ozvi se mi na %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-cy/translations.xml b/libraries/ui-strings/src/main/res/values-cy/translations.xml index 1aa932f8ed..9c4b04b9d5 100644 --- a/libraries/ui-strings/src/main/res/values-cy/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cy/translations.xml @@ -82,7 +82,7 @@ "Copïo dolen i\'r neges" "Copïo testun" "Creu" - "Creu ystafell" + "Creu ystafell" "Dadweithredu" "Cau cyfrif" "Gwrthod" @@ -439,13 +439,6 @@ Ydych chi\'n siŵr eich bod am barhau?" "Dyw rhai nodau ddim yn cael eu caniatáu. Dim ond llythrennau, digidau a\'r symbolau canlynol sy\'n cael eu cefnogi ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Nid yw rhai negeseuon wedi\'u hanfon" "Ymddiheuriadau, mae gwall wedi digwydd" - "Nid yw anfonwr y digwyddiad yn cyfateb i berchennog y ddyfais a\'i hanfonodd." - "Nid oes modd gwarantu dilysrwydd y neges hon sydd wedi\'i hamgryptio ar y ddyfais hon." - "Wedi\'i amgryptio gan ddefnyddiwr a wiriwyd gynt." - "Heb ei amgryptio." - "Wedi\'i amgryptio gan ddyfais anhysbys neu wedi\'i dileu." - "Wedi\'i amgryptio gan ddyfais nad yw wedi\'i wirio gan ei pherchennog." - "Wedi\'i amgryptio gan ddefnyddiwr heb ei wirio." "🔐️ Ymunwch â mi ar %1$s" "Hei, siaradwch â mi ar %1$s: %2$s" "Android %1$s" diff --git a/libraries/ui-strings/src/main/res/values-da/translations.xml b/libraries/ui-strings/src/main/res/values-da/translations.xml index 6dc36fac5a..fe555905ff 100644 --- a/libraries/ui-strings/src/main/res/values-da/translations.xml +++ b/libraries/ui-strings/src/main/res/values-da/translations.xml @@ -56,6 +56,7 @@ "Din avatar" "Accepter" "Tilføj billedtekst" + "Tilføj eksisterende rum" "Føj til tidslinje" "Tilbage" "Opkald" @@ -74,7 +75,8 @@ "Kopiér link til besked" "Kopiér tekst" "Opret" - "Opret et rum" + "Opret et rum" + "Opret en gruppe" "Deaktiver" "Deaktiver konto" "Afvis" @@ -91,6 +93,7 @@ "Slå til" "Afslut afstemning" "Indtast PIN-kode" + "Udforsk offentlige grupper" "Afslut" "Har du glemt din adgangskode?" "Videresend" @@ -162,6 +165,7 @@ "Tryk for at indlæse kort" "Tag billede" "Tryk for indstillinger" + "Oversæt" "Prøv igen" "Frigør" "Vis" @@ -191,6 +195,7 @@ "Kopieret til udklipsholder" "Ophavsret" "Opretter rum…" + "Opretter gruppe…" "Anmodning annulleret" "Forlod rummet" "Forlod gruppe" @@ -236,6 +241,7 @@ "Lyst tema" "Linje kopieret til udklipsholder" "Linket er kopieret til udklipsholderen" + "Forbind ny enhed" "Indlæser…" "Indlæser flere…" @@ -248,10 +254,12 @@ "Besked" "Beskedhandlinger" + "Beskeden blev ikke sendt" "Layout på beskeder" "Beskeden er fjernet" "Moderne" "Lydløs" + "Navn" "%1$s (%2$s)" "Ingen resultater" "Intet rumnavn" @@ -286,6 +294,7 @@ "Årsag" "Gendannelsesnøgle" "Opdaterer…" + "Fjerner…" "%1$d svar" @@ -309,6 +318,10 @@ "Sikkerhed" "Set af" "Vælg en konto" + + "%1$d valgt" + "%1$d valgt" + "Send til" "Sender…" "Afsendelse mislykkedes" @@ -325,6 +338,7 @@ "Noget gik galt" "Vi stødte på et problem. Prøv venligst igen." "Gruppe" + "Hvad handler denne gruppe om?" "%1$d Gruppe" "%1$d Grupper" @@ -332,6 +346,7 @@ "Starter samtale…" "Klistermærke" "Succes" + "Forslag" "Forslag" "Synkroniserer" "System" @@ -369,6 +384,9 @@ "Venter…" "Venter på denne besked" "Dig" + "%1$s(%2$s ) har delt denne besked siden du ikke var i rummet da den blev sendt." + "%1$s delte denne besked, siden du ikke var i rummet da den blev sendt." + "Dette rum er konfigureret, så nye medlemmer kan læse historikken.%1$s" "%1$ss identitet blev nulstillet. %2$s" "%1$ss %2$s identitet blev nulstillet. %3$s" "(%1$s)" @@ -413,13 +431,6 @@ Er du sikker på, at du vil fortsætte?" "Nogle tegn er ikke tilladt. Kun bogstaver, cifre og følgende symboler understøttes! $ & \'() * +/; =? @ [] - . _" "Nogle beskeder er ikke blevet sendt" "Beklager, der opstod en fejl" - "Afsenderen af begivenheden matcher ikke ejeren af den enhed, der sendte den." - "Ægtheden af denne krypterede besked kan ikke garanteres på denne enhed." - "Krypteret af en tidligere verificeret bruger." - "Ikke krypteret." - "Krypteret af en ukendt eller slettet enhed." - "Krypteret af en enhed, der ikke er verificeret af sin ejer." - "Krypteret af en ikke-verificeret bruger." "🔐️ Kom med mig til %1$s" "Hej, lad os snakkes på %1$s: %2$s" "%1$s Android" @@ -467,6 +478,7 @@ Er du sikker på, at du vil fortsætte?" "Del denne lokation" "Grupper, du har oprettet eller deltager i" "%1$s•%2$s" + "Opret grupper til at organisere rum" "%1$s gruppe" "Grupper" "Beskeden blev ikke sendt fordi %1$s s bekræftede identitet blev nulstillet." diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 60a77143d6..5528f3dc63 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -75,7 +75,7 @@ "Link zur Nachricht kopieren" "Text kopieren" "Erstellen" - "Chat erstellen" + "Chat erstellen" "Space erstellen" "Deaktivieren" "Nutzerkonto deaktivieren" @@ -293,6 +293,7 @@ Grund: %1$s." "Grund" "Wiederherstellungsschlüssel" "Wird erneuert…" + "Entferne…" "%1$d Antwort" "%1$d Antworten" @@ -317,6 +318,10 @@ Grund: %1$s." "Sicherheit" "Gesehen von" "Konto auswählen" + + "%1$d ausgewählt" + "%1$d ausgewählt" + "Senden an" "Wird gesendet…" "Senden fehlgeschlagen" @@ -380,6 +385,7 @@ Grund: %1$s." "Warte auf diese Nachricht" "Du" "%1$s (%2$s) hat diese Nachricht geteilt, weil du nicht im Chat warst, als sie verschickt wurde." + "Diese Nachricht wurde von %1$s weitergeleitet, da du zum Zeitpunkt des Versands kein Mitglied der Gruppe warst." "Diese Gruppe wurde so konfiguriert, dass neue Mitglieder den vergangenen Nachrichtenverlauf lesen können. %1$s" "%1$s\'s Identität has sich geändert. %2$s" "%1$s\'s %2$s Identität hat sich geändert. %3$s" @@ -425,13 +431,6 @@ Möchtest du wirklich fortfahren?" "Einige Zeichen sind nicht erlaubt. Es werden nur Buchstaben, Ziffern und die folgenden Symbole unterstützt: ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Einige Nachrichten wurden nicht gesendet" "Entschuldigung, es ist ein Fehler aufgetreten" - "Der Absender des Ereignisses stimmt nicht mit dem Besitzer des Gerätes überein, das es gesendet hat." - "Die Authentizität dieser verschlüsselten Nachricht kann auf diesem Gerät nicht garantiert werden." - "Verschlüsselt von einem zuvor verifizierten Nutzer." - "Unverschlüsselt." - "Verschlüsselt von einem unbekannten oder gelöschten Gerät." - "Verschlüsselt durch ein Gerät, das nicht von seinem Besitzer verifiziert wurde." - "Verschlüsselt durch einen nicht verifizierten Nutzer." "🔐️ Begleite mich auf %1$s" "Hey, sprich mit mir auf %1$s: %2$s" "%1$s Android" @@ -477,7 +476,6 @@ Möchtest du wirklich fortfahren?" "In Google Maps öffnen" "In OpenStreetMap öffnen" "Diesen Standort teilen" - "Das Hinzufügen eines Chats hat keinen Einfluss auf die Beitrittsregeln. Um die Regeln zu ändern, gehe zu \"Raum Info\" und dann zu \"Datenschutz und Sicherheit\"" "Von dir erstellte oder beigetretene Spaces." "%1$s • %2$s" "Erstelle einen Space, um Chats zu organisieren" diff --git a/libraries/ui-strings/src/main/res/values-el/translations.xml b/libraries/ui-strings/src/main/res/values-el/translations.xml index 98d20a1599..20f52b7208 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -58,7 +58,7 @@ "Αντιγραφή συνδέσμου στο μήνυμα" "Αντιγραφή κειμένου" "Δημιουργία" - "Δημιουργία αίθουσας" + "Δημιουργία αίθουσας" "Απενεργοποίηση" "Απενεργοποίηση λογαριασμού" "Απόρριψη" @@ -347,13 +347,6 @@ "Ορισμένοι χαρακτήρες δεν επιτρέπονται. Υποστηρίζονται μόνο γράμματα, ψηφία και τα ακόλουθα σύμβολα ! $ & \'() * +/; = ? @ [] - . _" "Ορισμένα μηνύματα δεν έχουν σταλεί" "Λυπούμαστε, παρουσιάστηκε σφάλμα" - "Ο αποστολέας του συμβάντος δεν ταιριάζει με τον κάτοχο της συσκευής που το έστειλε." - "Η αυθεντικότητα αυτού του κρυπτογραφημένου μηνύματος δεν είναι εγγυημένη σε αυτήν τη συσκευή." - "Κρυπτογραφημένο από έναν προηγουμένως επαληθευμένο χρήστη." - "Μη κρυπτογραφημένο." - "Κρυπτογραφημένο από άγνωστη ή διαγεγραμμένη συσκευή." - "Κρυπτογραφημένο από μια συσκευή που δεν έχει επαληθευτεί από τον ιδιοκτήτη της." - "Κρυπτογραφημένο από μη επαληθευμένο χρήστη." "🔐️ Έλα μαζί μου στο %1$s" "Γεια, μίλα μου στην εφαρμογή %1$s :%2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index 740f96edd4..4d47792f1a 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -56,7 +56,7 @@ "Copiar enlace al mensaje" "Copiar texto" "Crear" - "Crear una sala" + "Crear una sala" "Desactivar" "Desactivar cuenta" "Rechazar" @@ -338,12 +338,6 @@ Motivo: %1$s." "No se permiten algunos caracteres. Solo se admiten letras, dígitos y los siguientes símbolos ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Algunos mensajes no se han enviado" "Lo siento, se ha producido un error" - "La autenticidad de este mensaje cifrado no puede ser garantizada en este dispositivo." - "Cifrado por un usuario verificado anteriormente." - "No cifrado." - "Cifrado por un dispositivo desconocido o eliminado." - "Cifrado por un dispositivo no verificado por su propietario." - "Cifrado por un usuario no verificado." "🔐️ Únete a mí en %1$s" "Hola, puedes hablar conmigo en %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index 9bd2ef325c..25f5c035c1 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -74,7 +74,7 @@ "Kopeeri sõnumi link" "Kopeeri tekst" "Loo" - "Loo jututuba" + "Loo jututuba" "Eemalda konto" "Eemalda konto kasutusest" "Keeldu" @@ -420,13 +420,6 @@ Kas sa oled kindel, et soovid jätkata?" "Mõned tähemärgid pole lubatud. Kasuta vaid tähti, numbreid ja neid kirjavahemärke ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Mõned sõnumid on saatmata" "Vabandust, ilmnes viga" - "Sündmuse saatja ja seadme omanik pole vastavuses." - "Selle krüptitud sõnumi tõepärasus pole selles seadmes tagatud." - "Krüptitud varem verifitseeritud kasutaja poolt" - "Pole krüptitud." - "Krüptitud tundmatu või kustutatud seadme poolt." - "Krüptitud seadme poolt, mida tema omanik pole verifitseerinud." - "Krüptitud verifitseerimata kasutaja poolt." "🔐️ Liitu minuga rakenduses %1$s" "Hei, suhtle minuga %1$s võrgus: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-eu/translations.xml b/libraries/ui-strings/src/main/res/values-eu/translations.xml index 9a92bf6289..bef5f1a4b5 100644 --- a/libraries/ui-strings/src/main/res/values-eu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-eu/translations.xml @@ -62,7 +62,7 @@ "Kopiatu esteka mezura" "Kopiatu testua" "Sortu" - "Sortu gela" + "Sortu gela" "Desaktibatu" "Desaktibatu kontua" "Ukatu" @@ -356,7 +356,6 @@ Ziur jarraitu nahi duzula?" "Karaktere batzuk ez daude baimenduta. Hizkiak, zifrak, eta ondorengo ikurrak onartzen dira bakarrik: ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Mezu batzuk ez dira bidali" "Barka, errore bat gertatu da" - "Ez dago zifratuta." "🔐️ Zatoz nirekin %1$s(e)ra" "%1$s Android" "Astindu erroreen berri emateko" diff --git a/libraries/ui-strings/src/main/res/values-fa/translations.xml b/libraries/ui-strings/src/main/res/values-fa/translations.xml index 083f84ddc6..fbe716bc49 100644 --- a/libraries/ui-strings/src/main/res/values-fa/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fa/translations.xml @@ -71,7 +71,7 @@ "رونوشت از پیوند پیام" "رونوشت از متن" "ایجاد" - "ایجاد اتاق" + "ایجاد اتاق" "غیرفعّال" "غیرفعّال‌سازی حساب" "رد" @@ -361,12 +361,6 @@ "%1$s اجازه دسترسی به میکروفون شما را ندارد. دسترسی را برای ضبط پیام صوتی فعال کنید." "برخی پیام‌ها ارسال نشده‌اند" "متأسفیم ، خطایی رخ داد" - "اعتبار این پیام رمز شده نمی‌تواند روی این افزاره تأیید شود." - "رمز شده به دست کاربری از پیش تأیید شده." - "رمز نشده." - "رمز شده به دست افزاره‌ای ناشناخته یا حذف شده." - "رمز شده به دست افزاره‌ای که از سوی مالکش تأیید نشده." - "رمز شده به دست کاربری تأیید نشده." "🔐️ پییوستن به من روی %1$s" "درود. با من روی %1$s صحبت کن: %2$s" "%1$s اندروید" diff --git a/libraries/ui-strings/src/main/res/values-fi/translations.xml b/libraries/ui-strings/src/main/res/values-fi/translations.xml index 7d71dca9ef..bc1e0ab604 100644 --- a/libraries/ui-strings/src/main/res/values-fi/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fi/translations.xml @@ -74,7 +74,7 @@ "Kopioi linkki viestiin" "Kopioi teksti" "Luo" - "Luo huone" + "Luo huone" "Deaktivoi" "Deaktivoi tili" "Hylkää" @@ -414,13 +414,6 @@ Haluatko varmasti jatkaa?" "Jotkin merkit eivät ole sallittuja. Vain kirjaimet, numerot ja seuraavat symbolit ovat tuettuja ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Joitakin viestejä ei ole lähetetty" "Anteeksi, tapahtui virhe" - "Tapahtuman lähettäjä ei vastaa sitä lähettäneen laitteen omistajaa." - "Tämän salatun viestin aitoutta ei voida taata tällä laitteella." - "Aiemmin vahvistetun käyttäjän salaama." - "Ei salattu." - "Tuntemattoman tai poistetun laitteen salaama." - "Salattu laitteella, jota sen omistaja ei ole vahvistanut." - "Vahvistamattoman käyttäjän salaama." "🔐️ Liity seuraani %1$s -sovelluksessa" "Hei, keskustele kanssani %1$s -sovelluksessa: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index be99a39c7d..08c8919080 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -75,7 +75,7 @@ "Copier le lien vers le message" "Copier le texte" "Créer" - "Créer un salon" + "Créer un salon" "Créer un espace" "Désactiver" "Désactiver le compte" @@ -93,6 +93,7 @@ "Activer" "Terminer le sondage" "Saisir le code PIN" + "Explorer les espaces publics" "Terminer" "Mot de passe oublié ?" "Transférer" @@ -293,6 +294,7 @@ Raison : %1$s." "Raison" "Clé de récupération" "Actualisation…" + "Suppression…" "%1$d réponse" "%1$d réponses" @@ -302,6 +304,7 @@ Raison : %1$s." "Remonter un problème" "Rapport soumis" "Éditeur de texte enrichi" + "Rôle" "Salon" "Nom du salon" "par exemple, le nom de votre projet" @@ -317,6 +320,10 @@ Raison : %1$s." "Sécurité" "Vu par" "Choisir un compte" + + "%1$d élément sélectionné" + "%1$d éléments sélectionnés" + "Envoyer vers" "Envoi en cours…" "Échec de l’envoi" @@ -426,13 +433,6 @@ Raison : %1$s." "Certains caractères ne sont pas autorisés. Seuls les lettres, les chiffres et les symboles suivants sont utilisables ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Certains messages n’ont pas été envoyés" "Désolé, une erreur s’est produite" - "L’expéditeur de ce message ne correspond pas au propriétaire de l’appareil qui l’a envoyé." - "L’authenticité de ce message chiffré ne peut être garantie sur cet appareil." - "Chiffré par un utilisateur précédemment vérifié." - "Non chiffré." - "Chiffré par un appareil inconnu ou supprimé." - "Chiffré par un appareil non vérifié par son propriétaire." - "Chiffré par un utilisateur non vérifié." "🔐️ Rejoignez-moi sur %1$s" "Salut, parle-moi sur %1$s : %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-hr/translations.xml b/libraries/ui-strings/src/main/res/values-hr/translations.xml index 2d9676c36a..1bb8ceb426 100644 --- a/libraries/ui-strings/src/main/res/values-hr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hr/translations.xml @@ -76,7 +76,7 @@ "Kopiraj poveznicu u poruku" "Kopiraj tekst" "Stvori" - "Stvori sobu" + "Stvori sobu" "Deaktiviraj" "Deaktiviraj račun" "Odbij" @@ -428,13 +428,6 @@ Jeste li sigurni da želite nastaviti?" "Neki znakovi nisu dopušteni. Podržana su samo slova, brojke i simboli ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Neke poruke nisu poslane" "Žao nam je, došlo je do pogreške" - "Pošiljatelj događaja ne podudara se s vlasnikom uređaja koji ga je poslao." - "Autentičnost ove šifrirane poruke ne može se jamčiti na ovom uređaju" - "Šifrirao je prethodno provjereni korisnik." - "Nije šifrirano." - "Šifrirano nepoznatim ili izbrisanim uređajem." - "Šifrirano uređajem koji vlasnik nije potvrdio." - "Šifrirao neprovjereni korisnik." "🔐️ Pridruži mi se u %1$s" "Hej, razgovaraj sa mnom u aplikaciji %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index 259ef3dd15..088a09692e 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -56,6 +56,7 @@ "Saját profilkép" "Elfogadás" "Felirat hozzáadása" + "Létező szobák hozzáadása" "Hozzáadás az idővonalhoz" "Vissza" "Hívás" @@ -74,7 +75,8 @@ "Üzenethivatkozás másolása" "Szöveg másolása" "Létrehozás" - "Szoba létrehozása" + "Szoba létrehozása" + "Tér létrehozása" "Deaktiválás" "Fiók deaktiválása" "Elutasítás" @@ -91,6 +93,7 @@ "Engedélyezés" "Szavazás lezárása" "Adja meg a PIN-kódot" + "Nyilvános terek felfedezése" "Befejezés" "Elfelejtette a jelszót?" "Tovább" @@ -162,6 +165,7 @@ "Koppintson a térkép betöltéséhez" "Fénykép készítése" "Koppintson a beállításokért" + "Fordítás" "Próbálja újra" "Kitűzés feloldása" "Megtekintés" @@ -191,6 +195,7 @@ "A vágólapra másolva" "Szerzői jogok" "Szoba létrehozása…" + "Tér létrehozása…" "Kérés megszakítva" "Elhagyta a szobát" "Tér elhagyva" @@ -236,6 +241,7 @@ Ok: %1$s." "Világos" "A sor a vágólapra másolva" "Hivatkozás a vágólapra másolva" + "Új eszköz összekapcsolása" "Betöltés…" "Továbbiak betöltése…" @@ -248,10 +254,12 @@ Ok: %1$s." "Üzenet" "Üzenetműveletek" + "Az üzenet elküldése sikertelen" "Üzenet elrendezése" "Üzenet eltávolítva" "Modern" "Némítás" + "Név" "%1$s (%2$s)" "Nincs találat" "Nincs szobanév" @@ -286,6 +294,7 @@ Ok: %1$s." "Ok" "Helyreállítási kulcs" "Frissítés…" + "Eltávolítás…" "%1$d válasz" @@ -294,6 +303,7 @@ Ok: %1$s." "Probléma jelentése" "A jelentés elküldve" "Formázott szöveges szerkesztő" + "Szerepkör" "Szoba" "Szoba neve" "például a projektje neve" @@ -309,6 +319,10 @@ Ok: %1$s." "Biztonság" "Látta" "Fiók kiválasztása" + + "%1$d kiválasztva" + "%1$d kiválasztva" + "Címzett" "Küldés…" "A küldés sikertelen" @@ -325,6 +339,7 @@ Ok: %1$s." "Valamilyen hiba történt" "Problémába ütköztünk. Próbálja újra." "Tér" + "Miről szól ez a tér?" "%1$d tér" "%1$d tér" @@ -332,6 +347,7 @@ Ok: %1$s." "Csevegés megkezdése…" "Matrica" "Sikeres" + "Javaslat" "Javaslatok" "Szinkronizálás" "Rendszer" @@ -369,6 +385,9 @@ Ok: %1$s." "Várakozás…" "Várakozás a visszafejtési kulcsra" "Ön" + "%1$s (%2$s) megosztotta ezt az üzenetet, mivel Ön nem volt a szobában, amikor elküldték." + "%1$s megosztotta ezt az üzenetet, mivel Ön nem volt a szobában, amikor elküldték." + "Az elküldött üzenetek megosztásra kerülnek a szobába meghívott új tagokkal.%1$s" "%1$s személyazonossága megváltozott. %2$s" "%1$s (%2$s) személyazonossága megváltozott. %3$s" "(%1$s)" @@ -413,13 +432,6 @@ Biztos, hogy folytatja?" "Egyes karakterek nem engedélyezettek. Csak a betűk, a számjegyek és a következő szimbólumok támogatottak: $ & \'() * +/; =? @ [] - . _" "Néhány üzenet nem került elküldésre" "Elnézést, hiba történt" - "Az esemény feladója nem egyezik az eseményt küldő eszköz tulajdonosával." - "A titkosított üzenetek valódiságát ezen az eszközön nem lehet garantálni." - "Egy korábban ellenőrzött felhasználó által titkosítva." - "Nincs titkosítva." - "Ismeretlen vagy törölt eszköz által titkosítva." - "A tulajdonos által nem ellenőrzött eszköz által titkosítva." - "Nem ellenőrzött felhasználó által titkosítva." "🔐️ Csatlakozz hozzám itt: %1$s" "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" @@ -467,6 +479,7 @@ Biztos, hogy folytatja?" "E hely megosztása" "Létrehozott vagy olyan terek, melyekhez csatlakozott." "%1$s • %2$s" + "Terek létrehozása a szobák rendszerezéséhez" "%1$s tér" "Terek" "Az üzenet nem lett elküldve, mert %1$s ellenőrzött személyazonossága megváltozott." diff --git a/libraries/ui-strings/src/main/res/values-in/translations.xml b/libraries/ui-strings/src/main/res/values-in/translations.xml index cc2bad1f2b..00d5d35d18 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -20,8 +20,8 @@ "Pesan suara, durasi: %1$s, posisi saat ini: %2$s" "Kolom PIN" "Putar" - "Pemungutan suara" - "Pemungutan suara berakhir" + "Jajak pendapat" + "Jajak pendapat berakhir" "Bereaksi dengan %1$s" "Reaksi dengan emoji lain" "Dibaca oleh %1$s dan %2$s" @@ -34,6 +34,7 @@ "Hapus reaksi dengan %1$s" "Avatar ruangan" "Kirim berkas" + "Tindakan terbatas waktu diperlukan, Anda memiliki satu menit untuk memverifikasi" "Tampilkan kata sandi" "Mulai panggilan" "Avatar pengguna" @@ -64,21 +65,21 @@ "Salin tautan ke pesan" "Salin teks" "Buat" - "Buat ruangan" + "Buat ruangan" "Nonaktifkan" "Nonaktifkan akun" "Tolak" "Tolak dan blokir" - "Hapus pemungutan suara" + "Hapus jajak pendapat" "Nonaktifkan" "Abaikan" "Abaikan" "Selesai" - "Sunting" - "Sunting keterangan" - "Sunting pemungutan suara" + "Edit" + "Edit keterangan" + "Edit jajak pendapat" "Aktifkan" - "Akhiri pemungutan suara" + "Akhiri jajak pendapat" "Masukkan PIN" "Lupa kata sandi?" "Teruskan" @@ -180,7 +181,7 @@ "Jangan tampilkan ini lagi" "Pengunduhan gagal" "Mengunduh" - "(disunting)" + "(diedit)" "Penyuntingan" "Menyunting keterangan" "* %1$s %2$s" @@ -239,10 +240,10 @@ Alasan: %1$s." "Disematkan" "Silakan periksa koneksi internet Anda" "Mohon tunggu…" - "Apakah Anda yakin ingin mengakhiri pemungutan suara ini?" - "Pemungutan suara: %1$s" + "Apakah Anda yakin ingin mengakhiri jajak pendapat ini?" + "Jajak pendapat: %1$s" "Total suara: %1$s" - "Hasil akan terlihat setelah pemungutan suara berakhir" + "Hasil akan terlihat setelah jajak pendapat berakhir." "%d suara" @@ -353,13 +354,6 @@ Apakah Anda yakin ingin melanjutkan?" "Beberapa karakter tidak diperbolehkan. Hanya huruf, angka, dan simbol berikut didukung ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Beberapa pesan belum terkirim" "Maaf, terjadi kesalahan" - "Pengirim peristiwa tidak cocok dengan pemilik perangkat yang mengirimnya." - "Keaslian pesan terenkripsi ini tidak dapat dijamin pada perangkat ini." - "Dienkripsi oleh pengguna yang telah diverifikasi sebelumnya." - "Tidak dienkripsi." - "Dienkripsi oleh perangkat yang tidak dikenal atau dihapus." - "Dienkripsi oleh perangkat yang tidak diverifikasi oleh pemiliknya." - "Dienkripsi oleh pengguna yang tidak terverifikasi." "🔐️ Bergabunglah dengan saya di %1$s" "Hai, bicaralah dengan saya di %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index c309732f11..c8f5b41130 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -74,7 +74,7 @@ "Copia collegamento al messaggio" "Copia testo" "Crea" - "Crea una stanza" + "Crea una stanza" "Disattiva" "Disattiva account" "Rifiuta" @@ -414,13 +414,6 @@ Sei sicuro di voler continuare?" "Alcuni caratteri non sono consentiti. Sono supportate solo lettere, cifre e i seguenti simboli ! $ & \'() * +/; =? @ [] - . _" "Alcuni messaggi non sono stati inviati" "Siamo spiacenti, si è verificato un errore" - "Il mittente dell\'evento non corrisponde al proprietario del dispositivo che lo ha inviato." - "L\'autenticità di questo messaggio cifrato non può essere garantita su questo dispositivo." - "Cifrato da un utente precedentemente verificato." - "Non cifrato." - "Cifrato da un dispositivo sconosciuto o eliminato." - "Cifrato da un dispositivo non verificato dal proprietario." - "Cifrato da un utente non verificato." "🔐️ Unisciti a me su %1$s" "Ehi, parliamo su %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-ka/translations.xml b/libraries/ui-strings/src/main/res/values-ka/translations.xml index 0a7b612e55..b7a8819391 100644 --- a/libraries/ui-strings/src/main/res/values-ka/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ka/translations.xml @@ -46,7 +46,7 @@ "ბმულის კოპირება" "დააკოპირეთ შეტყობინების ბმული" "შექმნა" - "ოთახის შექმნა" + "ოთახის შექმნა" "უარყოფა" "გამოკითხვის წაშლა" "გამორთვა" diff --git a/libraries/ui-strings/src/main/res/values-ko/translations.xml b/libraries/ui-strings/src/main/res/values-ko/translations.xml index 2ea2017c76..982b774141 100644 --- a/libraries/ui-strings/src/main/res/values-ko/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ko/translations.xml @@ -72,7 +72,7 @@ "메시지에 링크 복사" "텍스트 복사" "만들기" - "방 만들기" + "방 만들기" "비활성화" "계정 비활성화" "거절" @@ -388,13 +388,6 @@ "일부 문자는 허용되지 않습니다. 로마자, 숫자 및 다음 기호만 지원됩니다! $ &amp; ' ( ) * + / ; = ? @ [ ] - . _" "일부 메시지가 전송되지 않았습니다" "이런, 오류가 발생했어요" - "이벤트의 발신자가 이벤트를 보낸 장치의 소유자와 일치하지 않습니다." - "이 장치에서는 이 암호화된 메시지의 진위 여부를 보장할 수 없습니다." - "이전에 검증된 사용자에 의해 암호화되었습니다." - "암호화되지 않음." - "알 수 없거나 삭제된 장치에 의해 암호화됩니다." - "소유자가 확인하지 않은 장치에 의해 암호화되었습니다." - "검증되지 않은 사용자에 의해 암호화되었습니다." "🔐️ %1$s에 참여하기" "%1$s에서 대화해요: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-lt/translations.xml b/libraries/ui-strings/src/main/res/values-lt/translations.xml index 90aed6b6f8..437f21a552 100644 --- a/libraries/ui-strings/src/main/res/values-lt/translations.xml +++ b/libraries/ui-strings/src/main/res/values-lt/translations.xml @@ -18,7 +18,7 @@ "Kopijuoti" "Kopijuoti nuorodą" "Sukurti" - "Kurti kambarį" + "Kurti kambarį" "Atmesti" "Išjungti" "Atlikta" diff --git a/libraries/ui-strings/src/main/res/values-nb/translations.xml b/libraries/ui-strings/src/main/res/values-nb/translations.xml index 05673571c6..cad286d988 100644 --- a/libraries/ui-strings/src/main/res/values-nb/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nb/translations.xml @@ -74,7 +74,7 @@ "Kopier lenke til melding" "Kopier tekst" "Opprett" - "Opprett et rom" + "Opprett rom" "Deaktiver" "Deaktiver kontoen" "Avslå" @@ -112,6 +112,7 @@ "Last inn mer" "Administrer konto" "Administrer enheter" + "Administrer rom" "Melding" "Minimer" "Neste" @@ -411,13 +412,6 @@ Er du sikker på at du vil fortsette?" "Noen tegn er ikke tillatt. Bare bokstaver, sifre og følgende symboler støttes! $ & \'() * +/; =? @ [] - . _" "Noen meldinger er ikke sendt" "Beklager, det oppstod en feil" - "Avsenderen av hendelsen samsvarer ikke med eieren av enheten som sendte den." - "Ektheten til denne krypterte meldingen kan ikke garanteres på denne enheten." - "Kryptert av en tidligere verifisert bruker." - "Ikke kryptert." - "Kryptert av en ukjent eller slettet enhet." - "Kryptert med en enhet som ikke er verifisert av eieren." - "Kryptert av en ubekreftet bruker." "🔐️ Bli med meg på %1$s" "Hei, snakk med meg på %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-nl/translations.xml b/libraries/ui-strings/src/main/res/values-nl/translations.xml index 790363ba54..b9675f8f76 100644 --- a/libraries/ui-strings/src/main/res/values-nl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nl/translations.xml @@ -69,7 +69,7 @@ "Kopieer link naar bericht" "Tekst kopiëren" "Aanmaken" - "Creëer een kamer" + "Creëer een kamer" "Sluiten" "Account sluiten" "Weigeren" @@ -317,12 +317,6 @@ Reden: %1$s." "%1$s heeft geen toegang tot je microfoon. Schakel toegang in om een spraakbericht op te nemen." "Sommige berichten zijn niet verzonden" "Sorry, er is een fout opgetreden" - "De echtheid van dit versleutelde bericht kan op dit apparaat niet worden gegarandeerd." - "Versleuteld door een eerder geverifieerde gebruiker." - "Niet versleuteld." - "Versleuteld door een onbekend of verwijderd apparaat." - "Versleuteld door een apparaat dat niet is geverifieerd door de eigenaar." - "Versleuteld door een niet-geverifieerde gebruiker." "🔐️ Sluit je bij mij aan op %1$s" "Hé, praat met me op %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-pl/translations.xml b/libraries/ui-strings/src/main/res/values-pl/translations.xml index aafac63332..bd6eaab990 100644 --- a/libraries/ui-strings/src/main/res/values-pl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pl/translations.xml @@ -76,7 +76,7 @@ "Kopiuj link do wiadomości" "Kopiuj tekst" "Utwórz" - "Utwórz pokój" + "Utwórz pokój" "Dezaktywuj" "Dezaktywuj konto" "Odrzuć" @@ -420,13 +420,6 @@ Czy na pewno chcesz kontynuować?" "Niektóre znaki są niedozwolone. Obsługiwane są tylko litery, cyfry i następujące symbole ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Niektóre wiadomości nie zostały wysłane" "Przepraszamy, wystąpił błąd" - "Nadawca zdarzenia nie pasuje do właściciela urządzenia, które je wysłał." - "Autentyczność tej wiadomości szyfrowanej nie jest gwarantowana na tym urządzeniu." - "Zaszyfrowane przez wcześniej zweryfikowanego użytkownika." - "Nieszyfrowany." - "Zaszyfrowana przez nieznane lub usunięte urządzenie." - "Zaszyfrowana przez urządzenie niezweryfikowane przez jego właściciela." - "Zaszyfrowana przez niezweryfikowanego użytkownika." "🔐️ Dołącz do mnie na %1$s" "Hej, porozmawiajmy na %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml index 3c87408694..49351c6b64 100644 --- a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml @@ -56,6 +56,7 @@ "Seu avatar" "Aceitar" "Adicionar legenda" + "Adicionar salas existentes" "Adicionar à linha do tempo" "Voltar" "Chamar" @@ -74,7 +75,7 @@ "Copiar link para a mensagem" "Copiar texto" "Criar" - "Criar uma sala" + "Criar sala" "Criar espaço" "Desativar" "Desativar conta" @@ -92,6 +93,7 @@ "Ativar" "Encerrar enquete" "Digitar PIN" + "Explorar espaços públicos" "Concluir" "Esqueceu a senha?" "Encaminhar" @@ -292,6 +294,7 @@ Motivo:​ %1$s." "Motivo" "Chave de recuperação" "Recarregando…" + "Removendo…" "%1$d resposta" "%1$d respostas" @@ -301,6 +304,7 @@ Motivo:​ %1$s." "Relatar um problema" "Relatório enviado" "Editor de rich text" + "Cargo" "Sala" "Nome da sala" "por exemplo, o nome do seu projeto" @@ -316,6 +320,10 @@ Motivo:​ %1$s." "Segurança" "Visto por" "Selecionar uma conta" + + "%1$d selecionado" + "%1$d selecionados" + "Enviar para" "Enviando…" "Envio falhou" @@ -378,6 +386,8 @@ Motivo:​ %1$s." "Aguardando…" "Aguardando por esta mensagem" "Você" + "%1$s (%2$s) compartilhou esta mensagem já que você não estava na sala quando foi enviada." + "%1$s compartilhou esta mensagem já que você não estava na sala quando foi enviada." "Esta sala foi configurada para que membros novos possam ler o histórico. %1$s" "A identidade de %1$s foi redefinida. %2$s" "A identidade de %1$s %2$s foi redefinida. %3$s" @@ -423,13 +433,6 @@ Você tem certeza de que deseja continuar?" "Alguns caracteres não são permitidos. Somente letras, dígitos e os seguintes símbolos são aceitos ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Algumas mensagens não foram enviadas" "Desculpe, ocorreu um erro" - "O remetente do evento não corresponde com o proprietário do dispositivo que o enviou." - "A autenticidade desta mensagem criptografada não pode ser garantida neste aparelho." - "Criptografado por um usuário verificado previamente." - "Não criptografado." - "Criptografado por um dispositivo desconhecido ou apagado." - "Criptografado por um dispositivo que não foi verificado pelo seu dono." - "Criptografado por um usuário não verificado." "🔐️ Junte-se a mim no %1$s" "Ei, fale comigo em %1$s: %2$s" "%1$s (Android)" @@ -477,6 +480,7 @@ Você tem certeza de que deseja continuar?" "Compartilhar esta localização" "Os espaços que você criou ou entrou." "%1$s • %2$s" + "Crie espaços para organizar salas" "Espaço %1$s" "Espaços" "Mensagem não enviada porque a identidade verificada de %1$s foi redefinida." diff --git a/libraries/ui-strings/src/main/res/values-pt/translations.xml b/libraries/ui-strings/src/main/res/values-pt/translations.xml index 019f2e6ac4..b6305c0544 100644 --- a/libraries/ui-strings/src/main/res/values-pt/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt/translations.xml @@ -74,7 +74,7 @@ "Copiar ligação da mensagem" "Copiar texto" "Criar" - "Criar uma sala" + "Criar uma sala" "Desativar" "Desativar conta" "Recusar" @@ -407,13 +407,6 @@ Tens a certeza de que queres continuar?" "Alguns caracteres não são permitidos. Apenas letras, dígitos e os seguintes símbolos são suportados! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Algumas mensagens não foram enviadas" "Pedimos desculpa, ocorreu um erro desconhecido" - "O remetente deste evento não é o dono do dispositivo que o enviou." - "A autenticidade desta mensagem cifrada não pode ser garantida neste dispositivo." - "Criptografado por um usuário verificado anteriormente." - "Não cifrado." - "Cifragem com origem num dispositivo eliminado ou desconhecido." - "Cifragem com origem num dispositivo não verificado pelo seu dono." - "Cifragem com origem num utilizador não verificado." "🔐️ Junta-te a mim na %1$s" "Alô! Fala comigo na %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index 0851be7648..652a285961 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -76,7 +76,7 @@ "Copiați linkul către mesaj" "Copiați textul" "Creați" - "Creați o cameră" + "Creați o cameră" "Dezactivați" "Dezactivați contul" "Refuzați" @@ -427,13 +427,6 @@ Sunteți sigur că doriți să continuați?" "Unele caractere nu sunt permise. Sunt acceptate doar literele, cifrele și următoarele simboluri ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Unele mesaje nu au fost trimise" "Ne pare rău, a apărut o eroare" - "Expeditorul evenimentului nu corespunde proprietarului dispozitivului care l-a trimis." - "Autenticitatea acestui mesaj criptat nu poate fi garantată pe acest dispozitiv." - "Criptat de un utilizator verificat anterior." - "Necriptat" - "Criptat de un dispozitiv necunoscut sau șters." - "Criptat de un dispozitiv care nu este verificat de proprietarul său." - "Criptat de un utilizator neverificat." "🔐️ Alăturați-vă mie în %1$s" "Hei, vorbește cu mine pe %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index 1078a92fd6..1d4379d9a2 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -58,6 +58,7 @@ "Ваш аватар" "Разрешить" "Добавить подпись" + "Добавить существующие комнаты" "Добавить в хронологию" "Назад" "Позвонить" @@ -76,7 +77,8 @@ "Скопировать ссылку в сообщение" "Копировать текст" "Создать" - "Создать комнату" + "Создать комнату" + "Создать пространство" "Отключить" "Отключить учётную запись" "Отклонить" @@ -164,6 +166,7 @@ "Нажмите, чтобы загрузить карту" "Сделать фото" "Нажмите для просмотра вариантов" + "Перевод" "Повторить попытку" "Открепить" "Просмотр" @@ -193,6 +196,7 @@ "Скопировано в буфер обмена" "Авторское право" "Создание комнаты…" + "Создание пространства…" "Запрос отменен" "Покинул комнату" "Покинуть пространство" @@ -238,6 +242,7 @@ "Светлое" "Строка скопирована в буфер обмена" "Ссылка скопирована в буфер обмена" + "Привязать новое устройство" "Загрузка…" "Загрузить больше…" @@ -252,10 +257,12 @@ "Сообщение" "Действия с сообщением" + "Ошибка отправки сообщения" "Оформление сообщения" "Сообщение удалено" "Современный" "Выкл. звук" + "Имя" "%1$s (%2$s)" "Ничего не найдено" "Название комнаты отсутствует" @@ -291,6 +298,7 @@ "Причина" "Ключ восстановления" "Обновление…" + "Удаление…" "%1$d ответ" "%1$d ответа" @@ -317,6 +325,11 @@ "Безопасность" "Просмотрено" "Выберите учетную запись" + + "%1$d выбран" + "%1$d выбрано" + "%1$d выбрано" + "Отправить" "Отправка…" "Сбой отправки" @@ -333,6 +346,7 @@ "Что-то пошло не так" "Мы столкнулись с проблемой. Пожалуйста, попробуйте еще раз." "Пространство" + "О чём это пространство?" "%1$d Пространство" "%1$d Пространств" @@ -341,6 +355,7 @@ "Чат запускается…" "Стикер" "Успешно" + "Рекомендуемые" "Предложения" "Синхронизация" "Системное" @@ -378,6 +393,8 @@ "Ожидание…" "Ожидание ключа расшифровки" "Вы" + "%1$s поделился этим сообщением, поскольку вас не было в комнате, когда оно было отправлено." + "Эта комната оборудована таким образом, чтобы новые участники могли ознакомиться с историей. %1$s" "Идентификатор %1$s изменился. %2$s" "Пользователь %1$s сменил имя на %2$s. %3$s" "(%1$s)" @@ -422,13 +439,6 @@ "Некоторые символы не допускаются. Поддерживаются только буквы, цифры и следующие символы! $ & \'() * +/; =? @ [] - . _" "Некоторые сообщения не были отправлены" "Извините, произошла ошибка" - "Отправитель события и владелец устройства не совпадают." - "Подлинность этого зашифрованного сообщения не может быть гарантирована на этом устройстве." - "Зашифровано ранее проверенным пользователем." - "Не зашифровано." - "Зашифровано неизвестным или удаленным устройством." - "Зашифровано устройством, не проверенным его владельцем." - "Зашифровано непроверенным пользователем." "🔐️ Присоединяйтесь ко мне в %1$s" "Привет, поговори со мной по %1$s: %2$s" "%1$s Android" @@ -477,6 +487,7 @@ "Поделиться этим местоположением" "Пространства, которые вы создали или к которым присоединились." "%1$s • %2$s" + "Создавайте пространства для организации комнат" "%1$s пространство" "Пространства" "Сообщение не отправлено, потому что подтвержденная личность %1$s была сброшена." diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index e209bd0a21..2d805a0272 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -58,6 +58,7 @@ "Váš profilový obrázok" "Prijať" "Pridať titulok" + "Pridať existujúce miestnosti" "Pridať na časovú os" "Späť" "Zavolať" @@ -76,7 +77,8 @@ "Kopírovať odkaz do správy" "Kopírovať text" "Vytvoriť" - "Vytvoriť miestnosť" + "Vytvoriť miestnosť" + "Vytvoriť priestor" "Deaktivovať" "Deaktivovať účet" "Odmietnuť" @@ -93,6 +95,7 @@ "Povoliť" "Ukončiť anketu" "Zadajte PIN" + "Preskúmajte verejné priestory" "Dokončiť" "Zabudnuté heslo?" "Preposlať" @@ -164,6 +167,7 @@ "Ťuknutím načítate mapu" "Urobiť fotku" "Klepnutím získate možnosti" + "Preložiť" "Skúste to znova" "Odopnúť" "Zobraziť" @@ -193,6 +197,7 @@ "Skopírované do schránky" "Autorské práva" "Vytváranie miestnosti…" + "Vytváranie priestoru…" "Žiadosť bola zrušená" "Opustil/a miestnosť" "Opustil priestor" @@ -238,6 +243,7 @@ Dôvod: %1$s." "Svetlý" "Riadok skopírovaný do schránky" "Odkaz bol skopírovaný do schránky" + "Prepojiť nové zariadenie" "Načítava sa…" "Načítava sa viac…" @@ -257,6 +263,7 @@ Dôvod: %1$s." "Správa odstránená" "Moderné" "Stlmiť" + "Meno" "%1$s (%2$s)" "Žiadne výsledky" "Žiadny názov miestnosti" @@ -292,6 +299,7 @@ Dôvod: %1$s." "Dôvod" "Kľúč na obnovenie" "Obnovuje sa…" + "Odstraňuje sa…" "%1$d odpoveď" "%1$d odpovede" @@ -302,6 +310,7 @@ Dôvod: %1$s." "Nahlásiť problém" "Nahlásenie bolo odoslané" "Rozšírený textový editor" + "Rola" "Miestnosť" "Názov miestnosti" "napr. názov vášho projektu" @@ -318,6 +327,11 @@ Dôvod: %1$s." "Bezpečnosť" "Videné" "Vyberte účet" + + "%1$d vybraná" + "%1$d vybrané" + "%1$d vybraných" + "Odoslať" "Odosiela sa…" "Odoslanie zlyhalo" @@ -334,6 +348,7 @@ Dôvod: %1$s." "Niečo sa pokazilo" "Vyskytol sa problém. Skúste to prosím znova." "Priestor" + "O čom je tento priestor?" "%1$d priestor" "%1$d priestory" @@ -342,6 +357,7 @@ Dôvod: %1$s." "Spustenie konverzácie…" "Nálepka" "Úspech" + "Navrhované" "Návrhy" "Synchronizuje sa" "Systém" @@ -379,7 +395,9 @@ Dôvod: %1$s." "Čaká sa…" "Čaká sa na dešifrovací kľúč" "Vy" - "Správy, ktoré odošlete, budú zdieľané s novými členmi pozvanými do tejto miestnosti. %1$s" + "%1$s (%2$s ) zdieľal túto správu v čase, keď ste neboli v miestnosti, keď bola odoslaná." + "%1$s zdieľal túto správu v čase, keď ste neboli v miestnosti, keď bola odoslaná." + "Táto miestnosť bola nakonfigurovaná tak, aby si noví členovia mohli prečítať históriu. %1$s" "Totožnosť používateľa %1$s sa obnovila.%2$s" "Totožnosť používateľa %1$s %2$s bola obnovená. %3$s" "(%1$s)" @@ -424,13 +442,6 @@ Naozaj chcete pokračovať?" "Niektoré znaky nie sú povolené. Podporované sú iba písmená, číslice a nasledujúce symboly ! $ & \'() * +/; =? @ [] - . _" "Niektoré správy neboli odoslané" "Prepáčte, vyskytla sa chyba" - "Odosielateľ udalosti sa nezhoduje s vlastníkom zariadenia, ktoré ju odoslalo." - "Pravosť tejto šifrovanej správy nie je možné zaručiť na tomto zariadení." - "Šifrované predtým overeným používateľom." - "Nie je šifrované." - "Zašifrované neznámym alebo odstráneným zariadením." - "Šifrované zariadením, ktoré nie je overené jeho majiteľom." - "Šifrované neovereným používateľom." "🔐️ Pripojte sa ku mne na %1$s" "Ahoj, porozprávajte sa so mnou na %1$s: %2$s" "%1$s Android" @@ -479,6 +490,7 @@ Naozaj chcete pokračovať?" "Zdieľajte túto polohu" "Priestory, ktoré ste vytvorili alebo ku ktorým ste sa pripojili." "%1$s • %2$s" + "Vytvorte priestory na usporiadanie miestností" "%1$s priestor" "Priestory" "Správa nebola odoslaná, pretože sa zmenila overená totožnosť používateľa %1$s." diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index 7d84b11d87..e40bc1febb 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -72,7 +72,7 @@ "Kopiera länk till meddelande" "Kopiera text" "Skapa" - "Skapa ett rum" + "Skapa ett rum" "Inaktivera" "Inaktivera konto" "Neka" @@ -392,13 +392,6 @@ Anledning:%1$s." "Vissa tecken är inte tillåtna. Endast bokstäver, siffror och följande symboler stöds ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Vissa meddelanden har inte skickats" "Tyvärr, ett fel uppstod" - "Avsändaren av händelsen matchar inte ägaren av den enhet som skickade den." - "Detta krypterade meddelandes äkthet kan inte garanteras på den här enheten." - "Krypterat av en tidigare verifierad användare." - "Inte krypterad." - "Krypterad av en okänd eller raderad enhet." - "Krypterad av en enhet som inte verifierats av ägaren." - "Krypterad av en overifierad användare." "🔐️ Häng med mig på %1$s" "Hallå, prata med mig på %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-tr/translations.xml b/libraries/ui-strings/src/main/res/values-tr/translations.xml index b7b0bc841d..26de6080dd 100644 --- a/libraries/ui-strings/src/main/res/values-tr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-tr/translations.xml @@ -54,7 +54,7 @@ "Bağlantıyı mesaja kopyala" "Metni kopyala" "Oluştur" - "Bir oda oluştur" + "Bir oda oluştur" "Devre dışı bırak" "Hesabı devre dışı bırak" "Reddet" @@ -327,12 +327,6 @@ Devam etmek istediğinizden emin misiniz?" "Bazı karakterlere izin verilmez. Yalnızca harfler, rakamlar ve aşağıdaki semboller desteklenir ! $ &amp; \' ( ) * + / ; = ? @ [ ] - . _" "Bazı mesajlar gönderilmedi" "Üzgünüz, bir hata oluştu" - "Bu şifrelenmiş mesajın doğruluğu bu cihazda garanti edilemez." - "Daha önce doğrulanmış bir kullanıcı tarafından şifrelenmiştir." - "Şifrelenmemiş." - "Bilinmeyen veya silinmiş bir cihaz tarafından şifrelenmiştir." - "Sahibi tarafından doğrulanmamış bir cihaz tarafından şifrelenmiştir." - "Doğrulanmamış bir kullanıcı tarafından şifrelenmiştir." "🔐️ Bana katılın %1$s" "Hey, benimle konuş %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-uk/translations.xml b/libraries/ui-strings/src/main/res/values-uk/translations.xml index 0b1083ea1a..a3eeef404c 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -76,7 +76,7 @@ "Скопіювати посилання на повідомлення" "Скопіювати текст" "Створити" - "Створити кімнату" + "Створити кімнату" "Деактивувати" "Деактивувати обліковий запис" "Відхилити" @@ -407,13 +407,6 @@ "Деякі символи не допускаються. Підтримуються тільки букви, цифри і наступні символи! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Деякі повідомлення не були надіслані" "Вибачте, сталася помилка" - "Відправник події не збігається з власником пристрою, який його надіслав." - "Автентичність цього зашифрованого повідомлення не може бути гарантована на цьому пристрої." - "Зашифровано попередньо перевіреним користувачем." - "Не зашифровано." - "Зашифровано невідомим або видаленим пристроєм." - "Зашифровано пристроєм, який не верифіковано його власником." - "Зашифровано неверифікованим користувачем." "🔐️ Приєднуйтеся до мене в %1$s" "Вітаю, поспілкуйтеся зі мною в %1$s: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-ur/translations.xml b/libraries/ui-strings/src/main/res/values-ur/translations.xml index 18d8b49ed5..8e9c0fb3be 100644 --- a/libraries/ui-strings/src/main/res/values-ur/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ur/translations.xml @@ -52,7 +52,7 @@ "ربط نقل کریں" "پیغام کا ربط نقل کریں" "تخلیق کریں" - "ایک کمرہ بنائیں" + "ایک کمرہ بنائیں" "غیر فعال کریں" "اکاؤنٹ کو غیر فعال کریں" "مسترد کریں" @@ -282,12 +282,6 @@ "%1$s کو آپ کے صوتگر تک رسائی حاصل کرنے کی اجازت نہیں ہے۔ صوتی پیغام ثبت کرنے کیلئے رسائی فعال کریں۔" "کچھ پیغامات ارسال نہیں ہوئے" "معذرت، ایک خرابی واقع ہوگئی" - "اس آلہ پر اس مرموز کردہ پیغام کی صداقت کی ضمانت نہیں دی جا سکتی۔" - "پہلے سے تصدیق شدہ صارف کے ذریعہ خفیہ کردہ۔" - "مرموز کردہ نہیں۔" - "کسی نامعلوم یا حذف شدہ آلے کے ذریعے مرموز کردہ۔" - "کسی ایسے آلے کے ذریعے مرموز کردہ جس کی توثیق اسکے مالک سے نہیں ہوئی۔" - "ایک غیر توثیق شدہ صارف کے ذریعے مرموز کردہ۔" "🔐️ %1$s پر میرے ساتھ شامل ہوں" "ارے، مجھ سے %1$s پر بات کریں: %2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-uz/translations.xml b/libraries/ui-strings/src/main/res/values-uz/translations.xml index 5d5b7ea4d4..22fa0a173a 100644 --- a/libraries/ui-strings/src/main/res/values-uz/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uz/translations.xml @@ -74,7 +74,7 @@ "Havolani xabaraga nusxalash" "Matnni nusxalash" "Yaratmoq" - "Xonani yaratish" + "Xonani yaratish" "Faolsizlantirish" "Hisobni faolsizlantirish" "Rad etish" @@ -401,13 +401,6 @@ Davom etasizmi?" "Ayrim belgilarga ruxsat berilmagan. Faqat harf, raqam va quyidagi belgilar ishlaydi! $ &’() * + /; =? @ [ ] -. _" "Bazi xabarlar yuborilmagan" "Kechirasiz, xatolik yuz berdi" - "Hodisani yuborgan shaxs uni yuborgan qurilmaning egasi bilan mos kelmaydi." - "Bu qurilmada shifrlangan xabarning haqiqiyligini kafolatlash imkonsiz." - "Avval tasdiqlangan foydalanuvchi tomonidan shifrlangan." - "Shifrlanmagan" - "Nomaʼlum yoki oʻchirib tashlangan qurilma tomonidan shifrlangan." - "Egasi tasdiqlamagan qurilma tomonidan shifrlangan." - "Tasdiqlanmagan foydalanuvchi tomonidan shifrlangan." "🔐️ Menga qo\'shiling%1$s" "Hey, men bilan gaplash%1$s :%2$s" "%1$sAndroid" diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index e70937357d..b0989f7d59 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -72,7 +72,7 @@ "複製訊息連結" "複製文字" "建立" - "建立聊天室" + "建立聊天室" "停用" "停用帳號" "拒絕" @@ -406,13 +406,6 @@ "不允許使用部份字元。僅支援字母、數字與以下符號 ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "有些訊息尚未傳送" "抱歉,發生錯誤" - "事件的傳送者與傳送該事件的裝置擁有者不相符。" - "無法在此裝置上保證此加密訊息的真實性。" - "由先前驗證的使用者加密。" - "未加密。" - "由未知或已刪除的裝置加密。" - "由未經其擁有者驗證的裝置加密。" - "由未經驗證的使用者加密。" "🔐️ 在 %1$s 上加入我" "嘿,來 %1$s 和我聊天:%2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values-zh/translations.xml b/libraries/ui-strings/src/main/res/values-zh/translations.xml index 7d63c0ccb9..37a39449ac 100644 --- a/libraries/ui-strings/src/main/res/values-zh/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh/translations.xml @@ -72,7 +72,7 @@ "复制消息链接" "复制文本" "创建" - "创建聊天室" + "创建聊天室" "停用" "停用账户" "拒绝" @@ -405,13 +405,6 @@ "不允许使用某些字符。仅支持字母、数字和以下符号 $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "某些信息尚未发送" "抱歉,发生了错误" - "事件发送者与发送设备的所有者不匹配。" - "此加密消息的真实性无法在此设备上保证。" - "由先前验证过的用户加密。" - "未加密。" - "由未知或已删除的设备加密。" - "由未经其所有者验证的设备加密。" - "由未经验证的用户加密。" "🔐️ 加入我 %1$s" "嗨!请通过 %1$s 与我联系:%2$s" "%1$s Android" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 70dd613526..e0e1af7bf3 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -75,7 +75,7 @@ "Copy link to message" "Copy text" "Create" - "Create a room" + "Create room" "Create space" "Deactivate" "Deactivate account" @@ -93,6 +93,7 @@ "Enable" "End poll" "Enter PIN" + "Explore public spaces" "Finish" "Forgot password?" "Forward" @@ -293,6 +294,7 @@ Reason: %1$s." "Reason" "Recovery key" "Refreshing…" + "Removing…" "%1$d reply" "%1$d replies" @@ -302,6 +304,7 @@ Reason: %1$s." "Report a problem" "Report submitted" "Rich text editor" + "Role" "Room" "Room name" "e.g. your project name" @@ -331,6 +334,7 @@ Reason: %1$s." "Server URL" "Settings" "Share space" + "New members see history" "Shared location" "Shared space" "Signing out" @@ -430,13 +434,6 @@ Are you sure you want to continue?" "Some characters are not allowed. Only letters, digits and the following symbols are supported ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" "Some messages have not been sent" "Sorry, an error occurred" - "The sender of the event does not match the owner of the device that sent it." - "The authenticity of this encrypted message can\'t be guaranteed on this device." - "Encrypted by a previously-verified user." - "Not encrypted." - "Encrypted by an unknown or deleted device." - "Encrypted by a device not verified by its owner." - "Encrypted by an unverified user." "🔐️ Join me on %1$s" "Hey, talk to me on %1$s: %2$s" "%1$s Android" @@ -482,7 +479,6 @@ Are you sure you want to continue?" "Open in Google Maps" "Open in OpenStreetMap" "Share this location" - "Adding a room will not affect the room access. To change the access go to Room info > Privacy & security." "Spaces you have created or joined." "%1$s • %2$s" "Create spaces to organize rooms" diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 4b5f9c9c50..3c9d6d8c19 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -45,7 +45,7 @@ private const val versionMonth = 1 * Release number in the month. Value must be in [0,99]. * Do not update this value. it is updated by the release script. */ -private const val versionReleaseNumber = 1 +private const val versionReleaseNumber = 2 object Versions { /** diff --git a/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt b/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt index b9cf87e233..6530a9f635 100644 --- a/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt +++ b/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt @@ -31,9 +31,6 @@ fun Project.setupDependencyInjection( // Apply Metro plugin and configure it applyPluginIfNeeded(libs.plugins.metro) - val metroExtension = extensions.getByName("metro") as MetroPluginExtension - metroExtension.contributesAsInject.value(true) - if (generateNodeFactories) { applyPluginIfNeeded(libs.plugins.ksp) diff --git a/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_3_de.png b/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_3_de.png new file mode 100644 index 0000000000..189800d372 --- /dev/null +++ b/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2861660cc598703ec8c687919fcd8cea070e36855b1e0a367fa0ed2408567843 +size 28133 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png index 0402613534..203a92c27f 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a5b7c5d7fa9a6f9156f25aa565944028149a7d2ab9f418a0b02a17d948a6768 -size 9415 +oid sha256:c68daca4f249aa40d4d9127cbfac1fcc7918bda3d719f29e67e465761c7a914d +size 37745 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png index 999bb266e8..c0356cae76 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53643845f3b082175ed3a33e0d8fd6edc4006cbe74a0c9db7a7100c2351acc4d -size 21744 +oid sha256:710e9c038f61e8393c91ea0197f9aa8abe28d69e161fa3138485c4f7a977cf72 +size 50773 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png index 999bb266e8..c0356cae76 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53643845f3b082175ed3a33e0d8fd6edc4006cbe74a0c9db7a7100c2351acc4d -size 21744 +oid sha256:710e9c038f61e8393c91ea0197f9aa8abe28d69e161fa3138485c4f7a977cf72 +size 50773 diff --git a/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png index a9a792c0df..1b6dbbfe37 100644 --- a/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7880e4b1e3dc8e2dc15b5f07ae82f5902145f35a9045af0f39feca4e1ce8cb00 -size 57904 +oid sha256:2bb046e5bcaacf32fcbb0f14f12e916215946e8fc9c4588f996d1be5a6737535 +size 85443 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_de.png index b0e3928a6c..62531d4a8e 100644 --- a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_de.png +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4da0c481bf69057e27beb209e281b5d3feb0c474bb7b145c4ea6e4c1193b9d76 -size 45544 +oid sha256:c9a0620d33542470c7f40ddc4855ecac79a2675cf3751578c62f088680dd4c71 +size 48730 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_de.png index 45e07ce005..23f45f69f5 100644 --- a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_de.png +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34ad9afc2f63d8d25263ec095bec2eca2b9c5eb9c0a2baf0e500e8f06fafd393 -size 30220 +oid sha256:0171264553b396860014eb34419845c43cb13aace5179fbddefe5f0266146acf +size 28253 diff --git a/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_0_de.png b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_0_de.png new file mode 100644 index 0000000000..7e2c2cc09e --- /dev/null +++ b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea8dc14eefab6c29a57a5e4d759425462ccaabdabfdb7be32b614273b7fab8bb +size 44340 diff --git a/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_1_de.png b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_1_de.png new file mode 100644 index 0000000000..24855ed212 --- /dev/null +++ b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3b9fec5f7660ac61e414b4d349bf20dc3a0ece02ccd7a8bbc97737e0320b14b +size 14553 diff --git a/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_2_de.png b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_2_de.png new file mode 100644 index 0000000000..600c0ca6fb --- /dev/null +++ b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46e9d744b477d3497a212383df8adcb219c4b0e5d0d9599d980e1e05a24f1610 +size 24045 diff --git a/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_3_de.png b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_3_de.png new file mode 100644 index 0000000000..878900ca93 --- /dev/null +++ b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85e670906cce0713c4b7e15c5a47d95f5ebb79d1127cd1228078284ef8de316c +size 18504 diff --git a/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_4_de.png b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_4_de.png new file mode 100644 index 0000000000..125077b32e --- /dev/null +++ b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad2245f61b17f96d9c8be88480100fa8ea30aba13a7fa652771c41c48447ff8b +size 48682 diff --git a/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_5_de.png b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_5_de.png new file mode 100644 index 0000000000..a56a67a41d --- /dev/null +++ b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:040fcb89307c02b6b91dc16c9258594eecd19a084d88ea6cdb254ac019c65119 +size 34329 diff --git a/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_6_de.png b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_6_de.png new file mode 100644 index 0000000000..f90caafb75 --- /dev/null +++ b/screenshots/de/features.space.impl.addroom_AddRoomToSpaceView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14320cb692b1442256bca8038083446c94e69c1466c3236158687345bfcab4e8 +size 49215 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png index 66bcb88c50..fdc9aef8c4 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6efe4f355c6372ad031136f6f2de51c3c6132e18ddc70f558c16ee921befefa0 -size 34819 +oid sha256:c3b9c9488c76261bde5ae405b6c8a08803609b2bc307d286ffbfbb039e0b22f5 +size 33231 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png index 71f98c9def..f7ce51b24e 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3af3d6c1c57c37f88f37624614a1bca7e9fcae0042482d293f2eef69a7350713 -size 35864 +oid sha256:3c919711998f143e1c226551c0a985e15aa620e593cee432405ecb5f91e87648 +size 34325 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png index 68e0fdf009..2bab5b45c6 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65fab252bd1c0920692404f0067323bfb33df9124ba5dc3ad5be5008295fbc0f -size 36158 +oid sha256:f4d4763ca65811e025ac4a1dedd46f97c4dd23c3891c1a62c35b9fc48add7103 +size 34619 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png index 1c22e938cb..e16b91cb01 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecb9ad959737f80864480f7fa3cce8e31a1b0c2aa84d5b49ff8b119b1aa5bc1e -size 65190 +oid sha256:0426eca186ea0477bbd5a0ee519e8dde9124d0afa3e8cabb06688badd24af46d +size 63688 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png index 52dd6c93a0..fe2d1b5f0f 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcdf0489810fe2035f072cdb5cc5e3c78cb3ee4e5d220148ed2721dbf324d7e6 -size 65805 +oid sha256:6d13ad4f724d1c1c08a90a5af3df4a776db1dc3fdbc99bc88e87e67924e7e01d +size 64315 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png index ff45615538..be59e3b20c 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a9166782d34d5388b8cdcdf270f8ac854bad44e24a30f35661af2a93cdbeb4a -size 60269 +oid sha256:eca9393172deeb61a1deecf7233b17f042a703651831f88fea051c189a120d26 +size 58781 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_6_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_6_de.png new file mode 100644 index 0000000000..d411088410 --- /dev/null +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01cac6159fa7bc2bc6a1ff5ed2fed3a8281073503d799de179af17436f668ef8 +size 37069 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_7_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_7_de.png new file mode 100644 index 0000000000..bae46c2a94 --- /dev/null +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e92d6b031cfc5189878dc367bac33970536b057c6c7956591b23ceaf4565e87e +size 37540 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_8_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_8_de.png new file mode 100644 index 0000000000..cf88d6f36c --- /dev/null +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:719783d1f82d9a9333f81a9bdd002c8c7290909f03a6b3f2d330a43c6542133f +size 52797 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index 3d2e829373..c332f363dc 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,80 +1,87 @@ // Generated file, do not edit export const screenshots = [ ["en","en-dark","de",], -["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20466,], +["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20476,], ["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_0_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_0_en",0,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_1_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_1_en",20466,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_2_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_2_en",20466,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_3_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_3_en",20466,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_4_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_4_en",20466,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_5_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_5_en",20466,], -["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20466,], -["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20466,], -["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20466,], -["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20466,], -["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20466,], -["features.login.impl.accountprovider_AccountProviderOtherView_Day_0_en","features.login.impl.accountprovider_AccountProviderOtherView_Night_0_en",20466,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_1_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_1_en",20476,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_2_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_2_en",20476,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_3_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_3_en",20476,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_4_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_4_en",20476,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_5_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_5_en",20476,], +["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20476,], +["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20476,], +["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20476,], +["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20476,], +["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20476,], +["features.login.impl.accountprovider_AccountProviderOtherView_Day_0_en","features.login.impl.accountprovider_AccountProviderOtherView_Night_0_en",20476,], ["features.login.impl.accountprovider_AccountProviderView_Day_0_en","features.login.impl.accountprovider_AccountProviderView_Night_0_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_1_en","features.login.impl.accountprovider_AccountProviderView_Night_1_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_2_en","features.login.impl.accountprovider_AccountProviderView_Night_2_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_3_en","features.login.impl.accountprovider_AccountProviderView_Night_3_en",0,], -["libraries.accountselect.impl_AccountSelectView_Day_0_en","libraries.accountselect.impl_AccountSelectView_Night_0_en",20466,], -["libraries.accountselect.impl_AccountSelectView_Day_1_en","libraries.accountselect.impl_AccountSelectView_Night_1_en",20466,], +["libraries.accountselect.impl_AccountSelectView_Day_0_en","libraries.accountselect.impl_AccountSelectView_Night_0_en",20476,], +["libraries.accountselect.impl_AccountSelectView_Day_1_en","libraries.accountselect.impl_AccountSelectView_Night_1_en",20476,], ["features.messages.impl.actionlist_ActionListViewContent_Day_0_en","features.messages.impl.actionlist_ActionListViewContent_Night_0_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20466,], +["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20476,], ["features.messages.impl.actionlist_ActionListViewContent_Day_1_en","features.messages.impl.actionlist_ActionListViewContent_Night_1_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20466,], -["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20466,], -["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20466,], -["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20466,], -["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20466,], -["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en","",20466,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en","",20466,], -["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20466,], -["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20466,], +["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20476,], +["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20476,], +["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20476,], +["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20476,], +["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20476,], +["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20476,], +["features.space.impl.addroom_AddRoomToSpaceView_Day_0_en","features.space.impl.addroom_AddRoomToSpaceView_Night_0_en",20479,], +["features.space.impl.addroom_AddRoomToSpaceView_Day_1_en","features.space.impl.addroom_AddRoomToSpaceView_Night_1_en",20479,], +["features.space.impl.addroom_AddRoomToSpaceView_Day_2_en","features.space.impl.addroom_AddRoomToSpaceView_Night_2_en",20479,], +["features.space.impl.addroom_AddRoomToSpaceView_Day_3_en","features.space.impl.addroom_AddRoomToSpaceView_Night_3_en",20479,], +["features.space.impl.addroom_AddRoomToSpaceView_Day_4_en","features.space.impl.addroom_AddRoomToSpaceView_Night_4_en",20479,], +["features.space.impl.addroom_AddRoomToSpaceView_Day_5_en","features.space.impl.addroom_AddRoomToSpaceView_Night_5_en",20479,], +["features.space.impl.addroom_AddRoomToSpaceView_Day_6_en","features.space.impl.addroom_AddRoomToSpaceView_Night_6_en",20479,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en","",20476,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en","",20476,], +["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20476,], +["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20476,], ["libraries.designsystem.theme.components_AllIcons_Icons_en","",0,], -["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20466,], -["features.analytics.impl_AnalyticsOptInView_Day_1_en","features.analytics.impl_AnalyticsOptInView_Night_1_en",20466,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20466,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en",20466,], -["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20466,], +["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20476,], +["features.analytics.impl_AnalyticsOptInView_Day_1_en","features.analytics.impl_AnalyticsOptInView_Night_1_en",20476,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20476,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en",20476,], +["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20476,], ["libraries.designsystem.components_Announcement_Day_0_en","libraries.designsystem.components_Announcement_Night_0_en",0,], -["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20466,], +["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20476,], ["libraries.designsystem.components.async_AsyncActionView_Day_0_en","libraries.designsystem.components.async_AsyncActionView_Night_0_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20466,], +["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20476,], ["libraries.designsystem.components.async_AsyncActionView_Day_2_en","libraries.designsystem.components.async_AsyncActionView_Night_2_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20466,], +["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20476,], ["libraries.designsystem.components.async_AsyncActionView_Day_4_en","libraries.designsystem.components.async_AsyncActionView_Night_4_en",0,], -["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20466,], +["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20476,], ["libraries.designsystem.components.async_AsyncIndicatorFailure_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorFailure_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncIndicatorLoading_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorLoading_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncLoading_Day_0_en","libraries.designsystem.components.async_AsyncLoading_Night_0_en",0,], -["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20466,], +["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20476,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_0_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_0_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_1_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_1_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_2_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_2_en",0,], @@ -84,19 +91,19 @@ export const screenshots = [ ["libraries.matrix.ui.components_AttachmentThumbnail_Day_6_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_6_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_7_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_7_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_8_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_8_en",0,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_0_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_1_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_2_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_3_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_4_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_5_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_6_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_7_en","",20466,], -["features.messages.impl.attachments.preview_AttachmentsPreviewView_8_en","",20466,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_0_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_1_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_2_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_3_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_4_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_5_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_6_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_7_en","",20476,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_8_en","",20476,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en",0,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en",0,], -["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20466,], +["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20476,], ["libraries.designsystem.components.avatar.internal_AvatarCluster_Avatars_en","",0,], ["libraries.matrix.ui.components_AvatarPickerSizes_Day_0_en","libraries.matrix.ui.components_AvatarPickerSizes_Night_0_en",0,], ["libraries.matrix.ui.components_AvatarPickerViewRtl_Day_0_en","libraries.matrix.ui.components_AvatarPickerViewRtl_Night_0_en",0,], @@ -126,22 +133,22 @@ export const screenshots = [ ["libraries.designsystem.modifiers_BackgroundVerticalGradientDisabled_Day_0_en","libraries.designsystem.modifiers_BackgroundVerticalGradientDisabled_Night_0_en",0,], ["libraries.designsystem.modifiers_BackgroundVerticalGradient_Day_0_en","libraries.designsystem.modifiers_BackgroundVerticalGradient_Night_0_en",0,], ["libraries.designsystem.components_Badge_Day_0_en","libraries.designsystem.components_Badge_Night_0_en",0,], -["features.home.impl.components_BatteryOptimizationBanner_Day_0_en","features.home.impl.components_BatteryOptimizationBanner_Night_0_en",20466,], +["features.home.impl.components_BatteryOptimizationBanner_Day_0_en","features.home.impl.components_BatteryOptimizationBanner_Night_0_en",20476,], ["libraries.designsystem.atomic.atoms_BetaLabel_Day_0_en","libraries.designsystem.atomic.atoms_BetaLabel_Night_0_en",0,], ["libraries.designsystem.components_BigIcon_Day_0_en","libraries.designsystem.components_BigIcon_Night_0_en",0,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20466,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20466,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20466,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20466,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20466,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20466,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20466,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20476,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20476,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20476,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20476,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20476,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20476,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20476,], ["libraries.designsystem.theme.components_BottomSheetDragHandle_Day_0_en","libraries.designsystem.theme.components_BottomSheetDragHandle_Night_0_en",0,], -["features.rageshake.impl.bugreport_BugReportViewDay_0_en","",20466,], -["features.rageshake.impl.bugreport_BugReportViewDay_1_en","",20466,], -["features.rageshake.impl.bugreport_BugReportViewDay_2_en","",20466,], -["features.rageshake.impl.bugreport_BugReportViewDay_3_en","",20466,], -["features.rageshake.impl.bugreport_BugReportViewDay_4_en","",20466,], +["features.rageshake.impl.bugreport_BugReportViewDay_0_en","",20476,], +["features.rageshake.impl.bugreport_BugReportViewDay_1_en","",20476,], +["features.rageshake.impl.bugreport_BugReportViewDay_2_en","",20476,], +["features.rageshake.impl.bugreport_BugReportViewDay_3_en","",20476,], +["features.rageshake.impl.bugreport_BugReportViewDay_4_en","",20476,], ["features.rageshake.impl.bugreport_BugReportViewNight_0_en","",0,], ["features.rageshake.impl.bugreport_BugReportViewNight_1_en","",0,], ["features.rageshake.impl.bugreport_BugReportViewNight_2_en","",0,], @@ -151,139 +158,139 @@ export const screenshots = [ ["libraries.designsystem.atomic.molecules_ButtonRowMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonRowMolecule_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_0_en","features.messages.impl.timeline.components_CallMenuItem_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_1_en","features.messages.impl.timeline.components_CallMenuItem_Night_1_en",0,], -["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20466,], -["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20466,], +["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20476,], +["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20476,], ["features.messages.impl.timeline.components_CallMenuItem_Day_4_en","features.messages.impl.timeline.components_CallMenuItem_Night_4_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_5_en","features.messages.impl.timeline.components_CallMenuItem_Night_5_en",0,], ["features.call.impl.ui_CallScreenView_Day_0_en","features.call.impl.ui_CallScreenView_Night_0_en",0,], -["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20466,], -["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20466,], -["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20466,], -["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20466,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20466,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_1_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_0_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_10_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_11_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_12_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_13_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_13_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_1_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_2_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_3_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_4_en",20466,], +["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20476,], +["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20476,], +["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20476,], +["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20476,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20476,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_1_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_0_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_10_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_11_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_12_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_13_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_13_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_1_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_2_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_3_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_4_en",20476,], ["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_5_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_5_en",0,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_7_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_8_en",20466,], -["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_9_en",20466,], -["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_0_en",20466,], -["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en",20466,], -["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en",20466,], -["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en",20466,], -["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en",20466,], -["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_5_en",20466,], -["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_6_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_6_en",20466,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_7_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_8_en",20476,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_9_en",20476,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_0_en",20476,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en",20476,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en",20476,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en",20476,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en",20476,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_5_en",20476,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_6_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_6_en",20476,], ["features.login.impl.changeserver_ChangeServerView_Day_0_en","features.login.impl.changeserver_ChangeServerView_Night_0_en",0,], -["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20466,], -["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20466,], -["features.login.impl.changeserver_ChangeServerView_Day_3_en","features.login.impl.changeserver_ChangeServerView_Night_3_en",20466,], -["features.login.impl.changeserver_ChangeServerView_Day_4_en","features.login.impl.changeserver_ChangeServerView_Night_4_en",20466,], -["features.login.impl.changeserver_ChangeServerView_Day_5_en","features.login.impl.changeserver_ChangeServerView_Night_5_en",20466,], +["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20476,], +["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20476,], +["features.login.impl.changeserver_ChangeServerView_Day_3_en","features.login.impl.changeserver_ChangeServerView_Night_3_en",20476,], +["features.login.impl.changeserver_ChangeServerView_Day_4_en","features.login.impl.changeserver_ChangeServerView_Night_4_en",20476,], +["features.login.impl.changeserver_ChangeServerView_Day_5_en","features.login.impl.changeserver_ChangeServerView_Night_5_en",20476,], ["libraries.matrix.ui.components_CheckableResolvedUserRow_en","",0,], -["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20466,], +["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20476,], ["libraries.designsystem.theme.components_Checkboxes_Toggles_en","",0,], -["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en",20466,], -["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en",20466,], -["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en",20466,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en",20466,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en",20466,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en",20466,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en",20466,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_4_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_4_en",20466,], +["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en",20476,], +["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en",20476,], +["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en",20476,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en",20476,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en",20476,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en",20476,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en",20476,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_4_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_4_en",20476,], ["libraries.designsystem.theme.components_CircularProgressIndicator_Progress_Indicators_en","",0,], ["libraries.designsystem.components_ClickableLinkText_Text_en","",0,], ["libraries.designsystem.theme_ColorAliases_Day_0_en","libraries.designsystem.theme_ColorAliases_Night_0_en",0,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_2_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_2_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_3_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_3_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_4_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_4_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_5_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_5_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_6_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_6_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_7_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_7_en",20466,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_8_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_8_en",20466,], -["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20466,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_2_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_2_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_3_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_3_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_4_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_4_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_5_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_5_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_6_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_6_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_7_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_7_en",20476,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_8_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_8_en",20476,], +["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20476,], ["libraries.textcomposer_ComposerModeView_Day_1_en","libraries.textcomposer_ComposerModeView_Night_1_en",0,], ["libraries.textcomposer_ComposerModeView_Day_2_en","libraries.textcomposer_ComposerModeView_Night_2_en",0,], ["libraries.textcomposer_ComposerModeView_Day_3_en","libraries.textcomposer_ComposerModeView_Night_3_en",0,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_6_en","",20467,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20466,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_6_en","",20467,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20466,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20466,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20466,], -["features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.home.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20466,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_6_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20476,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_6_en","",20476,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20476,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20476,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20476,], +["features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.home.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20476,], ["libraries.designsystem.components.dialogs_ConfirmationDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ConfirmationDialog_Day_0_en","libraries.designsystem.components.dialogs_ConfirmationDialog_Night_0_en",0,], ["features.networkmonitor.api.ui_ConnectivityIndicator_Day_0_en","features.networkmonitor.api.ui_ConnectivityIndicator_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en","libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en",0,], -["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20466,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20466,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20466,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20466,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20466,], -["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_0_en",20466,], -["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_1_en",20466,], -["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20466,], -["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20466,], -["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20466,], -["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20466,], -["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20466,], -["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20466,], -["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20466,], -["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20466,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_0_en","",20466,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_1_en","",20466,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_2_en","",20466,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_3_en","",20466,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_4_en","",20466,], +["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20476,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20476,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20476,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20476,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20476,], +["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_0_en",20476,], +["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_1_en",20476,], +["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20476,], +["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20476,], +["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20476,], +["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20476,], +["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20476,], +["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20476,], +["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20476,], +["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20476,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_0_en","",20476,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_1_en","",20476,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_2_en","",20476,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_3_en","",20476,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_4_en","",20476,], ["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en",0,], -["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20466,], -["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20466,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_0_en",20466,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_1_en",20466,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_2_en",20466,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_3_en",20466,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_4_en",20466,], +["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20476,], +["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20476,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_0_en",20476,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_1_en",20476,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_2_en",20476,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_3_en",20476,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_4_en",20476,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_0_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_0_en",0,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20466,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20466,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20466,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20476,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20476,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20476,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_4_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_4_en",0,], -["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20466,], +["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20476,], ["features.licenses.impl.details_DependenciesDetailsView_Day_0_en","features.licenses.impl.details_DependenciesDetailsView_Night_0_en",0,], -["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20466,], -["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20466,], -["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20466,], -["features.licenses.impl.list_DependencyLicensesListView_Day_3_en","features.licenses.impl.list_DependencyLicensesListView_Night_3_en",20466,], -["features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_0_en","features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Night_0_en",20466,], -["features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_1_en","features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Night_1_en",20466,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20466,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20466,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20466,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_3_en","features.preferences.impl.developer_DeveloperSettingsView_Night_3_en",20466,], +["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20476,], +["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20476,], +["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20476,], +["features.licenses.impl.list_DependencyLicensesListView_Day_3_en","features.licenses.impl.list_DependencyLicensesListView_Night_3_en",20476,], +["features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_0_en","features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Night_0_en",20476,], +["features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_1_en","features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Night_1_en",20476,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20476,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20476,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20476,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_3_en","features.preferences.impl.developer_DeveloperSettingsView_Night_3_en",20476,], ["libraries.designsystem.theme.components_DialogWithDestructiveButton_Dialog_with_destructive_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithOnlyMessageAndOkButton_Dialog_with_only_message_and_ok_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithThirdButton_Dialog_with_third_button_Dialogs_en","",0,], @@ -298,19 +305,19 @@ export const screenshots = [ ["libraries.designsystem.text_DpScale_1_0f__en","",0,], ["libraries.designsystem.text_DpScale_1_5f__en","",0,], ["libraries.designsystem.theme.components_DropdownMenuItem_Menus_en","",0,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20466,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20466,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20466,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20466,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20466,], -["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_0_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_0_en",20466,], -["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_1_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_1_en",20466,], -["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_2_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_2_en",20466,], -["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_3_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_3_en",20466,], -["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_4_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_4_en",20466,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20466,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_1_en",20466,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_2_en",20466,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20476,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20476,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20476,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20476,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20476,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_0_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_0_en",20476,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_1_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_1_en",20476,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_2_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_2_en",20476,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_3_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_3_en",20476,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_4_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_4_en",20476,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20476,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_1_en",20476,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_2_en",20476,], ["libraries.matrix.ui.components_EditableOrgAvatarRtl_Day_0_en","libraries.matrix.ui.components_EditableOrgAvatarRtl_Night_0_en",0,], ["libraries.matrix.ui.components_EditableOrgAvatar_Day_0_en","libraries.matrix.ui.components_EditableOrgAvatar_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_Night_0_en",0,], @@ -318,28 +325,28 @@ export const screenshots = [ ["libraries.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiItem_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiItem_Night_0_en",0,], -["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_0_en",20466,], -["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_1_en",20466,], +["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_0_en",20476,], +["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_1_en",20476,], ["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_2_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_2_en",0,], ["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_3_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_3_en",0,], ["libraries.ui.common.nodes_EmptyView_Day_0_en","libraries.ui.common.nodes_EmptyView_Night_0_en",0,], -["features.linknewdevice.impl.screens.number_EnterNumberView_Day_0_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_0_en",20466,], -["features.linknewdevice.impl.screens.number_EnterNumberView_Day_1_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_1_en",20466,], -["features.linknewdevice.impl.screens.number_EnterNumberView_Day_2_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_2_en",20466,], -["features.linknewdevice.impl.screens.number_EnterNumberView_Day_3_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_3_en",20466,], -["features.linknewdevice.impl.screens.number_EnterNumberView_Day_4_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_4_en",20466,], -["features.linknewdevice.impl.screens.number_EnterNumberView_Day_5_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_5_en",20466,], -["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20466,], -["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20466,], -["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_0_en","features.linknewdevice.impl.screens.error_ErrorView_Night_0_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_1_en","features.linknewdevice.impl.screens.error_ErrorView_Night_1_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_2_en","features.linknewdevice.impl.screens.error_ErrorView_Night_2_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_3_en","features.linknewdevice.impl.screens.error_ErrorView_Night_3_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_4_en","features.linknewdevice.impl.screens.error_ErrorView_Night_4_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_5_en","features.linknewdevice.impl.screens.error_ErrorView_Night_5_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_6_en","features.linknewdevice.impl.screens.error_ErrorView_Night_6_en",20466,], -["features.linknewdevice.impl.screens.error_ErrorView_Day_7_en","features.linknewdevice.impl.screens.error_ErrorView_Night_7_en",20466,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_0_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_0_en",20476,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_1_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_1_en",20476,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_2_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_2_en",20476,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_3_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_3_en",20476,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_4_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_4_en",20476,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_5_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_5_en",20476,], +["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20476,], +["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20476,], +["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_0_en","features.linknewdevice.impl.screens.error_ErrorView_Night_0_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_1_en","features.linknewdevice.impl.screens.error_ErrorView_Night_1_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_2_en","features.linknewdevice.impl.screens.error_ErrorView_Night_2_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_3_en","features.linknewdevice.impl.screens.error_ErrorView_Night_3_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_4_en","features.linknewdevice.impl.screens.error_ErrorView_Night_4_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_5_en","features.linknewdevice.impl.screens.error_ErrorView_Night_5_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_6_en","features.linknewdevice.impl.screens.error_ErrorView_Night_6_en",20476,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_7_en","features.linknewdevice.impl.screens.error_ErrorView_Night_7_en",20476,], ["features.messages.impl.timeline.debug_EventDebugInfoView_Day_0_en","features.messages.impl.timeline.debug_EventDebugInfoView_Night_0_en",0,], ["libraries.designsystem.components_ExpandableBottomSheetLayout_en","",0,], ["libraries.featureflag.ui_FeatureListView_Day_0_en","libraries.featureflag.ui_FeatureListView_Night_0_en",0,], @@ -358,46 +365,47 @@ export const screenshots = [ ["libraries.designsystem.theme.components_FloatingActionButton_Floating_Action_Buttons_en","",0,], ["libraries.designsystem.atomic.pages_FlowStepPage_Day_0_en","libraries.designsystem.atomic.pages_FlowStepPage_Night_0_en",0,], ["features.messages.impl.timeline.focus_FocusRequestStateView_Day_0_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_0_en",0,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20466,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20466,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20466,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20476,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20476,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20476,], ["features.messages.impl.timeline.components_FocusedEvent_Day_0_en","features.messages.impl.timeline.components_FocusedEvent_Night_0_en",0,], ["libraries.textcomposer.components_FormattingOption_Day_0_en","libraries.textcomposer.components_FormattingOption_Night_0_en",0,], ["features.forward.impl_ForwardMessagesView_Day_0_en","features.forward.impl_ForwardMessagesView_Night_0_en",0,], ["features.forward.impl_ForwardMessagesView_Day_1_en","features.forward.impl_ForwardMessagesView_Night_1_en",0,], ["features.forward.impl_ForwardMessagesView_Day_2_en","features.forward.impl_ForwardMessagesView_Night_2_en",0,], -["features.forward.impl_ForwardMessagesView_Day_3_en","features.forward.impl_ForwardMessagesView_Night_3_en",20466,], -["features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.home.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20466,], +["features.forward.impl_ForwardMessagesView_Day_3_en","features.forward.impl_ForwardMessagesView_Night_3_en",20476,], +["features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.home.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20476,], ["libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Night_0_en",0,], ["libraries.designsystem.components.button_GradientFloatingActionButton_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButton_Night_0_en",0,], ["features.messages.impl.timeline.components.group_GroupHeaderView_Day_0_en","features.messages.impl.timeline.components.group_GroupHeaderView_Night_0_en",0,], ["libraries.designsystem.atomic.pages_HeaderFooterPageScrollable_Day_0_en","libraries.designsystem.atomic.pages_HeaderFooterPageScrollable_Night_0_en",0,], ["libraries.designsystem.atomic.pages_HeaderFooterPage_Day_0_en","libraries.designsystem.atomic.pages_HeaderFooterPage_Night_0_en",0,], -["features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_en","features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Night_0_en",20466,], -["features.home.impl.spaces_HomeSpacesView_Day_0_en","features.home.impl.spaces_HomeSpacesView_Night_0_en",20466,], -["features.home.impl.spaces_HomeSpacesView_Day_1_en","features.home.impl.spaces_HomeSpacesView_Night_1_en",20466,], -["features.home.impl.spaces_HomeSpacesView_Day_2_en","features.home.impl.spaces_HomeSpacesView_Night_2_en",20467,], -["features.home.impl.components_HomeTopBarMultiAccount_Day_0_en","features.home.impl.components_HomeTopBarMultiAccount_Night_0_en",20466,], +["features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_en","features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Night_0_en",20476,], +["features.home.impl.spaces_HomeSpacesView_Day_0_en","features.home.impl.spaces_HomeSpacesView_Night_0_en",20476,], +["features.home.impl.spaces_HomeSpacesView_Day_1_en","features.home.impl.spaces_HomeSpacesView_Night_1_en",20476,], +["features.home.impl.spaces_HomeSpacesView_Day_2_en","features.home.impl.spaces_HomeSpacesView_Night_2_en",20476,], +["features.home.impl.spaces_HomeSpacesView_Day_3_en","features.home.impl.spaces_HomeSpacesView_Night_3_en",20479,], +["features.home.impl.components_HomeTopBarMultiAccount_Day_0_en","features.home.impl.components_HomeTopBarMultiAccount_Night_0_en",20476,], ["features.home.impl.components_HomeTopBarSpaces_Day_0_en","features.home.impl.components_HomeTopBarSpaces_Night_0_en",0,], -["features.home.impl.components_HomeTopBarWithIndicator_Day_0_en","features.home.impl.components_HomeTopBarWithIndicator_Night_0_en",20466,], -["features.home.impl.components_HomeTopBar_Day_0_en","features.home.impl.components_HomeTopBar_Night_0_en",20466,], +["features.home.impl.components_HomeTopBarWithIndicator_Day_0_en","features.home.impl.components_HomeTopBarWithIndicator_Night_0_en",20476,], +["features.home.impl.components_HomeTopBar_Day_0_en","features.home.impl.components_HomeTopBar_Night_0_en",20476,], ["features.home.impl_HomeViewA11y_en","",0,], -["features.home.impl_HomeView_Day_0_en","features.home.impl_HomeView_Night_0_en",20466,], -["features.home.impl_HomeView_Day_10_en","features.home.impl_HomeView_Night_10_en",20466,], +["features.home.impl_HomeView_Day_0_en","features.home.impl_HomeView_Night_0_en",20476,], +["features.home.impl_HomeView_Day_10_en","features.home.impl_HomeView_Night_10_en",20476,], ["features.home.impl_HomeView_Day_11_en","features.home.impl_HomeView_Night_11_en",0,], ["features.home.impl_HomeView_Day_12_en","features.home.impl_HomeView_Night_12_en",0,], -["features.home.impl_HomeView_Day_13_en","features.home.impl_HomeView_Night_13_en",20466,], -["features.home.impl_HomeView_Day_14_en","features.home.impl_HomeView_Night_14_en",20466,], -["features.home.impl_HomeView_Day_15_en","features.home.impl_HomeView_Night_15_en",20466,], -["features.home.impl_HomeView_Day_1_en","features.home.impl_HomeView_Night_1_en",20466,], -["features.home.impl_HomeView_Day_2_en","features.home.impl_HomeView_Night_2_en",20466,], -["features.home.impl_HomeView_Day_3_en","features.home.impl_HomeView_Night_3_en",20466,], -["features.home.impl_HomeView_Day_4_en","features.home.impl_HomeView_Night_4_en",20466,], -["features.home.impl_HomeView_Day_5_en","features.home.impl_HomeView_Night_5_en",20466,], -["features.home.impl_HomeView_Day_6_en","features.home.impl_HomeView_Night_6_en",20466,], -["features.home.impl_HomeView_Day_7_en","features.home.impl_HomeView_Night_7_en",20466,], -["features.home.impl_HomeView_Day_8_en","features.home.impl_HomeView_Night_8_en",20466,], -["features.home.impl_HomeView_Day_9_en","features.home.impl_HomeView_Night_9_en",20466,], +["features.home.impl_HomeView_Day_13_en","features.home.impl_HomeView_Night_13_en",20476,], +["features.home.impl_HomeView_Day_14_en","features.home.impl_HomeView_Night_14_en",20476,], +["features.home.impl_HomeView_Day_15_en","features.home.impl_HomeView_Night_15_en",20476,], +["features.home.impl_HomeView_Day_1_en","features.home.impl_HomeView_Night_1_en",20476,], +["features.home.impl_HomeView_Day_2_en","features.home.impl_HomeView_Night_2_en",20476,], +["features.home.impl_HomeView_Day_3_en","features.home.impl_HomeView_Night_3_en",20476,], +["features.home.impl_HomeView_Day_4_en","features.home.impl_HomeView_Night_4_en",20476,], +["features.home.impl_HomeView_Day_5_en","features.home.impl_HomeView_Night_5_en",20476,], +["features.home.impl_HomeView_Day_6_en","features.home.impl_HomeView_Night_6_en",20476,], +["features.home.impl_HomeView_Day_7_en","features.home.impl_HomeView_Night_7_en",20476,], +["features.home.impl_HomeView_Day_8_en","features.home.impl_HomeView_Night_8_en",20476,], +["features.home.impl_HomeView_Day_9_en","features.home.impl_HomeView_Night_9_en",20476,], ["libraries.designsystem.theme.components_HorizontalDivider_Dividers_en","",0,], ["libraries.designsystem.ruler_HorizontalRuler_Day_0_en","libraries.designsystem.ruler_HorizontalRuler_Night_0_en",0,], ["libraries.designsystem.theme.components_IconButton_Buttons_en","",0,], @@ -410,8 +418,8 @@ export const screenshots = [ ["appicon.enterprise_Icon_en","",0,], ["libraries.designsystem.icons_IconsOther_Day_0_en","libraries.designsystem.icons_IconsOther_Night_0_en",0,], ["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_0_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_0_en",0,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20466,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20466,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20476,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20476,], ["libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_0_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_10_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_10_en",0,], @@ -419,115 +427,115 @@ export const screenshots = [ ["libraries.matrix.ui.messages.reply_InReplyToView_Day_1_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_1_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_2_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_2_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_3_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_3_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20466,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20476,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_5_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_5_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_6_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_6_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_7_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_7_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20466,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20476,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_9_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_9_en",0,], -["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20466,], +["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20476,], ["features.verifysession.impl.incoming_IncomingVerificationViewA11y_en","",0,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en",20466,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en",20466,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en",20476,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en",20476,], ["libraries.designsystem.atomic.molecules_InfoListItemMolecule_Day_0_en","libraries.designsystem.atomic.molecules_InfoListItemMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.organisms_InfoListOrganism_Day_0_en","libraries.designsystem.atomic.organisms_InfoListOrganism_Night_0_en",0,], ["libraries.matrix.ui.media_InitialsAvatarBitmapGenerator_Day_0_en","libraries.matrix.ui.media_InitialsAvatarBitmapGenerator_Night_0_en",0,], -["features.call.impl.ui_InvalidAudioDeviceDialog_Day_0_en","features.call.impl.ui_InvalidAudioDeviceDialog_Night_0_en",20466,], -["features.invitepeople.impl_InvitePeopleView_Day_0_en","features.invitepeople.impl_InvitePeopleView_Night_0_en",20466,], -["features.invitepeople.impl_InvitePeopleView_Day_1_en","features.invitepeople.impl_InvitePeopleView_Night_1_en",20466,], +["features.call.impl.ui_InvalidAudioDeviceDialog_Day_0_en","features.call.impl.ui_InvalidAudioDeviceDialog_Night_0_en",20476,], +["features.invitepeople.impl_InvitePeopleView_Day_0_en","features.invitepeople.impl_InvitePeopleView_Night_0_en",20476,], +["features.invitepeople.impl_InvitePeopleView_Day_1_en","features.invitepeople.impl_InvitePeopleView_Night_1_en",20476,], ["features.invitepeople.impl_InvitePeopleView_Day_2_en","features.invitepeople.impl_InvitePeopleView_Night_2_en",0,], ["features.invitepeople.impl_InvitePeopleView_Day_3_en","features.invitepeople.impl_InvitePeopleView_Night_3_en",0,], -["features.invitepeople.impl_InvitePeopleView_Day_4_en","features.invitepeople.impl_InvitePeopleView_Night_4_en",20466,], -["features.invitepeople.impl_InvitePeopleView_Day_5_en","features.invitepeople.impl_InvitePeopleView_Night_5_en",20466,], -["features.invitepeople.impl_InvitePeopleView_Day_6_en","features.invitepeople.impl_InvitePeopleView_Night_6_en",20466,], -["features.invitepeople.impl_InvitePeopleView_Day_7_en","features.invitepeople.impl_InvitePeopleView_Night_7_en",20466,], +["features.invitepeople.impl_InvitePeopleView_Day_4_en","features.invitepeople.impl_InvitePeopleView_Night_4_en",20476,], +["features.invitepeople.impl_InvitePeopleView_Day_5_en","features.invitepeople.impl_InvitePeopleView_Night_5_en",20476,], +["features.invitepeople.impl_InvitePeopleView_Day_6_en","features.invitepeople.impl_InvitePeopleView_Night_6_en",20476,], +["features.invitepeople.impl_InvitePeopleView_Day_7_en","features.invitepeople.impl_InvitePeopleView_Night_7_en",20476,], ["features.invitepeople.impl_InvitePeopleView_Day_8_en","features.invitepeople.impl_InvitePeopleView_Night_8_en",0,], -["features.invitepeople.impl_InvitePeopleView_Day_9_en","features.invitepeople.impl_InvitePeopleView_Night_9_en",20466,], -["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20466,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en",20466,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en",20466,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en",20466,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en",20466,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en",20466,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en",20466,], +["features.invitepeople.impl_InvitePeopleView_Day_9_en","features.invitepeople.impl_InvitePeopleView_Night_9_en",20476,], +["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20476,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en",20476,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en",20476,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en",20476,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en",20476,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en",20476,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en",20476,], ["features.joinroom.impl_JoinRoomView_Day_0_en","features.joinroom.impl_JoinRoomView_Night_0_en",0,], -["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_13_en","features.joinroom.impl_JoinRoomView_Night_13_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_14_en","features.joinroom.impl_JoinRoomView_Night_14_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_15_en","features.joinroom.impl_JoinRoomView_Night_15_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_16_en","features.joinroom.impl_JoinRoomView_Night_16_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20466,], -["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",20466,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en",20466,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en",20466,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en",20466,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en",20466,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en",20466,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en",20466,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_10_en","features.knockrequests.impl.list_KnockRequestsListView_Night_10_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20466,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20466,], +["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_13_en","features.joinroom.impl_JoinRoomView_Night_13_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_14_en","features.joinroom.impl_JoinRoomView_Night_14_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_15_en","features.joinroom.impl_JoinRoomView_Night_15_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_16_en","features.joinroom.impl_JoinRoomView_Night_16_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20476,], +["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",20476,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en",20476,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en",20476,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en",20476,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en",20476,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en",20476,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en",20476,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_10_en","features.knockrequests.impl.list_KnockRequestsListView_Night_10_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20476,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20476,], ["libraries.designsystem.components_LabelledCheckbox_Toggles_en","",0,], -["features.preferences.impl.labs_LabsView_Day_0_en","features.preferences.impl.labs_LabsView_Night_0_en",20466,], -["features.preferences.impl.labs_LabsView_Day_1_en","features.preferences.impl.labs_LabsView_Night_1_en",20466,], +["features.preferences.impl.labs_LabsView_Day_0_en","features.preferences.impl.labs_LabsView_Night_0_en",20476,], +["features.preferences.impl.labs_LabsView_Day_1_en","features.preferences.impl.labs_LabsView_Night_1_en",20476,], ["features.leaveroom.impl_LeaveRoomView_Day_0_en","features.leaveroom.impl_LeaveRoomView_Night_0_en",0,], -["features.leaveroom.impl_LeaveRoomView_Day_1_en","features.leaveroom.impl_LeaveRoomView_Night_1_en",20466,], -["features.leaveroom.impl_LeaveRoomView_Day_2_en","features.leaveroom.impl_LeaveRoomView_Night_2_en",20466,], -["features.leaveroom.impl_LeaveRoomView_Day_3_en","features.leaveroom.impl_LeaveRoomView_Night_3_en",20466,], -["features.leaveroom.impl_LeaveRoomView_Day_4_en","features.leaveroom.impl_LeaveRoomView_Night_4_en",20466,], -["features.leaveroom.impl_LeaveRoomView_Day_5_en","features.leaveroom.impl_LeaveRoomView_Night_5_en",20466,], -["features.leaveroom.impl_LeaveRoomView_Day_6_en","features.leaveroom.impl_LeaveRoomView_Night_6_en",20466,], -["features.leaveroom.impl_LeaveRoomView_Day_7_en","features.leaveroom.impl_LeaveRoomView_Night_7_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_0_en","features.space.impl.leave_LeaveSpaceView_Night_0_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_1_en","features.space.impl.leave_LeaveSpaceView_Night_1_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_2_en","features.space.impl.leave_LeaveSpaceView_Night_2_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_3_en","features.space.impl.leave_LeaveSpaceView_Night_3_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_4_en","features.space.impl.leave_LeaveSpaceView_Night_4_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_5_en","features.space.impl.leave_LeaveSpaceView_Night_5_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_6_en","features.space.impl.leave_LeaveSpaceView_Night_6_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_7_en","features.space.impl.leave_LeaveSpaceView_Night_7_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_8_en","features.space.impl.leave_LeaveSpaceView_Night_8_en",20466,], -["features.space.impl.leave_LeaveSpaceView_Day_9_en","features.space.impl.leave_LeaveSpaceView_Night_9_en",20466,], +["features.leaveroom.impl_LeaveRoomView_Day_1_en","features.leaveroom.impl_LeaveRoomView_Night_1_en",20476,], +["features.leaveroom.impl_LeaveRoomView_Day_2_en","features.leaveroom.impl_LeaveRoomView_Night_2_en",20476,], +["features.leaveroom.impl_LeaveRoomView_Day_3_en","features.leaveroom.impl_LeaveRoomView_Night_3_en",20476,], +["features.leaveroom.impl_LeaveRoomView_Day_4_en","features.leaveroom.impl_LeaveRoomView_Night_4_en",20476,], +["features.leaveroom.impl_LeaveRoomView_Day_5_en","features.leaveroom.impl_LeaveRoomView_Night_5_en",20476,], +["features.leaveroom.impl_LeaveRoomView_Day_6_en","features.leaveroom.impl_LeaveRoomView_Night_6_en",20476,], +["features.leaveroom.impl_LeaveRoomView_Day_7_en","features.leaveroom.impl_LeaveRoomView_Night_7_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_0_en","features.space.impl.leave_LeaveSpaceView_Night_0_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_1_en","features.space.impl.leave_LeaveSpaceView_Night_1_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_2_en","features.space.impl.leave_LeaveSpaceView_Night_2_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_3_en","features.space.impl.leave_LeaveSpaceView_Night_3_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_4_en","features.space.impl.leave_LeaveSpaceView_Night_4_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_5_en","features.space.impl.leave_LeaveSpaceView_Night_5_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_6_en","features.space.impl.leave_LeaveSpaceView_Night_6_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_7_en","features.space.impl.leave_LeaveSpaceView_Night_7_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_8_en","features.space.impl.leave_LeaveSpaceView_Night_8_en",20476,], +["features.space.impl.leave_LeaveSpaceView_Day_9_en","features.space.impl.leave_LeaveSpaceView_Night_9_en",20476,], ["libraries.designsystem.background_LightGradientBackground_Day_0_en","libraries.designsystem.background_LightGradientBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_LinearProgressIndicator_Progress_Indicators_en","",0,], -["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_0_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_0_en",20466,], -["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_1_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_1_en",20467,], -["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_2_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_2_en",20466,], -["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_3_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_3_en",20466,], -["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_4_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_4_en",20467,], -["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_5_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_5_en",20466,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_0_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_0_en",20476,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_1_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_1_en",20476,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_2_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_2_en",20476,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_3_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_3_en",20476,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_4_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_4_en",20476,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_5_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_5_en",20476,], ["features.messages.impl.link_LinkView_Day_0_en","features.messages.impl.link_LinkView_Night_0_en",0,], -["features.messages.impl.link_LinkView_Day_1_en","features.messages.impl.link_LinkView_Night_1_en",20466,], +["features.messages.impl.link_LinkView_Day_1_en","features.messages.impl.link_LinkView_Night_1_en",20476,], ["libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ListDialog_Day_0_en","libraries.designsystem.components.dialogs_ListDialog_Night_0_en",0,], ["libraries.designsystem.theme.components_ListItemPrimaryActionWithIcon_List_item_-_Primary_action_&_Icon_List_items_en","",0,], @@ -582,41 +590,41 @@ export const screenshots = [ ["libraries.designsystem.theme.components_ListSupportingTextSmallPadding_List_supporting_text_-_small_padding_List_sections_en","",0,], ["libraries.textcomposer.components_LiveWaveformView_Day_0_en","libraries.textcomposer.components_LiveWaveformView_Night_0_en",0,], ["appnav.room.joined_LoadingRoomNodeView_Day_0_en","appnav.room.joined_LoadingRoomNodeView_Night_0_en",0,], -["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20466,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20466,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20466,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20466,], +["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20476,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20476,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20476,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20476,], ["appnav.loggedin_LoggedInView_Day_0_en","appnav.loggedin_LoggedInView_Night_0_en",0,], -["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20466,], -["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20466,], -["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20466,], -["features.login.impl.login_LoginModeView_Day_0_en","features.login.impl.login_LoginModeView_Night_0_en",20466,], -["features.login.impl.login_LoginModeView_Day_1_en","features.login.impl.login_LoginModeView_Night_1_en",20466,], -["features.login.impl.login_LoginModeView_Day_2_en","features.login.impl.login_LoginModeView_Night_2_en",20466,], -["features.login.impl.login_LoginModeView_Day_3_en","features.login.impl.login_LoginModeView_Night_3_en",20466,], -["features.login.impl.login_LoginModeView_Day_4_en","features.login.impl.login_LoginModeView_Night_4_en",20466,], -["features.login.impl.login_LoginModeView_Day_5_en","features.login.impl.login_LoginModeView_Night_5_en",20466,], -["features.login.impl.login_LoginModeView_Day_6_en","features.login.impl.login_LoginModeView_Night_6_en",20466,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20466,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20466,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20466,], -["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20466,], -["features.logout.impl_LogoutView_Day_10_en","features.logout.impl_LogoutView_Night_10_en",20466,], -["features.logout.impl_LogoutView_Day_11_en","features.logout.impl_LogoutView_Night_11_en",20466,], -["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20466,], -["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20466,], -["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20466,], -["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20466,], -["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20466,], -["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20466,], -["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20466,], -["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20466,], -["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20466,], +["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20476,], +["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20476,], +["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20476,], +["features.login.impl.login_LoginModeView_Day_0_en","features.login.impl.login_LoginModeView_Night_0_en",20476,], +["features.login.impl.login_LoginModeView_Day_1_en","features.login.impl.login_LoginModeView_Night_1_en",20476,], +["features.login.impl.login_LoginModeView_Day_2_en","features.login.impl.login_LoginModeView_Night_2_en",20476,], +["features.login.impl.login_LoginModeView_Day_3_en","features.login.impl.login_LoginModeView_Night_3_en",20476,], +["features.login.impl.login_LoginModeView_Day_4_en","features.login.impl.login_LoginModeView_Night_4_en",20476,], +["features.login.impl.login_LoginModeView_Day_5_en","features.login.impl.login_LoginModeView_Night_5_en",20476,], +["features.login.impl.login_LoginModeView_Day_6_en","features.login.impl.login_LoginModeView_Night_6_en",20476,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20476,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20476,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20476,], +["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20476,], +["features.logout.impl_LogoutView_Day_10_en","features.logout.impl_LogoutView_Night_10_en",20476,], +["features.logout.impl_LogoutView_Day_11_en","features.logout.impl_LogoutView_Night_11_en",20476,], +["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20476,], +["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20476,], +["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20476,], +["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20476,], +["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20476,], +["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20476,], +["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20476,], +["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20476,], +["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20476,], ["libraries.designsystem.components.button_MainActionButton_Buttons_en","",0,], -["features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Day_0_en","features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Night_0_en",20467,], -["features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Day_1_en","features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Night_1_en",20467,], -["features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Day_2_en","features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Night_2_en",20467,], -["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20466,], +["features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Day_0_en","features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Night_0_en",20476,], +["features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Day_1_en","features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Night_1_en",20476,], +["features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Day_2_en","features.securityandprivacy.impl.manageauthorizedspaces_ManageAuthorizedSpacesView_Night_2_en",20476,], +["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20476,], ["libraries.textcomposer.components.markdown_MarkdownTextInput_Day_0_en","libraries.textcomposer.components.markdown_MarkdownTextInput_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Night_0_en",0,], @@ -629,22 +637,22 @@ export const screenshots = [ ["libraries.matrix.ui.components_MatrixUserRow_Day_1_en","libraries.matrix.ui.components_MatrixUserRow_Night_1_en",0,], ["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en",0,], -["libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en",20466,], -["libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en",20466,], +["libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en",20476,], +["libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en",20476,], ["libraries.mediaviewer.impl.local.file_MediaFileView_Day_0_en","libraries.mediaviewer.impl.local.file_MediaFileView_Night_0_en",0,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_11_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_12_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en",20466,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en",20466,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_11_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_12_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en",20476,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en",20476,], ["libraries.mediaviewer.impl.local.image_MediaImageView_Day_0_en","libraries.mediaviewer.impl.local.image_MediaImageView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_0_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_1_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_1_en",0,], @@ -652,14 +660,14 @@ export const screenshots = [ ["libraries.mediaviewer.impl.local.video_MediaVideoView_Day_0_en","libraries.mediaviewer.impl.local.video_MediaVideoView_Night_0_en",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_0_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_10_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_11_en","",20466,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_12_en","",20466,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_11_en","",20476,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_12_en","",20476,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_13_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_14_en","",20466,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_14_en","",20476,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_15_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_16_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_1_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20466,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20476,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_3_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_4_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_5_en","",0,], @@ -673,7 +681,7 @@ export const screenshots = [ ["libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en","libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en",0,], ["libraries.designsystem.theme.components.previews_Menu_Menus_en","",0,], ["features.messages.impl.messagecomposer_MessageComposerViewVoice_Day_0_en","features.messages.impl.messagecomposer_MessageComposerViewVoice_Night_0_en",0,], -["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20466,], +["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20476,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_0_en","features.messages.impl.timeline.components_MessageEventBubble_Night_0_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_1_en","features.messages.impl.timeline.components_MessageEventBubble_Night_1_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_2_en","features.messages.impl.timeline.components_MessageEventBubble_Night_2_en",0,], @@ -682,7 +690,7 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessageEventBubble_Day_5_en","features.messages.impl.timeline.components_MessageEventBubble_Night_5_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_6_en","features.messages.impl.timeline.components_MessageEventBubble_Night_6_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_7_en","features.messages.impl.timeline.components_MessageEventBubble_Night_7_en",0,], -["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20466,], +["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20476,], ["features.messages.impl.timeline.components_MessageStateEventContainer_Day_0_en","features.messages.impl.timeline.components_MessageStateEventContainer_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonAdd_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonAdd_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonExtra_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonExtra_Night_0_en",0,], @@ -691,26 +699,26 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessagesReactionButton_Day_2_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_2_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_3_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_3_en",0,], ["features.messages.impl_MessagesViewA11y_en","",0,], -["features.messages.impl.topbars_MessagesViewTopBar_Day_0_en","features.messages.impl.topbars_MessagesViewTopBar_Night_0_en",20466,], -["features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_en","features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Night_0_en",20466,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20466,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20466,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20466,], -["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20466,], -["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20466,], -["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20466,], -["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",20466,], -["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20466,], -["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20466,], -["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20466,], -["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20466,], -["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20466,], -["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20466,], -["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20466,], -["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20466,], -["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20466,], +["features.messages.impl.topbars_MessagesViewTopBar_Day_0_en","features.messages.impl.topbars_MessagesViewTopBar_Night_0_en",20476,], +["features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_en","features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Night_0_en",20476,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20476,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20476,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20476,], +["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20476,], +["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20476,], +["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20476,], +["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",20476,], +["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20476,], +["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20476,], +["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20476,], +["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20476,], +["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20476,], +["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20476,], +["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20476,], +["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20476,], +["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20476,], ["features.migration.impl_MigrationView_Day_0_en","features.migration.impl_MigrationView_Night_0_en",0,], -["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20466,], +["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20476,], ["libraries.designsystem.theme.components_ModalBottomSheetDark_Bottom_Sheets_en","",0,], ["libraries.designsystem.theme.components_ModalBottomSheetLight_Bottom_Sheets_en","",0,], ["appicon.element_MonochromeIcon_en","",0,], @@ -721,113 +729,113 @@ export const screenshots = [ ["libraries.designsystem.components.list_MutipleSelectionListItemSelected_Multiple_selection_List_item_-_selection_in_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItem_Multiple_selection_List_item_-_no_selection_List_items_en","",0,], ["libraries.designsystem.theme.components_NavigationBar_App_Bars_en","",0,], -["features.home.impl.components_NewNotificationSoundBanner_Day_0_en","features.home.impl.components_NewNotificationSoundBanner_Night_0_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_13_en","features.preferences.impl.notifications_NotificationSettingsView_Night_13_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20466,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20466,], -["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20466,], +["features.home.impl.components_NewNotificationSoundBanner_Day_0_en","features.home.impl.components_NewNotificationSoundBanner_Night_0_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_13_en","features.preferences.impl.notifications_NotificationSettingsView_Night_13_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20476,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20476,], +["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20476,], ["features.linknewdevice.impl.screens.number.component_NumberTextField_Day_0_en","features.linknewdevice.impl.screens.number.component_NumberTextField_Night_0_en",0,], ["libraries.designsystem.atomic.pages_OnBoardingPage_Day_0_en","libraries.designsystem.atomic.pages_OnBoardingPage_Night_0_en",0,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_0_en","features.login.impl.screens.onboarding_OnBoardingView_Night_0_en",20466,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_1_en","features.login.impl.screens.onboarding_OnBoardingView_Night_1_en",20466,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_2_en","features.login.impl.screens.onboarding_OnBoardingView_Night_2_en",20466,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_3_en","features.login.impl.screens.onboarding_OnBoardingView_Night_3_en",20466,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_4_en","features.login.impl.screens.onboarding_OnBoardingView_Night_4_en",20466,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_5_en","features.login.impl.screens.onboarding_OnBoardingView_Night_5_en",20466,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_6_en","features.login.impl.screens.onboarding_OnBoardingView_Night_6_en",20466,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_7_en","features.login.impl.screens.onboarding_OnBoardingView_Night_7_en",20466,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_0_en","features.login.impl.screens.onboarding_OnBoardingView_Night_0_en",20476,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_1_en","features.login.impl.screens.onboarding_OnBoardingView_Night_1_en",20476,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_2_en","features.login.impl.screens.onboarding_OnBoardingView_Night_2_en",20476,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_3_en","features.login.impl.screens.onboarding_OnBoardingView_Night_3_en",20476,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_4_en","features.login.impl.screens.onboarding_OnBoardingView_Night_4_en",20476,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_5_en","features.login.impl.screens.onboarding_OnBoardingView_Night_5_en",20476,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_6_en","features.login.impl.screens.onboarding_OnBoardingView_Night_6_en",20476,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_7_en","features.login.impl.screens.onboarding_OnBoardingView_Night_7_en",20476,], ["libraries.designsystem.background_OnboardingBackground_Day_0_en","libraries.designsystem.background_OnboardingBackground_Night_0_en",0,], -["libraries.matrix.ui.components_OrganizationHeader_Day_0_en","libraries.matrix.ui.components_OrganizationHeader_Night_0_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_0_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_10_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_11_en",20466,], +["libraries.matrix.ui.components_OrganizationHeader_Day_0_en","libraries.matrix.ui.components_OrganizationHeader_Night_0_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_0_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_10_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_11_en",20476,], ["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_12_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_12_en",0,], ["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_13_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_13_en",0,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_1_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_2_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_3_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_4_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_5_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_6_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_7_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_8_en",20466,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_9_en",20466,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_1_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_2_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_3_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_4_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_5_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_6_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_7_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_8_en",20476,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_9_en",20476,], ["libraries.designsystem.theme.components_OutlinedButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonMediumLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonSmall_Buttons_en","",0,], -["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20466,], -["features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Day_0_en","features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Night_0_en",20467,], -["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20466,], -["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20466,], -["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20466,], -["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20466,], +["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20476,], +["features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Day_0_en","features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Night_0_en",20476,], +["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20476,], +["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20476,], +["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20476,], +["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20476,], ["features.lockscreen.impl.components_PinEntryTextField_Day_0_en","features.lockscreen.impl.components_PinEntryTextField_Night_0_en",0,], ["libraries.designsystem.components_PinIcon_Day_0_en","libraries.designsystem.components_PinIcon_Night_0_en",0,], ["features.lockscreen.impl.unlock.keypad_PinKeypad_Day_0_en","features.lockscreen.impl.unlock.keypad_PinKeypad_Night_0_en",0,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20466,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20466,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20476,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20476,], ["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_0_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_0_en",0,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20466,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20466,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20466,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20466,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20466,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20466,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20476,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20476,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20476,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20476,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20476,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20476,], ["libraries.designsystem.atomic.atoms_PlaceholderAtom_Day_0_en","libraries.designsystem.atomic.atoms_PlaceholderAtom_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_PlaybackSpeedButton_Day_0_en","libraries.designsystem.atomic.atoms_PlaybackSpeedButton_Night_0_en",0,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20466,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20466,], -["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20466,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20466,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20466,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20476,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20476,], +["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20476,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20476,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20476,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en",0,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Night_0_en",0,], -["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20466,], -["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20466,], -["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20466,], -["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20466,], -["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20466,], -["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20466,], -["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20466,], -["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20466,], -["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20466,], -["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20466,], -["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20466,], +["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20476,], +["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20476,], +["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20476,], +["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20476,], +["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20476,], +["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20476,], +["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20476,], +["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20476,], +["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20476,], +["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20476,], +["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20476,], ["features.poll.api.pollcontent_PollTitleView_Day_0_en","features.poll.api.pollcontent_PollTitleView_Night_0_en",0,], ["libraries.designsystem.components.preferences_PreferenceCategory_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceCheckbox_Preferences_en","",0,], @@ -841,208 +849,208 @@ export const screenshots = [ ["libraries.designsystem.components.preferences_PreferenceRow_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceSlide_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceSwitch_Preferences_en","",0,], -["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20466,], -["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20466,], -["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20466,], -["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20466,], +["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20476,], +["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20476,], +["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20476,], +["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20476,], ["features.messages.impl.timeline.components.event_ProgressButton_Day_0_en","features.messages.impl.timeline.components.event_ProgressButton_Night_0_en",0,], -["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20466,], -["libraries.designsystem.components_ProgressDialogWithContent_Day_0_en","libraries.designsystem.components_ProgressDialogWithContent_Night_0_en",20466,], +["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20476,], +["libraries.designsystem.components_ProgressDialogWithContent_Day_0_en","libraries.designsystem.components_ProgressDialogWithContent_Night_0_en",20476,], ["libraries.designsystem.components_ProgressDialogWithTextAndContent_Day_0_en","libraries.designsystem.components_ProgressDialogWithTextAndContent_Night_0_en",0,], -["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20466,], -["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20466,], -["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20466,], -["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20466,], -["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20466,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en",20466,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en",20466,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en",20466,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_3_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_3_en",20466,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20466,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20466,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20466,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20466,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20466,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20466,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20466,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20466,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20466,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20466,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20466,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20466,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20466,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20466,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20466,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20466,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en",20466,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_5_en",20466,], +["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20476,], +["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20476,], +["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20476,], +["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20476,], +["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20476,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en",20476,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en",20476,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en",20476,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_3_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_3_en",20476,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20476,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20476,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20476,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20476,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20476,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20476,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20476,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20476,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20476,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20476,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20476,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20476,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20476,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20476,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20476,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20476,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en",20476,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_5_en",20476,], ["libraries.qrcode_QrCodeView_en","",0,], ["libraries.designsystem.theme.components_RadioButton_Toggles_en","",0,], -["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20466,], -["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20466,], +["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20476,], +["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20476,], ["features.rageshake.api.preferences_RageshakePreferencesView_Day_1_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_1_en",0,], ["features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Day_0_en","features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Night_0_en",0,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20466,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20466,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20466,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20466,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20466,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_14_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20466,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20466,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20476,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20476,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20476,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20476,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20476,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_14_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20476,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20476,], ["libraries.designsystem.atomic.atoms_RedIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_RedIndicatorAtom_Night_0_en",0,], ["features.messages.impl.timeline.components_ReplySwipeIndicator_Day_0_en","features.messages.impl.timeline.components_ReplySwipeIndicator_Night_0_en",0,], -["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20466,], -["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20466,], -["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20466,], -["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20466,], -["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20466,], -["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20466,], -["features.reportroom.impl_ReportRoomView_Day_0_en","features.reportroom.impl_ReportRoomView_Night_0_en",20466,], -["features.reportroom.impl_ReportRoomView_Day_1_en","features.reportroom.impl_ReportRoomView_Night_1_en",20466,], -["features.reportroom.impl_ReportRoomView_Day_2_en","features.reportroom.impl_ReportRoomView_Night_2_en",20466,], -["features.reportroom.impl_ReportRoomView_Day_3_en","features.reportroom.impl_ReportRoomView_Night_3_en",20466,], -["features.reportroom.impl_ReportRoomView_Day_4_en","features.reportroom.impl_ReportRoomView_Night_4_en",20466,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20466,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20466,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20466,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20466,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20466,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20466,], +["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20476,], +["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20476,], +["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20476,], +["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20476,], +["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20476,], +["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20476,], +["features.reportroom.impl_ReportRoomView_Day_0_en","features.reportroom.impl_ReportRoomView_Night_0_en",20476,], +["features.reportroom.impl_ReportRoomView_Day_1_en","features.reportroom.impl_ReportRoomView_Night_1_en",20476,], +["features.reportroom.impl_ReportRoomView_Day_2_en","features.reportroom.impl_ReportRoomView_Night_2_en",20476,], +["features.reportroom.impl_ReportRoomView_Day_3_en","features.reportroom.impl_ReportRoomView_Night_3_en",20476,], +["features.reportroom.impl_ReportRoomView_Day_4_en","features.reportroom.impl_ReportRoomView_Night_4_en",20476,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20476,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20476,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20476,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20476,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20476,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20476,], ["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_0_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_0_en",0,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20466,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20466,], -["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20466,], -["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_0_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_1_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_2_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_3_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_4_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_5_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_6_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_7_en",20466,], -["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_8_en",20466,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20476,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20476,], +["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20476,], +["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_0_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_1_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_2_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_3_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_4_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_5_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_6_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_7_en",20476,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_8_en",20476,], ["libraries.matrix.ui.room.address_RoomAddressField_Day_0_en","libraries.matrix.ui.room.address_RoomAddressField_Night_0_en",0,], ["features.roomaliasresolver.impl_RoomAliasResolverView_Day_0_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_0_en",0,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",20466,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20466,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",20476,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20476,], ["features.roomdetails.impl_RoomDetailsA11y_en","",0,], -["features.roomdetails.impl_RoomDetailsDark_0_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_10_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_11_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_12_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_13_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_14_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_15_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_16_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_17_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_18_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_19_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_1_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_2_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_3_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_4_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_5_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_6_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_7_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_8_en","",20466,], -["features.roomdetails.impl_RoomDetailsDark_9_en","",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_0_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_0_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_1_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_1_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_2_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_2_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_3_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_3_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_4_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_5_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_5_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_6_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_7_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_7_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_8_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_8_en",20466,], -["features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_9_en",20466,], -["features.roomdetails.impl_RoomDetails_0_en","",20466,], -["features.roomdetails.impl_RoomDetails_10_en","",20466,], -["features.roomdetails.impl_RoomDetails_11_en","",20466,], -["features.roomdetails.impl_RoomDetails_12_en","",20466,], -["features.roomdetails.impl_RoomDetails_13_en","",20466,], -["features.roomdetails.impl_RoomDetails_14_en","",20466,], -["features.roomdetails.impl_RoomDetails_15_en","",20466,], -["features.roomdetails.impl_RoomDetails_16_en","",20466,], -["features.roomdetails.impl_RoomDetails_17_en","",20466,], -["features.roomdetails.impl_RoomDetails_18_en","",20466,], -["features.roomdetails.impl_RoomDetails_19_en","",20466,], -["features.roomdetails.impl_RoomDetails_1_en","",20466,], -["features.roomdetails.impl_RoomDetails_2_en","",20466,], -["features.roomdetails.impl_RoomDetails_3_en","",20466,], -["features.roomdetails.impl_RoomDetails_4_en","",20466,], -["features.roomdetails.impl_RoomDetails_5_en","",20466,], -["features.roomdetails.impl_RoomDetails_6_en","",20466,], -["features.roomdetails.impl_RoomDetails_7_en","",20466,], -["features.roomdetails.impl_RoomDetails_8_en","",20466,], -["features.roomdetails.impl_RoomDetails_9_en","",20466,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20466,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20466,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20466,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20466,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20466,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20466,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20466,], -["features.home.impl.components_RoomListContentView_Day_0_en","features.home.impl.components_RoomListContentView_Night_0_en",20466,], -["features.home.impl.components_RoomListContentView_Day_1_en","features.home.impl.components_RoomListContentView_Night_1_en",20466,], +["features.roomdetails.impl_RoomDetailsDark_0_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_10_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_11_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_12_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_13_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_14_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_15_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_16_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_17_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_18_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_19_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_1_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_2_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_3_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_4_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_5_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_6_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_7_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_8_en","",20476,], +["features.roomdetails.impl_RoomDetailsDark_9_en","",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_0_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_0_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_1_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_1_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_2_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_2_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_3_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_3_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_4_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_5_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_5_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_6_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_7_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_7_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_8_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_8_en",20476,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_9_en",20476,], +["features.roomdetails.impl_RoomDetails_0_en","",20476,], +["features.roomdetails.impl_RoomDetails_10_en","",20476,], +["features.roomdetails.impl_RoomDetails_11_en","",20476,], +["features.roomdetails.impl_RoomDetails_12_en","",20476,], +["features.roomdetails.impl_RoomDetails_13_en","",20476,], +["features.roomdetails.impl_RoomDetails_14_en","",20476,], +["features.roomdetails.impl_RoomDetails_15_en","",20476,], +["features.roomdetails.impl_RoomDetails_16_en","",20476,], +["features.roomdetails.impl_RoomDetails_17_en","",20476,], +["features.roomdetails.impl_RoomDetails_18_en","",20476,], +["features.roomdetails.impl_RoomDetails_19_en","",20476,], +["features.roomdetails.impl_RoomDetails_1_en","",20476,], +["features.roomdetails.impl_RoomDetails_2_en","",20476,], +["features.roomdetails.impl_RoomDetails_3_en","",20476,], +["features.roomdetails.impl_RoomDetails_4_en","",20476,], +["features.roomdetails.impl_RoomDetails_5_en","",20476,], +["features.roomdetails.impl_RoomDetails_6_en","",20476,], +["features.roomdetails.impl_RoomDetails_7_en","",20476,], +["features.roomdetails.impl_RoomDetails_8_en","",20476,], +["features.roomdetails.impl_RoomDetails_9_en","",20476,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20476,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20476,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20476,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20476,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20476,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20476,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20476,], +["features.home.impl.components_RoomListContentView_Day_0_en","features.home.impl.components_RoomListContentView_Night_0_en",20476,], +["features.home.impl.components_RoomListContentView_Day_1_en","features.home.impl.components_RoomListContentView_Night_1_en",20476,], ["features.home.impl.components_RoomListContentView_Day_2_en","features.home.impl.components_RoomListContentView_Night_2_en",0,], -["features.home.impl.components_RoomListContentView_Day_3_en","features.home.impl.components_RoomListContentView_Night_3_en",20466,], -["features.home.impl.components_RoomListContentView_Day_4_en","features.home.impl.components_RoomListContentView_Night_4_en",20466,], -["features.home.impl.components_RoomListContentView_Day_5_en","features.home.impl.components_RoomListContentView_Night_5_en",20466,], -["features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Day_0_en","features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Night_0_en",20466,], -["features.home.impl.filters_RoomListFiltersView_Day_0_en","features.home.impl.filters_RoomListFiltersView_Night_0_en",20466,], -["features.home.impl.filters_RoomListFiltersView_Day_1_en","features.home.impl.filters_RoomListFiltersView_Night_1_en",20466,], -["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_0_en",20466,], -["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_1_en",20466,], -["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_2_en",20466,], +["features.home.impl.components_RoomListContentView_Day_3_en","features.home.impl.components_RoomListContentView_Night_3_en",20476,], +["features.home.impl.components_RoomListContentView_Day_4_en","features.home.impl.components_RoomListContentView_Night_4_en",20476,], +["features.home.impl.components_RoomListContentView_Day_5_en","features.home.impl.components_RoomListContentView_Night_5_en",20476,], +["features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Day_0_en","features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Night_0_en",20476,], +["features.home.impl.filters_RoomListFiltersView_Day_0_en","features.home.impl.filters_RoomListFiltersView_Night_0_en",20476,], +["features.home.impl.filters_RoomListFiltersView_Day_1_en","features.home.impl.filters_RoomListFiltersView_Night_1_en",20476,], +["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_0_en",20476,], +["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_1_en",20476,], +["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_2_en",20476,], ["features.home.impl.search_RoomListSearchContent_Day_0_en","features.home.impl.search_RoomListSearchContent_Night_0_en",0,], -["features.home.impl.search_RoomListSearchContent_Day_1_en","features.home.impl.search_RoomListSearchContent_Night_1_en",20466,], -["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20466,], -["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20466,], -["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20466,], -["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20466,], -["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20466,], -["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",20466,], -["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_4_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_4_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_6_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_6_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en",20466,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_8_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_8_en",20466,], +["features.home.impl.search_RoomListSearchContent_Day_1_en","features.home.impl.search_RoomListSearchContent_Night_1_en",20476,], +["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20476,], +["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20476,], +["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20476,], +["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20476,], +["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20476,], +["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",20476,], +["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_4_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_4_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_6_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_6_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en",20476,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_8_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_8_en",20476,], ["features.roommembermoderation.impl_RoomMemberModerationView_Day_9_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_9_en",0,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20466,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20466,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20466,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20466,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20466,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20466,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20466,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20466,], -["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20466,], -["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20466,], -["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20466,], -["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20466,], -["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20466,], -["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20466,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20476,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20476,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20476,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20476,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20476,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20476,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20476,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20476,], +["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20476,], +["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20476,], +["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20476,], +["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20476,], +["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20476,], +["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20476,], ["features.home.impl.components_RoomSummaryPlaceholderRow_Day_0_en","features.home.impl.components_RoomSummaryPlaceholderRow_Night_0_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_0_en","features.home.impl.components_RoomSummaryRow_Night_0_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_10_en","features.home.impl.components_RoomSummaryRow_Night_10_en",0,], @@ -1065,16 +1073,16 @@ export const screenshots = [ ["features.home.impl.components_RoomSummaryRow_Day_26_en","features.home.impl.components_RoomSummaryRow_Night_26_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_27_en","features.home.impl.components_RoomSummaryRow_Night_27_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_28_en","features.home.impl.components_RoomSummaryRow_Night_28_en",0,], -["features.home.impl.components_RoomSummaryRow_Day_29_en","features.home.impl.components_RoomSummaryRow_Night_29_en",20466,], -["features.home.impl.components_RoomSummaryRow_Day_2_en","features.home.impl.components_RoomSummaryRow_Night_2_en",20466,], -["features.home.impl.components_RoomSummaryRow_Day_30_en","features.home.impl.components_RoomSummaryRow_Night_30_en",20466,], -["features.home.impl.components_RoomSummaryRow_Day_31_en","features.home.impl.components_RoomSummaryRow_Night_31_en",20466,], -["features.home.impl.components_RoomSummaryRow_Day_32_en","features.home.impl.components_RoomSummaryRow_Night_32_en",20466,], -["features.home.impl.components_RoomSummaryRow_Day_33_en","features.home.impl.components_RoomSummaryRow_Night_33_en",20466,], -["features.home.impl.components_RoomSummaryRow_Day_34_en","features.home.impl.components_RoomSummaryRow_Night_34_en",20466,], -["features.home.impl.components_RoomSummaryRow_Day_35_en","features.home.impl.components_RoomSummaryRow_Night_35_en",20466,], +["features.home.impl.components_RoomSummaryRow_Day_29_en","features.home.impl.components_RoomSummaryRow_Night_29_en",20476,], +["features.home.impl.components_RoomSummaryRow_Day_2_en","features.home.impl.components_RoomSummaryRow_Night_2_en",20476,], +["features.home.impl.components_RoomSummaryRow_Day_30_en","features.home.impl.components_RoomSummaryRow_Night_30_en",20476,], +["features.home.impl.components_RoomSummaryRow_Day_31_en","features.home.impl.components_RoomSummaryRow_Night_31_en",20476,], +["features.home.impl.components_RoomSummaryRow_Day_32_en","features.home.impl.components_RoomSummaryRow_Night_32_en",20476,], +["features.home.impl.components_RoomSummaryRow_Day_33_en","features.home.impl.components_RoomSummaryRow_Night_33_en",20476,], +["features.home.impl.components_RoomSummaryRow_Day_34_en","features.home.impl.components_RoomSummaryRow_Night_34_en",20476,], +["features.home.impl.components_RoomSummaryRow_Day_35_en","features.home.impl.components_RoomSummaryRow_Night_35_en",20476,], ["features.home.impl.components_RoomSummaryRow_Day_36_en","features.home.impl.components_RoomSummaryRow_Night_36_en",0,], -["features.home.impl.components_RoomSummaryRow_Day_37_en","features.home.impl.components_RoomSummaryRow_Night_37_en",20467,], +["features.home.impl.components_RoomSummaryRow_Day_37_en","features.home.impl.components_RoomSummaryRow_Night_37_en",20476,], ["features.home.impl.components_RoomSummaryRow_Day_3_en","features.home.impl.components_RoomSummaryRow_Night_3_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_4_en","features.home.impl.components_RoomSummaryRow_Night_4_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_5_en","features.home.impl.components_RoomSummaryRow_Night_5_en",0,], @@ -1082,117 +1090,117 @@ export const screenshots = [ ["features.home.impl.components_RoomSummaryRow_Day_7_en","features.home.impl.components_RoomSummaryRow_Night_7_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_8_en","features.home.impl.components_RoomSummaryRow_Night_8_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_9_en","features.home.impl.components_RoomSummaryRow_Night_9_en",0,], -["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20466,], -["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20466,], -["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20466,], +["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20476,], +["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20476,], +["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20476,], ["appicon.enterprise_RoundIcon_en","",0,], ["appicon.element_RoundIcon_en","",0,], ["libraries.designsystem.atomic.atoms_RoundedIconAtom_Day_0_en","libraries.designsystem.atomic.atoms_RoundedIconAtom_Night_0_en",0,], -["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20466,], -["libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en","libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en",20466,], -["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_0_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_0_en",20466,], -["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_1_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_1_en",20466,], -["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_2_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_2_en",20466,], -["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_3_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_3_en",20466,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20466,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20466,], +["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20476,], +["libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en","libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en",20476,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_0_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_0_en",20476,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_1_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_1_en",20476,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_2_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_2_en",20476,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_3_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_3_en",20476,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20476,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20476,], ["libraries.designsystem.theme.components_SearchBarActiveNoneQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithContent_Search_views_en","",0,], -["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20466,], +["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20476,], ["libraries.designsystem.theme.components_SearchBarActiveWithQueryNoBackButton_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarInactive_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchFieldsDark_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchFieldsLight_Search_views_en","",0,], -["features.startchat.impl.components_SearchMultipleUsersResultItem_en","",20466,], -["features.startchat.impl.components_SearchSingleUserResultItem_en","",20466,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20466,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20466,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20466,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20466,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20466,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20466,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20466,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20466,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_4_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20466,], -["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_5_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20466,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_5_en",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_0_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_10_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_11_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_12_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_13_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_14_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_15_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_16_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_17_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_19_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_1_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_20_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_21_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_22_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_23_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_2_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_3_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_4_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_5_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_6_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_7_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_8_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_9_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_0_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_10_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_11_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_12_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_13_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_14_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_15_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_16_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_17_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_19_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_1_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_20_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_21_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_22_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_23_en","",20467,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_2_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_3_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_4_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_5_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_6_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_7_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_8_en","",20466,], -["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_9_en","",20466,], +["features.startchat.impl.components_SearchMultipleUsersResultItem_en","",20476,], +["features.startchat.impl.components_SearchSingleUserResultItem_en","",20476,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20476,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20476,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20476,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20476,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20476,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20476,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20476,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20476,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_4_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20476,], +["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_5_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20476,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_5_en",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_0_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_10_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_11_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_12_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_13_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_14_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_15_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_16_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_17_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_19_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_1_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_20_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_21_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_22_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_23_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_2_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_3_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_4_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_5_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_6_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_7_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_8_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_9_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_0_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_10_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_11_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_12_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_13_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_14_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_15_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_16_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_17_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_19_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_1_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_20_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_21_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_22_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_23_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_2_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_3_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_4_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_5_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_6_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_7_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_8_en","",20476,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_9_en","",20476,], ["libraries.designsystem.atomic.atoms_SelectedIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_SelectedIndicatorAtom_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedRoomRtl_Day_0_en","libraries.matrix.ui.components_SelectedRoomRtl_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedRoomRtl_Day_1_en","libraries.matrix.ui.components_SelectedRoomRtl_Night_1_en",0,], @@ -1206,11 +1214,11 @@ export const screenshots = [ ["libraries.matrix.ui.components_SelectedUser_Day_1_en","libraries.matrix.ui.components_SelectedUser_Night_1_en",0,], ["libraries.matrix.ui.components_SelectedUsersRowList_Day_0_en","libraries.matrix.ui.components_SelectedUsersRowList_Night_0_en",0,], ["libraries.textcomposer.components_SendButtonIcon_Day_0_en","libraries.textcomposer.components_SendButtonIcon_Night_0_en",0,], -["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20466,], -["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20466,], -["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20466,], -["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20466,], -["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20466,], +["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20476,], +["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20476,], +["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20476,], +["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20476,], +["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20476,], ["libraries.matrix.ui.messages.sender_SenderName_Day_0_en","libraries.matrix.ui.messages.sender_SenderName_Night_0_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_1_en","libraries.matrix.ui.messages.sender_SenderName_Night_1_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_2_en","libraries.matrix.ui.messages.sender_SenderName_Night_2_en",0,], @@ -1220,28 +1228,28 @@ export const screenshots = [ ["libraries.matrix.ui.messages.sender_SenderName_Day_6_en","libraries.matrix.ui.messages.sender_SenderName_Night_6_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_7_en","libraries.matrix.ui.messages.sender_SenderName_Night_7_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_8_en","libraries.matrix.ui.messages.sender_SenderName_Night_8_en",0,], -["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20466,], -["features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.home.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20466,], -["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20466,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20466,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20466,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20466,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20466,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20466,], +["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20476,], +["features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.home.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20476,], +["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20476,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20476,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20476,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20476,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20476,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20476,], ["features.share.impl_ShareView_Day_0_en","features.share.impl_ShareView_Night_0_en",0,], ["features.share.impl_ShareView_Day_1_en","features.share.impl_ShareView_Night_1_en",0,], ["features.share.impl_ShareView_Day_2_en","features.share.impl_ShareView_Night_2_en",0,], -["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20466,], -["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20466,], -["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20466,], -["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20466,], -["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20466,], -["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20466,], -["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20466,], -["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20466,], -["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20466,], -["features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Day_0_en","features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Night_0_en",20466,], -["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20466,], +["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20476,], +["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20476,], +["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20476,], +["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20476,], +["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20476,], +["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20476,], +["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20476,], +["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20476,], +["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20476,], +["features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Day_0_en","features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Night_0_en",20476,], +["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20476,], ["libraries.designsystem.components_SimpleModalBottomSheet_Day_0_en","libraries.designsystem.components_SimpleModalBottomSheet_Night_0_en",0,], ["libraries.designsystem.components.dialogs_SingleSelectionDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_SingleSelectionDialog_Day_0_en","libraries.designsystem.components.dialogs_SingleSelectionDialog_Night_0_en",0,], @@ -1251,102 +1259,105 @@ export const screenshots = [ ["libraries.designsystem.components.list_SingleSelectionListItemUnselectedWithSupportingText_Single_selection_List_item_-_no_selection,_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_SingleSelectionListItem_Single_selection_List_item_-_no_selection_List_items_en","",0,], ["libraries.designsystem.theme.components_Sliders_Sliders_en","",0,], -["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20466,], +["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20476,], ["libraries.designsystem.theme.components_SnackbarWithActionAndCloseButton_Snackbar_with_action_and_close_button_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLineAndCloseButton_Snackbar_with_action_and_close_button_on_new_line_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLine_Snackbar_with_action_on_new_line_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithAction_Snackbar_with_action_Snackbars_en","",0,], ["libraries.designsystem.theme.components_Snackbar_Snackbar_Snackbars_en","",0,], -["features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_en","features.announcement.impl.spaces_SpaceAnnouncementView_Night_0_en",20466,], +["features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_en","features.announcement.impl.spaces_SpaceAnnouncementView_Night_0_en",20476,], ["libraries.designsystem.components.avatar.internal_SpaceAvatar_Avatars_en","",0,], -["libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en",20466,], -["libraries.matrix.ui.components_SpaceHeaderView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderView_Night_0_en",20466,], -["libraries.matrix.ui.components_SpaceInfoRow_Day_0_en","libraries.matrix.ui.components_SpaceInfoRow_Night_0_en",20466,], +["libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en",20476,], +["libraries.matrix.ui.components_SpaceHeaderView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderView_Night_0_en",20476,], +["libraries.matrix.ui.components_SpaceInfoRow_Day_0_en","libraries.matrix.ui.components_SpaceInfoRow_Night_0_en",20476,], ["libraries.matrix.ui.components_SpaceMembersViewNoHeroes_Day_0_en","libraries.matrix.ui.components_SpaceMembersViewNoHeroes_Night_0_en",0,], ["libraries.matrix.ui.components_SpaceMembersView_Day_0_en","libraries.matrix.ui.components_SpaceMembersView_Night_0_en",0,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en",20466,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_8_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_8_en",20466,], -["features.space.impl.settings_SpaceSettingsView_Day_0_en","features.space.impl.settings_SpaceSettingsView_Night_0_en",20466,], -["features.space.impl.settings_SpaceSettingsView_Day_1_en","features.space.impl.settings_SpaceSettingsView_Night_1_en",20466,], -["features.space.impl.settings_SpaceSettingsView_Day_2_en","features.space.impl.settings_SpaceSettingsView_Night_2_en",20466,], -["features.space.impl.settings_SpaceSettingsView_Day_3_en","features.space.impl.settings_SpaceSettingsView_Night_3_en",20466,], -["features.space.impl.root_SpaceView_Day_0_en","features.space.impl.root_SpaceView_Night_0_en",20466,], -["features.space.impl.root_SpaceView_Day_1_en","features.space.impl.root_SpaceView_Night_1_en",20466,], -["features.space.impl.root_SpaceView_Day_2_en","features.space.impl.root_SpaceView_Night_2_en",20466,], -["features.space.impl.root_SpaceView_Day_3_en","features.space.impl.root_SpaceView_Night_3_en",20466,], -["features.space.impl.root_SpaceView_Day_4_en","features.space.impl.root_SpaceView_Night_4_en",20466,], -["features.space.impl.root_SpaceView_Day_5_en","features.space.impl.root_SpaceView_Night_5_en",20466,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en",20476,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_8_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_8_en",20476,], +["features.space.impl.settings_SpaceSettingsView_Day_0_en","features.space.impl.settings_SpaceSettingsView_Night_0_en",20476,], +["features.space.impl.settings_SpaceSettingsView_Day_1_en","features.space.impl.settings_SpaceSettingsView_Night_1_en",20476,], +["features.space.impl.settings_SpaceSettingsView_Day_2_en","features.space.impl.settings_SpaceSettingsView_Night_2_en",20476,], +["features.space.impl.settings_SpaceSettingsView_Day_3_en","features.space.impl.settings_SpaceSettingsView_Night_3_en",20476,], +["features.space.impl.root_SpaceView_Day_0_en","features.space.impl.root_SpaceView_Night_0_en",20476,], +["features.space.impl.root_SpaceView_Day_1_en","features.space.impl.root_SpaceView_Night_1_en",20476,], +["features.space.impl.root_SpaceView_Day_2_en","features.space.impl.root_SpaceView_Night_2_en",20476,], +["features.space.impl.root_SpaceView_Day_3_en","features.space.impl.root_SpaceView_Night_3_en",20476,], +["features.space.impl.root_SpaceView_Day_4_en","features.space.impl.root_SpaceView_Night_4_en",20476,], +["features.space.impl.root_SpaceView_Day_5_en","features.space.impl.root_SpaceView_Night_5_en",20476,], +["features.space.impl.root_SpaceView_Day_6_en","features.space.impl.root_SpaceView_Night_6_en",20476,], +["features.space.impl.root_SpaceView_Day_7_en","features.space.impl.root_SpaceView_Night_7_en",20476,], +["features.space.impl.root_SpaceView_Day_8_en","features.space.impl.root_SpaceView_Night_8_en",20476,], ["libraries.designsystem.modifiers_SquareSizeModifierInsideSquare_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeHeight_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeWidth_en","",0,], -["features.startchat.impl.root_StartChatView_Day_0_en","features.startchat.impl.root_StartChatView_Night_0_en",20466,], -["features.startchat.impl.root_StartChatView_Day_1_en","features.startchat.impl.root_StartChatView_Night_1_en",20466,], -["features.startchat.impl.root_StartChatView_Day_2_en","features.startchat.impl.root_StartChatView_Night_2_en",20466,], -["features.startchat.impl.root_StartChatView_Day_3_en","features.startchat.impl.root_StartChatView_Night_3_en",20466,], -["features.startchat.impl.root_StartChatView_Day_4_en","features.startchat.impl.root_StartChatView_Night_4_en",20466,], -["features.startchat.impl.root_StartChatView_Day_5_en","features.startchat.impl.root_StartChatView_Night_5_en",20466,], -["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",20466,], +["features.startchat.impl.root_StartChatView_Day_0_en","features.startchat.impl.root_StartChatView_Night_0_en",20476,], +["features.startchat.impl.root_StartChatView_Day_1_en","features.startchat.impl.root_StartChatView_Night_1_en",20476,], +["features.startchat.impl.root_StartChatView_Day_2_en","features.startchat.impl.root_StartChatView_Night_2_en",20476,], +["features.startchat.impl.root_StartChatView_Day_3_en","features.startchat.impl.root_StartChatView_Night_3_en",20476,], +["features.startchat.impl.root_StartChatView_Day_4_en","features.startchat.impl.root_StartChatView_Night_4_en",20476,], +["features.startchat.impl.root_StartChatView_Day_5_en","features.startchat.impl.root_StartChatView_Night_5_en",20476,], +["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",20476,], ["features.location.api_StaticMapView_Day_0_en","features.location.api_StaticMapView_Night_0_en",0,], -["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20466,], +["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20476,], ["libraries.designsystem.atomic.pages_SunsetPage_Day_0_en","libraries.designsystem.atomic.pages_SunsetPage_Night_0_en",0,], ["libraries.designsystem.components.button_SuperButton_Day_0_en","libraries.designsystem.components.button_SuperButton_Night_0_en",0,], ["libraries.designsystem.theme.components_Surface_en","",0,], ["libraries.designsystem.theme.components_Switch_Toggles_en","",0,], -["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20466,], +["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20476,], ["libraries.designsystem.components.avatar.internal_TextAvatar_Avatars_en","",0,], ["libraries.designsystem.theme.components_TextButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMediumLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonSmall_Buttons_en","",0,], -["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20466,], -["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20466,], -["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20466,], -["libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerEditNotEncrypted_Night_0_en",20466,], -["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20466,], -["libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en",20466,], -["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20466,], -["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20466,], -["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20466,], -["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_0_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_10_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_11_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_1_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_2_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_3_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_4_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_5_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_6_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_7_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_8_en",20466,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_9_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20466,], -["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20466,], -["libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerSimpleNotEncrypted_Night_0_en",20466,], -["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20466,], -["libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerVoiceNotEncrypted_Night_0_en",20466,], +["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20476,], +["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20476,], +["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20476,], +["libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerEditNotEncrypted_Night_0_en",20476,], +["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20476,], +["libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en",20476,], +["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20476,], +["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20476,], +["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20476,], +["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_0_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_10_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_11_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_1_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_2_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_3_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_4_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_5_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_6_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_7_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_8_en",20476,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_9_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20476,], +["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20476,], +["libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerSimpleNotEncrypted_Night_0_en",20476,], +["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20476,], +["libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerVoiceNotEncrypted_Night_0_en",20476,], ["libraries.textcomposer_TextComposerVoice_Day_0_en","libraries.textcomposer_TextComposerVoice_Night_0_en",0,], ["libraries.designsystem.theme.components_TextDark_Text_en","",0,], -["libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en",20466,], -["libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en",20466,], +["libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en",20476,], +["libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en",20476,], ["libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en","",0,], ["libraries.designsystem.components.list_TextFieldListItemTextFieldValue_Text_field_List_item_-_textfieldvalue_List_items_en","",0,], ["libraries.designsystem.components.list_TextFieldListItem_Text_field_List_item_-_text_List_items_en","",0,], @@ -1358,16 +1369,16 @@ export const screenshots = [ ["libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_3_en","libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_3_en",0,], ["libraries.textcomposer.components_TextFormatting_Day_0_en","libraries.textcomposer.components_TextFormatting_Night_0_en",0,], ["libraries.designsystem.theme.components_TextLight_Text_en","",0,], -["features.messages.impl.timeline.components_ThreadSummaryView_Day_0_en","features.messages.impl.timeline.components_ThreadSummaryView_Night_0_en",20466,], -["features.messages.impl.topbars_ThreadTopBar_Day_0_en","features.messages.impl.topbars_ThreadTopBar_Night_0_en",20466,], -["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20466,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20466,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20466,], +["features.messages.impl.timeline.components_ThreadSummaryView_Day_0_en","features.messages.impl.timeline.components_ThreadSummaryView_Night_0_en",20476,], +["features.messages.impl.topbars_ThreadTopBar_Day_0_en","features.messages.impl.topbars_ThreadTopBar_Night_0_en",20476,], +["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20476,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20476,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20476,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_0_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_1_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_2_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20466,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20466,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20476,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20476,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_5_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_6_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_7_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_7_en",0,], @@ -1377,18 +1388,18 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20466,], +["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20476,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_0_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_1_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en",20466,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en",20476,], ["features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en","",0,], @@ -1396,18 +1407,18 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20466,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20466,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20476,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20476,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en",20466,], -["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20466,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20466,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en",20476,], +["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20476,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20476,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20466,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20466,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20476,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20476,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en",0,], @@ -1416,41 +1427,41 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20466,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20476,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20466,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20476,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Night_0_en",20466,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Night_0_en",20476,], ["features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20466,], +["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20476,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20466,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20466,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20476,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20476,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemInformativeView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemInformativeView_Night_0_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20466,], +["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20476,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20466,], -["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20466,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20476,], +["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20476,], ["features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20466,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20466,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20476,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20476,], ["features.messages.impl.timeline.components_TimelineItemReactionsView_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsView_Night_0_en",0,], -["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20466,], +["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20476,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_0_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_0_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_1_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_1_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_2_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_2_en",0,], @@ -1459,8 +1470,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_5_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_5_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_6_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_6_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_7_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_7_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20466,], -["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20466,], +["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20476,], +["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20476,], ["features.messages.impl.timeline.components_TimelineItemStateEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemStateEventRow_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStateView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStateView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStickerView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStickerView_Night_0_en",0,], @@ -1475,8 +1486,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_4_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_5_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20466,], -["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20466,], +["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20476,], +["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20476,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_2_en",0,], @@ -1499,84 +1510,84 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemVoiceView_Day_9_en","features.messages.impl.timeline.components.event_TimelineItemVoiceView_Night_9_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en",0,], -["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20466,], +["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20476,], ["features.messages.impl.timeline_TimelineView_Day_10_en","features.messages.impl.timeline_TimelineView_Night_10_en",0,], -["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20466,], -["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20466,], +["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20476,], +["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20476,], ["features.messages.impl.timeline_TimelineView_Day_2_en","features.messages.impl.timeline_TimelineView_Night_2_en",0,], ["features.messages.impl.timeline_TimelineView_Day_3_en","features.messages.impl.timeline_TimelineView_Night_3_en",0,], -["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20466,], +["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20476,], ["features.messages.impl.timeline_TimelineView_Day_5_en","features.messages.impl.timeline_TimelineView_Night_5_en",0,], -["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20466,], +["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20476,], ["features.messages.impl.timeline_TimelineView_Day_7_en","features.messages.impl.timeline_TimelineView_Night_7_en",0,], ["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",0,], ["features.messages.impl.timeline_TimelineView_Day_9_en","features.messages.impl.timeline_TimelineView_Night_9_en",0,], ["libraries.designsystem.components.avatar.internal_TombstonedRoomAvatar_Avatars_en","",0,], ["libraries.designsystem.theme.components_TopAppBarStr_App_Bars_en","",0,], ["libraries.designsystem.theme.components_TopAppBar_App_Bars_en","",0,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20466,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20466,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20466,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20466,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20466,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20466,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20466,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20466,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20476,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20476,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20476,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20476,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20476,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20476,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20476,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20476,], ["features.messages.impl.typing_TypingNotificationView_Day_0_en","features.messages.impl.typing_TypingNotificationView_Night_0_en",0,], -["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20466,], -["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20466,], -["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20466,], -["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20466,], -["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20466,], -["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20466,], +["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20476,], +["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20476,], +["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20476,], +["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20476,], +["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20476,], +["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20476,], ["features.messages.impl.typing_TypingNotificationView_Day_7_en","features.messages.impl.typing_TypingNotificationView_Night_7_en",0,], ["features.messages.impl.typing_TypingNotificationView_Day_8_en","features.messages.impl.typing_TypingNotificationView_Night_8_en",0,], ["libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en",0,], -["libraries.matrix.ui.components_UnresolvedUserRow_en","",20466,], +["libraries.matrix.ui.components_UnresolvedUserRow_en","",20476,], ["libraries.designsystem.components.avatar.internal_UserAvatarColors_Day_0_en","libraries.designsystem.components.avatar.internal_UserAvatarColors_Night_0_en",0,], -["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20466,], -["features.startchat.impl.components_UserListView_Day_0_en","features.startchat.impl.components_UserListView_Night_0_en",20466,], -["features.startchat.impl.components_UserListView_Day_1_en","features.startchat.impl.components_UserListView_Night_1_en",20466,], -["features.startchat.impl.components_UserListView_Day_2_en","features.startchat.impl.components_UserListView_Night_2_en",20466,], +["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20476,], +["features.startchat.impl.components_UserListView_Day_0_en","features.startchat.impl.components_UserListView_Night_0_en",20476,], +["features.startchat.impl.components_UserListView_Day_1_en","features.startchat.impl.components_UserListView_Night_1_en",20476,], +["features.startchat.impl.components_UserListView_Day_2_en","features.startchat.impl.components_UserListView_Night_2_en",20476,], ["features.startchat.impl.components_UserListView_Day_3_en","features.startchat.impl.components_UserListView_Night_3_en",0,], ["features.startchat.impl.components_UserListView_Day_4_en","features.startchat.impl.components_UserListView_Night_4_en",0,], ["features.startchat.impl.components_UserListView_Day_5_en","features.startchat.impl.components_UserListView_Night_5_en",0,], ["features.startchat.impl.components_UserListView_Day_6_en","features.startchat.impl.components_UserListView_Night_6_en",0,], -["features.startchat.impl.components_UserListView_Day_7_en","features.startchat.impl.components_UserListView_Night_7_en",20466,], +["features.startchat.impl.components_UserListView_Day_7_en","features.startchat.impl.components_UserListView_Night_7_en",20476,], ["features.startchat.impl.components_UserListView_Day_8_en","features.startchat.impl.components_UserListView_Night_8_en",0,], -["features.startchat.impl.components_UserListView_Day_9_en","features.startchat.impl.components_UserListView_Night_9_en",20466,], +["features.startchat.impl.components_UserListView_Day_9_en","features.startchat.impl.components_UserListView_Night_9_en",20476,], ["features.preferences.impl.user_UserPreferences_Day_0_en","features.preferences.impl.user_UserPreferences_Night_0_en",0,], ["features.preferences.impl.user_UserPreferences_Day_1_en","features.preferences.impl.user_UserPreferences_Night_1_en",0,], ["features.preferences.impl.user_UserPreferences_Day_2_en","features.preferences.impl.user_UserPreferences_Night_2_en",0,], -["features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en","features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en",20466,], -["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20466,], -["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20466,], -["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20466,], -["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20466,], -["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20466,], -["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20466,], -["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20466,], -["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20466,], -["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20466,], -["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",20466,], -["features.userprofile.shared_UserProfileView_Day_9_en","features.userprofile.shared_UserProfileView_Night_9_en",20466,], +["features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en","features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en",20476,], +["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20476,], +["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20476,], +["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20476,], +["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20476,], +["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20476,], +["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20476,], +["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20476,], +["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20476,], +["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20476,], +["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",20476,], +["features.userprofile.shared_UserProfileView_Day_9_en","features.userprofile.shared_UserProfileView_Night_9_en",20476,], ["features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en","features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en",0,], ["libraries.designsystem.ruler_VerticalRuler_Day_0_en","libraries.designsystem.ruler_VerticalRuler_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en",0,], -["features.preferences.impl.advanced_VideoQualitySelectorDialog_Day_0_en","features.preferences.impl.advanced_VideoQualitySelectorDialog_Night_0_en",20466,], -["features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en","features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en",20466,], +["features.preferences.impl.advanced_VideoQualitySelectorDialog_Day_0_en","features.preferences.impl.advanced_VideoQualitySelectorDialog_Night_0_en",20476,], +["features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en","features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en",20476,], ["features.viewfolder.impl.file_ViewFileView_Day_0_en","features.viewfolder.impl.file_ViewFileView_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_1_en","features.viewfolder.impl.file_ViewFileView_Night_1_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_2_en","features.viewfolder.impl.file_ViewFileView_Night_2_en",0,], -["features.viewfolder.impl.file_ViewFileView_Day_3_en","features.viewfolder.impl.file_ViewFileView_Night_3_en",20466,], +["features.viewfolder.impl.file_ViewFileView_Day_3_en","features.viewfolder.impl.file_ViewFileView_Night_3_en",20476,], ["features.viewfolder.impl.file_ViewFileView_Day_4_en","features.viewfolder.impl.file_ViewFileView_Night_4_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_5_en","features.viewfolder.impl.file_ViewFileView_Night_5_en",0,], ["features.viewfolder.impl.folder_ViewFolderView_Day_0_en","features.viewfolder.impl.folder_ViewFolderView_Night_0_en",0,], diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcher.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcher.kt index 7e11e703ec..75dffd0af1 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcher.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcher.kt @@ -46,13 +46,17 @@ class DefaultAnalyticsRoomListStateWatcher( return } + val longRunningTransaction = AnalyticsLongRunningTransaction.CatchUp + appNavigationStateService.appNavigationState .map { it.isInForeground } .distinctUntilChanged() .withPreviousValue() .onEach { (wasInForeground, isInForeground) -> if (isInForeground && roomListService.state.value != RoomListService.State.Running) { - analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.CatchUp) + analyticsService.startLongRunningTransaction(longRunningTransaction) + } else if (!isInForeground) { + analyticsService.removeLongRunningTransaction(longRunningTransaction) } if (wasInForeground == false && isInForeground) { @@ -64,7 +68,7 @@ class DefaultAnalyticsRoomListStateWatcher( roomListService.state .onEach { state -> if (state == RoomListService.State.Running && isWarmState.get()) { - analyticsService.finishLongRunningTransaction(AnalyticsLongRunningTransaction.CatchUp) + analyticsService.finishLongRunningTransaction(longRunningTransaction) } } .launchIn(coroutineScope) diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt index 49edc9b1a0..7297f02940 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProvider.kt @@ -11,7 +11,6 @@ package io.element.android.services.analyticsproviders.posthog import com.posthog.PostHogInterface import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.itf.VectorAnalyticsEvent import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties @@ -25,7 +24,6 @@ import timber.log.Timber // private val IGNORED_OPTIONS: Options? = null @ContributesIntoSet(AppScope::class) -@Inject class PosthogAnalyticsProvider( private val postHogFactory: PostHogFactory, ) : AnalyticsProvider { diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt index ef2d4e21d9..a24d9bd08f 100644 --- a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt @@ -12,7 +12,6 @@ import android.content.Context import androidx.annotation.VisibleForTesting import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.itf.VectorAnalyticsEvent import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties @@ -38,7 +37,6 @@ import kotlinx.coroutines.runBlocking import timber.log.Timber @ContributesIntoSet(AppScope::class) -@Inject class SentryAnalyticsProvider( @ApplicationContext private val context: Context, private val sentryDsn: SentryDsn?, diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt index b3db2afd38..dcd689c14a 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt @@ -28,6 +28,7 @@ class KonsistComposableTest { .functions() .withTopLevel() .withoutModifier(KoModifier.PRIVATE) + .withoutModifier(KoModifier.INTERNAL) .withoutNameEndingWith("Preview") .withAllAnnotationsOf(Composable::class) .withoutReceiverType() diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt index a5b2f1572c..ef6273580c 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt @@ -15,6 +15,8 @@ import com.lemonappdev.konsist.api.verify.assertFalse import com.lemonappdev.konsist.api.verify.assertTrue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.ContributesIntoMap +import dev.zacsweers.metro.ContributesIntoSet import dev.zacsweers.metro.Inject import org.junit.Test @@ -46,4 +48,26 @@ class KonsistDiTest { classDeclaration.hasAnnotationOf(Inject::class) } } + + @Test + fun `class annotated with @ContributesIntoSet does not need to be annotated with @Inject anymore`() { + Konsist + .scopeFromProject() + .classes() + .withAnnotationOf(ContributesIntoSet::class) + .assertFalse { classDeclaration -> + classDeclaration.hasAnnotationOf(Inject::class) + } + } + + @Test + fun `class annotated with @ContributesIntoMap does not need to be annotated with @Inject anymore`() { + Konsist + .scopeFromProject() + .classes() + .withAnnotationOf(ContributesIntoMap::class) + .assertFalse { classDeclaration -> + classDeclaration.hasAnnotationOf(Inject::class) + } + } } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index e4a9d60679..245f841d1a 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -101,7 +101,6 @@ class KonsistPreviewTest { "MessageComposerViewVoicePreview", "MessagesReactionButtonAddPreview", "MessagesReactionButtonExtraPreview", - "MessagesViewWithHistoryVisiblePreview", "MessagesViewWithIdentityChangePreview", "PendingMemberRowWithLongNamePreview", "PinUnlockViewInAppPreview", diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_7_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_7_en.png new file mode 100644 index 0000000000..9d1c0890ae --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c949830fae61474e63adb56f9d3b355db203a4c2902fcef4605ac6b6a5c5148a +size 45333 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_8_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_8_en.png new file mode 100644 index 0000000000..9ea4f88b93 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewDark_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4bf55777af9384a34c438de10887f5b6dff107b837f4bb7a2f974644c256047 +size 48431 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_7_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_7_en.png new file mode 100644 index 0000000000..cb9571dd0d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_7_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:286ec7cfa373ebf4bcd8704895a0398f4b4fe7710c2608b325a842dae3e7a294 +size 46955 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_8_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_8_en.png new file mode 100644 index 0000000000..80a7226c30 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_ConfigureRoomViewLight_8_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e95d2194cfc8b979624ce3b97250a85051d67bd8f5f61bb1e60d274c08f4649 +size 50132 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_SelectParentSpaceBottomSheet_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_SelectParentSpaceBottomSheet_Day_0_en.png new file mode 100644 index 0000000000..0f51143564 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_SelectParentSpaceBottomSheet_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7168b3f34dbd9bdbb34682aecbc1f81e16ae7be6af7e832f6e18e0d090f38f2 +size 21093 diff --git a/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_SelectParentSpaceBottomSheet_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_SelectParentSpaceBottomSheet_Night_0_en.png new file mode 100644 index 0000000000..96eeea0d38 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.createroom.impl.configureroom_SelectParentSpaceBottomSheet_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d03b34a155b919fd17b280448991299294a0feda120c1f1e4a4ce2d967073294 +size 19942 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_3_en.png new file mode 100644 index 0000000000..c8cfca0ff6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:119b7216f0be644eb1153439eebdaf0ebb9acae3b68161c66fa0fa8c21db8703 +size 24868 diff --git a/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_3_en.png new file mode 100644 index 0000000000..f0095cc4b9 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.home.impl.spaces_HomeSpacesView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec42ba9eb13978cadb3d556482413eda959178d9a32743c2469ac3702541e8b5 +size 24247 diff --git a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_0_en.png index 1e55a5b62a..4013443c6e 100644 --- a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:070f37faf63ee4bb7acabfbe19943181b367cb53d23978efe9ca669d6051e04c -size 9137 +oid sha256:267f997e9f23c0745f8ac27216c125da7eb78db9f7fa42e670e5e2b40b8eeec2 +size 37777 diff --git a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_1_en.png index db8bb9c93d..6ec7f4b6a1 100644 --- a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1a765e16b340e75d0657de9bbbb429d09ebffae9e94deee982a8bcfc74e21bf -size 21454 +oid sha256:5ffe121ccdc83d57224397ffd5ad5fc98acec3fd4b5eb5f495c173365ff39edd +size 50791 diff --git a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_9_en.png index db8bb9c93d..6ec7f4b6a1 100644 --- a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1a765e16b340e75d0657de9bbbb429d09ebffae9e94deee982a8bcfc74e21bf -size 21454 +oid sha256:5ffe121ccdc83d57224397ffd5ad5fc98acec3fd4b5eb5f495c173365ff39edd +size 50791 diff --git a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_0_en.png index 8a27251ee9..6e35fe8da2 100644 --- a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1787bd92c75748e69f346f7588170bf6fa9c626dd6544f4270f30f0f9b614037 -size 8631 +oid sha256:b7fdff51735509f84dcf9c51ffcf813bfd0844fd7ba4374b39a969e1f00089c0 +size 37941 diff --git a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_1_en.png index 07c21c8e8c..5408ef8812 100644 --- a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c4c5d0035baf55bc67abf83eae90fa0c16e044a0d0ab26634c02fb0ad5d4c26 -size 22062 +oid sha256:dada059f12c0fe97f63fed9e63e04e174f8b285a0f40b33e1d49e86039b9201c +size 51905 diff --git a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_9_en.png index 07c21c8e8c..5408ef8812 100644 --- a/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.invitepeople.impl_InvitePeopleView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c4c5d0035baf55bc67abf83eae90fa0c16e044a0d0ab26634c02fb0ad5d4c26 -size 22062 +oid sha256:dada059f12c0fe97f63fed9e63e04e174f8b285a0f40b33e1d49e86039b9201c +size 51905 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_en.png deleted file mode 100644 index 82395ab3e4..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0489a15e6f8b358780484ea33807aaa2079512f7077b81b1e7e455092968cd61 -size 25463 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Night_0_en.png deleted file mode 100644 index 75c8eb8e45..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da4fa289d3a4f32da964a47392458a09bdf80d4410e9ab9519225d077bb4a168 -size 28316 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_en.png deleted file mode 100644 index 6ebf12ac3d..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf7b2508bdf41e2ee9b8b26207c604232f54314797237aceec5675524a7c8ab5 -size 66644 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Night_0_en.png deleted file mode 100644 index 02b6564855..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e58f097893ebc5f8664283d3e22b055b7294ce6d6bf571b34507e2242f70e677 -size 68988 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png index b2de7d213c..bdc463b94b 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c46805397e92a263c4c19aa67c6a7b6ada90323604f1171c9dae21b29aa0d425 -size 45307 +oid sha256:d01613c76c2d699c8a53147debfebd20e9029d1f14dc1af8f664b9f707c099a5 +size 66532 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png index f3d7df2758..4bdcc92bb4 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.timeline.components_MessageShieldView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad9ebc7d4700f7f8b50c478bbf996e3d3b7d9b7f267c27fff9108274fb6d3f28 -size 43948 +oid sha256:2a7459fafcfa5f366e51b5ba64a4b9a9fe4f0af96f8c52c2af174a6f6206eba6 +size 64563 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png index 899bf4ad75..4f36513ee3 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dca37712b4df739fea42e8070761c431da4163732caa1d005bf50fd98b79764c -size 39730 +oid sha256:2ca04a8092b40b50f3724bca9213ac2be4742c6b754ef949566fd23b052ab808 +size 46501 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png index de5049cae0..84dcdeb6a6 100644 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.messages.impl.topbars_MessagesViewTopBar_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1bea37c40c43b5887cc87e0ed95552a5e42cf0b776a1342d66b2f0b2dd7836c -size 38283 +oid sha256:cbca529b51092d41afbd772cb2ed207709423c5590588a128a7c48bfad64917b +size 45275 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png deleted file mode 100644 index 3fe678a873..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_11_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47c0b2a5dedb5b8ca1a027dfe93b27b97069f4c65b9e1553d7847f5f06348678 -size 68593 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png deleted file mode 100644 index 826ba15d61..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Day_12_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e0581c343abffc4a71db827cdc5f8b183525daed8b17b80cd6b511cb70ae9a05 -size 66428 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png deleted file mode 100644 index 5b2e6c0429..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_11_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ea74e00b157d5e0f7caf71ba839f1523bfbc0684335ba270aec05187a88c3e5 -size 70237 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png deleted file mode 100644 index 57fac247d9..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl_MessagesView_Night_12_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c5d618df3faa09281e59897b7f478d12f5a0d907b47f58747607e1de8cbc6e61 -size 68146 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_en.png index 596057c7fe..1ed280cd90 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8c00c06c9da1432f196aafa11e529e393f6b03310fa4d9984bf66d4f7faa058 -size 44922 +oid sha256:144ec8da9382f33c7566452515a379eb7fec3e7c1cbd1c6c79a871e12093e6ee +size 48043 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_en.png index b4a57f090f..11fb25fb6f 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13d339ed6e27f32f7820048acf35008f406f7ad2597cf1487a05b0a7e6b1ab5b -size 28289 +oid sha256:fa16dfc8c993212b66a1ac610952bec40ac922bbefae6d69455335e38042aadc +size 26291 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_4_en.png index eab0932ee8..28b4849ecf 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_4_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_4_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f9eb9966b578538160a181719f3ea1a22caa0541ccc579f363f9e0f00f5851c -size 43811 +oid sha256:53cf288c75a59cb97d2c0d648273e233b0ac9ba41f04b2cc57f7fe24657cf924 +size 47069 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_6_en.png index a42d10c37b..45bae9b85f 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c3abff638ea74e7d2913ce45de920f896b546d705424ea09e10eda4b1ff6072 -size 27467 +oid sha256:992d2a4cafdcfcbf9e1b7517855bad1ec6141152b087012fa32c3da413b98921 +size 25636 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_0_en.png new file mode 100644 index 0000000000..cfcec20e41 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72074f44d83f1772e83de96fd86265a43cdd2123023ac65bee8ddf7abf4af37e +size 38249 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_1_en.png new file mode 100644 index 0000000000..a7e08a1c9f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a295c4f8c9e784861a3db01597be406615a4adb6c4d8997478336e3a3f30003 +size 11682 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_2_en.png new file mode 100644 index 0000000000..2575b9e323 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7c006740c4fd75d7efbd5abe59804d8d5fabbfd7f11b6d14c0b00fcb83850be +size 21434 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_3_en.png new file mode 100644 index 0000000000..a28bfd86a8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:841cfff6c0c0219e83e6ee7ca03060e143ddff72f7946e578498ffb6918fa37d +size 14200 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_4_en.png new file mode 100644 index 0000000000..a272355e5b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83d9e5eb832d1f52b17e35e18d1c834120a7b510c2a3cc1647dc35dfeeb8b0a9 +size 42762 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_5_en.png new file mode 100644 index 0000000000..4c049df7e7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d5fd28a7971e2f25fdf6d3e1b0af9a1d279369d2f1d656ff72a36a1badfd774 +size 29089 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_6_en.png new file mode 100644 index 0000000000..d1135ccc34 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d191722630f4eede4606f63053d29996ae32c728af85e198a390be82b84d274 +size 40323 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_0_en.png new file mode 100644 index 0000000000..bf6a20ec99 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd5f99d986f37e962322060add381b334c50a6fbc05a23fa09319b37cc86a090 +size 38078 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_1_en.png new file mode 100644 index 0000000000..489a7dcb1b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e011e95c98c9694980b031207b82c6feb0b9b51b06b233672bee2ee3ceb5b18c +size 11382 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_2_en.png new file mode 100644 index 0000000000..e43d5e7f3d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71bc3b3b49e37690e92fe23ab3c48cc286341e64c427471b7c940b65243bd998 +size 21776 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_3_en.png new file mode 100644 index 0000000000..7917998cd2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76ab4de8587bac55872b2807b342fdbdf72f771aed65ee342705f49a89c1f196 +size 13865 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_4_en.png new file mode 100644 index 0000000000..cb20383fc6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50898da4de06be0d2430d4635b7c6880701d60fb78940bb2135c943e2d9634b5 +size 42908 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_5_en.png new file mode 100644 index 0000000000..b3bab96efb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c4da20d886e0f2fadd57edaea13c2bae4d19fcf4c8a397a2dec92f68e14aad7 +size 27733 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_6_en.png new file mode 100644 index 0000000000..ebbc706877 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.addroom_AddRoomToSpaceView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ceeea93e8cd825cf82971abe76c5a0d28e0522ffa4694db588c87f9473cc117 +size 37422 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png index f69878ffd5..8f42888316 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e59a9e2ae6ef36f28e61534b5639314cc840953df51bb1660e77e8d565865357 -size 32998 +oid sha256:6444cece1e3f5dfc94591380837fb8aa8caecdc4a87881485278c036accf2007 +size 40411 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png index 7f66fa5a85..06b582ecc5 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b854ce2b0618ebcbc88eff9952d6869bceeb8b43f9eccb8f8e5feef225e0a4c2 -size 33181 +oid sha256:70e98028aafc62aeb86e69db82dce9b9bb0250d47339a81e8e832a0600d21217 +size 40588 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png index b0f6f289a0..02e3dd1ecb 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1cf063ee5c9fbc50a53445050780ba239d4fb0fd1e9903a578eab8dd3bfc257 -size 33496 +oid sha256:a6a950c626b57f2624ce8929f61dfa2fcdc25036e0b3ebb5ec87a00e7531aea8 +size 40898 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png index 97dbb10528..08f5676c82 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:525059397001897f705630b8ac5a661439a502f2e623fdca252f9e86f97133e4 -size 58181 +oid sha256:778488da7192f2c3f98367d9d424783a42343a4bff8b7616f6175952526a8d30 +size 57766 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_6_en.png index bc44bb4ce4..88df67a244 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59af097a2152b235c7df83aa76eea880ceef65edebebecb86f00745ec39712f4 -size 35937 +oid sha256:dbe04b9b4274183626f94d7014df9fc6462114226670161ffa3bb94b214b027e +size 34283 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_7_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_7_en.png index 68f915afff..aff1fc2e08 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Day_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5151bbd3effcd6a46724a0cfb13730e845a3b8ba489826146c76d0a797403e0 -size 36502 +oid sha256:b86c4e7011f15079e01e7aa8e8c26b94cffb4ef451b3374d3834641336d3d036 +size 34853 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png index 9d406ab88a..8c1cbb8f74 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47c8bdf4d153ecebe749ae3cacfe4c9a1c59ac836106fb0903dca80305aedac2 -size 32412 +oid sha256:9b221b06e7533c21ed4393f388630c7119a1e7087b3a33de6c2cf498b01bbe65 +size 39544 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png index d178c45fec..347a57d0a5 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33917297cb1c6d38e5b955757de50f4ff6b73844bb87e8e88d0885daa01b266b -size 32556 +oid sha256:dd9b3d59af7fc710b7ef8f16ed961086693aa685de93658e2df85e3edbd7f787 +size 39689 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png index 35ca1a2937..02c60e8300 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:930f763051533ea3aa4032e483e15e0c1ccb1d213d2e59bebe7f934517b500ba -size 32868 +oid sha256:db27594444024da1818bbb089fac602b0937ad786072ad3542aa822e5a29c8c4 +size 40008 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png index 5f2f43a920..53bcb97254 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_5_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd673c2cf628285836848a732d3972a17421a2394464a3a77e7a49e4c5a862f1 -size 56636 +oid sha256:21ec87536d892719f5343c0aacde2ed4be1a7d0987eff72675b03b4be5038f78 +size 56278 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_6_en.png index 3078485999..a734416241 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b459fa8aa195dcf85995ac51c2f079e9d2505efede138bcef94c0a5049e1f271 -size 35266 +oid sha256:8c01095702c3ab147349f9b60729b534ec1763bc5c04e43b6d571a52a115e10a +size 33634 diff --git a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_7_en.png b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_7_en.png index 7741306594..893ddc1840 100644 --- a/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_7_en.png +++ b/tests/uitests/src/test/snapshots/images/features.space.impl.root_SpaceView_Night_7_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36fd123fcff07169723857c99e4e375fe9bbb8193b2a6e97508343ea025d5787 -size 35782 +oid sha256:ae1a096a6de24c2c6517455cd632291ad9d3d45e16df02fa75c33f278e44b26e +size 34149 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index e6ea1df3cb..ad16666055 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -234,7 +234,8 @@ "includeRegex" : [ "screen\\.leave_space\\..*", "screen\\.space_settings\\..*", - "screen\\.space\\..*" + "screen\\.space\\..*", + "screen\\.space_add_rooms\\..*" ] }, { @@ -260,6 +261,7 @@ "screen_room_encrypted.*", "screen_room_invite.*", "screen\\.room\\.mention.*", + "crypto\\.event_authenticity\\..*", "screen_room_message.*", "screen_room_retry.*", "screen_room_timeline.*",