From 6e8d0ded36f4223f3dc2fc4ee830ba59493f19c5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 20 Mar 2024 12:59:02 +0100 Subject: [PATCH 001/124] Room Directory Search : setup the feature --- features/roomdirectory/api/build.gradle.kts | 28 ++++++++ .../api/RoomDirectoryEntryPoint.kt | 38 +++++++++++ features/roomdirectory/impl/build.gradle.kts | 52 +++++++++++++++ .../impl/DefaultRoomDirectoryEntryPoint.kt | 46 +++++++++++++ .../impl/RoomDirectoryFlowNode.kt | 66 +++++++++++++++++++ .../impl/search/RoomDirectorySearchEvents.kt | 25 +++++++ .../impl/search/RoomDirectorySearchNode.kt | 44 +++++++++++++ .../search/RoomDirectorySearchPresenter.kt | 40 +++++++++++ .../impl/search/RoomDirectorySearchState.kt | 21 ++++++ .../RoomDirectorySearchStateProvider.kt | 31 +++++++++ .../impl/search/RoomDirectorySearchView.kt | 48 ++++++++++++++ .../libraries/featureflag/api/FeatureFlags.kt | 7 ++ .../impl/StaticFeatureFlagProvider.kt | 1 + 13 files changed, 447 insertions(+) create mode 100644 features/roomdirectory/api/build.gradle.kts create mode 100644 features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt create mode 100644 features/roomdirectory/impl/build.gradle.kts create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchEvents.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt diff --git a/features/roomdirectory/api/build.gradle.kts b/features/roomdirectory/api/build.gradle.kts new file mode 100644 index 0000000000..c55c647e24 --- /dev/null +++ b/features/roomdirectory/api/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.roomdirectory.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt new file mode 100644 index 0000000000..033ec67b33 --- /dev/null +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.matrix.api.core.RoomId + +interface RoomDirectoryEntryPoint : FeatureEntryPoint { + + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + interface Callback : Plugin { + fun onRoomJoined(roomId: RoomId) + } +} + diff --git a/features/roomdirectory/impl/build.gradle.kts b/features/roomdirectory/impl/build.gradle.kts new file mode 100644 index 0000000000..0e35623401 --- /dev/null +++ b/features/roomdirectory/impl/build.gradle.kts @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.roomdirectory.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + api(projects.features.roomdirectory.api) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.designsystem) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) + + ksp(libs.showkase.processor) +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt new file mode 100644 index 0000000000..ac085b4d03 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultRoomDirectoryEntryPoint @Inject constructor() : RoomDirectoryEntryPoint { + + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDirectoryEntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : RoomDirectoryEntryPoint.NodeBuilder { + + override fun callback(callback: RoomDirectoryEntryPoint.Callback): RoomDirectoryEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } + } +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt new file mode 100644 index 0000000000..e053fa6c58 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl + +import android.os.Parcelable +import androidx.compose.runtime.Composable +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 com.bumble.appyx.navmodel.backstack.BackStack +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.roomdirectory.impl.search.RoomDirectorySearchNode +import io.element.android.libraries.architecture.BackstackView +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.SessionScope +import kotlinx.parcelize.Parcelize + +@ContributesNode(SessionScope::class) +class RoomDirectoryFlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : BaseFlowNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.Root -> { + createNode(buildContext) + } + } + } + + @Composable + override fun View(modifier: Modifier) { + BackstackView() + } +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchEvents.kt new file mode 100644 index 0000000000..f7212529b2 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchEvents.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search + +import io.element.android.libraries.matrix.api.core.RoomId + +sealed interface RoomDirectorySearchEvents { + data class Search(val query: String) : RoomDirectorySearchEvents + data object LoadMore : RoomDirectorySearchEvents + data class JoinRoom(val roomId: RoomId) : RoomDirectorySearchEvents +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt new file mode 100644 index 0000000000..1f459f404c --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search + +import androidx.compose.runtime.Composable +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 dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class RoomDirectorySearchNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: RoomDirectorySearchPresenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + RoomDirectorySearchView( + state = state, + modifier = modifier + ) + } +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt new file mode 100644 index 0000000000..d0cc6d47c4 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class RoomDirectorySearchPresenter @Inject constructor() : Presenter { + + @Composable + override fun present(): RoomDirectorySearchState { + + fun handleEvents(event: RoomDirectorySearchEvents) { + when (event) { + is RoomDirectorySearchEvents.JoinRoom -> TODO() + RoomDirectorySearchEvents.LoadMore -> TODO() + is RoomDirectorySearchEvents.Search -> TODO() + } + } + + return RoomDirectorySearchState( + eventSink = ::handleEvents + ) + } +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt new file mode 100644 index 0000000000..0e77801e12 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search + +data class RoomDirectorySearchState( + val eventSink: (RoomDirectorySearchEvents) -> Unit +) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt new file mode 100644 index 0000000000..31464fd17c --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class RoomDirectorySearchStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aRoomDirectorySearchState(), + // Add other states here + ) +} + +fun aRoomDirectorySearchState() = RoomDirectorySearchState( + eventSink = {} +) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt new file mode 100644 index 0000000000..d2e3ad1ec9 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.MaterialTheme +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.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun RoomDirectorySearchView( + state: RoomDirectorySearchState, + modifier: Modifier = Modifier, +) { + Box(modifier, contentAlignment = Alignment.Center) { + Text( + "RoomDirectorySearch feature view", + color = MaterialTheme.colorScheme.primary, + ) + } +} + +@PreviewsDayNight +@Composable +fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectorySearchState) = ElementPreview { + RoomDirectorySearchView( + state = state, + ) +} diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index a242d014d1..7140a89064 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -89,4 +89,11 @@ enum class FeatureFlags( defaultValue = true, isFinished = false, ), + RoomDirectorySearch( + key = "feature.roomdirectorysearch", + title = "Room directory search", + description = "Allow user to search for public rooms in his homeserver", + defaultValue = true, + isFinished = false, + ) } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index 13cc7da462..5b6ee3bc03 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -42,6 +42,7 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.MarkAsUnread -> true FeatureFlags.RoomListFilters -> false FeatureFlags.RoomModeration -> false + FeatureFlags.RoomDirectorySearch -> false } } else { false From a8028ba9684d3bea08c13cfd1c194060c5f01a93 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 20 Mar 2024 15:35:54 +0100 Subject: [PATCH 002/124] Room directory search : branch entry point --- .../android/appnav/LoggedInFlowNode.kt | 18 +++++++++++ .../roomlist/api/RoomListEntryPoint.kt | 1 + .../features/roomlist/impl/RoomListNode.kt | 5 +++ .../features/roomlist/impl/RoomListView.kt | 3 ++ .../impl/search/RoomListSearchView.kt | 31 ++++++++++++++++++- 5 files changed, 57 insertions(+), 1 deletion(-) 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 e4290d5bdf..9a96a01d27 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -53,6 +53,7 @@ import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.preferences.api.PreferencesEntryPoint +import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.securebackup.api.SecureBackupEntryPoint import io.element.android.features.verifysession.api.VerifySessionEntryPoint @@ -97,6 +98,7 @@ class LoggedInFlowNode @AssistedInject constructor( private val ftueState: FtueState, private val lockScreenEntryPoint: LockScreenEntryPoint, private val lockScreenStateService: LockScreenService, + private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint, private val matrixClient: MatrixClient, snackbarDispatcher: SnackbarDispatcher, ) : BaseFlowNode( @@ -225,6 +227,9 @@ class LoggedInFlowNode @AssistedInject constructor( @Parcelize data object Ftue : NavTarget + + @Parcelize + data object RoomDirectorySearch : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -270,6 +275,10 @@ class LoggedInFlowNode @AssistedInject constructor( override fun onReportBugClicked() { plugins().forEach { it.onOpenBugReport() } } + + override fun onRoomDirectorySearchClicked() { + backstack.push(NavTarget.RoomDirectorySearch) + } } roomListEntryPoint .nodeBuilder(this, buildContext) @@ -377,6 +386,15 @@ class LoggedInFlowNode @AssistedInject constructor( }) .build() } + NavTarget.RoomDirectorySearch -> { + roomDirectoryEntryPoint.nodeBuilder(this, buildContext) + .callback(object : RoomDirectoryEntryPoint.Callback { + override fun onRoomJoined(roomId: RoomId) { + coroutineScope.launch { attachRoom(roomId) } + } + }) + .build() + } } } diff --git a/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt b/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt index 0404ce18fc..c3b2ff36a2 100644 --- a/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt +++ b/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt @@ -38,5 +38,6 @@ interface RoomListEntryPoint : FeatureEntryPoint { fun onInvitesClicked() fun onRoomSettingsClicked(roomId: RoomId) fun onReportBugClicked() + fun onRoomDirectorySearchClicked() } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt index 5dec53e158..a9740bcfcb 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt @@ -91,6 +91,10 @@ class RoomListNode @AssistedInject constructor( } } + private fun onRoomDirectorySearchClicked() { + plugins().forEach { it.onRoomDirectorySearchClicked() } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -105,6 +109,7 @@ class RoomListNode @AssistedInject constructor( onInvitesClicked = this::onInvitesClicked, onRoomSettingsClicked = this::onRoomSettingsClicked, onMenuActionClicked = { onMenuActionClicked(activity, it) }, + onRoomDirectorySearchClicked = this::onRoomDirectorySearchClicked, modifier = modifier, ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 23a105f143..e0a3fc5201 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -59,6 +59,7 @@ fun RoomListView( onInvitesClicked: () -> Unit, onRoomSettingsClicked: (roomId: RoomId) -> Unit, onMenuActionClicked: (RoomListMenuAction) -> Unit, + onRoomDirectorySearchClicked: () -> Unit, modifier: Modifier = Modifier, ) { ConnectivityIndicatorContainer( @@ -99,6 +100,7 @@ fun RoomListView( state = state.searchState, onRoomClicked = onRoomClicked, onRoomLongClicked = { onRoomLongClicked(it) }, + onRoomDirectorySearchClicked = onRoomDirectorySearchClicked, modifier = Modifier .statusBarsPadding() .padding(top = topPadding) @@ -197,5 +199,6 @@ internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class) onInvitesClicked = {}, onRoomSettingsClicked = {}, onMenuActionClicked = {}, + onRoomDirectorySearchClicked = {}, ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt index eff6449449..e8c68cebf0 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt @@ -26,6 +26,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.QrCode import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextFieldDefaults @@ -50,8 +52,10 @@ import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.modifiers.applyIf 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.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.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -64,6 +68,7 @@ internal fun RoomListSearchView( state: RoomListSearchState, onRoomClicked: (RoomId) -> Unit, onRoomLongClicked: (RoomListRoomSummary) -> Unit, + onRoomDirectorySearchClicked: () -> Unit, modifier: Modifier = Modifier, ) { BackHandler(enabled = state.isSearchActive) { @@ -87,6 +92,7 @@ internal fun RoomListSearchView( state = state, onRoomClicked = onRoomClicked, onRoomLongClicked = onRoomLongClicked, + onRoomDirectorySearchClicked = onRoomDirectorySearchClicked, ) } } @@ -99,6 +105,7 @@ private fun RoomListSearchContent( state: RoomListSearchState, onRoomClicked: (RoomId) -> Unit, onRoomLongClicked: (RoomListRoomSummary) -> Unit, + onRoomDirectorySearchClicked: () -> Unit, ) { val borderColor = MaterialTheme.colorScheme.tertiary val strokeWidth = 1.dp @@ -169,6 +176,14 @@ private fun RoomListSearchContent( .padding(padding) .consumeWindowInsets(padding) ) { + if(state.query.isEmpty()){ + RoomDirectorySearchButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp, horizontal = 16.dp), + onClick = onRoomDirectorySearchClicked + ) + } LazyColumn( modifier = Modifier.weight(1f), ) { @@ -187,12 +202,26 @@ private fun RoomListSearchContent( } } +@Composable +private fun RoomDirectorySearchButton( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Button( + text = "Room directory", + leadingIcon = IconSource.Vector(CompoundIcons.ListBulleted()), + onClick = onClick, + modifier = modifier, + ) +} + @PreviewsDayNight @Composable internal fun RoomListSearchResultContentPreview(@PreviewParameter(RoomListSearchStateProvider::class) state: RoomListSearchState) = ElementPreview { RoomListSearchContent( state = state, onRoomClicked = {}, - onRoomLongClicked = {} + onRoomLongClicked = {}, + onRoomDirectorySearchClicked = {}, ) } From b0894fcd115c7b52dfafe8b24fd2d10bda3bf236 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 20 Mar 2024 18:32:41 +0100 Subject: [PATCH 003/124] Room directory search : start implementing ui with fake data --- features/roomdirectory/impl/build.gradle.kts | 1 + .../impl/search/RoomDirectorySearchNode.kt | 1 + .../search/RoomDirectorySearchPresenter.kt | 49 ++++- .../impl/search/RoomDirectorySearchState.kt | 5 + .../RoomDirectorySearchStateProvider.kt | 45 +++- .../impl/search/RoomDirectorySearchView.kt | 203 +++++++++++++++++- .../RoomDirectorySearchDataSource.kt | 80 +++++++ .../search/model/RoomDirectorySearchResult.kt | 28 +++ .../components/avatar/AvatarSize.kt | 4 +- 9 files changed, 401 insertions(+), 15 deletions(-) create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/datasource/RoomDirectorySearchDataSource.kt create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/model/RoomDirectorySearchResult.kt diff --git a/features/roomdirectory/impl/build.gradle.kts b/features/roomdirectory/impl/build.gradle.kts index 0e35623401..9d56802700 100644 --- a/features/roomdirectory/impl/build.gradle.kts +++ b/features/roomdirectory/impl/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) + implementation(projects.libraries.uiStrings) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt index 1f459f404c..1239dc5fab 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt @@ -38,6 +38,7 @@ class RoomDirectorySearchNode @AssistedInject constructor( val state = presenter.present() RoomDirectorySearchView( state = state, + onBackPressed = ::navigateUp, modifier = modifier ) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt index d0cc6d47c4..e0d6a9dbf4 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt @@ -17,24 +17,65 @@ package io.element.android.features.roomdirectory.impl.search import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import io.element.android.features.roomdirectory.impl.search.datasource.RoomDirectorySearchDataSource import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject -class RoomDirectorySearchPresenter @Inject constructor() : Presenter { +class RoomDirectorySearchPresenter @Inject constructor( + private val client: MatrixClient, + private val dataSource: RoomDirectorySearchDataSource, +) : Presenter { @Composable override fun present(): RoomDirectorySearchState { + var searchQuery by rememberSaveable { + mutableStateOf("") + } + + val results by dataSource.searchResults.collectAsState() + + val coroutineScope = rememberCoroutineScope() + + LaunchedEffect(searchQuery) { + dataSource.updateSearchQuery(searchQuery) + } + fun handleEvents(event: RoomDirectorySearchEvents) { when (event) { - is RoomDirectorySearchEvents.JoinRoom -> TODO() - RoomDirectorySearchEvents.LoadMore -> TODO() - is RoomDirectorySearchEvents.Search -> TODO() + is RoomDirectorySearchEvents.JoinRoom -> { + coroutineScope.joinRoom(event.roomId) + } + RoomDirectorySearchEvents.LoadMore -> { + coroutineScope.launch { + dataSource.loadMore() + } + } + is RoomDirectorySearchEvents.Search -> { + searchQuery = event.query + } } } return RoomDirectorySearchState( + query = searchQuery, + results = results, eventSink = ::handleEvents ) } + + private fun CoroutineScope.joinRoom(roomId: RoomId) = launch { + client.getRoom(roomId)?.join() + } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt index 0e77801e12..e1aa7dc832 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt @@ -16,6 +16,11 @@ package io.element.android.features.roomdirectory.impl.search +import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult +import kotlinx.collections.immutable.ImmutableList + data class RoomDirectorySearchState( + val query: String, + val results: ImmutableList, val eventSink: (RoomDirectorySearchEvents) -> Unit ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt index 31464fd17c..3dd56c77bb 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt @@ -17,15 +17,54 @@ package io.element.android.features.roomdirectory.impl.search import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult +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.RoomId +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf open class RoomDirectorySearchStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomDirectorySearchState(), - // Add other states here + aRoomDirectorySearchState( + query = "Element", + results = persistentListOf( + RoomDirectorySearchResult( + roomId = RoomId("@exa:matrix.org"), + name = "Element X Android", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "@exa:matrix.org", + name = "Element X Android", + url = null, + size = AvatarSize.RoomDirectorySearchItem + ), + canBeJoined = true, + ), + RoomDirectorySearchResult( + roomId = RoomId("@exi:matrix.org"), + name = "Element X iOS", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "@exi:matrix.org", + name = "Element X iOS", + url = null, + size = AvatarSize.RoomDirectorySearchItem + ), + canBeJoined = false, + ) + ) + ), ) } -fun aRoomDirectorySearchState() = RoomDirectorySearchState( - eventSink = {} +fun aRoomDirectorySearchState( + query: String = "", + results: ImmutableList = persistentListOf(), +) = RoomDirectorySearchState( + query = query, + results = results, + eventSink = { } ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt index d2e3ad1ec9..2af471c51f 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt @@ -16,27 +16,215 @@ package io.element.android.features.roomdirectory.impl.search -import androidx.compose.foundation.layout.Box -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +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.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow 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.roomdirectory.impl.search.model.RoomDirectorySearchResult +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.button.BackButton 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.IconButton +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 +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.ui.strings.CommonStrings @Composable fun RoomDirectorySearchView( state: RoomDirectorySearchState, + onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { - Box(modifier, contentAlignment = Alignment.Center) { - Text( - "RoomDirectorySearch feature view", - color = MaterialTheme.colorScheme.primary, - ) + + fun onQueryChanged(query: String) { + state.eventSink(RoomDirectorySearchEvents.Search(query)) } + + Scaffold( + modifier = modifier, + topBar = { + RoomDirectorySearchTopBar( + query = state.query, + onQueryChanged = ::onQueryChanged, + onBackPressed = onBackPressed, + ) + }, + content = { padding -> + RoomDirectorySearchContent( + state = state, + onResultClicked = { roomId -> + state.eventSink(RoomDirectorySearchEvents.JoinRoom(roomId)) + }, + modifier = Modifier + .padding(padding) + .consumeWindowInsets(padding) + ) + } + ) +} + +@Composable +private fun RoomDirectorySearchContent( + state: RoomDirectorySearchState, + onResultClicked: (RoomId) -> Unit, + modifier: Modifier = Modifier, +) { + LazyColumn(modifier = modifier) { + items(state.results) { result -> + RoomDirectorySearchResultRow( + result = result, + onClick = onResultClicked, + ) + } + } +} + +@Composable +private fun RoomDirectorySearchResultRow( + result: RoomDirectorySearchResult, + onClick: (RoomId) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable { onClick(result.roomId) } + .padding( + top = 12.dp, + bottom = 12.dp, + start = 16.dp, + ) + .height(IntrinsicSize.Min), + ) { + Avatar( + avatarData = result.avatarData, + modifier = Modifier.align(Alignment.CenterVertically) + ) + Column( + modifier = Modifier + .weight(1f) + .padding(start = 16.dp) + ) { + Text( + text = result.name, + maxLines = 1, + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPrimary, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = result.description, + maxLines = 1, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + overflow = TextOverflow.Ellipsis, + ) + } + if (result.canBeJoined) { + CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textSuccessPrimary) { + TextButton( + text = stringResource(id = CommonStrings.action_join), + onClick = { onClick(result.roomId) }, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 4.dp, end = 12.dp) + ) + } + } else { + Spacer(modifier = Modifier.width(24.dp)) + } + } +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun RoomDirectorySearchTopBar( + query: String, + onQueryChanged: (String) -> Unit, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + val borderColor = ElementTheme.colors.borderInteractivePrimary + val borderStroke = 1.dp + TopAppBar( + modifier = modifier.drawBehind { + drawLine( + color = borderColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = borderStroke.value + ) + }, + title = { + val focusRequester = FocusRequester() + TextField( + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester), + value = query, + singleLine = true, + onValueChange = onQueryChanged, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent, + ), + trailingIcon = { + if (query.isNotEmpty()) { + IconButton(onClick = { + onQueryChanged("") + }) { + Icon( + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_cancel), + ) + } + } + } + ) + LaunchedEffect(Unit) { + focusRequester.requestFocus() + } + }, + navigationIcon = { BackButton(onClick = onBackPressed) }, + ) } @PreviewsDayNight @@ -44,5 +232,6 @@ fun RoomDirectorySearchView( fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectorySearchState) = ElementPreview { RoomDirectorySearchView( state = state, + onBackPressed = {}, ) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/datasource/RoomDirectorySearchDataSource.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/datasource/RoomDirectorySearchDataSource.kt new file mode 100644 index 0000000000..6780c97017 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/datasource/RoomDirectorySearchDataSource.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search.datasource + +import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult +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.RoomId +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +class RoomDirectorySearchDataSource @Inject constructor( + +) { + + private val _searchResults = MutableStateFlow>(persistentListOf()) + + suspend fun updateSearchQuery(searchQuery: String) { + //TODO branch to matrix sdk + if (searchQuery.isEmpty()) { + _searchResults.value = persistentListOf() + } else { + delay(100) + emitFakeResults() + } + } + + suspend fun loadMore() { + //TODO branch to matrix sdk + } + + private fun emitFakeResults() { + _searchResults.value = persistentListOf( + RoomDirectorySearchResult( + roomId = RoomId("!exa:matrix.org"), + name = "Element X Android", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "!exa:matrix.org", + name = "Element X Android", + url = null, + size = AvatarSize.RoomDirectorySearchItem + ), + canBeJoined = true, + ), + RoomDirectorySearchResult( + roomId = RoomId("!exi:matrix.org"), + name = "Element X iOS", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "!exi:matrix.org", + name = "Element X iOS", + url = null, + size = AvatarSize.RoomDirectorySearchItem + ), + canBeJoined = false, + ) + ) + } + + val searchResults: StateFlow> = _searchResults +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/model/RoomDirectorySearchResult.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/model/RoomDirectorySearchResult.kt new file mode 100644 index 0000000000..ef531e716d --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/model/RoomDirectorySearchResult.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.search.model + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.matrix.api.core.RoomId + +data class RoomDirectorySearchResult( + val roomId: RoomId, + val name: String, + val description: String, + val avatarData: AvatarData, + val canBeJoined: Boolean, +) 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 0451cb5839..382aac5468 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 @@ -51,5 +51,7 @@ enum class AvatarSize(val dp: Dp) { NotificationsOptIn(32.dp), - CustomRoomNotificationSetting(36.dp) + CustomRoomNotificationSetting(36.dp), + + RoomDirectorySearchItem(36.dp), } From fa7a889a3f094638f4ff7b70db42840e0ae0e300 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 21 Mar 2024 17:22:00 +0100 Subject: [PATCH 004/124] Room directory : change names and adapt ui --- .../impl/RoomDirectoryFlowNode.kt | 4 +- .../RoomDirectoryEvents.kt} | 11 +- .../RoomDirectoryNode.kt} | 8 +- .../RoomDirectoryPresenter.kt} | 38 +++-- .../RoomDirectoryState.kt} | 13 +- .../RoomDirectoryStateProvider.kt} | 33 +++-- .../RoomDirectoryView.kt} | 136 ++++++++++++------ .../datasource/RoomDirectoryDataSource.kt} | 31 ++-- .../model/RoomDirectoryRoomSummary.kt} | 4 +- .../impl/search/RoomListSearchView.kt | 2 +- 10 files changed, 176 insertions(+), 104 deletions(-) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/RoomDirectorySearchEvents.kt => root/RoomDirectoryEvents.kt} (65%) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/RoomDirectorySearchNode.kt => root/RoomDirectoryNode.kt} (86%) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/RoomDirectorySearchPresenter.kt => root/RoomDirectoryPresenter.kt} (63%) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/RoomDirectorySearchState.kt => root/RoomDirectoryState.kt} (57%) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/RoomDirectorySearchStateProvider.kt => root/RoomDirectoryStateProvider.kt} (71%) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/RoomDirectorySearchView.kt => root/RoomDirectoryView.kt} (69%) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/datasource/RoomDirectorySearchDataSource.kt => root/datasource/RoomDirectoryDataSource.kt} (75%) rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/{search/model/RoomDirectorySearchResult.kt => root/model/RoomDirectoryRoomSummary.kt} (89%) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt index e053fa6c58..244e61b3b9 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt @@ -26,7 +26,7 @@ import com.bumble.appyx.navmodel.backstack.BackStack import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.roomdirectory.impl.search.RoomDirectorySearchNode +import io.element.android.features.roomdirectory.impl.root.RoomDirectoryNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode @@ -54,7 +54,7 @@ class RoomDirectoryFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { - createNode(buildContext) + createNode(buildContext) } } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt similarity index 65% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchEvents.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt index f7212529b2..267c889f61 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt @@ -14,12 +14,13 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search +package io.element.android.features.roomdirectory.impl.root import io.element.android.libraries.matrix.api.core.RoomId -sealed interface RoomDirectorySearchEvents { - data class Search(val query: String) : RoomDirectorySearchEvents - data object LoadMore : RoomDirectorySearchEvents - data class JoinRoom(val roomId: RoomId) : RoomDirectorySearchEvents +sealed interface RoomDirectoryEvents { + data class Search(val query: String) : RoomDirectoryEvents + data object LoadMore : RoomDirectoryEvents + data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents + data class SearchActiveChange(val isActive: Boolean) : RoomDirectoryEvents } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt similarity index 86% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt index 1239dc5fab..0da712eefe 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search +package io.element.android.features.roomdirectory.impl.root import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -27,16 +27,16 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) -class RoomDirectorySearchNode @AssistedInject constructor( +class RoomDirectoryNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val presenter: RoomDirectorySearchPresenter, + private val presenter: RoomDirectoryPresenter, ) : Node(buildContext, plugins = plugins) { @Composable override fun View(modifier: Modifier) { val state = presenter.present() - RoomDirectorySearchView( + RoomDirectoryView( state = state, onBackPressed = ::navigateUp, modifier = modifier diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt similarity index 63% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index e0d6a9dbf4..5ab2dfe6eb 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search +package io.element.android.features.roomdirectory.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -24,27 +24,31 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import io.element.android.features.roomdirectory.impl.search.datasource.RoomDirectorySearchDataSource +import io.element.android.features.roomdirectory.impl.root.datasource.RoomDirectoryDataSource import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject -class RoomDirectorySearchPresenter @Inject constructor( +class RoomDirectoryPresenter @Inject constructor( private val client: MatrixClient, - private val dataSource: RoomDirectorySearchDataSource, -) : Presenter { + private val dataSource: RoomDirectoryDataSource, +) : Presenter { @Composable - override fun present(): RoomDirectorySearchState { + override fun present(): RoomDirectoryState { var searchQuery by rememberSaveable { mutableStateOf("") } + var isSearchActive by rememberSaveable { + mutableStateOf(false) + } - val results by dataSource.searchResults.collectAsState() + val roomSummaries by dataSource.all.collectAsState() val coroutineScope = rememberCoroutineScope() @@ -52,25 +56,33 @@ class RoomDirectorySearchPresenter @Inject constructor( dataSource.updateSearchQuery(searchQuery) } - fun handleEvents(event: RoomDirectorySearchEvents) { + fun handleEvents(event: RoomDirectoryEvents) { when (event) { - is RoomDirectorySearchEvents.JoinRoom -> { + is RoomDirectoryEvents.JoinRoom -> { coroutineScope.joinRoom(event.roomId) } - RoomDirectorySearchEvents.LoadMore -> { + RoomDirectoryEvents.LoadMore -> { coroutineScope.launch { dataSource.loadMore() } } - is RoomDirectorySearchEvents.Search -> { + is RoomDirectoryEvents.Search -> { searchQuery = event.query } + is RoomDirectoryEvents.SearchActiveChange -> { + isSearchActive = event.isActive + if (!isSearchActive) { + searchQuery = "" + } + } } } - return RoomDirectorySearchState( + return RoomDirectoryState( query = searchQuery, - results = results, + isSearchActive = isSearchActive, + roomSummaries = roomSummaries, + searchResults = SearchBarResultState.Initial(), eventSink = ::handleEvents ) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt similarity index 57% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt index e1aa7dc832..3abf725a24 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt @@ -14,13 +14,16 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search +package io.element.android.features.roomdirectory.impl.root -import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult +import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import kotlinx.collections.immutable.ImmutableList -data class RoomDirectorySearchState( +data class RoomDirectoryState( val query: String, - val results: ImmutableList, - val eventSink: (RoomDirectorySearchEvents) -> Unit + val roomSummaries: ImmutableList, + val searchResults: SearchBarResultState>, + val isSearchActive: Boolean, + val eventSink: (RoomDirectoryEvents) -> Unit ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt similarity index 71% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index 3dd56c77bb..a9d1f2287f 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -14,24 +14,25 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search +package io.element.android.features.roomdirectory.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult +import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -open class RoomDirectorySearchStateProvider : PreviewParameterProvider { - override val values: Sequence +open class RoomDirectorySearchStateProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( - aRoomDirectorySearchState(), - aRoomDirectorySearchState( + aRoomDirectoryState(), + aRoomDirectoryState( query = "Element", - results = persistentListOf( - RoomDirectorySearchResult( + roomSummaries = persistentListOf( + RoomDirectoryRoomSummary( roomId = RoomId("@exa:matrix.org"), name = "Element X Android", description = "Element X is a secure, private and decentralized messenger.", @@ -43,7 +44,7 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider = persistentListOf(), -) = RoomDirectorySearchState( + isSearchActive: Boolean = false, + roomSummaries: ImmutableList = persistentListOf(), + searchResults: SearchBarResultState> = SearchBarResultState.Initial(), +) = RoomDirectoryState( query = query, - results = results, - eventSink = { } + isSearchActive = isSearchActive, + roomSummaries = roomSummaries, + searchResults = searchResults, + eventSink = {}, ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt similarity index 69% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 2af471c51f..b79e4cfd20 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/RoomDirectorySearchView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -14,14 +14,16 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search +package io.element.android.features.roomdirectory.impl.root import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -29,10 +31,8 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -47,46 +47,39 @@ 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.roomdirectory.impl.search.model.RoomDirectorySearchResult +import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.aliasScreenTitle 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.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.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList @Composable -fun RoomDirectorySearchView( - state: RoomDirectorySearchState, +fun RoomDirectoryView( + state: RoomDirectoryState, onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { - - fun onQueryChanged(query: String) { - state.eventSink(RoomDirectorySearchEvents.Search(query)) - } - Scaffold( modifier = modifier, topBar = { - RoomDirectorySearchTopBar( - query = state.query, - onQueryChanged = ::onQueryChanged, - onBackPressed = onBackPressed, - ) + RoomDirectoryTopBar(onBackPressed = onBackPressed) }, content = { padding -> - RoomDirectorySearchContent( + RoomDirectoryContent( state = state, onResultClicked = { roomId -> - state.eventSink(RoomDirectorySearchEvents.JoinRoom(roomId)) + state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) }, modifier = Modifier .padding(padding) @@ -96,16 +89,75 @@ fun RoomDirectorySearchView( ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable -private fun RoomDirectorySearchContent( - state: RoomDirectorySearchState, +private fun RoomDirectoryTopBar( + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + TopAppBar( + modifier = modifier, + navigationIcon = { + BackButton(onClick = onBackPressed) + }, + title = { + Text( + text = "Room directory", + style = ElementTheme.typography.aliasScreenTitle, + ) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun RoomDirectoryContent( + state: RoomDirectoryState, onResultClicked: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { - LazyColumn(modifier = modifier) { - items(state.results) { result -> - RoomDirectorySearchResultRow( - result = result, + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + SearchBar( + query = state.query, + onQueryChange = { query -> + state.eventSink(RoomDirectoryEvents.Search(query)) + }, + active = state.isSearchActive, + onActiveChange = { + state.eventSink(RoomDirectoryEvents.SearchActiveChange(it)) + }, + resultState = state.searchResults, + placeHolderTitle = stringResource(id = CommonStrings.action_search), + ) { results -> + RoomDirectoryRoomList( + rooms = results, + onResultClicked = onResultClicked, + ) + } + if (!state.isSearchActive) { + RoomDirectoryRoomList( + rooms = state.roomSummaries, + onResultClicked = onResultClicked, + ) + } + } +} + +@Composable +private fun RoomDirectoryRoomList( + rooms: ImmutableList, + onResultClicked: (RoomId) -> Unit, + modifier: Modifier = Modifier, +) { + LazyColumn( + modifier = modifier, + ) { + items(rooms) { room -> + RoomDirectoryRoomRow( + room = room, onClick = onResultClicked, ) } @@ -113,15 +165,15 @@ private fun RoomDirectorySearchContent( } @Composable -private fun RoomDirectorySearchResultRow( - result: RoomDirectorySearchResult, +private fun RoomDirectoryRoomRow( + room: RoomDirectoryRoomSummary, onClick: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { Row( modifier = modifier .fillMaxWidth() - .clickable { onClick(result.roomId) } + .clickable { onClick(room.roomId) } .padding( top = 12.dp, bottom = 12.dp, @@ -130,7 +182,7 @@ private fun RoomDirectorySearchResultRow( .height(IntrinsicSize.Min), ) { Avatar( - avatarData = result.avatarData, + avatarData = room.avatarData, modifier = Modifier.align(Alignment.CenterVertically) ) Column( @@ -139,30 +191,28 @@ private fun RoomDirectorySearchResultRow( .padding(start = 16.dp) ) { Text( - text = result.name, + text = room.name, maxLines = 1, style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textPrimary, overflow = TextOverflow.Ellipsis, ) Text( - text = result.description, + text = room.description, maxLines = 1, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary, overflow = TextOverflow.Ellipsis, ) } - if (result.canBeJoined) { - CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textSuccessPrimary) { - TextButton( - text = stringResource(id = CommonStrings.action_join), - onClick = { onClick(result.roomId) }, - modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 4.dp, end = 12.dp) - ) - } + if (room.canBeJoined) { + Text( + text = stringResource(id = CommonStrings.action_join), + color = ElementTheme.colors.textSuccessPrimary, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 4.dp, end = 12.dp) + ) } else { Spacer(modifier = Modifier.width(24.dp)) } @@ -229,8 +279,8 @@ private fun RoomDirectorySearchTopBar( @PreviewsDayNight @Composable -fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectorySearchState) = ElementPreview { - RoomDirectorySearchView( +fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { + RoomDirectoryView( state = state, onBackPressed = {}, ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/datasource/RoomDirectorySearchDataSource.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt similarity index 75% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/datasource/RoomDirectorySearchDataSource.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt index 6780c97017..2441c694ee 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/datasource/RoomDirectorySearchDataSource.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt @@ -14,24 +14,23 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search.datasource +package io.element.android.features.roomdirectory.impl.root.datasource -import io.element.android.features.roomdirectory.impl.search.model.RoomDirectorySearchResult +import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary 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.RoomId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOf import javax.inject.Inject -class RoomDirectorySearchDataSource @Inject constructor( - -) { - - private val _searchResults = MutableStateFlow>(persistentListOf()) +class RoomDirectoryDataSource @Inject constructor() { + private val _searchResults = MutableStateFlow>(persistentListOf()) suspend fun updateSearchQuery(searchQuery: String) { //TODO branch to matrix sdk @@ -39,7 +38,9 @@ class RoomDirectorySearchDataSource @Inject constructor( _searchResults.value = persistentListOf() } else { delay(100) - emitFakeResults() + _searchResults.value = all.value.filter { + it.name.contains(searchQuery) + }.toImmutableList() } } @@ -47,9 +48,10 @@ class RoomDirectorySearchDataSource @Inject constructor( //TODO branch to matrix sdk } - private fun emitFakeResults() { - _searchResults.value = persistentListOf( - RoomDirectorySearchResult( + + val all: StateFlow> = MutableStateFlow( + persistentListOf( + RoomDirectoryRoomSummary( roomId = RoomId("!exa:matrix.org"), name = "Element X Android", description = "Element X is a secure, private and decentralized messenger.", @@ -61,7 +63,7 @@ class RoomDirectorySearchDataSource @Inject constructor( ), canBeJoined = true, ), - RoomDirectorySearchResult( + RoomDirectoryRoomSummary( roomId = RoomId("!exi:matrix.org"), name = "Element X iOS", description = "Element X is a secure, private and decentralized messenger.", @@ -74,7 +76,6 @@ class RoomDirectorySearchDataSource @Inject constructor( canBeJoined = false, ) ) - } - - val searchResults: StateFlow> = _searchResults + ) + val searchResults: StateFlow> = _searchResults } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/model/RoomDirectorySearchResult.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt similarity index 89% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/model/RoomDirectorySearchResult.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt index ef531e716d..7aedc1305b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/search/model/RoomDirectorySearchResult.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package io.element.android.features.roomdirectory.impl.search.model +package io.element.android.features.roomdirectory.impl.root.model import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.matrix.api.core.RoomId -data class RoomDirectorySearchResult( +data class RoomDirectoryRoomSummary( val roomId: RoomId, val name: String, val description: String, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt index e8c68cebf0..9af30fbdcd 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt @@ -208,7 +208,7 @@ private fun RoomDirectorySearchButton( modifier: Modifier = Modifier ) { Button( - text = "Room directory", + text = "Browse all rooms", leadingIcon = IconSource.Vector(CompoundIcons.ListBulleted()), onClick = onClick, modifier = modifier, From 01e6b46323290db0f378b47b6dfda734c941213e Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 21 Mar 2024 19:14:05 +0100 Subject: [PATCH 005/124] Room Directory : start exposing the matrix apis --- .../libraries/matrix/api/MatrixClient.kt | 2 + .../api/roomdirectory/RoomDescription.kt | 36 +++++++ .../api/roomdirectory/RoomDirectorySearch.kt | 26 +++++ .../api/roomdirectory/RoomDirectoryService.kt | 21 ++++ .../libraries/matrix/impl/RustMatrixClient.kt | 11 +++ .../roomdirectory/RoomDescriptionMapper.kt | 42 ++++++++ .../RoomDirectorySearchExtension.kt | 45 +++++++++ .../RoomDirectorySearchProcessor.kt | 97 +++++++++++++++++++ .../roomdirectory/RustRoomDirectorySearch.kt | 65 +++++++++++++ .../roomdirectory/RustRoomDirectoryService.kt | 35 +++++++ .../libraries/matrix/test/FakeMatrixClient.kt | 5 + .../roomdirectory/FakeRoomDirectoryService.kt | 26 +++++ 12 files changed, 411 insertions(+) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 955c8d72fa..43ca8f6687 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults @@ -64,6 +65,7 @@ interface MatrixClient : Closeable { fun notificationService(): NotificationService fun notificationSettingsService(): NotificationSettingsService fun encryptionService(): EncryptionService + fun roomDirectoryService(): RoomDirectoryService suspend fun getCacheSize(): Long /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt new file mode 100644 index 0000000000..dddd556bbe --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.roomdirectory + +import io.element.android.libraries.matrix.api.core.RoomId + +data class RoomDescription( + val roomId: RoomId, + val name: String?, + val topic: String?, + val alias: String?, + val avatarUrl: String?, + val joinRule: JoinRule, + val isWorldReadable: Boolean, + val joinedMembers: Long +) { + enum class JoinRule { + PUBLIC, + KNOCK, + UNKNOWN + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt new file mode 100644 index 0000000000..1902c7aa3a --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.roomdirectory + +import kotlinx.coroutines.flow.SharedFlow + +interface RoomDirectorySearch { + suspend fun updateQuery(query: String?, batchSize: Int) + suspend fun loadMore() + suspend fun hasMoreToLoad(): Boolean + val results: SharedFlow> +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt new file mode 100644 index 0000000000..ebcff30671 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.roomdirectory + +interface RoomDirectoryService { + fun search(): RoomDirectorySearch +} 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 31e439bbec..280b56724b 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 @@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.awaitLoaded import io.element.android.libraries.matrix.api.sync.SyncService @@ -53,6 +54,7 @@ import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber import io.element.android.libraries.matrix.impl.room.RustMatrixRoom +import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline @@ -150,6 +152,13 @@ class RustMatrixClient( sessionCoroutineScope = sessionCoroutineScope, dispatchers = dispatchers, ) + + private val roomDirectoryService = RustRoomDirectoryService( + client = client, + sessionCoroutineScope = sessionCoroutineScope, + sessionDispatcher = sessionDispatcher, + ) + private val sessionDirectoryNameProvider = SessionDirectoryNameProvider() private val isLoggingOut = AtomicBoolean(false) @@ -428,6 +437,8 @@ class RustMatrixClient( override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService + override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService + override fun close() { sessionCoroutineScope.cancel() clientDelegateTaskHandle?.cancelAndDestroy() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt new file mode 100644 index 0000000000..52b8e1ba98 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomdirectory + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription +import org.matrix.rustcomponents.sdk.PublicRoomJoinRule +import org.matrix.rustcomponents.sdk.RoomDescription as RustRoomDescription + +class RoomDescriptionMapper { + + fun map(roomDescription: RustRoomDescription): RoomDescription { + return RoomDescription( + roomId = RoomId(roomDescription.roomId), + name = roomDescription.name, + topic = roomDescription.topic, + avatarUrl = roomDescription.avatarUrl, + alias = roomDescription.alias, + joinRule = when (roomDescription.joinRule) { + PublicRoomJoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC + PublicRoomJoinRule.KNOCK -> RoomDescription.JoinRule.KNOCK + null -> RoomDescription.JoinRule.UNKNOWN + }, + isWorldReadable = roomDescription.isWorldReadable, + joinedMembers = roomDescription.joinedMembers.toLong(), + ) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt new file mode 100644 index 0000000000..28e82f4e40 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomdirectory + +import io.element.android.libraries.matrix.impl.util.cancelAndDestroy +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.buffer +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.catch +import org.matrix.rustcomponents.sdk.RoomDirectorySearch +import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntriesListener +import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate +import timber.log.Timber + +internal fun RoomDirectorySearch.resultsFlow(): Flow> = + callbackFlow { + val listener = object : RoomDirectorySearchEntriesListener { + override fun onUpdate(roomEntriesUpdate: List) { + trySendBlocking(roomEntriesUpdate) + } + } + val result = results(listener) + awaitClose { + result.cancelAndDestroy() + } + }.catch { + Timber.d(it, "timelineDiffFlow() failed") + }.buffer(Channel.UNLIMITED) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt new file mode 100644 index 0000000000..bfa996ac0d --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomdirectory + +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate +import timber.log.Timber +import kotlin.coroutines.CoroutineContext + +class RoomDirectorySearchProcessor( + private val roomDescriptions: MutableSharedFlow>, + private val coroutineContext: CoroutineContext, + private val roomDescriptionMapper: RoomDescriptionMapper, +) { + + private val mutex = Mutex() + + suspend fun postUpdates(updates: List) { + updateRoomDescriptions { + Timber.v("Update room descriptions from postUpdates (with ${updates.size} items) on ${Thread.currentThread()}") + updates.forEach { update -> + applyUpdate(update) + } + } + } + + private suspend fun MutableList.applyUpdate(update: RoomDirectorySearchEntryUpdate) { + when (update) { + is RoomDirectorySearchEntryUpdate.Append -> { + val roomSummaries = update.values.map(roomDescriptionMapper::map) + addAll(roomSummaries) + } + is RoomDirectorySearchEntryUpdate.PushBack -> { + val roomSummary = roomDescriptionMapper.map(update.value) + add(roomSummary) + } + is RoomDirectorySearchEntryUpdate.PushFront -> { + val roomSummary = roomDescriptionMapper.map(update.value) + add(0, roomSummary) + } + is RoomDirectorySearchEntryUpdate.Set -> { + val roomSummary = roomDescriptionMapper.map(update.value) + this[update.index.toInt()] = roomSummary + } + is RoomDirectorySearchEntryUpdate.Insert -> { + val roomSummary = roomDescriptionMapper.map(update.value) + add(update.index.toInt(), roomSummary) + } + is RoomDirectorySearchEntryUpdate.Remove -> { + removeAt(update.index.toInt()) + } + is RoomDirectorySearchEntryUpdate.Reset -> { + clear() + addAll(update.values.map(roomDescriptionMapper::map)) + } + RoomDirectorySearchEntryUpdate.PopBack -> { + removeLastOrNull() + } + RoomDirectorySearchEntryUpdate.PopFront -> { + removeFirstOrNull() + } + RoomDirectorySearchEntryUpdate.Clear -> { + clear() + } + is RoomDirectorySearchEntryUpdate.Truncate -> { + subList(update.length.toInt(), size).clear() + } + } + } + + private suspend fun updateRoomDescriptions(block: suspend MutableList.() -> Unit) = withContext(coroutineContext) { + mutex.withLock { + val current = roomDescriptions.replayCache.lastOrNull() + val mutableRoomSummaries = current.orEmpty().toMutableList() + block(mutableRoomSummaries) + roomDescriptions.emit(mutableRoomSummaries) + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt new file mode 100644 index 0000000000..448a229538 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomdirectory + +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySearch + +class RustRoomDirectorySearch( + private val inner: InnerRoomDirectorySearch, + private val sessionCoroutineScope: CoroutineScope, + private val sessionDispatcher: CoroutineDispatcher, +) : RoomDirectorySearch { + + private val _results: MutableStateFlow> = + MutableStateFlow(emptyList()) + + private val processor = RoomDirectorySearchProcessor(_results, sessionDispatcher, RoomDescriptionMapper()) + + init { + sessionCoroutineScope.launch(sessionDispatcher) { + inner + .resultsFlow() + .onEach { updates -> + processor.postUpdates(updates) + } + .launchIn(this) + } + } + + override suspend fun updateQuery(query: String?, batchSize: Int) { + inner.search(query, batchSize.toUInt()) + } + + override suspend fun loadMore() { + inner.nextPage() + } + + override suspend fun hasMoreToLoad(): Boolean { + return !inner.isAtLastPage() + } + + override val results: SharedFlow> = _results +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt new file mode 100644 index 0000000000..9644d5db7a --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.roomdirectory + +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import org.matrix.rustcomponents.sdk.Client + +class RustRoomDirectoryService( + private val client: Client, + private val sessionCoroutineScope: CoroutineScope, + private val sessionDispatcher: CoroutineDispatcher, +) : RoomDirectoryService { + + override fun search(): RoomDirectorySearch { + return RustRoomDirectorySearch(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher) + } + +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index d7c3f71e64..1ac68a5fa5 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser @@ -39,6 +40,7 @@ import io.element.android.libraries.matrix.test.media.FakeMediaLoader import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.pushers.FakePushersService +import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryService import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService @@ -65,6 +67,7 @@ class FakeMatrixClient( private val notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), private val syncService: FakeSyncService = FakeSyncService(), private val encryptionService: FakeEncryptionService = FakeEncryptionService(), + private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(), private val accountManagementUrlString: Result = Result.success(null), ) : MatrixClient { var setDisplayNameCalled: Boolean = false @@ -126,6 +129,8 @@ class FakeMatrixClient( override fun syncService() = syncService + override fun roomDirectoryService() = roomDirectoryService + override suspend fun getCacheSize(): Long { return 0 } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt new file mode 100644 index 0000000000..f4b99f09a1 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.test.roomdirectory + +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService + +class FakeRoomDirectoryService : RoomDirectoryService { + override fun search(): RoomDirectorySearch { + TODO("Not yet implemented") + } +} From 4c5ae6ae4bcd9afe658c81ecbbeb4b8467022efd Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Mar 2024 11:17:19 +0100 Subject: [PATCH 006/124] RoomDirectory : continue implementing the search --- .../impl/root/RoomDirectoryPresenter.kt | 71 ++++--- .../impl/root/RoomDirectoryState.kt | 12 +- .../impl/root/RoomDirectoryStateProvider.kt | 22 +-- .../impl/root/RoomDirectoryView.kt | 179 ++++++++---------- .../datasource/RoomDirectoryDataSource.kt | 81 -------- ...omSummary.kt => RoomDescriptionUiModel.kt} | 19 +- .../components/avatar/AvatarSize.kt | 2 +- ...irectorySearch.kt => RoomDirectoryList.kt} | 8 +- .../api/roomdirectory/RoomDirectoryService.kt | 2 +- .../matrix/impl/di/SessionMatrixModule.kt | 6 + ...torySearch.kt => RustRoomDirectoryList.kt} | 25 +-- .../roomdirectory/RustRoomDirectoryService.kt | 6 +- .../roomdirectory/FakeRoomDirectoryService.kt | 4 +- 13 files changed, 190 insertions(+), 247 deletions(-) delete mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/{RoomDirectoryRoomSummary.kt => RoomDescriptionUiModel.kt} (60%) rename libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/{RoomDirectorySearch.kt => RoomDirectoryList.kt} (79%) rename libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/{RustRoomDirectorySearch.kt => RustRoomDirectoryList.kt} (71%) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 5ab2dfe6eb..78300287bf 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -21,73 +21,98 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import io.element.android.features.roomdirectory.impl.root.datasource.RoomDirectoryDataSource +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.features.roomdirectory.impl.root.model.toUiModel import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.RoomId -import kotlinx.coroutines.CoroutineScope +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( private val client: MatrixClient, - private val dataSource: RoomDirectoryDataSource, + private val roomDirectoryService: RoomDirectoryService, ) : Presenter { + private val roomDirectoryList = roomDirectoryService.createRoomDirectoryList() + @Composable override fun present(): RoomDirectoryState { var searchQuery by rememberSaveable { mutableStateOf("") } - var isSearchActive by rememberSaveable { - mutableStateOf(false) + val allRooms by roomDirectoryList.collectItemsAsState() + val hasMoreToLoad by produceState(initialValue = true, allRooms) { + value = roomDirectoryList.hasMoreToLoad() } - - val roomSummaries by dataSource.all.collectAsState() - val coroutineScope = rememberCoroutineScope() - LaunchedEffect(searchQuery) { - dataSource.updateSearchQuery(searchQuery) + roomDirectoryList.filter(searchQuery, 20) } - fun handleEvents(event: RoomDirectoryEvents) { when (event) { is RoomDirectoryEvents.JoinRoom -> { - coroutineScope.joinRoom(event.roomId) + //coroutineScope.joinRoom(event.roomId) } RoomDirectoryEvents.LoadMore -> { coroutineScope.launch { - dataSource.loadMore() + roomDirectoryList.loadMore() } } is RoomDirectoryEvents.Search -> { searchQuery = event.query } is RoomDirectoryEvents.SearchActiveChange -> { - isSearchActive = event.isActive - if (!isSearchActive) { - searchQuery = "" - } + } } } return RoomDirectoryState( query = searchQuery, - isSearchActive = isSearchActive, - roomSummaries = roomSummaries, - searchResults = SearchBarResultState.Initial(), + roomDescriptions = allRooms, + displayLoadMoreIndicator = hasMoreToLoad, eventSink = ::handleEvents ) } - private fun CoroutineScope.joinRoom(roomId: RoomId) = launch { - client.getRoom(roomId)?.join() + @Composable + private fun searchResults( + filteredRooms: ImmutableList, + hasMoreToLoad: Boolean, + isSearchActive: Boolean, + ): SearchBarResultState> { + return if (!isSearchActive) { + SearchBarResultState.Initial() + } else { + if (filteredRooms.isEmpty() && !hasMoreToLoad) { + SearchBarResultState.NoResultsFound() + } else { + SearchBarResultState.Results(filteredRooms) + } + } } + + @Composable + private fun RoomDirectoryList.collectItemsAsState() = remember { + items.map { list -> + list + .map { roomDescription -> roomDescription.toUiModel() } + .toImmutableList() + }.flowOn(Dispatchers.Default) + }.collectAsState(persistentListOf()) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt index 3abf725a24..1afd53f146 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt @@ -16,14 +16,14 @@ package io.element.android.features.roomdirectory.impl.root -import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import kotlinx.collections.immutable.ImmutableList data class RoomDirectoryState( val query: String, - val roomSummaries: ImmutableList, - val searchResults: SearchBarResultState>, - val isSearchActive: Boolean, + val roomDescriptions: ImmutableList, + val displayLoadMoreIndicator: Boolean, val eventSink: (RoomDirectoryEvents) -> Unit -) +) { + val displayEmptyState = roomDescriptions.isEmpty() && !displayLoadMoreIndicator +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index a9d1f2287f..30acb14dba 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -31,8 +31,8 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider = persistentListOf(), - searchResults: SearchBarResultState> = SearchBarResultState.Initial(), + displayLoadMoreIndicator: Boolean = false, + roomDescriptions: ImmutableList = persistentListOf(), + searchResults: SearchBarResultState> = SearchBarResultState.Initial(), ) = RoomDirectoryState( query = query, - isSearchActive = isSearchActive, - roomSummaries = roomSummaries, - searchResults = searchResults, + roomDescriptions = roomDescriptions, + displayLoadMoreIndicator = displayLoadMoreIndicator, eventSink = {}, ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index b79e4cfd20..0bff02cdc2 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -17,37 +17,33 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow 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.roomdirectory.impl.root.model.RoomDirectoryRoomSummary +import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview @@ -56,7 +52,6 @@ import io.element.android.libraries.designsystem.theme.aliasScreenTitle 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.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.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -109,71 +104,107 @@ private fun RoomDirectoryTopBar( ) } -@OptIn(ExperimentalMaterial3Api::class) @Composable private fun RoomDirectoryContent( state: RoomDirectoryState, onResultClicked: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - SearchBar( + Column(modifier = modifier) { + SearchTextField( query = state.query, - onQueryChange = { query -> - state.eventSink(RoomDirectoryEvents.Search(query)) - }, - active = state.isSearchActive, - onActiveChange = { - state.eventSink(RoomDirectoryEvents.SearchActiveChange(it)) - }, - resultState = state.searchResults, - placeHolderTitle = stringResource(id = CommonStrings.action_search), - ) { results -> - RoomDirectoryRoomList( - rooms = results, - onResultClicked = onResultClicked, - ) - } - if (!state.isSearchActive) { - RoomDirectoryRoomList( - rooms = state.roomSummaries, - onResultClicked = onResultClicked, - ) - } + onQueryChange = { state.eventSink(RoomDirectoryEvents.Search(it)) }, + placeholder = stringResource(id = CommonStrings.action_search), + modifier = Modifier.fillMaxWidth(), + ) + RoomDirectoryRoomList( + roomDescriptions = state.roomDescriptions, + onResultClicked = onResultClicked, + ) } } @Composable private fun RoomDirectoryRoomList( - rooms: ImmutableList, + roomDescriptions: ImmutableList, onResultClicked: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { LazyColumn( modifier = modifier, ) { - items(rooms) { room -> + items(roomDescriptions) { roomDescription -> RoomDirectoryRoomRow( - room = room, + roomDescription = roomDescription, onClick = onResultClicked, ) } } } +@Composable +private fun SearchTextField( + query: String, + onQueryChange: (String) -> Unit, + placeholder: String, + modifier: Modifier = Modifier, + colors: TextFieldColors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + unfocusedPlaceholderColor = ElementTheme.colors.textPlaceholder, + focusedPlaceholderColor = ElementTheme.colors.textPlaceholder, + focusedTextColor = ElementTheme.colors.textPrimary, + unfocusedTextColor = ElementTheme.colors.textPrimary, + focusedIndicatorColor = ElementTheme.colors.borderInteractiveSecondary, + unfocusedIndicatorColor = ElementTheme.colors.borderInteractiveSecondary, + ), +) { + val focusManager = LocalFocusManager.current + TextField( + modifier = modifier, + textStyle = ElementTheme.typography.fontBodyLgRegular, + singleLine = true, + value = query, + onValueChange = onQueryChange, + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + } + ), + colors = colors, + placeholder = { Text(placeholder) }, + trailingIcon = { + if (query.isNotEmpty()) { + IconButton( + onClick = { + onQueryChange("") + } + ) { + Icon( + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_clear), + ) + } + } else { + Icon( + imageVector = CompoundIcons.Search(), + contentDescription = stringResource(CommonStrings.action_search), + ) + } + }, + ) +} + @Composable private fun RoomDirectoryRoomRow( - room: RoomDirectoryRoomSummary, + roomDescription: RoomDescriptionUiModel, onClick: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { Row( modifier = modifier .fillMaxWidth() - .clickable { onClick(room.roomId) } + .clickable { onClick(roomDescription.roomId) } .padding( top = 12.dp, bottom = 12.dp, @@ -182,7 +213,7 @@ private fun RoomDirectoryRoomRow( .height(IntrinsicSize.Min), ) { Avatar( - avatarData = room.avatarData, + avatarData = roomDescription.avatarData, modifier = Modifier.align(Alignment.CenterVertically) ) Column( @@ -191,21 +222,21 @@ private fun RoomDirectoryRoomRow( .padding(start = 16.dp) ) { Text( - text = room.name, + text = roomDescription.name, maxLines = 1, style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textPrimary, overflow = TextOverflow.Ellipsis, ) Text( - text = room.description, + text = roomDescription.description, maxLines = 1, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary, overflow = TextOverflow.Ellipsis, ) } - if (room.canBeJoined) { + if (roomDescription.canBeJoined) { Text( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, @@ -219,64 +250,6 @@ private fun RoomDirectoryRoomRow( } } -@Composable -@OptIn(ExperimentalMaterial3Api::class) -private fun RoomDirectorySearchTopBar( - query: String, - onQueryChanged: (String) -> Unit, - onBackPressed: () -> Unit, - modifier: Modifier = Modifier, -) { - val borderColor = ElementTheme.colors.borderInteractivePrimary - val borderStroke = 1.dp - TopAppBar( - modifier = modifier.drawBehind { - drawLine( - color = borderColor, - start = Offset(0f, size.height), - end = Offset(size.width, size.height), - strokeWidth = borderStroke.value - ) - }, - title = { - val focusRequester = FocusRequester() - TextField( - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), - value = query, - singleLine = true, - onValueChange = onQueryChanged, - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = Color.Transparent, - disabledContainerColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - ), - trailingIcon = { - if (query.isNotEmpty()) { - IconButton(onClick = { - onQueryChanged("") - }) { - Icon( - imageVector = CompoundIcons.Close(), - contentDescription = stringResource(CommonStrings.action_cancel), - ) - } - } - } - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } - }, - navigationIcon = { BackButton(onClick = onBackPressed) }, - ) -} - @PreviewsDayNight @Composable fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt deleted file mode 100644 index 2441c694ee..0000000000 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/datasource/RoomDirectoryDataSource.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2024 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.roomdirectory.impl.root.datasource - -import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryRoomSummary -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.RoomId -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -class RoomDirectoryDataSource @Inject constructor() { - private val _searchResults = MutableStateFlow>(persistentListOf()) - - suspend fun updateSearchQuery(searchQuery: String) { - //TODO branch to matrix sdk - if (searchQuery.isEmpty()) { - _searchResults.value = persistentListOf() - } else { - delay(100) - _searchResults.value = all.value.filter { - it.name.contains(searchQuery) - }.toImmutableList() - } - } - - suspend fun loadMore() { - //TODO branch to matrix sdk - } - - - val all: StateFlow> = MutableStateFlow( - persistentListOf( - RoomDirectoryRoomSummary( - roomId = RoomId("!exa:matrix.org"), - name = "Element X Android", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "!exa:matrix.org", - name = "Element X Android", - url = null, - size = AvatarSize.RoomDirectorySearchItem - ), - canBeJoined = true, - ), - RoomDirectoryRoomSummary( - roomId = RoomId("!exi:matrix.org"), - name = "Element X iOS", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "!exi:matrix.org", - name = "Element X iOS", - url = null, - size = AvatarSize.RoomDirectorySearchItem - ), - canBeJoined = false, - ) - ) - ) - val searchResults: StateFlow> = _searchResults -} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt similarity index 60% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt index 7aedc1305b..c7424eebf0 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryRoomSummary.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt @@ -17,12 +17,29 @@ package io.element.android.features.roomdirectory.impl.root.model 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.RoomId +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription -data class RoomDirectoryRoomSummary( +data class RoomDescriptionUiModel( val roomId: RoomId, val name: String, val description: String, val avatarData: AvatarData, val canBeJoined: Boolean, ) + +fun RoomDescription.toUiModel(): RoomDescriptionUiModel { + return RoomDescriptionUiModel( + roomId = roomId, + name = name ?: "", + description = topic ?: "", + avatarData = AvatarData( + id = roomId.value, + name = name ?: "", + url = avatarUrl, + size = AvatarSize.RoomDirectoryItem, + ), + canBeJoined = joinRule == RoomDescription.JoinRule.PUBLIC, + ) +} 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 382aac5468..2dc6c8875f 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 @@ -53,5 +53,5 @@ enum class AvatarSize(val dp: Dp) { CustomRoomNotificationSetting(36.dp), - RoomDirectorySearchItem(36.dp), + RoomDirectoryItem(36.dp), } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt similarity index 79% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt index 1902c7aa3a..ce50125b65 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectorySearch.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt @@ -16,11 +16,11 @@ package io.element.android.libraries.matrix.api.roomdirectory -import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.Flow -interface RoomDirectorySearch { - suspend fun updateQuery(query: String?, batchSize: Int) +interface RoomDirectoryList { + suspend fun filter(filter: String?, batchSize: Int) suspend fun loadMore() suspend fun hasMoreToLoad(): Boolean - val results: SharedFlow> + val items: Flow> } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt index ebcff30671..fe9beb27d6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt @@ -17,5 +17,5 @@ package io.element.android.libraries.matrix.api.roomdirectory interface RoomDirectoryService { - fun search(): RoomDirectorySearch + fun createRoomDirectoryList(): RoomDirectoryList } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt index 17ea8ee444..bf5c4c601f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.verification.SessionVerificationService import kotlinx.coroutines.CoroutineScope @@ -68,4 +69,9 @@ object SessionMatrixModule { fun provideSessionCoroutineScope(matrixClient: MatrixClient): CoroutineScope { return matrixClient.sessionCoroutineScope } + + @Provides + fun providesRoomDirectoryService(matrixClient: MatrixClient): RoomDirectoryService { + return matrixClient.roomDirectoryService() + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt similarity index 71% rename from libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt rename to libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index 448a229538..1dbd62317c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectorySearch.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -17,26 +17,28 @@ package io.element.android.libraries.matrix.impl.roomdirectory import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription -import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.milliseconds import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySearch -class RustRoomDirectorySearch( +class RustRoomDirectoryList( private val inner: InnerRoomDirectorySearch, private val sessionCoroutineScope: CoroutineScope, private val sessionDispatcher: CoroutineDispatcher, -) : RoomDirectorySearch { +) : RoomDirectoryList { - private val _results: MutableStateFlow> = - MutableStateFlow(emptyList()) + private val _items = MutableSharedFlow>() - private val processor = RoomDirectorySearchProcessor(_results, sessionDispatcher, RoomDescriptionMapper()) + private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper()) init { sessionCoroutineScope.launch(sessionDispatcher) { @@ -49,8 +51,8 @@ class RustRoomDirectorySearch( } } - override suspend fun updateQuery(query: String?, batchSize: Int) { - inner.search(query, batchSize.toUInt()) + override suspend fun filter(filter: String?, batchSize: Int) { + inner.search(filter, batchSize.toUInt()) } override suspend fun loadMore() { @@ -61,5 +63,6 @@ class RustRoomDirectorySearch( return !inner.isAtLastPage() } - override val results: SharedFlow> = _results + @OptIn(FlowPreview::class) + override val items: Flow> = _items.debounce(200.milliseconds) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt index 9644d5db7a..7022b2e21b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt @@ -16,7 +16,7 @@ package io.element.android.libraries.matrix.impl.roomdirectory -import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -28,8 +28,8 @@ class RustRoomDirectoryService( private val sessionDispatcher: CoroutineDispatcher, ) : RoomDirectoryService { - override fun search(): RoomDirectorySearch { - return RustRoomDirectorySearch(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher) + override fun createRoomDirectoryList(): RoomDirectoryList { + return RustRoomDirectoryList(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt index f4b99f09a1..9281a41bc8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt @@ -16,11 +16,11 @@ package io.element.android.libraries.matrix.test.roomdirectory -import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectorySearch +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService class FakeRoomDirectoryService : RoomDirectoryService { - override fun search(): RoomDirectorySearch { + override fun createRoomDirectoryList(): RoomDirectoryList { TODO("Not yet implemented") } } From e4c711842838ee50181bbb88b8c074349c3a609b Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Mar 2024 18:21:03 +0100 Subject: [PATCH 007/124] Room directory : fix pagination and add empty state. --- .../impl/root/RoomDirectoryEvents.kt | 1 - .../impl/root/RoomDirectoryPresenter.kt | 23 ------ .../impl/root/RoomDirectoryView.kt | 75 ++++++++++++++----- .../api/roomdirectory/RoomDirectoryList.kt | 4 +- .../RoomDirectorySearchProcessor.kt | 18 ++--- .../roomdirectory/RustRoomDirectoryList.kt | 30 ++++++-- 6 files changed, 91 insertions(+), 60 deletions(-) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt index 267c889f61..771a8ceb6c 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt @@ -22,5 +22,4 @@ sealed interface RoomDirectoryEvents { data class Search(val query: String) : RoomDirectoryEvents data object LoadMore : RoomDirectoryEvents data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents - data class SearchActiveChange(val isActive: Boolean) : RoomDirectoryEvents } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 78300287bf..70d65bfb37 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -26,14 +26,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel import io.element.android.features.roomdirectory.impl.root.model.toUiModel import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers @@ -76,9 +73,6 @@ class RoomDirectoryPresenter @Inject constructor( is RoomDirectoryEvents.Search -> { searchQuery = event.query } - is RoomDirectoryEvents.SearchActiveChange -> { - - } } } @@ -90,23 +84,6 @@ class RoomDirectoryPresenter @Inject constructor( ) } - @Composable - private fun searchResults( - filteredRooms: ImmutableList, - hasMoreToLoad: Boolean, - isSearchActive: Boolean, - ): SearchBarResultState> { - return if (!isSearchActive) { - SearchBarResultState.Initial() - } else { - if (filteredRooms.isEmpty() && !hasMoreToLoad) { - SearchBarResultState.NoResultsFound() - } else { - SearchBarResultState.Results(filteredRooms) - } - } - } - @Composable private fun RoomDirectoryList.collectItemsAsState() = remember { items.map { list -> diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 0bff02cdc2..3bc7a0886c 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -17,6 +17,7 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row @@ -26,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardActions @@ -33,6 +35,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -49,6 +52,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.aliasScreenTitle +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator 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.Scaffold @@ -77,8 +81,8 @@ fun RoomDirectoryView( state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) }, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) } ) @@ -119,7 +123,10 @@ private fun RoomDirectoryContent( ) RoomDirectoryRoomList( roomDescriptions = state.roomDescriptions, + displayLoadMoreIndicator = state.displayLoadMoreIndicator, + displayEmptyState = state.displayEmptyState, onResultClicked = onResultClicked, + onReachedLoadMore = { state.eventSink(RoomDirectoryEvents.LoadMore) }, ) } } @@ -127,18 +134,52 @@ private fun RoomDirectoryContent( @Composable private fun RoomDirectoryRoomList( roomDescriptions: ImmutableList, + displayLoadMoreIndicator: Boolean, + displayEmptyState: Boolean, onResultClicked: (RoomId) -> Unit, + onReachedLoadMore: () -> Unit, modifier: Modifier = Modifier, ) { - LazyColumn( - modifier = modifier, - ) { + LazyColumn(modifier = modifier) { items(roomDescriptions) { roomDescription -> RoomDirectoryRoomRow( roomDescription = roomDescription, onClick = onResultClicked, ) } + if (displayEmptyState) { + item { + Text( + text = stringResource(id = CommonStrings.common_no_results), + style = ElementTheme.typography.fontBodyLgRegular, + color = ElementTheme.colors.textPlaceholder, + modifier = Modifier.padding(16.dp) + ) + } + } + if (displayLoadMoreIndicator) { + item { + LoadMoreIndicator(modifier = Modifier.fillMaxWidth()) + LaunchedEffect(Unit) { + onReachedLoadMore() + } + } + } + } +} + +@Composable +private fun LoadMoreIndicator(modifier: Modifier = Modifier) { + Box( + modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(8.dp), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + strokeWidth = 2.dp, + ) } } @@ -203,14 +244,14 @@ private fun RoomDirectoryRoomRow( ) { Row( modifier = modifier - .fillMaxWidth() - .clickable { onClick(roomDescription.roomId) } - .padding( - top = 12.dp, - bottom = 12.dp, - start = 16.dp, - ) - .height(IntrinsicSize.Min), + .fillMaxWidth() + .clickable { onClick(roomDescription.roomId) } + .padding( + top = 12.dp, + bottom = 12.dp, + start = 16.dp, + ) + .height(IntrinsicSize.Min), ) { Avatar( avatarData = roomDescription.avatarData, @@ -218,8 +259,8 @@ private fun RoomDirectoryRoomRow( ) Column( modifier = Modifier - .weight(1f) - .padding(start = 16.dp) + .weight(1f) + .padding(start = 16.dp) ) { Text( text = roomDescription.name, @@ -241,8 +282,8 @@ private fun RoomDirectoryRoomRow( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 4.dp, end = 12.dp) + .align(Alignment.CenterVertically) + .padding(start = 4.dp, end = 12.dp) ) } else { Spacer(modifier = Modifier.width(24.dp)) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt index ce50125b65..f232983620 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt @@ -19,8 +19,8 @@ package io.element.android.libraries.matrix.api.roomdirectory import kotlinx.coroutines.flow.Flow interface RoomDirectoryList { - suspend fun filter(filter: String?, batchSize: Int) - suspend fun loadMore() + suspend fun filter(filter: String?, batchSize: Int): Result + suspend fun loadMore(): Result suspend fun hasMoreToLoad(): Boolean val items: Flow> } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt index bfa996ac0d..f060635cdf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt @@ -42,27 +42,27 @@ class RoomDirectorySearchProcessor( } } - private suspend fun MutableList.applyUpdate(update: RoomDirectorySearchEntryUpdate) { + private fun MutableList.applyUpdate(update: RoomDirectorySearchEntryUpdate) { when (update) { is RoomDirectorySearchEntryUpdate.Append -> { val roomSummaries = update.values.map(roomDescriptionMapper::map) addAll(roomSummaries) } is RoomDirectorySearchEntryUpdate.PushBack -> { - val roomSummary = roomDescriptionMapper.map(update.value) - add(roomSummary) + val roomDescription = roomDescriptionMapper.map(update.value) + add(roomDescription) } is RoomDirectorySearchEntryUpdate.PushFront -> { - val roomSummary = roomDescriptionMapper.map(update.value) - add(0, roomSummary) + val roomDescription = roomDescriptionMapper.map(update.value) + add(0, roomDescription) } is RoomDirectorySearchEntryUpdate.Set -> { - val roomSummary = roomDescriptionMapper.map(update.value) - this[update.index.toInt()] = roomSummary + val roomDescription = roomDescriptionMapper.map(update.value) + this[update.index.toInt()] = roomDescription } is RoomDirectorySearchEntryUpdate.Insert -> { - val roomSummary = roomDescriptionMapper.map(update.value) - add(update.index.toInt(), roomSummary) + val roomDescription = roomDescriptionMapper.map(update.value) + add(update.index.toInt(), roomDescription) } is RoomDirectorySearchEntryUpdate.Remove -> { removeAt(update.index.toInt()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index 1dbd62317c..7b7296db8e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.roomdirectory import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview @@ -32,12 +33,11 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySe class RustRoomDirectoryList( private val inner: InnerRoomDirectorySearch, - private val sessionCoroutineScope: CoroutineScope, - private val sessionDispatcher: CoroutineDispatcher, + sessionCoroutineScope: CoroutineScope, + sessionDispatcher: CoroutineDispatcher, ) : RoomDirectoryList { - private val _items = MutableSharedFlow>() - + private val _items = MutableSharedFlow>(replay = 1) private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper()) init { @@ -51,12 +51,26 @@ class RustRoomDirectoryList( } } - override suspend fun filter(filter: String?, batchSize: Int) { - inner.search(filter, batchSize.toUInt()) + override suspend fun filter(filter: String?, batchSize: Int): Result { + return try { + inner.search(filter = filter, batchSize = batchSize.toUInt()) + Result.success(Unit) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Result.failure(e) + } } - override suspend fun loadMore() { - inner.nextPage() + override suspend fun loadMore(): Result { + return try { + inner.nextPage() + Result.success(Unit) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Result.failure(e) + } } override suspend fun hasMoreToLoad(): Boolean { From 0c96769e8b29450784bbd668da1604f2570650f0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 25 Mar 2024 20:10:16 +0100 Subject: [PATCH 008/124] Room directory : start branching join event --- .../android/appnav/LoggedInFlowNode.kt | 2 +- .../api/RoomDirectoryEntryPoint.kt | 2 +- .../impl/DefaultRoomDirectoryEntryPoint.kt | 3 +- .../impl/RoomDirectoryFlowNode.kt | 66 ------------------- .../impl/root/RoomDirectoryEvents.kt | 3 - .../impl/root/RoomDirectoryNode.kt | 10 +++ .../impl/root/RoomDirectoryPresenter.kt | 11 ++-- .../impl/root/RoomDirectoryView.kt | 42 ++++++------ 8 files changed, 39 insertions(+), 100 deletions(-) delete mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt 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 9a96a01d27..82a645b636 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -389,7 +389,7 @@ class LoggedInFlowNode @AssistedInject constructor( NavTarget.RoomDirectorySearch -> { roomDirectoryEntryPoint.nodeBuilder(this, buildContext) .callback(object : RoomDirectoryEntryPoint.Callback { - override fun onRoomJoined(roomId: RoomId) { + override fun onJoinRoom(roomId: RoomId) { coroutineScope.launch { attachRoom(roomId) } } }) diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt index 033ec67b33..a665f9f23c 100644 --- a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt @@ -32,7 +32,7 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint { } interface Callback : Plugin { - fun onRoomJoined(roomId: RoomId) + fun onJoinRoom(roomId: RoomId) } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt index ac085b4d03..11b5b1b27d 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt @@ -21,6 +21,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint +import io.element.android.features.roomdirectory.impl.root.RoomDirectoryNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.AppScope import javax.inject.Inject @@ -39,7 +40,7 @@ class DefaultRoomDirectoryEntryPoint @Inject constructor() : RoomDirectoryEntryP } override fun build(): Node { - return parentNode.createNode(buildContext, plugins) + return parentNode.createNode(buildContext, plugins) } } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt deleted file mode 100644 index 244e61b3b9..0000000000 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/RoomDirectoryFlowNode.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2024 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.roomdirectory.impl - -import android.os.Parcelable -import androidx.compose.runtime.Composable -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 com.bumble.appyx.navmodel.backstack.BackStack -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.roomdirectory.impl.root.RoomDirectoryNode -import io.element.android.libraries.architecture.BackstackView -import io.element.android.libraries.architecture.BaseFlowNode -import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.di.SessionScope -import kotlinx.parcelize.Parcelize - -@ContributesNode(SessionScope::class) -class RoomDirectoryFlowNode @AssistedInject constructor( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, -) : BaseFlowNode( - backstack = BackStack( - initialElement = NavTarget.Root, - savedStateMap = buildContext.savedStateMap, - ), - buildContext = buildContext, - plugins = plugins, -) { - - sealed interface NavTarget : Parcelable { - @Parcelize - data object Root : NavTarget - } - - override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { - return when (navTarget) { - NavTarget.Root -> { - createNode(buildContext) - } - } - } - - @Composable - override fun View(modifier: Modifier) { - BackstackView() - } -} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt index 771a8ceb6c..f105a19c53 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt @@ -16,10 +16,7 @@ package io.element.android.features.roomdirectory.impl.root -import io.element.android.libraries.matrix.api.core.RoomId - sealed interface RoomDirectoryEvents { data class Search(val query: String) : RoomDirectoryEvents data object LoadMore : RoomDirectoryEvents - data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt index 0da712eefe..5c2cbf654c 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt @@ -21,10 +21,13 @@ 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 com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.RoomId @ContributesNode(SessionScope::class) class RoomDirectoryNode @AssistedInject constructor( @@ -33,11 +36,18 @@ class RoomDirectoryNode @AssistedInject constructor( private val presenter: RoomDirectoryPresenter, ) : Node(buildContext, plugins = plugins) { + private fun onJoinRoom(roomId: RoomId) { + plugins().forEach { + it.onJoinRoom(roomId) + } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() RoomDirectoryView( state = state, + onJoinRoom = ::onJoinRoom, onBackPressed = ::navigateUp, modifier = modifier ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 70d65bfb37..bfd88f4e28 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -28,20 +28,20 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import io.element.android.features.roomdirectory.impl.root.model.toUiModel import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( - private val client: MatrixClient, - private val roomDirectoryService: RoomDirectoryService, + private val dispatchers: CoroutineDispatchers, + roomDirectoryService: RoomDirectoryService, ) : Presenter { private val roomDirectoryList = roomDirectoryService.createRoomDirectoryList() @@ -62,9 +62,6 @@ class RoomDirectoryPresenter @Inject constructor( } fun handleEvents(event: RoomDirectoryEvents) { when (event) { - is RoomDirectoryEvents.JoinRoom -> { - //coroutineScope.joinRoom(event.roomId) - } RoomDirectoryEvents.LoadMore -> { coroutineScope.launch { roomDirectoryList.loadMore() @@ -90,6 +87,6 @@ class RoomDirectoryPresenter @Inject constructor( list .map { roomDescription -> roomDescription.toUiModel() } .toImmutableList() - }.flowOn(Dispatchers.Default) + }.flowOn(dispatchers.computation) }.collectAsState(persistentListOf()) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 3bc7a0886c..fecebf572f 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -67,6 +67,7 @@ import kotlinx.collections.immutable.ImmutableList fun RoomDirectoryView( state: RoomDirectoryState, onBackPressed: () -> Unit, + onJoinRoom: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -77,12 +78,10 @@ fun RoomDirectoryView( content = { padding -> RoomDirectoryContent( state = state, - onResultClicked = { roomId -> - state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) - }, + onResultClicked = onJoinRoom, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) } ) @@ -171,10 +170,10 @@ private fun RoomDirectoryRoomList( @Composable private fun LoadMoreIndicator(modifier: Modifier = Modifier) { Box( - modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(8.dp), + modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(8.dp), contentAlignment = Alignment.Center, ) { CircularProgressIndicator( @@ -244,14 +243,14 @@ private fun RoomDirectoryRoomRow( ) { Row( modifier = modifier - .fillMaxWidth() - .clickable { onClick(roomDescription.roomId) } - .padding( - top = 12.dp, - bottom = 12.dp, - start = 16.dp, - ) - .height(IntrinsicSize.Min), + .fillMaxWidth() + .clickable { onClick(roomDescription.roomId) } + .padding( + top = 12.dp, + bottom = 12.dp, + start = 16.dp, + ) + .height(IntrinsicSize.Min), ) { Avatar( avatarData = roomDescription.avatarData, @@ -259,8 +258,8 @@ private fun RoomDirectoryRoomRow( ) Column( modifier = Modifier - .weight(1f) - .padding(start = 16.dp) + .weight(1f) + .padding(start = 16.dp) ) { Text( text = roomDescription.name, @@ -282,8 +281,8 @@ private fun RoomDirectoryRoomRow( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 4.dp, end = 12.dp) + .align(Alignment.CenterVertically) + .padding(start = 4.dp, end = 12.dp) ) } else { Spacer(modifier = Modifier.width(24.dp)) @@ -296,6 +295,7 @@ private fun RoomDirectoryRoomRow( fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { RoomDirectoryView( state = state, + onJoinRoom = {}, onBackPressed = {}, ) } From 90b2a65c1a8b2e0bce2e3996d38bfd814a119834 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Mar 2024 12:32:15 +0100 Subject: [PATCH 009/124] Room directory : implement simple join room --- .../android/appnav/LoggedInFlowNode.kt | 2 +- .../api/RoomDirectoryEntryPoint.kt | 2 +- .../impl/root/RoomDirectoryEvents.kt | 4 ++ .../impl/root/RoomDirectoryNode.kt | 6 +-- .../impl/root/RoomDirectoryPresenter.kt | 22 ++++++++++ .../impl/root/RoomDirectoryState.kt | 3 ++ .../impl/root/RoomDirectoryStateProvider.kt | 6 +-- .../impl/root/RoomDirectoryView.kt | 18 ++++++-- .../libraries/matrix/api/MatrixClient.kt | 1 + .../libraries/matrix/impl/RustMatrixClient.kt | 44 +++++++++++++++---- 10 files changed, 89 insertions(+), 19 deletions(-) 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 82a645b636..a9dd485285 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -389,7 +389,7 @@ class LoggedInFlowNode @AssistedInject constructor( NavTarget.RoomDirectorySearch -> { roomDirectoryEntryPoint.nodeBuilder(this, buildContext) .callback(object : RoomDirectoryEntryPoint.Callback { - override fun onJoinRoom(roomId: RoomId) { + override fun onOpenRoom(roomId: RoomId) { coroutineScope.launch { attachRoom(roomId) } } }) diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt index a665f9f23c..8fbf342697 100644 --- a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt @@ -32,7 +32,7 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint { } interface Callback : Plugin { - fun onJoinRoom(roomId: RoomId) + fun onOpenRoom(roomId: RoomId) } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt index f105a19c53..37e0ffb3c6 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt @@ -16,7 +16,11 @@ package io.element.android.features.roomdirectory.impl.root +import io.element.android.libraries.matrix.api.core.RoomId + sealed interface RoomDirectoryEvents { + data class JoinRoom(val roomId: RoomId) : RoomDirectoryEvents data class Search(val query: String) : RoomDirectoryEvents data object LoadMore : RoomDirectoryEvents + data object JoinRoomDismissError : RoomDirectoryEvents } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt index 5c2cbf654c..c99d30640a 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt @@ -36,9 +36,9 @@ class RoomDirectoryNode @AssistedInject constructor( private val presenter: RoomDirectoryPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onJoinRoom(roomId: RoomId) { + private fun onRoomJoined(roomId: RoomId) { plugins().forEach { - it.onJoinRoom(roomId) + it.onOpenRoom(roomId) } } @@ -47,7 +47,7 @@ class RoomDirectoryNode @AssistedInject constructor( val state = presenter.present() RoomDirectoryView( state = state, - onJoinRoom = ::onJoinRoom, + onRoomJoined = ::onRoomJoined, onBackPressed = ::navigateUp, modifier = modifier ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index bfd88f4e28..9e91193114 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -18,6 +18,7 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -27,13 +28,17 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import io.element.android.features.roomdirectory.impl.root.model.toUiModel +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.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.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -41,6 +46,7 @@ import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, + private val matrixClient: MatrixClient, roomDirectoryService: RoomDirectoryService, ) : Presenter { @@ -56,6 +62,9 @@ class RoomDirectoryPresenter @Inject constructor( val hasMoreToLoad by produceState(initialValue = true, allRooms) { value = roomDirectoryList.hasMoreToLoad() } + val joinRoomAction: MutableState> = remember { + mutableStateOf(AsyncAction.Uninitialized) + } val coroutineScope = rememberCoroutineScope() LaunchedEffect(searchQuery) { roomDirectoryList.filter(searchQuery, 20) @@ -70,6 +79,12 @@ class RoomDirectoryPresenter @Inject constructor( is RoomDirectoryEvents.Search -> { searchQuery = event.query } + is RoomDirectoryEvents.JoinRoom -> { + coroutineScope.joinRoom(joinRoomAction, event.roomId) + } + RoomDirectoryEvents.JoinRoomDismissError -> { + joinRoomAction.value = AsyncAction.Uninitialized + } } } @@ -77,10 +92,17 @@ class RoomDirectoryPresenter @Inject constructor( query = searchQuery, roomDescriptions = allRooms, displayLoadMoreIndicator = hasMoreToLoad, + joinRoomAction = joinRoomAction.value, eventSink = ::handleEvents ) } + private fun CoroutineScope.joinRoom(state: MutableState>, roomId: RoomId) = launch { + state.runUpdatingState { + matrixClient.joinRoom(roomId) + } + } + @Composable private fun RoomDirectoryList.collectItemsAsState() = remember { items.map { list -> diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt index 1afd53f146..937f78a69b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt @@ -17,12 +17,15 @@ package io.element.android.features.roomdirectory.impl.root import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList data class RoomDirectoryState( val query: String, val roomDescriptions: ImmutableList, val displayLoadMoreIndicator: Boolean, + val joinRoomAction: AsyncAction, val eventSink: (RoomDirectoryEvents) -> Unit ) { val displayEmptyState = roomDescriptions.isEmpty() && !displayLoadMoreIndicator diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index 30acb14dba..8261959016 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -18,9 +18,9 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -63,13 +63,13 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider = persistentListOf(), - searchResults: SearchBarResultState> = SearchBarResultState.Initial(), + joinRoomAction: AsyncAction = AsyncAction.Uninitialized, ) = RoomDirectoryState( query = query, roomDescriptions = roomDescriptions, displayLoadMoreIndicator = displayLoadMoreIndicator, + joinRoomAction = joinRoomAction, eventSink = {}, ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index fecebf572f..bfd25d7f3d 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -47,6 +47,7 @@ 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.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview @@ -66,10 +67,15 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun RoomDirectoryView( state: RoomDirectoryState, + onRoomJoined: (RoomId) -> Unit, onBackPressed: () -> Unit, - onJoinRoom: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { + + fun joinRoom(roomId: RoomId) { + state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) + } + Scaffold( modifier = modifier, topBar = { @@ -78,13 +84,19 @@ fun RoomDirectoryView( content = { padding -> RoomDirectoryContent( state = state, - onResultClicked = onJoinRoom, + onResultClicked = ::joinRoom, modifier = Modifier .padding(padding) .consumeWindowInsets(padding) ) } ) + AsyncActionView( + async = state.joinRoomAction, + onSuccess = onRoomJoined, + onErrorDismiss = { + state.eventSink(RoomDirectoryEvents.JoinRoomDismissError) + }) } @OptIn(ExperimentalMaterial3Api::class) @@ -295,7 +307,7 @@ private fun RoomDirectoryRoomRow( fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { RoomDirectoryView( state = state, - onJoinRoom = {}, + onRoomJoined = {}, onBackPressed = {}, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 43ca8f6687..e6c536ee72 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -59,6 +59,7 @@ interface MatrixClient : Closeable { suspend fun setDisplayName(displayName: String): Result suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result suspend fun removeAvatar(): Result + suspend fun joinRoom(roomId: RoomId): Result fun syncService(): SyncService fun sessionVerificationService(): SessionVerificationService fun pushersService(): PushersService 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 280b56724b..277288205f 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 @@ -73,6 +73,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow @@ -103,6 +104,8 @@ import org.matrix.rustcomponents.sdk.use import timber.log.Timber import java.io.File import java.util.concurrent.atomic.AtomicBoolean +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds import org.matrix.rustcomponents.sdk.CreateRoomParameters as RustCreateRoomParameters import org.matrix.rustcomponents.sdk.RoomPreset as RustRoomPreset import org.matrix.rustcomponents.sdk.RoomVisibility as RustRoomVisibility @@ -320,6 +323,22 @@ class RustMatrixClient( } } + /** + * Wait for the room to be available in the room list. + * @param roomId the room id to wait for + * @param timeout the timeout to wait for the room to be available + * @throws TimeoutCancellationException if the room is not available after the timeout + */ + private suspend fun awaitRoom(roomId: RoomId, timeout: Duration) { + withTimeout(timeout) { + roomListService.allRooms.summaries + .filter { roomSummaries -> + roomSummaries.map { it.identifier() }.contains(roomId.value) + } + .first() + } + } + private suspend fun pairOfRoom(roomId: RoomId): Pair? { val cachedRoomListItem = innerRoomListService.roomOrNull(roomId.value) val fullRoom = cachedRoomListItem?.fullRoomWithTimeline(filter = eventFilters) @@ -369,14 +388,11 @@ class RustMatrixClient( powerLevelContentOverride = defaultRoomCreationPowerLevels, ) val roomId = RoomId(client.createRoom(rustParams)) - - // Wait to receive the room back from the sync - withTimeout(30_000L) { - roomListService.allRooms.summaries - .filter { roomSummaries -> - roomSummaries.map { it.identifier() }.contains(roomId.value) - } - .first() + // Wait to receive the room back from the sync but do not returns failure if it fails. + try { + awaitRoom(roomId, 30.seconds) + } catch (e: Exception) { + Timber.e(e, "Timeout waiting for the room to be available in the room list") } roomId } @@ -425,6 +441,18 @@ class RustMatrixClient( runCatching { client.removeAvatar() } } + override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { + runCatching { + client.joinRoomById(roomId.value).destroy() + try { + awaitRoom(roomId, 10.seconds) + } catch (e: Exception) { + Timber.e(e, "Timeout waiting for the room to be available in the room list") + } + roomId + } + } + override fun syncService(): SyncService = rustSyncService override fun sessionVerificationService(): SessionVerificationService = verificationService From 153e88dade103a99be2faa044e89e31559928158 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Mar 2024 15:18:16 +0100 Subject: [PATCH 010/124] RoomList Search : do not persist isActive when leaving composition --- .../roomlist/impl/search/RoomListSearchPresenter.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt index 78bcda07f1..d751651ef4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt @@ -21,7 +21,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Presenter import kotlinx.collections.immutable.persistentListOf @@ -32,10 +32,11 @@ class RoomListSearchPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): RoomListSearchState { - var isSearchActive by rememberSaveable { + // Do not use rememberSaveable so that search is not active when the user navigates back to the screen + var isSearchActive by remember { mutableStateOf(false) } - var searchQuery by rememberSaveable { + var searchQuery by remember { mutableStateOf("") } From d3f8962b2bcfbea7bba6e62c6764631d9b573171 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 26 Mar 2024 16:46:14 +0100 Subject: [PATCH 011/124] version++ --- plugins/src/main/kotlin/Versions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index abc46f7d38..d1c61e4285 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -56,7 +56,7 @@ private const val versionMinor = 4 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 7 +private const val versionPatch = 8 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch From 16056ef78710dfe9a888706dc0834454db7b07ab Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 26 Mar 2024 17:42:34 +0100 Subject: [PATCH 012/124] Rename `PollAnswerItem.isDisclosed` to `PollAnswerItem.showVotes` for clarity. Indeed the value is set to true for disclosed poll or if poll is ended. --- .../poll/api/pollcontent/PollAnswerItem.kt | 4 ++-- .../poll/api/pollcontent/PollAnswerView.kt | 18 +++++++++--------- .../pollcontent/PollContentStateFixtures.kt | 18 +++++++++--------- .../poll/api/pollcontent/PollContentView.kt | 2 +- .../model/DefaultPollContentStateFactory.kt | 2 +- .../pollcontent/PollContentStateFactoryTest.kt | 16 ++++++++-------- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt index 56dbaeb3ca..c82f1cb734 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt @@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.api.poll.PollAnswer * @property isSelected whether the user has selected this answer. * @property isEnabled whether the answer can be voted. * @property isWinner whether this is the winner answer in the poll. - * @property isDisclosed whether the votes for this answer should be disclosed. + * @property showVotes whether the votes for this answer should be displayed. * @property votesCount the number of votes for this answer. * @property percentage the percentage of votes for this answer. */ @@ -34,7 +34,7 @@ data class PollAnswerItem( val isSelected: Boolean, val isEnabled: Boolean, val isWinner: Boolean, - val isDisclosed: Boolean, + val showVotes: Boolean, val votesCount: Int, val percentage: Float, ) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt index a84ef502ca..4f230eeaf6 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt @@ -79,7 +79,7 @@ internal fun PollAnswerView( text = answerItem.answer.text, style = if (answerItem.isWinner) ElementTheme.typography.fontBodyLgMedium else ElementTheme.typography.fontBodyLgRegular, ) - if (answerItem.isDisclosed) { + if (answerItem.showVotes) { Text( modifier = Modifier.align(Alignment.Bottom), text = pluralStringResource( @@ -98,7 +98,7 @@ internal fun PollAnswerView( color = if (answerItem.isWinner) ElementTheme.colors.textSuccessPrimary else answerItem.isEnabled.toEnabledColor(), progress = { when { - answerItem.isDisclosed -> answerItem.percentage + answerItem.showVotes -> answerItem.percentage answerItem.isSelected -> 1f else -> 0f } @@ -114,7 +114,7 @@ internal fun PollAnswerView( @Composable internal fun PollAnswerDisclosedNotSelectedPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false), + answerItem = aPollAnswerItem(showVotes = true, isSelected = false), ) } @@ -122,7 +122,7 @@ internal fun PollAnswerDisclosedNotSelectedPreview() = ElementPreview { @Composable internal fun PollAnswerDisclosedSelectedPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true), + answerItem = aPollAnswerItem(showVotes = true, isSelected = true), ) } @@ -130,7 +130,7 @@ internal fun PollAnswerDisclosedSelectedPreview() = ElementPreview { @Composable internal fun PollAnswerUndisclosedNotSelectedPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = false, isSelected = false), + answerItem = aPollAnswerItem(showVotes = false, isSelected = false), ) } @@ -138,7 +138,7 @@ internal fun PollAnswerUndisclosedNotSelectedPreview() = ElementPreview { @Composable internal fun PollAnswerUndisclosedSelectedPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = false, isSelected = true), + answerItem = aPollAnswerItem(showVotes = false, isSelected = true), ) } @@ -146,7 +146,7 @@ internal fun PollAnswerUndisclosedSelectedPreview() = ElementPreview { @Composable internal fun PollAnswerEndedWinnerNotSelectedPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false, isEnabled = false, isWinner = true), + answerItem = aPollAnswerItem(showVotes = true, isSelected = false, isEnabled = false, isWinner = true), ) } @@ -154,7 +154,7 @@ internal fun PollAnswerEndedWinnerNotSelectedPreview() = ElementPreview { @Composable internal fun PollAnswerEndedWinnerSelectedPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = true), + answerItem = aPollAnswerItem(showVotes = true, isSelected = true, isEnabled = false, isWinner = true), ) } @@ -162,6 +162,6 @@ internal fun PollAnswerEndedWinnerSelectedPreview() = ElementPreview { @Composable internal fun PollAnswerEndedSelectedPreview() = ElementPreview { PollAnswerView( - answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = false), + answerItem = aPollAnswerItem(showVotes = true, isSelected = true, isEnabled = false, isWinner = false), ) } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt index 206ee93ce0..9ec3ec3754 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt @@ -27,11 +27,11 @@ fun aPollQuestion() = "What type of food should we have at the party?" fun aPollAnswerItemList( hasVotes: Boolean = true, isEnded: Boolean = false, - isDisclosed: Boolean = true, + showVotes: Boolean = true, ) = persistentListOf( aPollAnswerItem( answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"), - isDisclosed = isDisclosed, + showVotes = showVotes, isEnabled = !isEnded, isWinner = isEnded, votesCount = if (hasVotes) 5 else 0, @@ -39,7 +39,7 @@ fun aPollAnswerItemList( ), aPollAnswerItem( answer = PollAnswer("option_2", "Chinese \uD83C\uDDE8\uD83C\uDDF3"), - isDisclosed = isDisclosed, + showVotes = showVotes, isEnabled = !isEnded, isWinner = false, votesCount = 0, @@ -47,7 +47,7 @@ fun aPollAnswerItemList( ), aPollAnswerItem( answer = PollAnswer("option_3", "Brazilian \uD83C\uDDE7\uD83C\uDDF7"), - isDisclosed = isDisclosed, + showVotes = showVotes, isEnabled = !isEnded, isWinner = false, isSelected = true, @@ -55,7 +55,7 @@ fun aPollAnswerItemList( percentage = if (hasVotes) 0.1f else 0f ), aPollAnswerItem( - isDisclosed = isDisclosed, + showVotes = showVotes, isEnabled = !isEnded, votesCount = if (hasVotes) 4 else 0, percentage = if (hasVotes) 0.4f else 0f, @@ -70,7 +70,7 @@ fun aPollAnswerItem( isSelected: Boolean = false, isEnabled: Boolean = true, isWinner: Boolean = false, - isDisclosed: Boolean = true, + showVotes: Boolean = true, votesCount: Int = 4, percentage: Float = 0.4f, ) = PollAnswerItem( @@ -78,7 +78,7 @@ fun aPollAnswerItem( isSelected = isSelected, isEnabled = isEnabled, isWinner = isWinner, - isDisclosed = isDisclosed, + showVotes = showVotes, votesCount = votesCount, percentage = percentage ) @@ -87,14 +87,14 @@ fun aPollContentState( eventId: EventId? = null, isMine: Boolean = false, isEnded: Boolean = false, - isDisclosed: Boolean = true, + showVotes: Boolean = true, isPollEditable: Boolean = true, hasVotes: Boolean = true, question: String = aPollQuestion(), pollKind: PollKind = PollKind.Disclosed, answerItems: ImmutableList = aPollAnswerItemList( isEnded = isEnded, - isDisclosed = isDisclosed, + showVotes = showVotes, hasVotes = hasVotes ), ) = PollContentState( diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt index 3862c385dd..a77753fc4c 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt @@ -245,7 +245,7 @@ internal fun PollContentUndisclosedPreview() = ElementPreview { PollContentView( eventId = EventId("\$anEventId"), question = "What type of food should we have at the party?", - answerItems = aPollAnswerItemList(isDisclosed = false), + answerItems = aPollAnswerItemList(showVotes = false), pollKind = PollKind.Undisclosed, isPollEnded = false, isPollEditable = false, diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt index 3cbe132c85..a661d7a1bc 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt @@ -60,7 +60,7 @@ class DefaultPollContentStateFactory @Inject constructor( isSelected = isSelected, isEnabled = !isPollEnded, isWinner = isWinner, - isDisclosed = content.kind.isDisclosed || isPollEnded, + showVotes = content.kind.isDisclosed || isPollEnded, votesCount = answerVoteCount, percentage = percentage, ) diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt index d39064d3a0..03058029a6 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt @@ -131,7 +131,7 @@ class PollContentStateFactoryTest { val state = factory.create(eventTimelineItem, aPollContent(PollKind.Undisclosed)) val expectedState = aPollContentState(pollKind = PollKind.Undisclosed).let { it.copy( - answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = false) }.toImmutableList() + answerItems = it.answerItems.map { answerItem -> answerItem.copy(showVotes = false) }.toImmutableList() ) } assertThat(state).isEqualTo(expectedState) @@ -147,10 +147,10 @@ class PollContentStateFactoryTest { val expectedState = aPollContentState( pollKind = PollKind.Undisclosed, answerItems = listOf( - aPollAnswerItem(answer = A_POLL_ANSWER_1, isDisclosed = false, votesCount = 3, percentage = 0.3f), - aPollAnswerItem(answer = A_POLL_ANSWER_2, isDisclosed = false, isSelected = true, votesCount = 6, percentage = 0.6f), - aPollAnswerItem(answer = A_POLL_ANSWER_3, isDisclosed = false), - aPollAnswerItem(answer = A_POLL_ANSWER_4, isDisclosed = false, votesCount = 1, percentage = 0.1f), + aPollAnswerItem(answer = A_POLL_ANSWER_1, showVotes = false, votesCount = 3, percentage = 0.3f), + aPollAnswerItem(answer = A_POLL_ANSWER_2, showVotes = false, isSelected = true, votesCount = 6, percentage = 0.6f), + aPollAnswerItem(answer = A_POLL_ANSWER_3, showVotes = false), + aPollAnswerItem(answer = A_POLL_ANSWER_4, showVotes = false, votesCount = 1, percentage = 0.1f), ), ) assertThat(state).isEqualTo(expectedState) @@ -164,7 +164,7 @@ class PollContentStateFactoryTest { pollKind = PollKind.Undisclosed ).let { it.copy( - answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = true, isEnabled = false) }.toImmutableList(), + answerItems = it.answerItems.map { answerItem -> answerItem.copy(showVotes = true, isEnabled = false) }.toImmutableList(), ) } assertThat(state).isEqualTo(expectedState) @@ -258,7 +258,7 @@ class PollContentStateFactoryTest { isSelected: Boolean = false, isEnabled: Boolean = true, isWinner: Boolean = false, - isDisclosed: Boolean = true, + showVotes: Boolean = true, votesCount: Int = 0, percentage: Float = 0f, ) = PollAnswerItem( @@ -266,7 +266,7 @@ class PollContentStateFactoryTest { isSelected = isSelected, isEnabled = isEnabled, isWinner = isWinner, - isDisclosed = isDisclosed, + showVotes = showVotes, votesCount = votesCount, percentage = percentage, ) From 3f85d4e566c34d3b0a4c4e1e32da1a75d35b2fb9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 26 Mar 2024 17:56:25 +0100 Subject: [PATCH 013/124] Add trophy icon next the the winner vote in polls. --- changelog.d/2608.misc | 1 + .../poll/api/pollcontent/PollAnswerView.kt | 38 ++++++++++++++----- .../src/main/res/drawable/ic_winner.xml | 28 ++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 changelog.d/2608.misc create mode 100644 libraries/designsystem/src/main/res/drawable/ic_winner.xml diff --git a/changelog.d/2608.misc b/changelog.d/2608.misc new file mode 100644 index 0000000000..aa2f48b5cc --- /dev/null +++ b/changelog.d/2608.misc @@ -0,0 +1 @@ + Make completed poll more clearly visible diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt index 4f230eeaf6..70658e65a8 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt @@ -41,6 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.LinearProgress import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor import io.element.android.libraries.designsystem.toEnabledColor +import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.ui.strings.CommonPlurals @Composable @@ -80,16 +81,35 @@ internal fun PollAnswerView( style = if (answerItem.isWinner) ElementTheme.typography.fontBodyLgMedium else ElementTheme.typography.fontBodyLgRegular, ) if (answerItem.showVotes) { - Text( - modifier = Modifier.align(Alignment.Bottom), - text = pluralStringResource( - id = CommonPlurals.common_poll_votes_count, - count = answerItem.votesCount, - answerItem.votesCount - ), - style = if (answerItem.isWinner) ElementTheme.typography.fontBodySmMedium else ElementTheme.typography.fontBodySmRegular, - color = if (answerItem.isWinner) ElementTheme.colors.textPrimary else ElementTheme.colors.textSecondary, + val text = pluralStringResource( + id = CommonPlurals.common_poll_votes_count, + count = answerItem.votesCount, + answerItem.votesCount ) + Row( + modifier = Modifier.align(Alignment.Bottom), + verticalAlignment = Alignment.CenterVertically, + ) { + if (answerItem.isWinner) { + Icon( + resourceId = CommonDrawables.ic_winner, + contentDescription = null, + tint = ElementTheme.colors.iconAccentTertiary, + ) + Spacer(modifier = Modifier.width(2.dp)) + Text( + text = text, + style = ElementTheme.typography.fontBodySmMedium, + color = ElementTheme.colors.textPrimary, + ) + } else { + Text( + text = text, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } } } Spacer(modifier = Modifier.height(10.dp)) diff --git a/libraries/designsystem/src/main/res/drawable/ic_winner.xml b/libraries/designsystem/src/main/res/drawable/ic_winner.xml new file mode 100644 index 0000000000..20754ecfe8 --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable/ic_winner.xml @@ -0,0 +1,28 @@ + + + + + + + + From ea73c517c4caa411a4dbd4c856bf4a12fe34c18d Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 26 Mar 2024 17:34:45 +0000 Subject: [PATCH 014/124] Update screenshots --- ...werEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,en].png | 4 ++-- ...rEndedWinnerNotSelected-Night-4_6_null,NEXUS_5,1.0,en].png | 4 ++-- ...AnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,en].png | 4 ++-- ...swerEndedWinnerSelected-Night-5_7_null,NEXUS_5,1.0,en].png | 4 ++-- ...PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,en].png | 4 ++-- ...llContentCreatorEnded-Night-12_14_null,NEXUS_5,1.0,en].png | 4 ++-- ...ed_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,en].png | 4 ++-- ..._null_PollContentEnded-Night-9_11_null,NEXUS_5,1.0,en].png | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,en].png index 1ae62e8a37..80f3750a02 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:013bfb3d3bd78d8db9f0ac51b11a41c2b82daaa28f87cdfd866260d3fb40bf0d -size 23125 +oid sha256:5855e940f101e95ff1b0f01418240fc826543528e071193ede089b740d763b4e +size 23575 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Night-4_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Night-4_6_null,NEXUS_5,1.0,en].png index 243da80797..7cb778cd9c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Night-4_6_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Night-4_6_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c039ab6649ff160b540256dacba1037b15c5907d9994a041cbab3be0ee5cacd -size 21283 +oid sha256:651a5479a412f527fc1cae7b5b34ec7a53aa84ec06c97f64d84e0ee6dd5a03d4 +size 21734 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,en].png index e9142acb1d..67fc39d99b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a99ff857afe6478e579c96b984e1abdbf981638e6964a7d2c94b53153aff078d -size 23135 +oid sha256:8fc99783d3ecf3be99b2ae66d20072fcff29f04d5dc3c0ad59904421168559f2 +size 23582 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Night-5_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Night-5_7_null,NEXUS_5,1.0,en].png index f0115f6311..5b49dbe518 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Night-5_7_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Night-5_7_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d97ba3288f5fcdaaed917a994df9273d3e82d5a6e0c7978044dbaf03152e3269 -size 21270 +oid sha256:1b4f569efd9c37fc699b2833e69794094d2f9e0188805192a0a13cb01a96d0e7 +size 21723 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,en].png index fec8ca5d89..5fc3514931 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2730173af19765a502b68278e709f441a1ddbfae88024b416029f7c87a21537 -size 49108 +oid sha256:6b4eb2a1fa6bd966b4031df5af47c019af6500a4e36c95495619d149b90fb8c6 +size 49376 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Night-12_14_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Night-12_14_null,NEXUS_5,1.0,en].png index dd786988f0..4ee4c80d4f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Night-12_14_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Night-12_14_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d7cf73a11d38d17cd6ddbfd1819757a8f9db9db75da225ed0a9d9467644847e -size 45981 +oid sha256:9049f20df151c4ec18be7fd58813b00d2c647118e558eaa491e9aa71a9d882f6 +size 46313 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,en].png index fec8ca5d89..5fc3514931 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2730173af19765a502b68278e709f441a1ddbfae88024b416029f7c87a21537 -size 49108 +oid sha256:6b4eb2a1fa6bd966b4031df5af47c019af6500a4e36c95495619d149b90fb8c6 +size 49376 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Night-9_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Night-9_11_null,NEXUS_5,1.0,en].png index dd786988f0..4ee4c80d4f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Night-9_11_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Night-9_11_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d7cf73a11d38d17cd6ddbfd1819757a8f9db9db75da225ed0a9d9467644847e -size 45981 +oid sha256:9049f20df151c4ec18be7fd58813b00d2c647118e558eaa491e9aa71a9d882f6 +size 46313 From b90081800133b8789cb671393a5cb6d8690ffd97 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Mar 2024 12:51:36 +0100 Subject: [PATCH 015/124] RoomDirectory : continue improving interactions --- features/roomdirectory/api/build.gradle.kts | 1 + .../roomdirectory/api/RoomDescription.kt | 28 +++++++ .../impl/root/RoomDirectoryPresenter.kt | 56 ++++++++------ .../impl/root/RoomDirectoryState.kt | 4 +- .../impl/root/RoomDirectoryStateProvider.kt | 8 +- .../impl/root/RoomDirectoryView.kt | 8 +- ...scriptionUiModel.kt => RoomDescription.kt} | 20 ++--- .../impl/root/model/RoomDirectoryListState.kt | 34 +++++++++ .../api/roomdirectory/RoomDirectoryList.kt | 8 +- .../api/roomdirectory/RoomDirectoryService.kt | 4 +- .../libraries/matrix/impl/RustMatrixClient.kt | 4 +- .../roomdirectory/RustRoomDirectoryList.kt | 73 +++++++++++-------- .../roomdirectory/RustRoomDirectoryService.kt | 6 +- .../roomdirectory/FakeRoomDirectoryService.kt | 3 +- 14 files changed, 171 insertions(+), 86 deletions(-) create mode 100644 features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt rename features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/{RoomDescriptionUiModel.kt => RoomDescription.kt} (71%) create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt diff --git a/features/roomdirectory/api/build.gradle.kts b/features/roomdirectory/api/build.gradle.kts index c55c647e24..04a813bd0f 100644 --- a/features/roomdirectory/api/build.gradle.kts +++ b/features/roomdirectory/api/build.gradle.kts @@ -25,4 +25,5 @@ android { dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.designsystem) } diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt new file mode 100644 index 0000000000..5b945b2a7d --- /dev/null +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.api + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.matrix.api.core.RoomId + +data class RoomDescription( + val roomId: RoomId, + val name: String, + val description: String, + val avatarData: AvatarData, + val canBeJoined: Boolean, +) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 9e91193114..2247a82b55 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -22,12 +22,12 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import io.element.android.features.roomdirectory.impl.root.model.toUiModel +import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryListState +import io.element.android.features.roomdirectory.impl.root.model.toFeatureModel import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState @@ -36,9 +36,9 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService -import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -47,34 +47,43 @@ import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, private val matrixClient: MatrixClient, - roomDirectoryService: RoomDirectoryService, + private val roomDirectoryService: RoomDirectoryService, ) : Presenter { - private val roomDirectoryList = roomDirectoryService.createRoomDirectoryList() - @Composable override fun present(): RoomDirectoryState { - + var loadingMore by remember { + mutableStateOf(false) + } var searchQuery by rememberSaveable { - mutableStateOf("") + mutableStateOf(null) } - val allRooms by roomDirectoryList.collectItemsAsState() - val hasMoreToLoad by produceState(initialValue = true, allRooms) { - value = roomDirectoryList.hasMoreToLoad() + val coroutineScope = rememberCoroutineScope() + val roomDirectoryList = remember { + roomDirectoryService.createRoomDirectoryList(coroutineScope) } + val listState by roomDirectoryList.collectState() val joinRoomAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val coroutineScope = rememberCoroutineScope() LaunchedEffect(searchQuery) { + if (searchQuery == null) return@LaunchedEffect + //debounce search query + delay(300) + //cancel load more right away + loadingMore = false roomDirectoryList.filter(searchQuery, 20) } + LaunchedEffect(loadingMore) { + if (loadingMore) { + roomDirectoryList.loadMore() + loadingMore = false + } + } fun handleEvents(event: RoomDirectoryEvents) { when (event) { RoomDirectoryEvents.LoadMore -> { - coroutineScope.launch { - roomDirectoryList.loadMore() - } + loadingMore = true } is RoomDirectoryEvents.Search -> { searchQuery = event.query @@ -89,9 +98,9 @@ class RoomDirectoryPresenter @Inject constructor( } return RoomDirectoryState( - query = searchQuery, - roomDescriptions = allRooms, - displayLoadMoreIndicator = hasMoreToLoad, + query = searchQuery.orEmpty(), + roomDescriptions = listState.items, + displayLoadMoreIndicator = listState.hasMoreToLoad, joinRoomAction = joinRoomAction.value, eventSink = ::handleEvents ) @@ -104,11 +113,12 @@ class RoomDirectoryPresenter @Inject constructor( } @Composable - private fun RoomDirectoryList.collectItemsAsState() = remember { - items.map { list -> - list - .map { roomDescription -> roomDescription.toUiModel() } + private fun RoomDirectoryList.collectState() = remember { + state.map { + val items = it.items + .map { roomDescription -> roomDescription.toFeatureModel() } .toImmutableList() + RoomDirectoryListState(items = items, hasMoreToLoad = it.hasMoreToLoad) }.flowOn(dispatchers.computation) - }.collectAsState(persistentListOf()) + }.collectAsState(RoomDirectoryListState.Default) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt index 937f78a69b..526139338d 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt @@ -16,14 +16,14 @@ package io.element.android.features.roomdirectory.impl.root -import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList data class RoomDirectoryState( val query: String, - val roomDescriptions: ImmutableList, + val roomDescriptions: ImmutableList, val displayLoadMoreIndicator: Boolean, val joinRoomAction: AsyncAction, val eventSink: (RoomDirectoryEvents) -> Unit diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index 8261959016..ff12bc7ee6 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -17,7 +17,7 @@ package io.element.android.features.roomdirectory.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -32,7 +32,7 @@ open class RoomDirectorySearchStateProvider : PreviewParameterProvider = persistentListOf(), + roomDescriptions: ImmutableList = persistentListOf(), joinRoomAction: AsyncAction = AsyncAction.Uninitialized, ) = RoomDirectoryState( query = query, diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index bfd25d7f3d..6b5d94eb63 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -46,7 +46,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.roomdirectory.impl.root.model.RoomDescriptionUiModel +import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.button.BackButton @@ -144,7 +144,7 @@ private fun RoomDirectoryContent( @Composable private fun RoomDirectoryRoomList( - roomDescriptions: ImmutableList, + roomDescriptions: ImmutableList, displayLoadMoreIndicator: Boolean, displayEmptyState: Boolean, onResultClicked: (RoomId) -> Unit, @@ -185,7 +185,7 @@ private fun LoadMoreIndicator(modifier: Modifier = Modifier) { modifier .fillMaxWidth() .wrapContentHeight() - .padding(8.dp), + .padding(24.dp), contentAlignment = Alignment.Center, ) { CircularProgressIndicator( @@ -249,7 +249,7 @@ private fun SearchTextField( @Composable private fun RoomDirectoryRoomRow( - roomDescription: RoomDescriptionUiModel, + roomDescription: RoomDescription, onClick: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt similarity index 71% rename from features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt rename to features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt index c7424eebf0..405d4241b2 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescriptionUiModel.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt @@ -16,30 +16,22 @@ package io.element.android.features.roomdirectory.impl.root.model +import io.element.android.features.roomdirectory.api.RoomDescription 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.RoomId -import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription as MatrixRoomDescription -data class RoomDescriptionUiModel( - val roomId: RoomId, - val name: String, - val description: String, - val avatarData: AvatarData, - val canBeJoined: Boolean, -) - -fun RoomDescription.toUiModel(): RoomDescriptionUiModel { - return RoomDescriptionUiModel( +fun MatrixRoomDescription.toFeatureModel(): RoomDescription { + return RoomDescription( roomId = roomId, name = name ?: "", - description = topic ?: "", + description = topic ?: alias ?: roomId.value, avatarData = AvatarData( id = roomId.value, name = name ?: "", url = avatarUrl, size = AvatarSize.RoomDirectoryItem, ), - canBeJoined = joinRule == RoomDescription.JoinRule.PUBLIC, + canBeJoined = joinRule == MatrixRoomDescription.JoinRule.PUBLIC, ) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt new file mode 100644 index 0000000000..d85295ca7e --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.root.model + +import io.element.android.features.roomdirectory.api.RoomDescription +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +internal data class RoomDirectoryListState( + val hasMoreToLoad: Boolean, + val items: ImmutableList, +) { + + companion object { + val Default = RoomDirectoryListState( + hasMoreToLoad = true, + items = persistentListOf() + ) + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt index f232983620..2311c5afee 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt @@ -21,6 +21,10 @@ import kotlinx.coroutines.flow.Flow interface RoomDirectoryList { suspend fun filter(filter: String?, batchSize: Int): Result suspend fun loadMore(): Result - suspend fun hasMoreToLoad(): Boolean - val items: Flow> + val state: Flow + + data class State( + val hasMoreToLoad: Boolean, + val items: List, + ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt index fe9beb27d6..26df48be71 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt @@ -16,6 +16,8 @@ package io.element.android.libraries.matrix.api.roomdirectory +import kotlinx.coroutines.CoroutineScope + interface RoomDirectoryService { - fun createRoomDirectoryList(): RoomDirectoryList + fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList } 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 277288205f..fffdfa8c7c 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 @@ -76,6 +76,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -158,7 +159,6 @@ class RustMatrixClient( private val roomDirectoryService = RustRoomDirectoryService( client = client, - sessionCoroutineScope = sessionCoroutineScope, sessionDispatcher = sessionDispatcher, ) @@ -441,7 +441,7 @@ class RustMatrixClient( runCatching { client.removeAvatar() } } - override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { + override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { runCatching { client.joinRoomById(roomId.value).destroy() try { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index 7b7296db8e..f2e26f51a7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -19,64 +19,79 @@ package io.element.android.libraries.matrix.impl.roomdirectory import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.milliseconds -import org.matrix.rustcomponents.sdk.RoomDirectorySearch as InnerRoomDirectorySearch +import org.matrix.rustcomponents.sdk.RoomDirectorySearch +import kotlin.coroutines.CoroutineContext class RustRoomDirectoryList( - private val inner: InnerRoomDirectorySearch, - sessionCoroutineScope: CoroutineScope, - sessionDispatcher: CoroutineDispatcher, + private val inner: RoomDirectorySearch, + coroutineScope: CoroutineScope, + private val coroutineContext: CoroutineContext, ) : RoomDirectoryList { - private val _items = MutableSharedFlow>(replay = 1) - private val processor = RoomDirectorySearchProcessor(_items, sessionDispatcher, RoomDescriptionMapper()) + private val hasMoreToLoad = MutableStateFlow(true) + private val items = MutableSharedFlow>(replay = 1) + private val processor = RoomDirectorySearchProcessor(items, coroutineContext, RoomDescriptionMapper()) init { - sessionCoroutineScope.launch(sessionDispatcher) { - inner - .resultsFlow() - .onEach { updates -> - processor.postUpdates(updates) - } - .launchIn(this) - } + launchIn(coroutineScope) + } + + private fun launchIn(coroutineScope: CoroutineScope) { + inner + .resultsFlow() + .onEach { updates -> + processor.postUpdates(updates) + } + .flowOn(coroutineContext) + .launchIn(coroutineScope) } override suspend fun filter(filter: String?, batchSize: Int): Result { - return try { + return execute { inner.search(filter = filter, batchSize = batchSize.toUInt()) - Result.success(Unit) - } catch (e: CancellationException) { - throw e - } catch (e: Exception) { - Result.failure(e) } } override suspend fun loadMore(): Result { - return try { + return execute { inner.nextPage() + } + } + + private suspend fun execute(action: suspend () -> Unit): Result { + return try { + // We always assume there is more to load until we know there isn't. + // As accessing hasMoreToLoad is otherwise blocked by the current action. + hasMoreToLoad.value = true + action() Result.success(Unit) } catch (e: CancellationException) { throw e } catch (e: Exception) { Result.failure(e) + } finally { + hasMoreToLoad.value = hasMoreToLoad() } } - override suspend fun hasMoreToLoad(): Boolean { + private suspend fun hasMoreToLoad(): Boolean { return !inner.isAtLastPage() } - @OptIn(FlowPreview::class) - override val items: Flow> = _items.debounce(200.milliseconds) + override val state: Flow = + combine(hasMoreToLoad, items) { hasMoreToLoad, items -> + RoomDirectoryList.State( + hasMoreToLoad = hasMoreToLoad, + items = items + ) + } + .flowOn(coroutineContext) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt index 7022b2e21b..7627f90261 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt @@ -24,12 +24,10 @@ import org.matrix.rustcomponents.sdk.Client class RustRoomDirectoryService( private val client: Client, - private val sessionCoroutineScope: CoroutineScope, private val sessionDispatcher: CoroutineDispatcher, ) : RoomDirectoryService { - override fun createRoomDirectoryList(): RoomDirectoryList { - return RustRoomDirectoryList(client.roomDirectorySearch(), sessionCoroutineScope, sessionDispatcher) + override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList { + return RustRoomDirectoryList(client.roomDirectorySearch(), scope, sessionDispatcher) } - } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt index 9281a41bc8..25f714cfde 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt @@ -18,9 +18,10 @@ package io.element.android.libraries.matrix.test.roomdirectory import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService +import kotlinx.coroutines.CoroutineScope class FakeRoomDirectoryService : RoomDirectoryService { - override fun createRoomDirectoryList(): RoomDirectoryList { + override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList { TODO("Not yet implemented") } } From d632e216b7690037c81dac06462e1d0b225249c6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 27 Mar 2024 12:57:50 +0100 Subject: [PATCH 016/124] RoomDirectory : branch feature flag --- .../impl/search/RoomListSearchPresenter.kt | 5 +++++ .../roomlist/impl/search/RoomListSearchState.kt | 5 ++++- .../impl/search/RoomListSearchStateProvider.kt | 3 +++ .../roomlist/impl/search/RoomListSearchView.kt | 16 +++++++--------- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt index d751651ef4..4ae7f091fe 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenter.kt @@ -24,11 +24,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject class RoomListSearchPresenter @Inject constructor( private val dataSource: RoomListSearchDataSource, + private val featureFlagService: FeatureFlagService, ) : Presenter { @Composable override fun present(): RoomListSearchState { @@ -63,12 +66,14 @@ class RoomListSearchPresenter @Inject constructor( } } + val isRoomDirectorySearchEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch).collectAsState(initial = false) val searchResults by dataSource.roomSummaries.collectAsState(initial = persistentListOf()) return RoomListSearchState( isSearchActive = isSearchActive, query = searchQuery, results = searchResults, + isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled, eventSink = ::handleEvents ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchState.kt index c4b24dc798..92e70ad039 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchState.kt @@ -23,5 +23,8 @@ data class RoomListSearchState( val isSearchActive: Boolean, val query: String, val results: ImmutableList, + val isRoomDirectorySearchEnabled: Boolean, val eventSink: (RoomListSearchEvents) -> Unit -) +) { + val displayRoomDirectorySearch = query.isEmpty() && isRoomDirectorySearchEnabled +} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchStateProvider.kt index ae722a4b04..c4dcbab1ec 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchStateProvider.kt @@ -26,6 +26,7 @@ class RoomListSearchStateProvider : PreviewParameterProvider get() = sequenceOf( aRoomListSearchState(), + aRoomListSearchState(isRoomDirectorySearchEnabled = true), aRoomListSearchState( isSearchActive = true, query = "Test", @@ -38,10 +39,12 @@ fun aRoomListSearchState( isSearchActive: Boolean = false, query: String = "", results: ImmutableList = persistentListOf(), + isRoomDirectorySearchEnabled: Boolean = false, eventSink: (RoomListSearchEvents) -> Unit = { }, ) = RoomListSearchState( isSearchActive = isSearchActive, query = query, results = results, + isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled, eventSink = eventSink, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt index 9af30fbdcd..53528e0558 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt @@ -26,8 +26,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.QrCode import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextFieldDefaults @@ -133,8 +131,8 @@ private fun RoomListSearchContent( val focusRequester = FocusRequester() TextField( modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), + .fillMaxWidth() + .focusRequester(focusRequester), value = filter, singleLine = true, onValueChange = { state.eventSink(RoomListSearchEvents.QueryChanged(it)) }, @@ -173,14 +171,14 @@ private fun RoomListSearchContent( ) { padding -> Column( modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) { - if(state.query.isEmpty()){ + if (state.displayRoomDirectorySearch) { RoomDirectorySearchButton( modifier = Modifier - .fillMaxWidth() - .padding(vertical = 24.dp, horizontal = 16.dp), + .fillMaxWidth() + .padding(vertical = 24.dp, horizontal = 16.dp), onClick = onRoomDirectorySearchClicked ) } From 4162c888ca15a18079aa17378d278b22c77e274e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:04:24 +0000 Subject: [PATCH 017/124] Update dependency com.posthog:posthog-android to v3.1.16 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccf78465d3..1f2f995732 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -175,7 +175,7 @@ opusencoder = "io.element.android:opusencoder:1.1.0" kotlinpoet = "com.squareup:kotlinpoet:1.16.0" # Analytics -posthog = "com.posthog:posthog-android:3.1.15" +posthog = "com.posthog:posthog-android:3.1.16" sentry = "io.sentry:sentry-android:7.6.0" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.14.0" From aaa38427abb043fed98054d79fd60db4b6404faa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:22:20 +0000 Subject: [PATCH 018/124] Update dependency app.cash.molecule:molecule-runtime to v1.4.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccf78465d3..08edca798c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -152,7 +152,7 @@ showkase = { module = "com.airbnb.android:showkase", version.ref = "showkase" } showkase_processor = { module = "com.airbnb.android:showkase-processor", version.ref = "showkase" } jsoup = "org.jsoup:jsoup:1.17.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } -molecule-runtime = "app.cash.molecule:molecule-runtime:1.4.1" +molecule-runtime = "app.cash.molecule:molecule-runtime:1.4.2" timber = "com.jakewharton.timber:timber:5.0.1" matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.12" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } From 44125567f63f2c5a20ac20eca48e1d194cc0adda Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Mar 2024 10:31:49 +0100 Subject: [PATCH 019/124] Tests : create lambda recorder --- .../tests/testutils/lambda/Assertions.kt | 92 ++++++++++++++++++ .../tests/testutils/lambda/LambdaRecorder.kt | 97 +++++++++++++++++++ .../testutils/lambda/ParameterMatcher.kt | 41 ++++++++ 3 files changed, 230 insertions(+) create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt new file mode 100644 index 0000000000..4b08a160ba --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.tests.testutils.lambda + +fun assert(lambdaRecorder: LambdaRecorder): LambdaRecorderAssertions { + return lambdaRecorder.assertions() +} + +class LambdaRecorderAssertions internal constructor( + private val parametersSequence: List>, +) { + fun isCalledOnce(): CalledOnceParametersAssertions { + return CalledOnceParametersAssertions( + assertions = isCalledExactly(1) + ) + } + + fun isNeverCalled() { + isCalledExactly(0) + } + + fun isCalledExactly(times: Int): ParametersAssertions { + if (parametersSequence.size != times) { + throw AssertionError("Expected to be called $times, but was called ${parametersSequence.size} times") + } + return ParametersAssertions(parametersSequence) + } +} + +class CalledOnceParametersAssertions internal constructor(private val assertions: ParametersAssertions) { + fun with(vararg matchers: ParameterMatcher) { + assertions.withSequence(matchers.toList()) + } + + fun withNoParameter() { + assertions.withNoParameter() + } +} + +class ParametersAssertions internal constructor( + private val parametersSequence: List> +) { + fun withSequence(vararg matchersSequence: List) { + if (parametersSequence.size != matchersSequence.size) { + throw AssertionError("Expected ${matchersSequence.size} parameters, but got ${parametersSequence.size} parameters") + } + parametersSequence.zip(matchersSequence).forEach { (parameters, matchers) -> + if (parameters.size != matchers.size) { + throw AssertionError("Expected ${matchers.size} parameters, but got ${parameters.size} parameters") + } + parameters.zip(matchers).forEachIndexed { j, (param, matcher) -> + if (!matcher.match(param)) { + throw AssertionError("Parameter $j does not match the expected value") + } + } + } + for (i in parametersSequence.indices) { + val params = parametersSequence[i] + val checker = matchersSequence[i] + if (params.size != checker.size) { + throw AssertionError("Expected ${checker.size} parameters, but got ${params.size} parameters") + } + for (j in params.indices) { + val param = params[j] + val check = checker[j] + if (!check.match(param)) { + throw AssertionError("Parameter $j does not match the expected value") + } + } + } + } + + fun withNoParameter() { + if (parametersSequence.any { it.isNotEmpty() }) { + throw AssertionError("Expected no parameters, but got some") + } + } +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt new file mode 100644 index 0000000000..52c3dc63a8 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.tests.testutils.lambda + +/** + * A recorder that can be used to record the parameters of lambda invocation. + */ +abstract class LambdaRecorder internal constructor() { + private val parametersSequence: MutableList> = mutableListOf() + + internal fun onInvoke(vararg params: Any?) { + parametersSequence.add(params.toList()) + } + + fun assertions(): LambdaRecorderAssertions { + return LambdaRecorderAssertions(parametersSequence = parametersSequence) + } +} + +inline fun lambdaRecorder( + noinline block: () -> R +): LambdaNoParamRecorder { + return LambdaNoParamRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T) -> R +): LambdaOneParamRecorder { + return LambdaOneParamRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T1, T2) -> R +): LambdaTwoParamsRecorder { + return LambdaTwoParamsRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T1, T2, T3) -> R +): LambdaThreeParamsRecorder { + return LambdaThreeParamsRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T1, T2, T3, T4) -> R +): LambdaFourParamsRecorder { + return LambdaFourParamsRecorder(block) +} + +class LambdaNoParamRecorder(val block: () -> R) : LambdaRecorder(), () -> R { + override fun invoke(): R { + onInvoke() + return block() + } +} + +class LambdaOneParamRecorder(val block: (T) -> R) : LambdaRecorder(), (T) -> R { + override fun invoke(p: T): R { + onInvoke(p) + return block(p) + } +} + +class LambdaTwoParamsRecorder(val block: (T1, T2) -> R) : LambdaRecorder(), (T1, T2) -> R { + override fun invoke(p1: T1, p2: T2): R { + onInvoke(p1, p2) + return block(p1, p2) + } +} + +class LambdaThreeParamsRecorder(val block: (T1, T2, T3) -> R) : LambdaRecorder(), (T1, T2, T3) -> R { + override fun invoke(p1: T1, p2: T2, p3: T3): R { + onInvoke(p1, p2, p3) + return block(p1, p2, p3) + } +} + +class LambdaFourParamsRecorder(val block: (T1, T2, T3, T4) -> R) : LambdaRecorder(), (T1, T2, T3, T4) -> R { + override fun invoke(p1: T1, p2: T2, p3: T3, p4: T4): R { + onInvoke(p1, p2, p3, p4) + return block(p1, p2, p3, p4) + } +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt new file mode 100644 index 0000000000..b070204d28 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.tests.testutils.lambda + +/** + * A matcher that can be used to match parameters in lambda calls. + * This is useful to assert that a lambda has been called with specific parameters. + */ +interface ParameterMatcher { + fun match(param: Any?): Boolean +} + +/** + * A matcher that matches a specific value. + * Can be used to assert that a lambda has been called with a specific value. + */ +fun value(expectedValue: T) = object : ParameterMatcher { + override fun match(param: Any?) = param == expectedValue +} + +/** + * A matcher that matches any value. + * Can be used when we don't care about the value of a parameter. + */ +fun any() = object : ParameterMatcher { + override fun match(param: Any?) = true +} From 46008c2887e9e1b514ed8f9b177987a03f128e68 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 Mar 2024 11:29:56 +0100 Subject: [PATCH 020/124] Fix analytics: spaceId have the same syntax as roomId. Use MatrixRoom.isSpace instead of MatrixPatterns.isSpaceId (#2612) Also remoce useless orFalse() calls. --- changelog.d/2612.bugfix | 1 + .../android/libraries/matrix/api/room/MatrixRoom.kt | 1 + .../android/libraries/matrix/impl/room/RustMatrixRoom.kt | 3 +++ .../android/libraries/matrix/test/room/FakeMatrixRoom.kt | 1 + .../services/analytics/api/extensions/JoinedRoomExt.kt | 8 +++----- .../services/analytics/api/extensions/ViewRoomExt.kt | 6 ++---- 6 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 changelog.d/2612.bugfix diff --git a/changelog.d/2612.bugfix b/changelog.d/2612.bugfix new file mode 100644 index 0000000000..fa6a0667e9 --- /dev/null +++ b/changelog.d/2612.bugfix @@ -0,0 +1 @@ +Fix analytics issue around room considered as space by mistake. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 7e407aad4f..3e01155a86 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -55,6 +55,7 @@ interface MatrixRoom : Closeable { val topic: String? val avatarUrl: String? val isEncrypted: Boolean + val isSpace: Boolean val isDirect: Boolean val isPublic: Boolean val activeMemberCount: Long diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 622d39bd61..c0e35707a1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -213,6 +213,9 @@ class RustMatrixRoom( override val isPublic: Boolean get() = innerRoom.isPublic() + override val isSpace: Boolean + get() = innerRoom.isSpace() + override val isDirect: Boolean get() = innerRoom.isDirect() diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 1872d2e60d..32575e5924 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -77,6 +77,7 @@ class FakeMatrixRoom( override val alias: String? = null, override val alternativeAliases: List = emptyList(), override val isPublic: Boolean = true, + override val isSpace: Boolean = false, override val isDirect: Boolean = false, override val isOneToOne: Boolean = false, override val joinedMemberCount: Long = 123L, diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/JoinedRoomExt.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/JoinedRoomExt.kt index d46d474dfc..c8ab1fad06 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/JoinedRoomExt.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/JoinedRoomExt.kt @@ -17,8 +17,6 @@ package io.element.android.services.analytics.api.extensions import im.vector.app.features.analytics.plan.JoinedRoom -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.room.MatrixRoom fun Long?.toAnalyticsRoomSize(): JoinedRoom.RoomSize { @@ -34,9 +32,9 @@ fun Long?.toAnalyticsRoomSize(): JoinedRoom.RoomSize { fun MatrixRoom.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom { return JoinedRoom( - isDM = this.isDirect.orFalse(), - isSpace = MatrixPatterns.isSpaceId(this.roomId.value), - roomSize = this.joinedMemberCount.toAnalyticsRoomSize(), + isDM = isDirect, + isSpace = isSpace, + roomSize = joinedMemberCount.toAnalyticsRoomSize(), trigger = trigger ) } diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/ViewRoomExt.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/ViewRoomExt.kt index 3cbfdf7292..2845129fa8 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/ViewRoomExt.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/extensions/ViewRoomExt.kt @@ -17,16 +17,14 @@ package io.element.android.services.analytics.api.extensions import im.vector.app.features.analytics.plan.ViewRoom -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.room.MatrixRoom fun MatrixRoom.toAnalyticsViewRoom(trigger: ViewRoom.Trigger? = null, selectedSpace: MatrixRoom? = null, viaKeyboard: Boolean? = null): ViewRoom { val activeSpace = selectedSpace?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home return ViewRoom( - isDM = this.isDirect.orFalse(), - isSpace = MatrixPatterns.isSpaceId(this.roomId.value), + isDM = isDirect, + isSpace = isSpace, trigger = trigger, activeSpace = activeSpace, viaKeyboard = viaKeyboard From 19523339af36aa2cf69285ca8c8194d51bcf95e7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Mar 2024 14:59:22 +0100 Subject: [PATCH 021/124] Tests : improve lambda recorder --- .../tests/testutils/lambda/Assertions.kt | 26 ++++---------- .../tests/testutils/lambda/LambdaRecorder.kt | 36 +++++++++++++------ .../testutils/lambda/ParameterMatcher.kt | 2 ++ 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt index 4b08a160ba..af241fdea0 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt @@ -56,29 +56,17 @@ class ParametersAssertions internal constructor( ) { fun withSequence(vararg matchersSequence: List) { if (parametersSequence.size != matchersSequence.size) { - throw AssertionError("Expected ${matchersSequence.size} parameters, but got ${parametersSequence.size} parameters") + throw AssertionError("Lambda was called ${parametersSequence.size} times, but only ${matchersSequence.size} assertions were provided") } - parametersSequence.zip(matchersSequence).forEach { (parameters, matchers) -> + parametersSequence.zip(matchersSequence).forEachIndexed { invocationIndex, (parameters, matchers) -> if (parameters.size != matchers.size) { - throw AssertionError("Expected ${matchers.size} parameters, but got ${parameters.size} parameters") + throw AssertionError("Expected ${matchers.size} parameters, but got ${parameters.size} parameters during invocation #$invocationIndex") } - parameters.zip(matchers).forEachIndexed { j, (param, matcher) -> + parameters.zip(matchers).forEachIndexed { paramIndex, (param, matcher) -> if (!matcher.match(param)) { - throw AssertionError("Parameter $j does not match the expected value") - } - } - } - for (i in parametersSequence.indices) { - val params = parametersSequence[i] - val checker = matchersSequence[i] - if (params.size != checker.size) { - throw AssertionError("Expected ${checker.size} parameters, but got ${params.size} parameters") - } - for (j in params.indices) { - val param = params[j] - val check = checker[j] - if (!check.match(param)) { - throw AssertionError("Parameter $j does not match the expected value") + throw AssertionError( + "Parameter #$paramIndex does not match the expected value (actual=$param,expected=$matcher) during invocation #$invocationIndex" + ) } } } diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt index 52c3dc63a8..b7beaaa5e9 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt @@ -19,10 +19,15 @@ package io.element.android.tests.testutils.lambda /** * A recorder that can be used to record the parameters of lambda invocation. */ -abstract class LambdaRecorder internal constructor() { +abstract class LambdaRecorder internal constructor( + private val assertNoInvocation: Boolean, +) { private val parametersSequence: MutableList> = mutableListOf() internal fun onInvoke(vararg params: Any?) { + if (assertNoInvocation) { + throw AssertionError("This lambda should never be called.") + } parametersSequence.add(params.toList()) } @@ -32,64 +37,73 @@ abstract class LambdaRecorder internal constructor() { } inline fun lambdaRecorder( + ensureNeverCalled: Boolean = false, noinline block: () -> R ): LambdaNoParamRecorder { - return LambdaNoParamRecorder(block) + return LambdaNoParamRecorder(ensureNeverCalled, block) } inline fun lambdaRecorder( + ensureNeverCalled: Boolean = false, noinline block: (T) -> R ): LambdaOneParamRecorder { - return LambdaOneParamRecorder(block) + return LambdaOneParamRecorder(ensureNeverCalled, block) } inline fun lambdaRecorder( + ensureNeverCalled: Boolean = false, noinline block: (T1, T2) -> R ): LambdaTwoParamsRecorder { - return LambdaTwoParamsRecorder(block) + return LambdaTwoParamsRecorder(ensureNeverCalled, block) } inline fun lambdaRecorder( + ensureNeverCalled: Boolean = false, noinline block: (T1, T2, T3) -> R ): LambdaThreeParamsRecorder { - return LambdaThreeParamsRecorder(block) + return LambdaThreeParamsRecorder(ensureNeverCalled, block) } inline fun lambdaRecorder( + ensureNeverCalled: Boolean = false, noinline block: (T1, T2, T3, T4) -> R ): LambdaFourParamsRecorder { - return LambdaFourParamsRecorder(block) + return LambdaFourParamsRecorder(ensureNeverCalled, block) } -class LambdaNoParamRecorder(val block: () -> R) : LambdaRecorder(), () -> R { +class LambdaNoParamRecorder(ensureNeverCalled: Boolean, val block: () -> R) : LambdaRecorder(ensureNeverCalled), () -> R { override fun invoke(): R { onInvoke() return block() } } -class LambdaOneParamRecorder(val block: (T) -> R) : LambdaRecorder(), (T) -> R { +class LambdaOneParamRecorder(ensureNeverCalled: Boolean, val block: (T) -> R) : LambdaRecorder(ensureNeverCalled), (T) -> R { override fun invoke(p: T): R { onInvoke(p) return block(p) } } -class LambdaTwoParamsRecorder(val block: (T1, T2) -> R) : LambdaRecorder(), (T1, T2) -> R { +class LambdaTwoParamsRecorder(ensureNeverCalled: Boolean, val block: (T1, T2) -> R) : LambdaRecorder(ensureNeverCalled), (T1, T2) -> R { override fun invoke(p1: T1, p2: T2): R { onInvoke(p1, p2) return block(p1, p2) } } -class LambdaThreeParamsRecorder(val block: (T1, T2, T3) -> R) : LambdaRecorder(), (T1, T2, T3) -> R { +class LambdaThreeParamsRecorder(ensureNeverCalled: Boolean, val block: (T1, T2, T3) -> R) : LambdaRecorder( + ensureNeverCalled +), (T1, T2, T3) -> R { override fun invoke(p1: T1, p2: T2, p3: T3): R { onInvoke(p1, p2, p3) return block(p1, p2, p3) } } -class LambdaFourParamsRecorder(val block: (T1, T2, T3, T4) -> R) : LambdaRecorder(), (T1, T2, T3, T4) -> R { +class LambdaFourParamsRecorder(ensureNeverCalled: Boolean, val block: (T1, T2, T3, T4) -> R) : LambdaRecorder( + ensureNeverCalled +), (T1, T2, T3, T4) -> R { override fun invoke(p1: T1, p2: T2, p3: T3, p4: T4): R { onInvoke(p1, p2, p3, p4) return block(p1, p2, p3, p4) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt index b070204d28..dd509ed262 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt @@ -30,6 +30,7 @@ interface ParameterMatcher { */ fun value(expectedValue: T) = object : ParameterMatcher { override fun match(param: Any?) = param == expectedValue + override fun toString(): String = "value($expectedValue)" } /** @@ -38,4 +39,5 @@ fun value(expectedValue: T) = object : ParameterMatcher { */ fun any() = object : ParameterMatcher { override fun match(param: Any?) = true + override fun toString(): String = "any()" } From 3f1f76474570090575f3914a78ec6dc741411900 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Mar 2024 17:03:34 +0100 Subject: [PATCH 022/124] Room directory : add tests and cleanup --- .idea/kotlinc.xml | 2 +- .../api/RoomDirectoryEntryPoint.kt | 2 - features/roomdirectory/impl/build.gradle.kts | 9 + .../impl/DefaultRoomDirectoryEntryPoint.kt | 2 - .../impl/root/RoomDirectoryNode.kt | 1 - .../impl/root/RoomDirectoryPresenter.kt | 11 +- .../impl/root/RoomDirectoryStateProvider.kt | 63 +++--- .../impl/root/RoomDirectoryView.kt | 48 ++--- .../roomdirectory/impl/root/di/JoinRoom.kt | 32 +++ .../impl/root/model/RoomDirectoryListState.kt | 1 - .../roomdirectory/impl/root/FakeJoinRoom.kt | 26 +++ .../impl/root/RoomDirectoryPresenterTest.kt | 182 ++++++++++++++++++ .../impl/root/RoomDirectoryViewTest.kt | 112 +++++++++++ .../roomlist/impl/RoomListViewTest.kt | 2 + .../search/RoomListSearchPresenterTests.kt | 22 ++- .../libraries/matrix/impl/RustMatrixClient.kt | 3 +- .../roomdirectory/RoomDescriptionMapper.kt | 1 - .../RoomDirectorySearchProcessor.kt | 1 - .../roomdirectory/RustRoomDirectoryList.kt | 1 - .../roomdirectory/RustRoomDirectoryService.kt | 1 - .../libraries/matrix/test/FakeMatrixClient.kt | 5 + .../roomdirectory/FakeRoomDirectoryList.kt | 31 +++ .../roomdirectory/FakeRoomDirectoryService.kt | 8 +- .../roomdirectory/RoomDescriptionFixture.kt | 41 ++++ .../android/libraries/testtags/TestTags.kt | 5 + .../android/samples/minimal/RoomListScreen.kt | 4 +- .../android/tests/testutils/PresenterTest.kt | 34 ++++ 27 files changed, 573 insertions(+), 77 deletions(-) create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt create mode 100644 features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt create mode 100644 features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt create mode 100644 features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 8d81632f83..fe63bb677d 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt index 8fbf342697..5a693a4a83 100644 --- a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId interface RoomDirectoryEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { @@ -35,4 +34,3 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint { fun onOpenRoom(roomId: RoomId) } } - diff --git a/features/roomdirectory/impl/build.gradle.kts b/features/roomdirectory/impl/build.gradle.kts index 9d56802700..d26a75ca8c 100644 --- a/features/roomdirectory/impl/build.gradle.kts +++ b/features/roomdirectory/impl/build.gradle.kts @@ -25,6 +25,11 @@ plugins { android { namespace = "io.element.android.features.roomdirectory.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -41,13 +46,17 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.testtags) testImplementation(libs.test.junit) + testImplementation(libs.androidx.compose.ui.test.junit) + testImplementation(libs.test.robolectric) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt index 11b5b1b27d..c15a748a9e 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt @@ -28,12 +28,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultRoomDirectoryEntryPoint @Inject constructor() : RoomDirectoryEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDirectoryEntryPoint.NodeBuilder { val plugins = ArrayList() return object : RoomDirectoryEntryPoint.NodeBuilder { - override fun callback(callback: RoomDirectoryEntryPoint.Callback): RoomDirectoryEntryPoint.NodeBuilder { plugins += callback return this diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt index c99d30640a..dc3581589e 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt @@ -35,7 +35,6 @@ class RoomDirectoryNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: RoomDirectoryPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onRoomJoined(roomId: RoomId) { plugins().forEach { it.onOpenRoom(roomId) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 2247a82b55..95cfa8166b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -26,13 +26,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import io.element.android.features.roomdirectory.impl.root.di.JoinRoom import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryListState import io.element.android.features.roomdirectory.impl.root.model.toFeatureModel 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.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.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService @@ -46,10 +46,9 @@ import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, - private val matrixClient: MatrixClient, + private val joinRoom: JoinRoom, private val roomDirectoryService: RoomDirectoryService, ) : Presenter { - @Composable override fun present(): RoomDirectoryState { var loadingMore by remember { @@ -68,9 +67,9 @@ class RoomDirectoryPresenter @Inject constructor( } LaunchedEffect(searchQuery) { if (searchQuery == null) return@LaunchedEffect - //debounce search query + // debounce search query delay(300) - //cancel load more right away + // cancel load more right away loadingMore = false roomDirectoryList.filter(searchQuery, 20) } @@ -108,7 +107,7 @@ class RoomDirectoryPresenter @Inject constructor( private fun CoroutineScope.joinRoom(state: MutableState>, roomId: RoomId) = launch { state.runUpdatingState { - matrixClient.joinRoom(roomId) + joinRoom(roomId) } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index ff12bc7ee6..3fa7877b6f 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -25,39 +25,14 @@ import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -open class RoomDirectorySearchStateProvider : PreviewParameterProvider { +open class RoomDirectoryStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomDirectoryState(), aRoomDirectoryState( query = "Element", - roomDescriptions = persistentListOf( - RoomDescription( - roomId = RoomId("@exa:matrix.org"), - name = "Element X Android", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "@exa:matrix.org", - name = "Element X Android", - url = null, - size = AvatarSize.RoomDirectoryItem - ), - canBeJoined = true, - ), - RoomDescription( - roomId = RoomId("@exi:matrix.org"), - name = "Element X iOS", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "@exi:matrix.org", - name = "Element X iOS", - url = null, - size = AvatarSize.RoomDirectoryItem - ), - canBeJoined = false, - ) - ) - ), + roomDescriptions = aRoomDescriptionList(), + ) ) } @@ -66,10 +41,40 @@ fun aRoomDirectoryState( displayLoadMoreIndicator: Boolean = false, roomDescriptions: ImmutableList = persistentListOf(), joinRoomAction: AsyncAction = AsyncAction.Uninitialized, + eventSink: (RoomDirectoryEvents) -> Unit = {}, ) = RoomDirectoryState( query = query, roomDescriptions = roomDescriptions, displayLoadMoreIndicator = displayLoadMoreIndicator, joinRoomAction = joinRoomAction, - eventSink = {}, + eventSink = eventSink, ) + +fun aRoomDescriptionList(): ImmutableList { + return persistentListOf( + RoomDescription( + roomId = RoomId("!exa:matrix.org"), + name = "Element X Android", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "!exa:matrix.org", + name = "Element X Android", + url = null, + size = AvatarSize.RoomDirectoryItem + ), + canBeJoined = true, + ), + RoomDescription( + roomId = RoomId("!exi:matrix.org"), + name = "Element X iOS", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "!exi:matrix.org", + name = "Element X iOS", + url = null, + size = AvatarSize.RoomDirectoryItem + ), + canBeJoined = false, + ) + ) +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 6b5d94eb63..a40855fcc3 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter @@ -61,6 +62,7 @@ import io.element.android.libraries.designsystem.theme.components.Text 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.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -71,7 +73,6 @@ fun RoomDirectoryView( onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { - fun joinRoom(roomId: RoomId) { state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) } @@ -86,8 +87,8 @@ fun RoomDirectoryView( state = state, onResultClicked = ::joinRoom, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) } ) @@ -96,7 +97,8 @@ fun RoomDirectoryView( onSuccess = onRoomJoined, onErrorDismiss = { state.eventSink(RoomDirectoryEvents.JoinRoomDismissError) - }) + } + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -171,7 +173,7 @@ private fun RoomDirectoryRoomList( if (displayLoadMoreIndicator) { item { LoadMoreIndicator(modifier = Modifier.fillMaxWidth()) - LaunchedEffect(Unit) { + LaunchedEffect(onReachedLoadMore) { onReachedLoadMore() } } @@ -182,10 +184,10 @@ private fun RoomDirectoryRoomList( @Composable private fun LoadMoreIndicator(modifier: Modifier = Modifier) { Box( - modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(24.dp), + modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(24.dp), contentAlignment = Alignment.Center, ) { CircularProgressIndicator( @@ -213,7 +215,7 @@ private fun SearchTextField( ) { val focusManager = LocalFocusManager.current TextField( - modifier = modifier, + modifier = modifier.testTag(TestTags.searchTextField.value), textStyle = ElementTheme.typography.fontBodyLgRegular, singleLine = true, value = query, @@ -255,14 +257,14 @@ private fun RoomDirectoryRoomRow( ) { Row( modifier = modifier - .fillMaxWidth() - .clickable { onClick(roomDescription.roomId) } - .padding( - top = 12.dp, - bottom = 12.dp, - start = 16.dp, - ) - .height(IntrinsicSize.Min), + .fillMaxWidth() + .clickable { onClick(roomDescription.roomId) } + .padding( + top = 12.dp, + bottom = 12.dp, + start = 16.dp, + ) + .height(IntrinsicSize.Min), ) { Avatar( avatarData = roomDescription.avatarData, @@ -270,8 +272,8 @@ private fun RoomDirectoryRoomRow( ) Column( modifier = Modifier - .weight(1f) - .padding(start = 16.dp) + .weight(1f) + .padding(start = 16.dp) ) { Text( text = roomDescription.name, @@ -293,8 +295,8 @@ private fun RoomDirectoryRoomRow( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 4.dp, end = 12.dp) + .align(Alignment.CenterVertically) + .padding(start = 4.dp, end = 12.dp) ) } else { Spacer(modifier = Modifier.width(24.dp)) @@ -304,7 +306,7 @@ private fun RoomDirectoryRoomRow( @PreviewsDayNight @Composable -fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { +internal fun RoomDirectoryViewPreview(@PreviewParameter(RoomDirectoryStateProvider::class) state: RoomDirectoryState) = ElementPreview { RoomDirectoryView( state = state, onRoomJoined = {}, diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt new file mode 100644 index 0000000000..983d2a1dd2 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.root.di + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import javax.inject.Inject + +interface JoinRoom { + suspend operator fun invoke(roomId: RoomId): Result +} + +@ContributesBinding(SessionScope::class) +class DefaultJoinRoom @Inject constructor(private val client: MatrixClient) : JoinRoom { + override suspend fun invoke(roomId: RoomId) = client.joinRoom(roomId) +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt index d85295ca7e..60f344f67b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt @@ -24,7 +24,6 @@ internal data class RoomDirectoryListState( val hasMoreToLoad: Boolean, val items: ImmutableList, ) { - companion object { val Default = RoomDirectoryListState( hasMoreToLoad = true, diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt new file mode 100644 index 0000000000..3f4d17aefd --- /dev/null +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.root + +import io.element.android.features.roomdirectory.impl.root.di.JoinRoom +import io.element.android.libraries.matrix.api.core.RoomId + +class FakeJoinRoom( + var lambda: (RoomId) -> Result = { Result.success(it) } +) : JoinRoom { + override suspend fun invoke(roomId: RoomId) = lambda(roomId) +} diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt new file mode 100644 index 0000000000..eefafc86e1 --- /dev/null +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.root + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.roomdirectory.impl.root.di.JoinRoom +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryList +import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryService +import io.element.android.libraries.matrix.test.roomdirectory.aRoomDescription +import io.element.android.tests.testutils.lambda.any +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) class RoomDirectoryPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = createRoomDirectoryPresenter() + presenter.test { + val initialState = awaitItem() + assertThat(initialState.query).isEmpty() + assertThat(initialState.displayEmptyState).isFalse() + assertThat(initialState.joinRoomAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.roomDescriptions).isEmpty() + assertThat(initialState.displayLoadMoreIndicator).isTrue() + } + } + + @Test + fun `present - room directory list emits empty state`() = runTest { + val directoryListStateFlow = MutableSharedFlow(replay = 1) + val roomDirectoryList = FakeRoomDirectoryList(directoryListStateFlow) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + skipItems(1) + directoryListStateFlow.emit( + RoomDirectoryList.State(false, emptyList()) + ) + awaitItem().also { state -> + assertThat(state.displayEmptyState).isTrue() + } + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - room directory list emits non-empty state`() = runTest { + val directoryListStateFlow = MutableSharedFlow(replay = 1) + val roomDirectoryList = FakeRoomDirectoryList(directoryListStateFlow) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + skipItems(1) + directoryListStateFlow.emit( + RoomDirectoryList.State( + hasMoreToLoad = true, + items = listOf(aRoomDescription()) + ) + ) + awaitItem().also { state -> + assertThat(state.displayEmptyState).isFalse() + assertThat(state.roomDescriptions).hasSize(1) + } + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - emit search event`() = runTest { + val filterLambda = lambdaRecorder { _: String?, _: Int -> + Result.success(Unit) + } + val roomDirectoryList = FakeRoomDirectoryList(filterLambda = filterLambda) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + awaitItem().also { state -> + state.eventSink(RoomDirectoryEvents.Search("test")) + } + awaitItem().also { state -> + assertThat(state.query).isEqualTo("test") + } + advanceUntilIdle() + cancelAndIgnoreRemainingEvents() + } + assert(filterLambda) + .isCalledOnce() + .with(value("test"), any()) + } + + @Test + fun `present - emit load more event`() = runTest { + val loadMoreLambda = lambdaRecorder { -> + Result.success(Unit) + } + val roomDirectoryList = FakeRoomDirectoryList(loadMoreLambda = loadMoreLambda) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + awaitItem().also { state -> + state.eventSink(RoomDirectoryEvents.LoadMore) + } + advanceUntilIdle() + cancelAndIgnoreRemainingEvents() + } + assert(loadMoreLambda) + .isCalledOnce() + .withNoParameter() + } + + @Test + fun `present - emit join room event`() = runTest { + val joinRoomSuccess = lambdaRecorder { roomId: RoomId -> + Result.success(roomId) + } + val joinRoomFailure = lambdaRecorder { roomId: RoomId -> + Result.failure(RuntimeException("Failed to join room $roomId")) + } + val fakeJoinRoom = FakeJoinRoom(joinRoomSuccess) + val presenter = createRoomDirectoryPresenter(joinRoom = fakeJoinRoom) + presenter.test { + awaitItem().also { state -> + state.eventSink(RoomDirectoryEvents.JoinRoom(A_ROOM_ID)) + } + awaitItem().also { state -> + assertThat(state.joinRoomAction).isEqualTo(AsyncAction.Success(A_ROOM_ID)) + fakeJoinRoom.lambda = joinRoomFailure + state.eventSink(RoomDirectoryEvents.JoinRoom(A_ROOM_ID)) + } + awaitItem().also { state -> + assertThat(state.joinRoomAction).isInstanceOf(AsyncAction.Failure::class.java) + } + } + assert(joinRoomSuccess) + .isCalledOnce() + .with(value(A_ROOM_ID)) + assert(joinRoomFailure) + .isCalledOnce() + .with(value(A_ROOM_ID)) + } + + private fun TestScope.createRoomDirectoryPresenter( + roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService( + createRoomDirectoryListFactory = { FakeRoomDirectoryList() } + ), + joinRoom: JoinRoom = FakeJoinRoom { Result.success(it) }, + ): RoomDirectoryPresenter { + return RoomDirectoryPresenter( + dispatchers = testCoroutineDispatchers(), + joinRoom = joinRoom, + roomDirectoryService = roomDirectoryService, + ) + } +} diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt new file mode 100644 index 0000000000..bcac35fc3a --- /dev/null +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdirectory.impl.root + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.testtags.TestTags +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.ensureCalledOnceWithParam +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RoomDirectoryViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `typing text in search field emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDirectoryView( + state = aRoomDirectoryState( + eventSink = eventsRecorder, + ) + ) + rule.onNodeWithTag(TestTags.searchTextField.value).performTextInput( + text = "Test" + ) + eventsRecorder.assertSingle(RoomDirectoryEvents.Search("Test")) + } + + @Test + fun `clicking on room item emits the expected Event`() { + val eventsRecorder = EventsRecorder() + val state = aRoomDirectoryState( + roomDescriptions = aRoomDescriptionList(), + eventSink = eventsRecorder, + ) + rule.setRoomDirectoryView(state = state) + val clickedRoom = state.roomDescriptions.first() + rule.onNodeWithText(clickedRoom.name).performClick() + eventsRecorder.assertSingle(RoomDirectoryEvents.JoinRoom(clickedRoom.roomId)) + } + + @Test + fun `composing load more indicator emits expected Event`() { + val eventsRecorder = EventsRecorder() + val state = aRoomDirectoryState( + displayLoadMoreIndicator = true, + eventSink = eventsRecorder, + ) + rule.setRoomDirectoryView(state = state) + eventsRecorder.assertSingle(RoomDirectoryEvents.LoadMore) + } + + @Test + fun `when joining room with success then onRoomJoined lambda is called once`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + val roomDescriptions = aRoomDescriptionList() + val joinedRoomId = roomDescriptions.first().roomId + val state = aRoomDirectoryState( + joinRoomAction = AsyncAction.Success(joinedRoomId), + roomDescriptions = roomDescriptions, + eventSink = eventsRecorder, + ) + ensureCalledOnceWithParam(joinedRoomId) { callback -> + rule.setRoomDirectoryView( + state = state, + onRoomJoined = callback, + ) + } + } +} + +private fun AndroidComposeTestRule.setRoomDirectoryView( + state: RoomDirectoryState, + onBackPressed: () -> Unit = EnsureNeverCalled(), + onRoomJoined: (RoomId) -> Unit = EnsureNeverCalledWithParam(), +) { + setContent { + RoomDirectoryView( + state = state, + onRoomJoined = onRoomJoined, + onBackPressed = onBackPressed, + ) + } +} diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt index f545b27860..c860b6fc42 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt @@ -191,6 +191,7 @@ private fun AndroidComposeTestRule.setRoomL onInvitesClicked: () -> Unit = EnsureNeverCalled(), onRoomSettingsClicked: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onMenuActionClicked: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(), + onRoomDirectorySearchClicked: () -> Unit = EnsureNeverCalled(), ) { setContent { RoomListView( @@ -203,6 +204,7 @@ private fun AndroidComposeTestRule.setRoomL onInvitesClicked = onInvitesClicked, onRoomSettingsClicked = onRoomSettingsClicked, onMenuActionClicked = onMenuActionClicked, + onRoomDirectorySearchClicked = onRoomDirectorySearchClicked, ) } } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt index d3fc434f25..b3463c549f 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt @@ -23,6 +23,9 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService 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 @@ -128,10 +131,26 @@ class RoomListSearchPresenterTests { } } } + + @Test + fun `present - room directory search`() = runTest { + val featureFlagService = FakeFeatureFlagService() + featureFlagService.setFeatureEnabled(FeatureFlags.RoomDirectorySearch, true) + val presenter = createRoomListSearchPresenter(featureFlagService = featureFlagService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + awaitItem().let { state -> + assertThat(state.isRoomDirectorySearchEnabled).isTrue() + } + } + } } fun TestScope.createRoomListSearchPresenter( roomListService: RoomListService = FakeRoomListService(), + featureFlagService: FeatureFlagService = FakeFeatureFlagService(), ): RoomListSearchPresenter { return RoomListSearchPresenter( dataSource = RoomListSearchDataSource( @@ -141,6 +160,7 @@ fun TestScope.createRoomListSearchPresenter( roomLastMessageFormatter = FakeRoomLastMessageFormatter(), ), coroutineDispatchers = testCoroutineDispatchers(), - ) + ), + featureFlagService = featureFlagService, ) } 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 fffdfa8c7c..481c094fd4 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 @@ -76,7 +76,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -441,7 +440,7 @@ class RustMatrixClient( runCatching { client.removeAvatar() } } - override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { + override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { runCatching { client.joinRoomById(roomId.value).destroy() try { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt index 52b8e1ba98..91b84e48c0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt @@ -22,7 +22,6 @@ import org.matrix.rustcomponents.sdk.PublicRoomJoinRule import org.matrix.rustcomponents.sdk.RoomDescription as RustRoomDescription class RoomDescriptionMapper { - fun map(roomDescription: RustRoomDescription): RoomDescription { return RoomDescription( roomId = RoomId(roomDescription.roomId), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt index f060635cdf..aff631d6b4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt @@ -30,7 +30,6 @@ class RoomDirectorySearchProcessor( private val coroutineContext: CoroutineContext, private val roomDescriptionMapper: RoomDescriptionMapper, ) { - private val mutex = Mutex() suspend fun postUpdates(updates: List) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index f2e26f51a7..9b444d5ce5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -35,7 +35,6 @@ class RustRoomDirectoryList( coroutineScope: CoroutineScope, private val coroutineContext: CoroutineContext, ) : RoomDirectoryList { - private val hasMoreToLoad = MutableStateFlow(true) private val items = MutableSharedFlow>(replay = 1) private val processor = RoomDirectorySearchProcessor(items, coroutineContext, RoomDescriptionMapper()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt index 7627f90261..2939001b21 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt @@ -26,7 +26,6 @@ class RustRoomDirectoryService( private val client: Client, private val sessionDispatcher: CoroutineDispatcher, ) : RoomDirectoryService { - override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList { return RustRoomDirectoryList(client.roomDirectorySearch(), scope, sessionDispatcher) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 1ac68a5fa5..247bc4bbe9 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -94,6 +94,9 @@ class FakeMatrixClient( private var setDisplayNameResult: Result = Result.success(Unit) private var uploadAvatarResult: Result = Result.success(Unit) private var removeAvatarResult: Result = Result.success(Unit) + var joinRoomLambda: suspend (RoomId) -> Result = { + Result.success(it) + } override suspend fun getRoom(roomId: RoomId): MatrixRoom? { return getRoomResults[roomId] @@ -181,6 +184,8 @@ class FakeMatrixClient( return removeAvatarResult } + override suspend fun joinRoom(roomId: RoomId): Result = joinRoomLambda(roomId) + override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService override fun pushersService(): PushersService = pushersService diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt new file mode 100644 index 0000000000..b01501d328 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.test.roomdirectory + +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +class FakeRoomDirectoryList( + override val state: Flow = emptyFlow(), + val filterLambda: (String?, Int) -> Result = { _, _ -> Result.success(Unit) }, + val loadMoreLambda: () -> Result = { Result.success(Unit) } +) : RoomDirectoryList { + override suspend fun filter(filter: String?, batchSize: Int) = filterLambda(filter, batchSize) + + override suspend fun loadMore(): Result = loadMoreLambda() +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt index 25f714cfde..68926b9deb 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt @@ -20,8 +20,8 @@ import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import kotlinx.coroutines.CoroutineScope -class FakeRoomDirectoryService : RoomDirectoryService { - override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList { - TODO("Not yet implemented") - } +class FakeRoomDirectoryService( + private val createRoomDirectoryListFactory: (CoroutineScope) -> RoomDirectoryList = { throw AssertionError("Configure a proper factory.") } +) : RoomDirectoryService { + override fun createRoomDirectoryList(scope: CoroutineScope) = createRoomDirectoryListFactory(scope) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt new file mode 100644 index 0000000000..3e53a0c2e3 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.test.roomdirectory + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription +import io.element.android.libraries.matrix.test.A_ROOM_ID + +fun aRoomDescription( + roomId: RoomId = A_ROOM_ID, + name: String? = null, + topic: String? = null, + alias: String? = null, + avatarUrl: String? = null, + joinRule: RoomDescription.JoinRule = RoomDescription.JoinRule.UNKNOWN, + isWorldReadable: Boolean = true, + joinedMembers: Long = 2L +) = RoomDescription( + roomId = roomId, + name = name, + topic = topic, + alias = alias, + avatarUrl = avatarUrl, + joinRule = joinRule, + isWorldReadable = isWorldReadable, + joinedMembers = joinedMembers +) diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index 1d237979e6..4374d77e52 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -100,4 +100,9 @@ object TestTags { * Timeline item. */ val timelineItemSenderInfo = TestTag("timeline_item-sender_info") + + /** + * Search field. + */ + val searchTextField = TestTag("search_text_field") } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index e651e8f968..827038785c 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -118,7 +118,8 @@ class RoomListScreen( roomListService = matrixClient.roomListService, roomSummaryFactory = roomListRoomSummaryFactory, coroutineDispatchers = coroutineDispatchers, - ) + ), + featureFlagService = featureFlagService, ), sessionPreferencesStore = DefaultSessionPreferencesStore( context = context, @@ -156,6 +157,7 @@ class RoomListScreen( onInvitesClicked = {}, onRoomSettingsClicked = {}, onMenuActionClicked = {}, + onRoomDirectorySearchClicked = {}, modifier = modifier, ) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt new file mode 100644 index 0000000000..2735827134 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.tests.testutils + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.TurbineTestContext +import app.cash.turbine.test +import io.element.android.libraries.architecture.Presenter +import kotlin.time.Duration + +suspend fun Presenter.test( + timeout: Duration? = null, + name: String? = null, + validate: suspend TurbineTestContext.() -> Unit, +) { + moleculeFlow(RecompositionMode.Immediate) { + present() + }.test(timeout, name, validate) +} From 2103306387d9859015ecdac53e10a7774b03094c Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 28 Mar 2024 16:15:11 +0000 Subject: [PATCH 023/124] Update screenshots --- ..._null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 3 +++ ..._null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,en].png | 3 +++ ...ull_RoomDirectoryView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 3 +++ ...ull_RoomDirectoryView-Night-0_2_null_1,NEXUS_5,1.0,en].png | 3 +++ ...stSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...stSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png | 3 +++ ...SearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...SearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png | 3 +++ ...r_Avatar_null_Avatars_Avatar_0_null_60,NEXUS_5,1.0,en].png | 3 +++ ...r_Avatar_null_Avatars_Avatar_0_null_61,NEXUS_5,1.0,en].png | 3 +++ ...r_Avatar_null_Avatars_Avatar_0_null_62,NEXUS_5,1.0,en].png | 3 +++ 11 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_60,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_61,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_62,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..bd44675e52 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b6110eb2eb66c404f787472dd18608a27a3c8ce19c794ed4b3f67e186720978 +size 13703 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8d0538f792 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2b6cc7995880af654439c4f45d6e3ff35ff9c92609ad136fcdfbb2050edea44 +size 30484 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ab56f08348 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9eafd587065c22d19814285b63e7690f5ff4bd1ed0765bd9802e61d6d8ed0ba7 +size 12915 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1ee85c5f13 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a7b5c11dbc45a804d34a0105c49c7bd15ab1f5d6441f0e95dba84fd8a9eada6 +size 29080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png index 5cc0279077..872a2f2274 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1625ac34428f660c235d1d64f5de867baa6c0ca296f0a93d81588d633d6a74bf -size 30082 +oid sha256:615e00dfa47d40fd459d268d6e68c61a501be7e4a24bfe7d18f3a647da8c5f08 +size 10595 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5cc0279077 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1625ac34428f660c235d1d64f5de867baa6c0ca296f0a93d81588d633d6a74bf +size 30082 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png index f7230cc774..845fd9fc0a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:264d9373767b6b59d0cea4bfa4d148a453058be70e7805581fe96d7448ef5232 -size 29978 +oid sha256:421e47de6c2bc7fd0a5de84b6db4bea79c7312442244ec11506164150762943b +size 9787 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f7230cc774 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:264d9373767b6b59d0cea4bfa4d148a453058be70e7805581fe96d7448ef5232 +size 29978 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_60,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_60,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ec81aa14bc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_60,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7c46e25d0b339f237f5c5d98d850664638b2084b3fe4a75e5d7b50d3b678b8b +size 18999 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_61,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_61,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..27df947568 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_61,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:150055f181351f24e22ba29492904b2296edc7c826aaa81952216734ec4431f9 +size 18098 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_62,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_62,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0f2ea2dda3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_62,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a623b5b5bd5700f38b9eb790a37a5b029e1a74b8ae9811f9732dec808cacc770 +size 21231 From 42004f251bbcc260ee8b44aa78c4709e533460e9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Mar 2024 18:07:48 +0100 Subject: [PATCH 024/124] Room directory : more cleanup and more tests --- .../impl/src/main/res/values/localazy.xml | 4 ++ .../impl/root/RoomDirectoryStateProvider.kt | 17 +++++- .../impl/root/RoomDirectoryView.kt | 44 ++++++++------- .../impl/src/main/res/values/localazy.xml | 5 ++ .../impl/search/RoomListSearchView.kt | 11 ++-- .../src/main/res/values/localazy.xml | 53 ++++++++++++++++++- tools/localazy/config.json | 6 +++ 7 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 features/roomdirectory/impl/src/main/res/values/localazy.xml diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index cde723257f..b6bf76c440 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -29,7 +29,11 @@ "Demote" "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges." "Demote yourself?" + "%1$s (Pending)" "Edit Moderators" + "Admins" + "Moderators" + "Members" "You have unsaved changes." "Save changes?" "Add topic" diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index 3fa7877b6f..efb6624260 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -32,7 +32,22 @@ open class RoomDirectoryStateProvider : PreviewParameterProvider + + "Failed loading" + "Room directory" + diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt index 53528e0558..80657ed4fc 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchView.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.roomlist.impl.R import io.element.android.features.roomlist.impl.components.RoomSummaryRow import io.element.android.features.roomlist.impl.contentType import io.element.android.features.roomlist.impl.model.RoomListRoomSummary @@ -131,8 +132,8 @@ private fun RoomListSearchContent( val focusRequester = FocusRequester() TextField( modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), + .fillMaxWidth() + .focusRequester(focusRequester), value = filter, singleLine = true, onValueChange = { state.eventSink(RoomListSearchEvents.QueryChanged(it)) }, @@ -171,8 +172,8 @@ private fun RoomListSearchContent( ) { padding -> Column( modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) { if (state.displayRoomDirectorySearch) { RoomDirectorySearchButton( @@ -206,7 +207,7 @@ private fun RoomDirectorySearchButton( modifier: Modifier = Modifier ) { Button( - text = "Browse all rooms", + text = stringResource(id = R.string.screen_roomlist_room_directory_button_title), leadingIcon = IconSource.Vector(CompoundIcons.ListBulleted()), onClick = onClick, modifier = modifier, diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 97ddec4b3b..1bc20364ce 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -254,8 +254,6 @@ "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." - "Failed loading" - "Room directory" "Failed processing media to upload, please try again." "Could not retrieve user details" "Block" @@ -274,4 +272,55 @@ "Version: %1$s (%2$s)" "en" "en" + "Troubleshoot" + "Troubleshoot notifications" + "Run tests" + "Run tests again" + "Some tests failed. Please check the details." + "Run the tests to detect any issue in your configuration that may make notifications not behave as expected." + "Attempt to fix" + "All tests passed successfully." + "Troubleshoot notifications" + "Some tests require your attention. Please check the details." + "Check that the application can show notifications." + "Check permissions" + "Get the name of the current provider." + "No push providers selected." + "Current push provider: %1$s." + "Current push provider" + "Ensure that the application has at least one push provider." + "No push providers found." + + "Found %1$d push provider: %2$s" + "Found %1$d push providers: %2$s" + + "Detect push providers" + "Check that the application can display notification." + "The notification has not been clicked." + "Cannot display the notification." + "The notification has been clicked!" + "Display notification" + "Please click on the notification to continue the test." + "Ensure that Firebase is available." + "Firebase is not available." + "Firebase is available." + "Check Firebase" + "Ensure that Firebase token is available." + "Firebase token is not known." + "Firebase token: %1$s." + "Check Firebase token" + "Ensure that the application is receiving push." + "Error: pusher has rejected the request." + "Error: %1$s." + "Error, cannot test push." + "Error, timeout waiting for push." + "Push loop back took %1$d ms." + "Test Push loop back" + "Ensure that UnifiedPush distributors are available." + "No push distributors found." + + "%1$d distributor found: %2$s." + "%1$d distributors found: %2$s." + + "Check UnifiedPush" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index b3ad636c86..aaa1f6734b 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -197,6 +197,12 @@ "screen_app_lock_.*", "screen_signout_in_progress_dialog_content" ] + }, + { + "name" : ":features:roomdirectory:impl", + "includeRegex" : [ + "screen_room_directory_.*" + ] } ] } From 31e95b1634fc45a3169cb499b7cfa8d918e7ab33 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 Mar 2024 18:13:40 +0100 Subject: [PATCH 025/124] Fix crash observed when going back to the room list. Protect MatrixRoom access when the room is disposed. --- changelog.d/2619.bugfix | 1 + .../matrix/impl/room/RustMatrixRoom.kt | 30 +++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) create mode 100644 changelog.d/2619.bugfix diff --git a/changelog.d/2619.bugfix b/changelog.d/2619.bugfix new file mode 100644 index 0000000000..a9d7b3f497 --- /dev/null +++ b/changelog.d/2619.bugfix @@ -0,0 +1 @@ +Fix crash observed when going back to the room list. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index c0e35707a1..7ab6e3640b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -182,48 +182,40 @@ class RustMatrixRoom( } override val name: String? - get() { - return roomListItem.name() - } + get() = runCatching { roomListItem.name() }.getOrDefault(null) override val displayName: String - get() { - return innerRoom.displayName() - } + get() = runCatching { innerRoom.displayName() }.getOrDefault("") override val topic: String? - get() { - return innerRoom.topic() - } + get() = runCatching { innerRoom.topic() }.getOrDefault(null) override val avatarUrl: String? - get() { - return roomListItem.avatarUrl() ?: innerRoom.avatarUrl() - } + get() = runCatching { roomListItem.avatarUrl() ?: innerRoom.avatarUrl() }.getOrDefault(null) override val isEncrypted: Boolean get() = runCatching { innerRoom.isEncrypted() }.getOrDefault(false) override val alias: String? - get() = innerRoom.canonicalAlias() + get() = runCatching { innerRoom.canonicalAlias() }.getOrDefault(null) override val alternativeAliases: List - get() = innerRoom.alternativeAliases() + get() = runCatching { innerRoom.alternativeAliases() }.getOrDefault(emptyList()) override val isPublic: Boolean - get() = innerRoom.isPublic() + get() = runCatching { innerRoom.isPublic() }.getOrDefault(false) override val isSpace: Boolean - get() = innerRoom.isSpace() + get() = runCatching { innerRoom.isSpace() }.getOrDefault(false) override val isDirect: Boolean - get() = innerRoom.isDirect() + get() = runCatching { innerRoom.isDirect() }.getOrDefault(false) override val joinedMemberCount: Long - get() = innerRoom.joinedMembersCount().toLong() + get() = runCatching { innerRoom.joinedMembersCount().toLong() }.getOrDefault(0) override val activeMemberCount: Long - get() = innerRoom.activeMembersCount().toLong() + get() = runCatching { innerRoom.activeMembersCount().toLong() }.getOrDefault(0) override suspend fun updateMembers() { val useCache = membersStateFlow.value is MatrixRoomMembersState.Unknown From e5db80b0ebb409133ea2c952363cc344a6b9fb1f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 Mar 2024 18:12:42 +0100 Subject: [PATCH 026/124] Compact code. --- .../libraries/matrix/impl/RustMatrixClient.kt | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) 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 31e439bbec..394d56587b 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 @@ -208,16 +208,15 @@ class RustMatrixClient( } } - private val rustRoomListService: RoomListService = - RustRoomListService( + override val roomListService: RoomListService = RustRoomListService( + innerRoomListService = innerRoomListService, + sessionCoroutineScope = sessionCoroutineScope, + sessionDispatcher = sessionDispatcher, + roomListFactory = RoomListFactory( innerRoomListService = innerRoomListService, sessionCoroutineScope = sessionCoroutineScope, - sessionDispatcher = sessionDispatcher, - roomListFactory = RoomListFactory( - innerRoomListService = innerRoomListService, - sessionCoroutineScope = sessionCoroutineScope, - ), - ) + ), + ) private val eventFilters = TimelineEventTypeFilter.exclude( listOf( @@ -238,12 +237,11 @@ class RustMatrixClient( ).map(FilterTimelineEventType::State) ) - override val roomListService: RoomListService - get() = rustRoomListService - - private val rustMediaLoader = RustMediaLoader(baseCacheDirectory, dispatchers, client) - override val mediaLoader: MatrixMediaLoader - get() = rustMediaLoader + override val mediaLoader: MatrixMediaLoader = RustMediaLoader( + baseCacheDirectory = baseCacheDirectory, + dispatchers = dispatchers, + innerClient = client, + ) private val roomMembershipObserver = RoomMembershipObserver() From d94f4b0392750cda411b0bf4f0450db43fa1a600 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 28 Mar 2024 17:25:55 +0000 Subject: [PATCH 027/124] Update screenshots --- ...w_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,en].png | 3 +++ ...w_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,en].png | 3 +++ ...w_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,en].png | 3 +++ ...null_RoomDirectoryView-Night-0_2_null_2,NEXUS_5,1.0,en].png | 3 +++ ...null_RoomDirectoryView-Night-0_2_null_3,NEXUS_5,1.0,en].png | 3 +++ ...null_RoomDirectoryView-Night-0_2_null_4,NEXUS_5,1.0,en].png | 3 +++ 6 files changed, 18 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..158c2f7cf5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f7885c0ee25d12dc5bb25940b35e7153f95ffc35cbe6fac21839390d52faeac +size 32326 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c77504c186 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:776d8f8a15bac930896a8baaf46f69fddf24fdf5c51c1ab6f1bbc48ef89259d6 +size 31607 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0eb807db6d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b349aa0ce78db6396094adda401f52133c32c16e695a051ba115be0143c31de9 +size 34972 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..80d7eb5d17 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6af6d92766e7249ee95582a32ac17ddd5c3bcc29c9b78d9ba9ca6bcb27b69c8c +size 30828 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..34fa07061a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b434b666666da81963d8ac89e9669f059443cd529103735944e6a15ba5ba46b5 +size 28646 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..cbf54de729 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0190f4e7d06f5b46147531d7224e24662e0a7ef6289d516cf082d59ab63627f +size 31342 From 88249ca58816a5c044ba20b41c2c6902e0361162 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 20:42:08 +0000 Subject: [PATCH 028/124] Update dependency com.squareup.retrofit2:retrofit-bom to v2.11.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0060fad424..c2e2993ef6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -120,7 +120,7 @@ network_okhttp_logging = { module = "com.squareup.okhttp3:logging-interceptor" } network_okhttp_okhttp = { module = "com.squareup.okhttp3:okhttp" } network_okhttp = { module = "com.squareup.okhttp3:okhttp" } network_mockwebserver = { module = "com.squareup.okhttp3:mockwebserver" } -network_retrofit_bom = "com.squareup.retrofit2:retrofit-bom:2.10.0" +network_retrofit_bom = "com.squareup.retrofit2:retrofit-bom:2.11.0" network_retrofit = { module = "com.squareup.retrofit2:retrofit" } network_retrofit_converter_serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization" } From 5f8b74055d698ed86525061c56c7bacce9a741a0 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 29 Mar 2024 11:37:00 +0100 Subject: [PATCH 029/124] RoomDirectory : address pr reviews --- features/roomdirectory/impl/build.gradle.kts | 2 -- .../roomdirectory/impl/root/RoomDirectoryPresenter.kt | 4 ++-- .../element/android/libraries/featureflag/api/FeatureFlags.kt | 2 +- .../libraries/matrix/api/roomdirectory/RoomDescription.kt | 2 +- .../matrix/impl/roomdirectory/RoomDescriptionMapper.kt | 2 +- .../matrix/test/roomdirectory/RoomDescriptionFixture.kt | 2 +- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/features/roomdirectory/impl/build.gradle.kts b/features/roomdirectory/impl/build.gradle.kts index d26a75ca8c..85bb195da3 100644 --- a/features/roomdirectory/impl/build.gradle.kts +++ b/features/roomdirectory/impl/build.gradle.kts @@ -14,8 +14,6 @@ * limitations under the License. */ -// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed -@Suppress("DSL_SCOPE_VIOLATION") plugins { id("io.element.android-compose-library") alias(libs.plugins.anvil) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 95cfa8166b..5d4cef55cb 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -67,10 +67,10 @@ class RoomDirectoryPresenter @Inject constructor( } LaunchedEffect(searchQuery) { if (searchQuery == null) return@LaunchedEffect - // debounce search query - delay(300) // cancel load more right away loadingMore = false + // debounce search query + delay(300) roomDirectoryList.filter(searchQuery, 20) } LaunchedEffect(loadingMore) { diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 7140a89064..3a52e63b52 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -92,7 +92,7 @@ enum class FeatureFlags( RoomDirectorySearch( key = "feature.roomdirectorysearch", title = "Room directory search", - description = "Allow user to search for public rooms in his homeserver", + description = "Allow user to search for public rooms in their homeserver", defaultValue = true, isFinished = false, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt index dddd556bbe..78d6cb0c94 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt @@ -26,7 +26,7 @@ data class RoomDescription( val avatarUrl: String?, val joinRule: JoinRule, val isWorldReadable: Boolean, - val joinedMembers: Long + val numberOfMembers: Long ) { enum class JoinRule { PUBLIC, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt index 91b84e48c0..876a58d3a5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt @@ -35,7 +35,7 @@ class RoomDescriptionMapper { null -> RoomDescription.JoinRule.UNKNOWN }, isWorldReadable = roomDescription.isWorldReadable, - joinedMembers = roomDescription.joinedMembers.toLong(), + numberOfMembers = roomDescription.joinedMembers.toLong(), ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt index 3e53a0c2e3..6e96ca4452 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt @@ -37,5 +37,5 @@ fun aRoomDescription( avatarUrl = avatarUrl, joinRule = joinRule, isWorldReadable = isWorldReadable, - joinedMembers = joinedMembers + numberOfMembers = joinedMembers ) From d34c4605a41cad1008d8af7fa5ef4983a2f591f8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 29 Mar 2024 11:37:29 +0100 Subject: [PATCH 030/124] RoomDirectory : use same logic for name description computation --- .../impl/root/model/RoomDescription.kt | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt index 405d4241b2..be36eb5053 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt @@ -22,13 +22,29 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription as MatrixRoomDescription fun MatrixRoomDescription.toFeatureModel(): RoomDescription { + fun name(): String { + return name ?: alias ?: roomId.value + } + + fun description(): String { + val topic = topic + val alias = alias + val name = name + return when { + topic != null -> topic + name != null && alias != null -> alias + name == null && alias == null -> "" + else -> roomId.value + } + } + return RoomDescription( roomId = roomId, - name = name ?: "", - description = topic ?: alias ?: roomId.value, + name = name(), + description = description(), avatarData = AvatarData( id = roomId.value, - name = name ?: "", + name = name, url = avatarUrl, size = AvatarSize.RoomDirectoryItem, ), From 11dfb32f72f13094ebafdf76ab88d825e88281d2 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 29 Mar 2024 11:37:36 +0100 Subject: [PATCH 031/124] Room directory : fix ci --- libraries/ui-strings/src/main/res/values-be/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-cs/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-de/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-fr/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-ru/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-sk/translations.xml | 2 -- libraries/ui-strings/src/main/res/values-uk/translations.xml | 2 -- 7 files changed, 14 deletions(-) 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 f163de2540..70641271b8 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -258,8 +258,6 @@ "Не ўдалося выбраць носьбіт, паўтарыце спробу." "Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз." "Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз." - "Памылка загрузкі" - "Каталог пакояў" "Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз." "Не ўдалося атрымаць інфармацыю пра карыстальніка" "Заблакіраваць" 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 6975c64660..9dde757dc1 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -258,8 +258,6 @@ "Výběr média se nezdařil, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." - "Načítání se nezdařilo" - "Adresář místností" "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nepodařilo se načíst údaje o uživateli" "Zablokovat" 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 83b3dcac0a..8a3e035833 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -254,8 +254,6 @@ "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Das Hochladen der Medien ist fehlgeschlagen. Bitte versuche es erneut." - "Fehler beim Laden" - "Raumverzeichnis" "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Benutzerdetails konnten nicht abgerufen werden" "Blockieren" 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 219b8501c5..e3b6e3d0d9 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -254,8 +254,6 @@ "Échec de la sélection du média, veuillez réessayer." "Échec du traitement des médias à télécharger, veuillez réessayer." "Échec du téléchargement du média, veuillez réessayer." - "Échec du chargement" - "Annuaire des salons" "Échec du traitement des médias à télécharger, veuillez réessayer." "Impossible de récupérer les détails de l’utilisateur" "Bloquer" 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 01a23b4c24..7758a05eeb 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -256,8 +256,6 @@ "Не удалось выбрать носитель, попробуйте еще раз." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось загрузить медиафайлы, попробуйте еще раз." - "Сбой загрузки" - "Каталог комнат" "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось получить данные о пользователе" "Заблокировать" 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 9c1df72eaf..f49201bda1 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -257,8 +257,6 @@ "Nepodarilo sa vybrať médium, skúste to prosím znova." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa nahrať médiá, skúste to prosím znova." - "Načítanie zlyhalo" - "Adresár miestností" "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa získať údaje o používateľovi" "Zablokovať" 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 542ff641aa..a97f92401a 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -256,8 +256,6 @@ "Не вдалося вибрати медіафайл, спробуйте ще раз." "Не вдалося обробити медіафайл для завантаження, спробуйте ще раз." "Не вдалося завантажити медіафайл, спробуйте ще раз." - "Не вдалося завантажити" - "Каталог кімнат" "Не вдалося обробити медіафайл для завантаження, спробуйте ще раз." "Не вдалося отримати дані користувача" "Заблокувати" From ff9603f33871cdef025453d0b96cd3f246030d60 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 29 Mar 2024 16:17:49 +0100 Subject: [PATCH 032/124] RoomDirectory : fix maestro (search screen is closed automatically) --- .maestro/tests/roomList/searchRoomList.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.maestro/tests/roomList/searchRoomList.yaml b/.maestro/tests/roomList/searchRoomList.yaml index 8b41c4d259..5d5e01de84 100644 --- a/.maestro/tests/roomList/searchRoomList.yaml +++ b/.maestro/tests/roomList/searchRoomList.yaml @@ -7,8 +7,4 @@ appId: ${MAESTRO_APP_ID} - tapOn: ${MAESTRO_ROOM_NAME} # Back from timeline - back -- assertVisible: "MyR" -- hideKeyboard -# Back from search -- back - runFlow: ../assertions/assertHomeDisplayed.yaml From 83255831337d31555104ccf3a20b376cdf59fd09 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 29 Mar 2024 16:17:57 +0100 Subject: [PATCH 033/124] RoomDirectory : set default value of FeatureFlag to false. --- .../element/android/libraries/featureflag/api/FeatureFlags.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 3a52e63b52..5642eefbd2 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -93,7 +93,7 @@ enum class FeatureFlags( key = "feature.roomdirectorysearch", title = "Room directory search", description = "Allow user to search for public rooms in their homeserver", - defaultValue = true, + defaultValue = false, isFinished = false, ) } From 06b9e1d3c2193a9a0abc9589d063ae883f0d90bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 30 Mar 2024 02:36:14 +0000 Subject: [PATCH 034/124] Update dependency org.robolectric:robolectric to v4.12 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2e2993ef6..4eb7bb2d48 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -136,7 +136,7 @@ test_konsist = "com.lemonappdev:konsist:0.13.0" test_turbine = "app.cash.turbine:turbine:1.1.0" test_truth = "com.google.truth:truth:1.4.2" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.15" -test_robolectric = "org.robolectric:robolectric:4.11.1" +test_robolectric = "org.robolectric:robolectric:4.12" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } # Others From b13dbcc6a36ad2ffa8ed8e4cbc6a0993f8edcc18 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 31 Mar 2024 14:14:55 +0000 Subject: [PATCH 035/124] Update plugin dependencycheck to v9.1.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2e2993ef6..97fc21687e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -217,7 +217,7 @@ anvil = { id = "com.squareup.anvil", version.ref = "anvil" } detekt = "io.gitlab.arturbosch.detekt:1.23.6" ktlint = "org.jlleitschuh.gradle.ktlint:12.1.0" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" -dependencycheck = "org.owasp.dependencycheck:9.0.10" +dependencycheck = "org.owasp.dependencycheck:9.1.0" dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:1.3.3" kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } From 5809864182f648ea6d0b8cbe4fc17e248aac15c1 Mon Sep 17 00:00:00 2001 From: bmarty <3940906+bmarty@users.noreply.github.com> Date: Mon, 1 Apr 2024 00:17:18 +0000 Subject: [PATCH 036/124] Sync Strings from Localazy --- .../src/main/res/values-hu/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-hu/translations.xml | 4 +- .../src/main/res/values-be/translations.xml | 4 +- .../src/main/res/values-hu/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 8 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-hu/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 6 +- .../src/main/res/values-hu/translations.xml | 8 +- .../src/main/res/values-sv/translations.xml | 3 + .../src/main/res/values-hu/translations.xml | 4 +- .../src/main/res/values-be/translations.xml | 6 +- .../src/main/res/values-hu/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 20 +++-- .../src/main/res/values-bg/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 4 + .../src/main/res/values-de/translations.xml | 6 +- .../src/main/res/values-fr/translations.xml | 4 + .../src/main/res/values-hu/translations.xml | 4 + .../src/main/res/values-in/translations.xml | 4 + .../src/main/res/values-it/translations.xml | 3 + .../src/main/res/values-ro/translations.xml | 1 + .../src/main/res/values-ru/translations.xml | 4 + .../src/main/res/values-sk/translations.xml | 4 + .../src/main/res/values-uk/translations.xml | 11 ++- .../src/main/res/values-be/translations.xml | 5 ++ .../src/main/res/values-cs/translations.xml | 5 ++ .../src/main/res/values-de/translations.xml | 5 ++ .../src/main/res/values-fr/translations.xml | 5 ++ .../src/main/res/values-hu/translations.xml | 5 ++ .../src/main/res/values-ru/translations.xml | 5 ++ .../src/main/res/values-sk/translations.xml | 5 ++ .../src/main/res/values-uk/translations.xml | 5 ++ .../src/main/res/values-be/translations.xml | 4 +- .../src/main/res/values-hu/translations.xml | 3 +- .../src/main/res/values-in/translations.xml | 1 + .../src/main/res/values-uk/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 4 +- .../src/main/res/values-sv/translations.xml | 32 ++++++++ .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 38 ++++----- .../src/main/res/values-hu/translations.xml | 2 +- .../src/main/res/values-uk/translations.xml | 8 +- .../src/main/res/values-hu/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 20 ++--- .../src/main/res/values-hu/translations.xml | 8 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 77 +++++++++++++++--- .../src/main/res/values-bg/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 53 ++++++++++++ .../src/main/res/values-de/translations.xml | 51 ++++++++++++ .../src/main/res/values-fr/translations.xml | 51 ++++++++++++ .../src/main/res/values-hu/translations.xml | 81 +++++++++++++++---- .../src/main/res/values-in/translations.xml | 51 ++++++++++++ .../src/main/res/values-ru/translations.xml | 55 +++++++++++++ .../src/main/res/values-sk/translations.xml | 53 ++++++++++++ .../src/main/res/values-uk/translations.xml | 4 +- ...tSelected-Day-4_5_null,NEXUS_5,1.0,de].png | 4 +- ...rSelected-Day-5_6_null,NEXUS_5,1.0,de].png | 4 +- ...orEnded-Day-12_13_null,NEXUS_5,1.0,de].png | 4 +- ...entEnded-Day-9_10_null,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-4_5_null_3,NEXUS_5,1.0,de].png | 4 +- ...oryView-Day-0_1_null_0,NEXUS_5,1.0,de].png | 3 + ...oryView-Day-0_1_null_1,NEXUS_5,1.0,de].png | 3 + ...oryView-Day-0_1_null_2,NEXUS_5,1.0,de].png | 3 + ...oryView-Day-0_1_null_3,NEXUS_5,1.0,de].png | 3 + ...oryView-Day-0_1_null_4,NEXUS_5,1.0,de].png | 3 + ...ntent-Day-13_14_null_1,NEXUS_5,1.0,de].png | 3 + ...oBlurShadow-Day_0_null,NEXUS_5,1.0,de].png | 3 + screenshots/html/data.js | 13 ++- 73 files changed, 696 insertions(+), 128 deletions(-) create mode 100644 features/roomdirectory/impl/src/main/res/values-be/translations.xml create mode 100644 features/roomdirectory/impl/src/main/res/values-cs/translations.xml create mode 100644 features/roomdirectory/impl/src/main/res/values-de/translations.xml create mode 100644 features/roomdirectory/impl/src/main/res/values-fr/translations.xml create mode 100644 features/roomdirectory/impl/src/main/res/values-hu/translations.xml create mode 100644 features/roomdirectory/impl/src/main/res/values-ru/translations.xml create mode 100644 features/roomdirectory/impl/src/main/res/values-sk/translations.xml create mode 100644 features/roomdirectory/impl/src/main/res/values-uk/translations.xml create mode 100644 screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png diff --git a/features/call/src/main/res/values-hu/translations.xml b/features/call/src/main/res/values-hu/translations.xml index c85521c392..fee5a163bb 100644 --- a/features/call/src/main/res/values-hu/translations.xml +++ b/features/call/src/main/res/values-hu/translations.xml @@ -1,6 +1,6 @@ "Folyamatban lévő hívás" - "Koppints a híváshoz való visszatéréshez" + "Koppintson a híváshoz való visszatéréshez" "☎️ Hívás folyamatban" diff --git a/features/createroom/impl/src/main/res/values-be/translations.xml b/features/createroom/impl/src/main/res/values-be/translations.xml index 066f3332de..3c81815d7d 100644 --- a/features/createroom/impl/src/main/res/values-be/translations.xml +++ b/features/createroom/impl/src/main/res/values-be/translations.xml @@ -7,7 +7,7 @@ "Паведамленні ў гэтым пакоі зашыфраваны. Гэта шыфраванне нельга адключыць." "Прыватны пакой (толькі па запрашэнні)" "Паведамленні не зашыфраваны, і кожны можа іх прачытаць. Вы можаце ўключыць шыфраванне пазней." - "Адкрыты пакой (для ўсіх)" + "Публічны пакой (для ўсіх)" "Назва пакоя" "Стварыце пакой" "Тэма (неабавязкова)" diff --git a/features/leaveroom/api/src/main/res/values-be/translations.xml b/features/leaveroom/api/src/main/res/values-be/translations.xml index 79a0789738..0e80bcd433 100644 --- a/features/leaveroom/api/src/main/res/values-be/translations.xml +++ b/features/leaveroom/api/src/main/res/values-be/translations.xml @@ -3,5 +3,5 @@ "Вы ўпэўнены, што хочаце пакінуць гэту размову? Гэта размова не з\'яўляецца публічнай, і вы не зможаце далучыцца зноў без запрашэння." "Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Вы тут адзіны карыстальнік. Калі вы выйдзеце, ніхто не зможа далучыцца ў будучыні, у тым ліку і вы." "Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Гэты пакой не агульнадаступны, і вы не зможаце далучыцца да яго зноў без запрашэння." - "Вы ўпэўнены, што жадаеце пакінуць пакой?" + "Вы ўпэўнены, што хочаце пакінуць пакой?" diff --git a/features/leaveroom/api/src/main/res/values-hu/translations.xml b/features/leaveroom/api/src/main/res/values-hu/translations.xml index c75cddfd05..8b5136f495 100644 --- a/features/leaveroom/api/src/main/res/values-hu/translations.xml +++ b/features/leaveroom/api/src/main/res/values-hu/translations.xml @@ -2,6 +2,6 @@ "Biztos, hogy elhagyja ezt a beszélgetést? Ez a beszélgetés nem nyilvános, és meghívás nélkül nem fog tudni visszacsatlakozni." "Biztos, hogy elhagyja ezt a szobát? Ön az egyedüli ember itt. Ha kilép, akkor senki sem fog tudni csatlakozni a jövőben, Önt is beleértve." - "Biztos, hogy elhagyod ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fogsz tudni újra belépni." - "Biztos, hogy elhagyod a szobát?" + "Biztos, hogy elhagyja ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fog tudni újra belépni." + "Biztos, hogy elhagyja a szobát?" diff --git a/features/lockscreen/impl/src/main/res/values-be/translations.xml b/features/lockscreen/impl/src/main/res/values-be/translations.xml index 7a7d4acf7e..f786884ed0 100644 --- a/features/lockscreen/impl/src/main/res/values-be/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-be/translations.xml @@ -25,12 +25,12 @@ "Вы выходзіце з сістэмы" "У вас %1$d спроба разблакіроўкі" - "У вас %1$d спроб разблакіроўкі" + "У вас %1$d спробы разблакіроўкі" "У вас %1$d спроб разблакіроўкі" "Няправільны PIN-код. У вас застаўся %1$d шанец" - "Няправільны PIN-код. У вас застаўася %1$d шанцаў" + "Няправільны PIN-код. У вас застаўася %1$d шанца" "Няправільны PIN-код. У вас застаўася %1$d шанцаў" "Выкарыстоўваць біяметрыю" 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 86cd1e073e..d469e76630 100644 --- a/features/login/impl/src/main/res/values-hu/translations.xml +++ b/features/login/impl/src/main/res/values-hu/translations.xml @@ -8,7 +8,7 @@ "Itt lesznek a beszélgetései – ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez." "Hamarosan bejelentkezik ide: %s" "Itt lesznek a beszélgetései – ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez." - "Hamarosan létrehozol egy fiókot itt: %s" + "Hamarosan létrehoz egy fiókot itt: %s" "A Matrix.org egy nagy, ingyenes kiszolgáló a nyilvános Matrix-hálózaton, a biztonságos, decentralizált kommunikáció érdekében, amelyet a Matrix.org Alapítvány üzemeltet." "Egyéb" "Másik fiókszolgáltató, például a saját privát kiszolgáló vagy egy munkahelyi fiók használata." diff --git a/features/logout/impl/src/main/res/values-be/translations.xml b/features/logout/impl/src/main/res/values-be/translations.xml index 1398ae20d7..62425e93f6 100644 --- a/features/logout/impl/src/main/res/values-be/translations.xml +++ b/features/logout/impl/src/main/res/values-be/translations.xml @@ -1,6 +1,6 @@ - "Вы ўпэўнены, што жадаеце выйсці?" + "Вы ўпэўнены, што хочаце выйсці?" "Выйсці" "Выйсці" "Выхад…" 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 c91bb96d04..ee875ab37a 100644 --- a/features/messages/impl/src/main/res/values-be/translations.xml +++ b/features/messages/impl/src/main/res/values-be/translations.xml @@ -5,7 +5,7 @@ "Ежа & Напоі" "Жывёлы & Прырода" "Аб\'екты" - "Усмешкі & Людзі" + "Усмешкі & Удзельнікі" "Падарожжы & Месцы" "Сімвалы" "Заблакіраваць карыстальніка" @@ -16,13 +16,13 @@ "Зрабіць фота" "Запісаць відэа" "Далучэнне" - "Фота & Відэа Бібліятэка" + "Бібліятэка фота & відэа" "Месцазнаходжанне" "Апытанне" "Фармаціраванне тэксту" "Гісторыя паведамленняў зараз недаступна." "Гісторыя паведамленняў у гэтым пакоі недаступная. Праверце гэтую прыладу, каб убачыць гісторыю паведамленняў." - "Вы жадаеце запрасіць іх назад?" + "Вы хочаце запрасіць іх назад?" "Вы адзін у гэтым чаце" "Апавясціць увесь пакой" "Усе" @@ -39,7 +39,7 @@ "Новы" "%1$d змена ў пакоі" - "%1$d змен у пакоі" + "%1$d змены ў пакоі" "%1$d змен у пакоі" diff --git a/features/poll/impl/src/main/res/values-be/translations.xml b/features/poll/impl/src/main/res/values-be/translations.xml index 3749e1ec4c..4526df8572 100644 --- a/features/poll/impl/src/main/res/values-be/translations.xml +++ b/features/poll/impl/src/main/res/values-be/translations.xml @@ -8,7 +8,7 @@ "Пытанне або тэма" "Пра што апытанне?" "Стварэнне апытання" - "Вы ўпэўнены, што жадаеце выдаліць гэтае апытанне?" + "Вы ўпэўнены, што хочаце выдаліць гэтае апытанне?" "Выдаліць апытанне" "Рэдагаваць апытанне" "Немагчыма знайсці бягучыя апытанні." diff --git a/features/poll/impl/src/main/res/values-hu/translations.xml b/features/poll/impl/src/main/res/values-hu/translations.xml index 74b83c8942..310c1a77db 100644 --- a/features/poll/impl/src/main/res/values-hu/translations.xml +++ b/features/poll/impl/src/main/res/values-hu/translations.xml @@ -4,7 +4,7 @@ "Eredmények megjelenítése csak a szavazás befejezése után" "Szavazatok elrejtése" "%1$d. lehetőség" - "A módosítások nem lettek mentve. Biztos, hogy vissza akar lépni?" + "A módosítások nem lettek mentve. Biztos, hogy visszalép?" "Kérdés vagy téma" "Miről szól ez a szavazás?" "Szavazás létrehozása" diff --git a/features/preferences/impl/src/main/res/values-be/translations.xml b/features/preferences/impl/src/main/res/values-be/translations.xml index ce931f9c5f..8f20b79a09 100644 --- a/features/preferences/impl/src/main/res/values-be/translations.xml +++ b/features/preferences/impl/src/main/res/values-be/translations.xml @@ -1,6 +1,6 @@ - "Рэжым распрацоўніка" + "Рэжым распрацоўшчыка" "Падайце распрацоўнікам доступ да функцый і функцыянальным магчымасцям." "Базавы URL сервера званкоў Element" "Задайце свой сервер Element Call." @@ -10,7 +10,7 @@ "Калі выключыць, вашы пасведчанні аб прачытанні нікому не будуць адпраўляцца. Вы па-ранейшаму будзеце атрымліваць пасведчанні аб прачытанні ад іншых карыстальнікаў." "Падзяліцеся прысутнасцю" "Калі гэта выключана, вы не зможаце адпраўляць або атрымліваць апавяшчэнні аб прачытанні або апавяшчэнні аб наборы тэксту" - "Уключыце опцыю для прагляду крыніцы паведамлення на часовай шкале." + "Уключыце опцыю для прагляду паведамленняў у хроніцы." "У вас няма заблакіраваных карыстальнікаў" "Разблакіраваць" "Вы зноў зможаце ўбачыць усе паведамленні." @@ -44,7 +44,7 @@ "Усе" "Згадванні" "Апавясціць мяне" - "Апавясціць мяне ў @room" + "Апавясціць пра @room" "Каб атрымліваць апавяшчэнні, змяніце свой %1$s." "налады сістэмы" "Сістэмныя апавяшчэнні выключаны" diff --git a/features/preferences/impl/src/main/res/values-hu/translations.xml b/features/preferences/impl/src/main/res/values-hu/translations.xml index f57308e21d..739722f07b 100644 --- a/features/preferences/impl/src/main/res/values-hu/translations.xml +++ b/features/preferences/impl/src/main/res/values-hu/translations.xml @@ -1,16 +1,16 @@ "Fejlesztői mód" - "Engedélyezd, hogy elérd a fejlesztőknek szánt funkciókat." + "Engedélyezze, hogy elérje a fejlesztőknek szánt funkciókat." "Egyéni Element Call alapwebcím" "Egyéni alapwebcím beállítása az Element Callhoz." - "Érvénytelen webcím, győződj meg arról, hogy szerepel-e benne a protokoll (http/https), és hogy helyes-e a cím." - "A formázott szöveges szerkesztő letiltása, hogy kézzel írhass Markdownt." + "Érvénytelen webcím, győződjön meg arról, hogy szerepel-e benne a protokoll (http/https), és hogy helyes-e a cím." + "A formázott szöveges szerkesztő letiltása, hogy kézzel írhasson Markdownt." "Olvasási visszaigazolások" "Ha ki van kapcsolva, az olvasási visszaigazolások nem lesznek elküldve senkinek. A többi felhasználó olvasási visszaigazolását továbbra is meg fogja kapni." "Jelenlét megosztása" "Ha ki van kapcsolva, nem tud olvasási visszaigazolást vagy írási értesítést küldeni és fogadni" - "Engedélyezd a beállítást az üzenet forrásának megjelenítéséhez az idővonalon." + "Engedélyezze a beállítást az üzenet forrásának megjelenítéséhez az idővonalon." "Nincsenek letiltott felhasználók" "Letiltás feloldása" "Újra láthatja az összes üzenetét." diff --git a/features/preferences/impl/src/main/res/values-sv/translations.xml b/features/preferences/impl/src/main/res/values-sv/translations.xml index 7bcf1c81f0..aa4eea5a2e 100644 --- a/features/preferences/impl/src/main/res/values-sv/translations.xml +++ b/features/preferences/impl/src/main/res/values-sv/translations.xml @@ -2,6 +2,9 @@ "Utvecklarläge" "Aktivera för att ha tillgång till funktionalitet för utvecklare." + "Anpassad bas-URL för Element Call" + "Ange en anpassad bas-URL för Element Call." + "Ogiltig URL, se till att du inkluderar protokollet (http/https) och rätt adress." "Inaktivera rik-text-redigeraren för att skriva Markdown manuellt." "Avblockera" "Du kommer att kunna se alla meddelanden från dem igen." diff --git a/features/rageshake/api/src/main/res/values-hu/translations.xml b/features/rageshake/api/src/main/res/values-hu/translations.xml index 5b228df7a0..e2fc862b9d 100644 --- a/features/rageshake/api/src/main/res/values-hu/translations.xml +++ b/features/rageshake/api/src/main/res/values-hu/translations.xml @@ -1,7 +1,7 @@ - "Az %1$s összeomlott a legutóbbi használata óta. Megosztod velünk az összeomlás-jelentést?" - "Úgy tűnik, mintha dühösen ráznád a telefont. Megnyitod a hibajelentési képernyőt?" + "Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?" + "Úgy tűnik, mintha dühösen rázná a telefont. Megnyitja a hibajelentési képernyőt?" "Ideges rázás" "Észlelési küszöb" diff --git a/features/rageshake/impl/src/main/res/values-be/translations.xml b/features/rageshake/impl/src/main/res/values-be/translations.xml index 277ba258be..8bf98b9c85 100644 --- a/features/rageshake/impl/src/main/res/values-be/translations.xml +++ b/features/rageshake/impl/src/main/res/values-be/translations.xml @@ -8,10 +8,10 @@ "Апішыце праблему…" "Калі магчыма, калі ласка, напішыце апісанне на англійскай мове." "Апісанне занадта кароткае. Дайце больш падрабязную інфармацыю аб тым, што адбылося. Дзякуй!" - "Адправіць часопісы збояў" - "Дазволіць часопісы" + "Адправіць журналы збояў" + "Дазволіць журналы" "Адправіць здымак экрана" "Каб пераканацца, што ўсё працуе правільна, у паведамленне будуць уключаны часопісы. Каб адправіць паведамленне без часопісаў, адключыце гэтую наладу." "Пры апошнім выкарыстанні %1$s адбыўся збой. Жадаеце падзяліцца справаздачай аб збоі?" - "Прагляд часопісаў" + "Прагляд журналаў" diff --git a/features/rageshake/impl/src/main/res/values-hu/translations.xml b/features/rageshake/impl/src/main/res/values-hu/translations.xml index 616fd1d6cc..1ac8c4c285 100644 --- a/features/rageshake/impl/src/main/res/values-hu/translations.xml +++ b/features/rageshake/impl/src/main/res/values-hu/translations.xml @@ -12,6 +12,6 @@ "Naplók engedélyezése" "Képernyőkép küldése" "A naplók szerepelni fognak az üzenetben, hogy megbizonyosodhassunk arról, hogy minden megfelelően működik-e. Ha naplók nélkül szeretné elküldeni az üzenetet, akkor kapcsolja ki ezt a beállítást." - "Az %1$s összeomlott a legutóbbi használata óta. Megosztod velünk az összeomlás-jelentést?" + "Az %1$s összeomlott a legutóbbi használata óta. Megosztja velünk az összeomlás-jelentést?" "Naplók megtekintése" diff --git a/features/roomdetails/impl/src/main/res/values-be/translations.xml b/features/roomdetails/impl/src/main/res/values-be/translations.xml index 9419d246cc..68ef4d8903 100644 --- a/features/roomdetails/impl/src/main/res/values-be/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-be/translations.xml @@ -29,7 +29,11 @@ "Паніжэнне ўзроўню" "Вы не зможаце адмяніць гэтае змяненне, бо паніжаеце сябе. Калі вы апошні адміністратар у пакоі, вярнуць права будзе немагчыма." "Панізіць сябе?" + "%1$s (У чаканні)" "Рэдагаваць мадэратараў" + "Адміністратары" + "Мадэратары" + "Удзельнікі" "У вас ёсць незахаваныя змены." "Захаваць змены?" "Дадаць тэму" @@ -46,8 +50,8 @@ "Запрасіць карыстальникаў" "Пакінуць размову" "Пакінуць пакой" - "Карыстальніцкі" - "Па змаўчанні" + "Уласныя" + "Стандартныя" "Апавяшчэнні" "Ролі і дазволы" "Назва пакоя" @@ -62,7 +66,7 @@ "Блакіроўка %1$s" "%1$d карыстальнік" - "%1$d карыстальнікаў" + "%1$d карыстальніка" "%1$d карыстальнікаў" "Выдаліць і заблакіраваць удзельніка" @@ -83,14 +87,14 @@ "Удзельнікі пакоя" "Разблакіроўка %1$s" "Дазволіць карыстальніцкую наладу" - "Калі гэта ўключыць, ваша налада па змаўчанні будзе адменена" + "Калі гэта ўключыць, ваша налада прадвызначана будзе адменена" "Апавяшчаць мяне ў гэтым чаце для" - "Вы можаце змяніць яго ў сваім %1$s." - "глабальныя налады" - "Налада па змаўчанні" + "Вы можаце змяніць гэта ў %1$s." + "асноўных наладах" + "Стандартная налада" "Выдаліць карыстальніцкую наладу" "Падчас загрузкі налад апавяшчэнняў адбылася памылка." - "Не атрымалася аднавіць рэжым па змаўчанні, паспрабуйце яшчэ раз." + "Не атрымалася аднавіць прадвызначаны рэжым, паспрабуйце яшчэ раз." "Не ўдалося наладзіць рэжым, паспрабуйце яшчэ раз." "Ваш хатні сервер не падтрымлівае гэту опцыю ў зашыфраваных пакоях, вы не атрымаеце апавяшчэнне ў гэтым пакоі." "Усе паведамленні" diff --git a/features/roomdetails/impl/src/main/res/values-bg/translations.xml b/features/roomdetails/impl/src/main/res/values-bg/translations.xml index 80223c8cfa..5cc105f657 100644 --- a/features/roomdetails/impl/src/main/res/values-bg/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-bg/translations.xml @@ -5,6 +5,7 @@ "Отблокиране" "Отблокиране на потребителя" "Анкети" + "Членове" "Добавяне на тема" "Вече е член" "Вече е бил поканен" diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index a45c7e25a1..93e7fcf389 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -29,7 +29,11 @@ "Degradovat" "Tuto změnu nebudete moci vrátit zpět, protože sami degradujete, pokud jste posledním privilegovaným uživatelem v místnosti, nebude možné znovu získat oprávnění." "Degradovat se?" + "%1$s (čekající)" "Upravit moderátory" + "Správci" + "Moderátoři" + "Členové" "Máte neuložené změny." "Uložit změny?" "Přidat téma" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index fe16998a72..980f149bad 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -29,7 +29,11 @@ "Zurückstufen" "Du stufst dich selbst herab. Diese Änderung kann nicht rückgängig gemacht werden. Wenn du der letzte Benutzer mit dieser Rolle bist, ist es nicht möglich, diese Rolle wiederzuerlangen." "Möchtest Du Dich selbst herabstufen?" + "%1$s (Ausstehend)" "Moderatoren bearbeiten" + "Administratoren" + "Moderatoren" + "Mitglieder" "Du hast nicht gespeicherte Änderungen." "Änderungen speichern?" "Thema hinzufügen" @@ -76,7 +80,7 @@ "Gesperrt" "Mitglieder" "Ausstehend" - "%1$s wird entfernt" + "%1$s wird entfernt." "Administrator" "Moderator" "Raummitglieder" diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml index c88514f68b..8479044057 100644 --- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml @@ -29,7 +29,11 @@ "Rétrograder" "Vous ne pourrez pas annuler ce changement car vous vous rétrogradez, si vous êtes le dernier utilisateur privilégié du salon il sera impossible de retrouver les privilèges." "Vous rétrograder ?" + "%1$s (En attente)" "Modifier les modérateurs" + "Administrateurs" + "Modérateurs" + "Membres" "Vous avez des modifications non-enregistrées." "Enregistrer les modifications?" "Ajouter un sujet" 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 3640f1e853..66c05c38a7 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -29,7 +29,11 @@ "Lefokozás" "Ezt a változtatást nem fogja tudni visszavonni, mivel lefokozza magát, ha Ön az utolsó jogosultságokkal rendelkező felhasználó a szobában, akkor lehetetlen lesz visszaszerezni a jogosultságokat." "Lefokozza magát?" + "%1$s (függőben)" "Moderátorok szerkesztése" + "Rendszergazdák" + "Moderátorok" + "Tagok" "Mentetlen módosításai vannak." "Menti a változtatásokat?" "Téma hozzáadása" 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 ba3e609d44..ddfd4859ea 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -29,7 +29,11 @@ "Turunkan" "Anda tidak akan dapat mengurungkan perubahan ini karena Anda sedang menurunkan Anda sendiri, jika Anda merupakan pengguna dengan hak khusus dalam ruangan maka tidak akan memungkinkan untuk mendapatkan hak tersebut lagi." "Turunkan Anda sendiri?" + "%1$s (Tertunda)" "Sunting Moderator" + "Admin" + "Moderator" + "Anggota" "Anda memiliki perubahan yang belum disimpan." "Simpan perubahan?" "Tambahkan topik" diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml index 02e7f03cba..77b46cdeda 100644 --- a/features/roomdetails/impl/src/main/res/values-it/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -30,6 +30,9 @@ "Non potrai annullare questa modifica perché ti stai declassando, se sei l\'ultimo utente privilegiato nella stanza, sarà impossibile riottenere i privilegi." "Declassare te stesso?" "Modifica moderatori" + "Amministratori" + "Moderatori" + "Membri" "Hai delle modifiche non salvate." "Salvare le modifiche?" "Aggiungi argomento" diff --git a/features/roomdetails/impl/src/main/res/values-ro/translations.xml b/features/roomdetails/impl/src/main/res/values-ro/translations.xml index 787622103b..0f4c19b4d8 100644 --- a/features/roomdetails/impl/src/main/res/values-ro/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ro/translations.xml @@ -10,6 +10,7 @@ "Serverul dumneavoastră nu acceptă această opțiune în camerele criptate, este posibil să nu primiți notificări în unele camere." "Sondaje" "Toți" + "Membri" "Adăugare subiect" "Deja membru" "Deja invitat" 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 401b4a7c0c..200b2ef3c7 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -29,7 +29,11 @@ "Понизить уровень" "Вы не сможете отменить это изменение, так как понижаете себя статус. Если вы являетесь последним привилегированным пользователем в комнате, восстановить привилегии будет невозможно." "Понизить свой уровень?" + "%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 78d4d842d9..c85b04017c 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -29,7 +29,11 @@ "Znížiť" "Túto zmenu nebudete môcť vrátiť späť, pretože znižujete svoju úroveň. Ak ste posledným privilegovaným používateľom v miestnosti, nebude možné získať znova oprávnenia." "Znížiť svoju úroveň?" + "%1$s (Čaká sa)" "Upraviť moderátorov" + "Správcovia" + "Moderátori" + "Členovia" "Máte neuložené zmeny." "Uložiť zmeny?" "Pridať tému" diff --git a/features/roomdetails/impl/src/main/res/values-uk/translations.xml b/features/roomdetails/impl/src/main/res/values-uk/translations.xml index b42e1a5e63..415334df5a 100644 --- a/features/roomdetails/impl/src/main/res/values-uk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-uk/translations.xml @@ -11,18 +11,18 @@ "Опитування" "Тільки для адміністраторів" "Заблоковувати людей" - "Видалити повідомлення" + "Вилучати повідомлення" "Усі" - "Запросити людей" + "Запрошувати людей" "Модерація учасників" "Повідомлення та зміст" "Адміністратори та модератори" - "Видалити людей" + "Вилучати людей" "Змінити аватар кімнати" "Деталі кімнати" "Змінити назву кімнати" "Змінити тему кімнати" - "Надіслати повідомлення" + "Надсилати повідомлення" "Керувати адмінами" "Ви не зможете скасувати цю дію. Ви просуваєте користувача, щоб він мав такий же рівень прав, як і ви." "Додати адміністратора?" @@ -30,6 +30,9 @@ "Ви не зможете скасувати цю зміну, оскільки ви знижуєте себе, якщо ви останній привілейований користувач у кімнаті, відновити привілеї буде неможливо." "Понизити себе?" "Керувати модераторами" + "Адміністратори" + "Модератори" + "Учасники" "У вас є не збережені зміни." "Зберегти зміни?" "Додати тему" diff --git a/features/roomdirectory/impl/src/main/res/values-be/translations.xml b/features/roomdirectory/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..16b52ab04d --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,5 @@ + + + "Памылка загрузкі" + "Каталог пакояў" + diff --git a/features/roomdirectory/impl/src/main/res/values-cs/translations.xml b/features/roomdirectory/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..49ae689e43 --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,5 @@ + + + "Načítání se nezdařilo" + "Adresář místností" + diff --git a/features/roomdirectory/impl/src/main/res/values-de/translations.xml b/features/roomdirectory/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..8c60d845b2 --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,5 @@ + + + "Fehler beim Laden" + "Raumverzeichnis" + diff --git a/features/roomdirectory/impl/src/main/res/values-fr/translations.xml b/features/roomdirectory/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..25c8064237 --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,5 @@ + + + "Échec du chargement" + "Annuaire des salons" + diff --git a/features/roomdirectory/impl/src/main/res/values-hu/translations.xml b/features/roomdirectory/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..fcaa3eb1bf --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,5 @@ + + + "Sikertelen betöltés" + "Szobakatalógus" + diff --git a/features/roomdirectory/impl/src/main/res/values-ru/translations.xml b/features/roomdirectory/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..92e16fadaa --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,5 @@ + + + "Сбой загрузки" + "Каталог комнат" + diff --git a/features/roomdirectory/impl/src/main/res/values-sk/translations.xml b/features/roomdirectory/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..9d4e0bfd61 --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,5 @@ + + + "Načítanie zlyhalo" + "Adresár miestností" + diff --git a/features/roomdirectory/impl/src/main/res/values-uk/translations.xml b/features/roomdirectory/impl/src/main/res/values-uk/translations.xml new file mode 100644 index 0000000000..4201f040e9 --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-uk/translations.xml @@ -0,0 +1,5 @@ + + + "Не вдалося завантажити" + "Каталог кімнат" + diff --git a/features/roomlist/impl/src/main/res/values-be/translations.xml b/features/roomlist/impl/src/main/res/values-be/translations.xml index e229e68f26..39cbd31bc6 100644 --- a/features/roomlist/impl/src/main/res/values-be/translations.xml +++ b/features/roomlist/impl/src/main/res/values-be/translations.xml @@ -1,7 +1,7 @@ "Ваша рэзервовая копія чата зараз не сінхранізавана. Вам трэба пацвердзіць ключ аднаўлення, каб захаваць доступ да рэзервовай копіі чата." - "Пацвердзіце ключ аднаўлення" + "Увядзіце ключ аднаўлення" "Гэта аднаразовы працэс, дзякуем за чаканне." "Налада ўліковага запісу." "Стварыце новую размову або пакой" @@ -14,7 +14,7 @@ "Нізкі прыярытэт" "Вы можаце прыбраць фільтры, каб убачыць іншыя вашыя чаты." "У вас няма чатаў для гэтай катэгорыі" - "Людзі" + "Удзельнікі" "У вас пакуль няма асабістых паведамленняў" "Пакоі" "Вас пакуль няма ў ніводным пакоі" diff --git a/features/roomlist/impl/src/main/res/values-hu/translations.xml b/features/roomlist/impl/src/main/res/values-hu/translations.xml index 16ba862467..559a481246 100644 --- a/features/roomlist/impl/src/main/res/values-hu/translations.xml +++ b/features/roomlist/impl/src/main/res/values-hu/translations.xml @@ -1,6 +1,6 @@ - "A csevegés biztonsági mentése nincs szinkronban. Meg kell erősítened a helyreállítási kulcsát, hogy továbbra is hozzáférj a csevegés biztonsági mentéséhez." + "A csevegés biztonsági mentése nincs szinkronban. Meg kell erősítenie a helyreállítási kulcsát, hogy továbbra is hozzáférjen a csevegés biztonsági mentéséhez." "Helyreállítási kulcs megerősítése" "Ez egy egyszeri folyamat, köszönjük a türelmét." "A fiók beállítása." @@ -24,6 +24,7 @@ Nincs olvasatlan üzenete!" "Összes csevegés" "Megjelölés olvasottként" "Megjelölés olvasatlanként" + "Összes szoba böngészése" "Ú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/roomlist/impl/src/main/res/values-in/translations.xml b/features/roomlist/impl/src/main/res/values-in/translations.xml index e84fd81fa7..74c90c1319 100644 --- a/features/roomlist/impl/src/main/res/values-in/translations.xml +++ b/features/roomlist/impl/src/main/res/values-in/translations.xml @@ -24,6 +24,7 @@ Anda tidak memiliki pesan yang belum dibaca!" "Semua Obrolan" "Tandai sebagai dibaca" "Tandai sebagai belum dibaca" + "Cari semua ruangan" "Sepertinya Anda menggunakan perangkat baru. Verifikasi dengan perangkat lain untuk mengakses pesan terenkripsi Anda selanjutnya." "Verifikasi bahwa ini Anda" diff --git a/features/roomlist/impl/src/main/res/values-uk/translations.xml b/features/roomlist/impl/src/main/res/values-uk/translations.xml index 3aaf84f9a5..d7ebacd5f7 100644 --- a/features/roomlist/impl/src/main/res/values-uk/translations.xml +++ b/features/roomlist/impl/src/main/res/values-uk/translations.xml @@ -15,7 +15,7 @@ "Ви можете зняти фільтри, щоб побачити інші ваші чати" "Ви не маєте чатів для цієї категорії" "Люди" - "У вас ще немає жодного особистого чату" + "Ви ще не маєте жодного особистого чату" "Кімнати" "Ви ще не учасник жодної кімнати" "Непрочитані" diff --git a/features/securebackup/impl/src/main/res/values-be/translations.xml b/features/securebackup/impl/src/main/res/values-be/translations.xml index d16399978d..38215dfd20 100644 --- a/features/securebackup/impl/src/main/res/values-be/translations.xml +++ b/features/securebackup/impl/src/main/res/values-be/translations.xml @@ -5,13 +5,13 @@ "Рэзервовае капіраванне гарантуе, што вы не страціце сваю гісторыю паведамленняў. %1$s." "Рэзервовая копія" "Змяніць ключ аднаўлення" - "Пацвердзіце ключ аднаўлення" + "Увядзіце ключ аднаўлення" "Ваша рэзервовая копія чата зараз не сінхранізавана." "Наладзьце аднаўленне" "Атрымайце доступ да зашыфраваных паведамленняў, калі вы страціце ўсе свае прылады або выйдзеце з сістэмы %1$s усюды." "Адключыць" "Вы страціце зашыфраваныя паведамленні, калі выйдзеце з усіх прылад." - "Вы ўпэўнены, што жадаеце адключыць рэзервовае капіраванне?" + "Вы ўпэўнены, што хочаце адключыць рэзервовае капіраванне?" "Адключэнне рэзервовага капіравання прывядзе да выдалення бягучай рэзервовай копіі ключа шыфравання і адключэння іншых функцый бяспекі. У гэтым выпадку вы:" "Няма зашыфраванай гісторыі паведамленняў на новых прыладах" "Калі вы выходзіце з сістэмы, то губляеце доступ да зашыфраваных паведамленняў %1$s усюды" diff --git a/features/securebackup/impl/src/main/res/values-sv/translations.xml b/features/securebackup/impl/src/main/res/values-sv/translations.xml index db8306b302..99c9c97b8a 100644 --- a/features/securebackup/impl/src/main/res/values-sv/translations.xml +++ b/features/securebackup/impl/src/main/res/values-sv/translations.xml @@ -4,5 +4,37 @@ "Slå på säkerhetskopiering" "Säkerhetskopior ser till att du inte blir av med din meddelandehistorik. %1$s." "Säkerhetskopia" + "Byt återställningsnyckel" + "Ange återställningsnyckel" + "Din chattsäkerhetskopia är för närvarande osynkroniserad." "Ställ in återställning" + "Få tillgång till dina krypterade meddelanden om du tappar bort alla dina enheter eller blir utloggad ur %1$s överallt." + "Stäng av" + "Du kommer att förlora dina krypterade meddelanden om du loggas ut från alla enheter." + "Är du säker på att du vill stänga av säkerhetskopiering?" + "Om du stänger av säkerhetskopiering tas din nuvarande säkerhetskopiering av krypteringsnycklar bort och andra säkerhetsfunktioner stängs av. I det här fallet kommer du att:" + "Inte ha krypterad meddelandehistorik på nya enheter" + "Förlora åtkomsten till dina krypterade meddelanden om du loggas ut ur %1$s överallt" + "Är du säker på att du vill stänga av säkerhetskopiering?" + "Få en ny återställningsnyckel om du har tappat bort din befintliga. När du har bytt din återställningsnyckel fungerar din gamla inte längre." + "Generera en ny återställningsnyckel" + "Se till att du kan lagra din återställningsnyckel någonstans säkert" + "Återställningsnyckel ändrad" + "Byt återställningsnyckel?" + "Ange din återställningsnyckel för att bekräfta åtkomst till din chattsäkerhetskopia." + "Ange koden på 48 tecken." + "Ange …" + "Återställningsnyckel bekräftad" + "Ange din återställningsnyckel" + "Spara återställningsnyckeln" + "Skriv ner din återställningsnyckel någonstans säkert eller spara den i en lösenordshanterare." + "Tryck för att kopiera återställningsnyckeln" + "Spara din återställningsnyckel" + "Du kommer inte att kunna komma åt din nya återställningsnyckel efter det här steget." + "Har du sparat din återställningsnyckel?" + "Din chattsäkerhetskopia skyddas av en återställningsnyckel. Om du behöver en ny återställningsnyckel efter installationen kan du återskapa genom att välja ”Byt återställningsnyckel”." + "Generera din återställningsnyckel" + "Se till att du kan lagra din återställningsnyckel någonstans säkert" + "Konfiguration av återställning lyckades" + "Ställ in återställning" diff --git a/features/signedout/impl/src/main/res/values-be/translations.xml b/features/signedout/impl/src/main/res/values-be/translations.xml index 42cb5e41ff..d95aa0df81 100644 --- a/features/signedout/impl/src/main/res/values-be/translations.xml +++ b/features/signedout/impl/src/main/res/values-be/translations.xml @@ -1,6 +1,6 @@ - "Вы змянілі свой пароль на іншым сеансе" + "Вы змянілі свой пароль у іншым сеансе" "Вы выдалілі сеанс з іншага сеансу" "Адміністратар вашага сервера ануляваў ваш доступ" "Магчыма, вы выйшлі з сістэмы па адной з прычын, пералічаных ніжэй. Калі ласка, увайдзіце яшчэ раз, каб працягнуць выкарыстанне %s." diff --git a/libraries/eventformatter/impl/src/main/res/values-be/translations.xml b/libraries/eventformatter/impl/src/main/res/values-be/translations.xml index f7f1a652ba..00e6e1665e 100644 --- a/libraries/eventformatter/impl/src/main/res/values-be/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-be/translations.xml @@ -1,32 +1,32 @@ "(аватар таксама быў зменены)" - "%1$s змяніў аватар" + "%1$s змяніў(-ла) аватар" "Вы змянілі свой аватар" - "%1$s быў паніжаны да ўдзельніка" - "%1$s быў паніжаны да мадэратара" - "%1$s змяніў сваё адлюстраванае імя з %2$s на %3$s" + "%1$s быў паніжаны(-на) да ўдзельніка" + "%1$s быў паніжаны(-на) да мадэратара" + "%1$s змяніў(-ла) сваё адлюстраванае імя з %2$s на %3$s" "Вы змянілі сваё адлюстраванае імя з %1$s на %2$s" - "%1$s выдаліў сваё адлюстраванае імя (яно было %2$s)" + "%1$s выдаліў(-ла) сваё адлюстраванае імя (яно было %2$s)" "Вы выдалілі сваё адлюстраванае імя (яно было %1$s)" "%1$s усталявалі сваё адлюстраванае імя на %2$s" "Вы ўстанавілі адлюстраванае імя на %1$s" - "%1$s быў павышаны да адміністратара" - "%1$s быў павышаны да мадэратара" - "%1$s змяніў аватар пакоя" + "%1$s быў(-ла) павышаны(-на) да адміністратара" + "%1$s быў(-ла) павышаны(-на) да мадэратара" + "%1$s змяніў(-ла) аватар пакоя" "Вы змянілі аватар пакоя" "%1$s выдаліў(-ла) аватар пакоя" "Вы выдалілі аватар пакоя" - "%1$s заблакіраваў %2$s" + "%1$s заблакіраваў(-ла) %2$s" "Вы заблакіравалі %1$s" - "%1$s стварыў пакой" + "%1$s стварыў(-ла) пакой" "Вы стварылі пакой" - "%1$s запрасіў %2$s" + "%1$s запрасіў(-ла) %2$s" "%1$s прыняў(-ла) запрашэнне" "Вы прынялі запрашэнне" "Вы запрасілі %1$s" - "%1$s запрасіў вас" - "%1$s далучыўся да пакоя" + "%1$s запрасіў(-ла) вас" + "%1$s далучыўся(-лась) да пакоя" "Вы далучыліся да пакоя" "%1$s прасіў(-ла) далучыцца" "%1$s дазволіў(-ла) %2$s далучыцца" @@ -37,17 +37,17 @@ "%1$s адхіліў(-ла) ваш запыт на далучэнне" "%1$s больш не зацікаўлены(-на) у далучэнні" "Вы адмянілі запыт на далучэнне" - "%1$s выйшаў з пакоя" + "%1$s выйшаў(-ла) з пакоя" "Вы выйшлі з пакоя" - "%1$s змяніў назву пакоя на: %2$s" + "%1$s змяніў(-ла) назву пакоя на: %2$s" "Вы змянілі назву пакоя на: %1$s" "%1$s выдаліў(-ла) назву пакоя" "Вы выдалілі назву пакоя" - "%1$s не зрабіў ніякіх змен" + "%1$s не зрабіў(-ла) ніякіх змен" "Вы не зрабілі ніякіх змен" - "%1$s адхіліў запрашэнне" + "%1$s адхіліў(-ла) запрашэнне" "Вы адхілілі запрашэнне" - "%1$s выдаліў %2$s" + "%1$s выдаліў(-ла) %2$s" "Вы выдалілі %1$s" "%1$s адправіў(-ла) запрашэнне %2$s далучыцца да пакоя" "Вы адправілі запрашэнне %1$s далучыцца да пакоя" @@ -57,7 +57,7 @@ "Вы змянілі тэму на: %1$s" "%1$s выдаліў(-ла) тэму пакоя" "Вы выдалілі тэму пакоя" - "%1$s разблакіраваў %2$s" + "%1$s разблакіраваў(-ла) %2$s" "Вы разблакіравалі %1$s" "%1$s унеслі невядомую змену ў сяброўства" diff --git a/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml b/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml index 5aded12d62..5bcb239126 100644 --- a/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-hu/translations.xml @@ -16,7 +16,7 @@ "%1$s megváltoztatta a szoba profilképét" "Megváltoztatta a szoba profilképét" "%1$s eltávolította a szoba profilképét" - "Eltávolítottad a szoba profilképét" + "Eltávolította a szoba profilképét" "%1$s kitiltotta: %2$s" "Kitiltotta: %1$s" "%1$s létrehozta a szobát" diff --git a/libraries/eventformatter/impl/src/main/res/values-uk/translations.xml b/libraries/eventformatter/impl/src/main/res/values-uk/translations.xml index fab8a16e94..eccaa950eb 100644 --- a/libraries/eventformatter/impl/src/main/res/values-uk/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-uk/translations.xml @@ -3,16 +3,16 @@ "(аватар теж було змінено)" "%1$s змінив (-ла) свій аватар" "Ви змінили свій аватар" - "%1$s був понижений до члена" - "%1$s був понижений до модератора" + "%1$s понижено до учасника" + "%1$s понижено до модератора" "%1$s змінив (-ла) своє імʼя з %2$s на %3$s" "Ви змінили своє ім\'я з %1$s на %2$s" "%1$s видалив (-ла) своє ім\'я (було %2$s)" "Ви видалили своє ім\'я (було%1$s)" "%1$s змінив (-ла) своє ім\'я на %2$s" "Ви змінили своє імʼя на %1$s" - "%1$s був підвищений до адміністратора" - "%1$s був підвищений до модератора" + "%1$s підвищено до адміністратора" + "%1$s підвищено до модератора" "%1$s змінив (-ла) аватар кімнати" "Ви змінили аватар кімнати" "%1$s видалив (-ла) аватар кімнати" diff --git a/libraries/permissions/api/src/main/res/values-hu/translations.xml b/libraries/permissions/api/src/main/res/values-hu/translations.xml index aea9a1f772..de3573fce2 100644 --- a/libraries/permissions/api/src/main/res/values-hu/translations.xml +++ b/libraries/permissions/api/src/main/res/values-hu/translations.xml @@ -1,7 +1,7 @@ "Hogy az alkalmazás használhassa a kamerát, adja meg az engedélyt a rendszerbeállításokban." - "Add meg az engedélyt a rendszerbeállításokban." + "Adja meg az engedélyt a rendszerbeállításokban." "Hogy az alkalmazás használhassa a mikrofont, adja meg az engedélyt a rendszerbeállításokban." "Hogy az alkalmazás megjeleníthesse az értesítéseket, adja meg az engedélyt a rendszerbeállításokban." diff --git a/libraries/push/impl/src/main/res/values-be/translations.xml b/libraries/push/impl/src/main/res/values-be/translations.xml index 4c7ce25f88..f947eb3a00 100644 --- a/libraries/push/impl/src/main/res/values-be/translations.xml +++ b/libraries/push/impl/src/main/res/values-be/translations.xml @@ -6,12 +6,12 @@ "Ціхія апавяшчэнні" "%1$s: %2$d паведамленне" - "%1$s: %2$d паведамленняў" + "%1$s: %2$d паведамлення" "%1$s: %2$d паведамленняў" "%d апавяшчэнне" - "%d апавяшчэнняў" + "%d апавяшчэння" "%d апавяшчэнняў" "Апавяшчэнне" @@ -20,28 +20,28 @@ "Адхіліць" "%d запрашэнне" - "%d запрашэнняў" + "%d запрашэння" "%d запрашэнняў" - "Запрасіў вас у чат" - "Згадаў вас: %1$s" + "Запрасіў(-ла) вас у чат" + "Згадаў(-ла) вас: %1$s" "Новыя паведамленні" "%d новае паведамленне" - "%d новых паведамленняў" + "%d новых паведамлення" "%d новых паведамленняў" - "Адрэагаваў на %1$s" + "Адрэагаваў(-ла) на %1$s" "Пазначыць як прачытанае" "Хуткі адказ" - "Запрасіў вас далучыцца да пакоя" + "Запрасіў(-ла) вас далучыцца да пакоя" "Я" "Вы праглядаеце апавяшчэнне! Націсніце мяне!" "%1$s: %2$s" "%1$s: %2$s %3$s" "%d непрачытанае апавяшчэнне" - "%d непрачытаных апавяшчэнняў" + "%d непрачытаных апавяшчэння" "%d непрачытаных апавяшчэнняў" "%1$s і %2$s" @@ -49,7 +49,7 @@ "%1$s у %2$s і %3$s" "%d пакой" - "%d пакояў" + "%d пакоя" "%d пакояў" "Выберыце спосаб атрымання апавяшчэнняў" 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 eac6103de7..de719fc0c6 100644 --- a/libraries/push/impl/src/main/res/values-hu/translations.xml +++ b/libraries/push/impl/src/main/res/values-hu/translations.xml @@ -13,7 +13,7 @@ "%d értesítés" "Értesítés" - "** Nem sikerült elküldeni – kérlek nyisd meg a szobát" + "** Nem sikerült elküldeni – nyissa meg a szobát" "Csatlakozás" "Elutasítás" @@ -30,9 +30,9 @@ "Ezzel reagált: %1$s" "Megjelölés olvasottként" "Gyors válasz" - "Meghívott, hogy csatlakozz a szobához" + "Meghívta, hogy csatlakozzon a szobához" "Én" - "Az értesítést nézed! Kattints ide!" + "Az értesítést nézi! Kattintson ide!" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -46,7 +46,7 @@ "%d szoba" "%d szoba" - "Válaszd ki az értesítések fogadásának módját" + "Válassza ki az értesítések fogadási módját" "Háttérszinkronizálás" "Google szolgáltatások" "A Google Play szolgáltatások nem találhatók. Előfordulhat, hogy az értesítések nem működnek megfelelően." diff --git a/libraries/textcomposer/impl/src/main/res/values-be/translations.xml b/libraries/textcomposer/impl/src/main/res/values-be/translations.xml index 5da9e78476..7f5693b0a4 100644 --- a/libraries/textcomposer/impl/src/main/res/values-be/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-be/translations.xml @@ -5,7 +5,7 @@ "Закрыць параметры фарматавання" "Пераключыць блок кода" "Паведамленне…" - "Стварыце спасылку" + "Стварыць спасылку" "Рэдагаваць спасылку" "Ужыць тоўсты шрыфт" "Ужыць курсіўны фармат" 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 70641271b8..223ee9740a 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -34,13 +34,13 @@ "Запісаць галасавое паведамленне." "Спыніць запіс" "Прыняць" - "Дадаць на часовую шкалу" + "Дадаць у хроніку" "Назад" "Скасаваць" "Выбраць фота" "Ачысціць" "Закрыць" - "Поўная праверка" + "Праверка завершана" "Пацвердзіць" "Працягнуць" "Капіраваць" @@ -90,7 +90,7 @@ "Паскардзіцца на змест" "Скінуць" "Паўтарыць" - "Паўтарыце расшыфроўку" + "Паўтарыць расшыфроўку" "Захаваць" "Пошук" "Адправіць" @@ -108,7 +108,7 @@ "Зрабіць фота" "Дакраніцеся, каб убачыць параметры" "Паўтарыць спробу" - "Паказаць крыніцу" + "Прагляд зыходнага кода" "Так" "Аб праграме" "Палітыка дапушчальнага выкарыстання" @@ -119,7 +119,7 @@ "Заблакіраваныя карыстальнікі" "Бурбалкі" "Ідзе выклік (не падтрымліваецца)" - "Рэзервовае капіраванне чата" + "Рэзервовае капіраванне чатаў" "Аўтарскае права" "Стварэнне пакоя…" "Выйшаў з пакоя" @@ -150,9 +150,9 @@ "Спасылка скапіравана ў буфер абмену" "Загрузка…" - "%1$d карыстальнік" - "%1$d карыстальнікаў" - "%1$d карыстальнікаў" + "%1$d удзельнік" + "%1$d удзельніка" + "%1$d удзельнікаў" "Паведамленне" "Дзеянні з паведамленням" @@ -164,7 +164,7 @@ "Па-за сеткай" "або" "Пароль" - "Людзі" + "Удзельнікі" "Пастаянная спасылка" "Дазвол" "Вы ўпэўнены, што хочаце скончыць гэтае апытанне?" @@ -173,7 +173,7 @@ "Вынікі будуць паказаны пасля завяршэння апытання" "%d голас" - "%d галасоў" + "%d галасы" "%d галасоў" "Палітыка прыватнасці" @@ -248,13 +248,13 @@ "🔐️ Далучайцеся да мяне %1$s" "Гэй, пагавары са мной у %1$s: %2$s" "%1$s Android" - "Rageshake паведаміць пра памылку" + "Паведаміць аб памылцы з дапамогай Rageshake" "Пацвердзіце гэтую прыладу, каб наладзіць бяспечны абмен паведамленнямі." "Пацвердзіце, што гэта вы" "Цяпер вы можаце бяспечна чытаць і адпраўляць паведамленні, і ўсе, з кім вы маеце зносіны ў чаце, таксама могуць давяраць гэтай прыладзе." "Прылада праверана" "Выкарыстоўвайце іншую прыладу" - "Чакаем іншую прыладу…" + "Чаканне на іншай прыладзе…" "Не ўдалося выбраць носьбіт, паўтарыце спробу." "Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз." "Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз." @@ -275,4 +275,57 @@ "Месцазнаходжанне" "Версія: %1$s (%2$s)" "be" + "Выпраўленне непаладак" + "Выпраўленне непаладак з апавяшчэннямі" + "Запусціць тэсты" + "Запусціце тэсты яшчэ раз" + "Некаторыя тэсты не ўдаліся. Калі ласка, праглядзіце дэталі." + "Запусціце тэсты, каб выявіць праблемы ў вашай канфігурацыі, з-за якіх апавяшчэння могуць паводзіць сябе не так, як чакалася." + "Спроба выпраўлення" + "Усе тэсты паспяхова пройдзены." + "Выпраўленне непаладак з апавяшчэннямі" + "Некаторыя тэсты патрабуюць вашай увагі. Калі ласка, праглядзіце дэталі." + "Пераканайцеся, што праграма можа паказваць апавяшчэнні." + "Праверце дазволы" + "Атрымаць назву бягучага пастаўшчыка." + "Пастаўшчыкі push-апавяшчэнняў не выбраны." + "Бягучы пастаўшчык push-апавяшчэнняў: %1$s." + "Бягучы пастаўшчык push-апавяшчэнняў" + "Пераканайцеся, што ў праграме ёсць хаця б адзін пастаўшчык push-апавяшчэнняў." + "Пастаўшчыкі push-апавяшчэнняў не знойдзены." + + "Знайшлі %1$d пастаўшчыка push-апавяшчэнняў: %2$s" + "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" + "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" + + "Выяўленне пастаўшчыкоў push-паслуг" + "Праверце, ці можа праграма паказваць апавяшчэнні." + "Апавяшчэнне не было націснута." + "Немагчыма паказаць апавяшчэнне." + "Апавяшчэнне было націснута!" + "Паказаць апавяшчэнне" + "Націсніце на апавяшчэнне, каб працягнуць тэст." + "Пераканайцеся, што Firebase даступны." + "Firebase недаступны." + "Firebase даступны." + "Праверыць Firebase" + "Пераканайцеся, што маркер Firebase даступны." + "Маркер Firebase невядомы." + "Маркер Firebase: %1$s." + "Праверце маркер Firebase" + "Пераканайцеся, што праграма атрымлівае push-апавяшчэнні." + "Памылка: pusher адхіліў запыт." + "Памылка: %1$s." + "Памылка, немагчыма праверыць push-апавяшчэнне." + "Памылка, тайм-аўт у чаканні push-апавяшчэння." + "Зварот цыклу назад заняў %1$d мс." + "Тэст Націсніце кнопку вярнуцца" + "Пераканайцеся, што размеркавальнікі UnifiedPush даступныя." + "Размеркавальнікі не знойдзены." + + "%1$d знойдзены размеркавальнік: %2$s." + "%1$d знойдзена размеркавальніка: %2$s." + "%1$d знойдзена размеркавальнікаў: %2$s." + + "Праверыць UnifiedPush" 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 48c602706b..0e6a31b71d 100644 --- a/libraries/ui-strings/src/main/res/values-bg/translations.xml +++ b/libraries/ui-strings/src/main/res/values-bg/translations.xml @@ -211,4 +211,5 @@ "Местоположение" "Версия: %1$s (%2$s)" "bg" + "Грешка: %1$s" 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 9dde757dc1..04968d8548 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -275,4 +275,57 @@ "Poloha" "Verze: %1$s (%2$s)" "en" + "Odstraňování problémů" + "Odstraňování problémů s upozorněními" + "Spustit testy" + "Spustit testy znovu" + "Některé testy selhaly. Zkontrolujte prosím podrobnosti." + "Spusťte testy, abyste zjistili jakýkoli problém ve vaší konfiguraci, který může způsobit, že se oznámení nebudou chovat podle očekávání." + "Pokus o opravu" + "Všechny testy proběhly úspěšně." + "Odstraňování problémů s upozorněními" + "Některé testy vyžadují vaši pozornost. Zkontrolujte prosím podrobnosti." + "Ujistěte se, že aplikace může zobrazovat oznámení." + "Kontrola oprávnění" + "Získat název aktuálního poskytovatele." + "Nebyli vybráni žádní push poskytovatelé." + "Aktuální push poskytovatel: %1$s." + "Aktuální push poskytovatel" + "Ujistěte se, že aplikace má alespoň jednoho push poskytovatele." + "Nebyli nalezeni žádní push poskytovatelé." + + "Nalezen %1$d push poskytovatel: %2$s" + "Nalezeni %1$d push poskytovatelé: %2$s" + "Nalezeno %1$d push poskytovatelů: %2$s" + + "Zjistit push poskytovatele" + "Zkontrolujte, zda aplikace může zobrazit oznámení." + "Na oznámení nebylo kliknuto." + "Oznámení nelze zobrazit." + "Na oznámení bylo kliknuto!" + "Zobrazit oznámení" + "Kliknutím na oznámení pokračujte v testu." + "Ujistěte se, že je k dispozici Firebase." + "Firebase není k dispozici." + "Firebase je k dispozici." + "Zkontrolovat Firebase" + "Ujistěte se, že je k dispozici Firebase token." + "Firebase token není znám." + "Firebase token: %1$s." + "Zkontrolovat Firebase token" + "Ujistěte se, že aplikace přijímá push." + "Chyba: pusher odmítl požadavek." + "Chyba: %1$s." + "Chyba, nelze otestovat push." + "Chyba, časový limit čekání na push." + "Push zpětná smyčka trvala %1$d ms." + "Otestovat push zpětnou smyčku" + "Ujistěte se, že jsou k dispozici distributoři UnifiedPush." + "Nebyli nalezeni žádní push distributoři." + + "Nalezen %1$d distributor: %2$s." + "Nalezeni %1$d distributoři: %2$s." + "Nalezeno %1$d distributorů: %2$s." + + "Zkontrolovat UnifiedPush" 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 8a3e035833..19408138b7 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -271,4 +271,55 @@ "Standort" "Version: %1$s (%2$s)" "en" + "Fehlerbehebung" + "Fehlerbehebung für Benachrichtigungen" + "Tests durchführen" + "Tests erneut durchführen" + "Einige Tests sind fehlgeschlagen. Bitte überprüfe die Details." + "Führe die Tests durch, um Probleme zu erkennen, die dazu führen können, dass sich die Benachrichtigungen nicht wie erwartet verhalten." + "Versuche das Problem zu beheben" + "Alle Tests wurden erfolgreich bestanden." + "Fehlerbehebung für Benachrichtigungen" + "Einige Tests erfordern deine Aufmerksamkeit. Bitte überprüfe die Details." + "Stelle sicher, dass die Anwendung Benachrichtigungen anzeigen kann." + "Berechtigungen überprüfen" + "Ermittele den Namen des aktuellen Anbieters." + "Kein Push-Anbieter ausgewählt." + "Aktueller Push-Anbieter:%1$s." + "Aktueller Push-Anbieter" + "Stelle sicher, dass die Anwendung mindestens einen Push-Anbieter hat." + "Keine Push-Anbieter gefunden." + + "%1$d Push-Anbieter gefunden: %2$s" + "%1$d Push-Anbieter gefunden: %2$s" + + "Push-Anbieter erkennen" + "Prüfe, ob die Anwendung Benachrichtigungen anzeigen kann." + "Die Benachrichtigung wurde nicht angeklickt." + "Die Benachrichtigung kann nicht angezeigt werden." + "Die Benachrichtigung wurde angeklickt!" + "Benachrichtigung anzeigen" + "Bitte klicke auf die Benachrichtigung, um den Test fortzusetzen." + "Stelle sicher, dass Firebase verfügbar ist." + "Firebase ist nicht verfügbar." + "Firebase ist verfügbar." + "Überprüfe Firebase" + "Stelle sicher, dass der Firebase Token verfügbar ist." + "Firebase Token ist nicht bekannt." + "Firebase Token: %1$s." + "Prüfe Firebase Token" + "Stelle sicher, dass die Anwendung Push-Nachrichten empfängt." + "Fehler: Der Pusher hat die Anfrage abgelehnt." + "Fehler:%1$s." + "Fehler: Push kann nicht getestet werden." + "Fehler: Timeout beim Warten auf Push." + "Push-Loop-Back Dauer: %1$d ms." + "Teste Push-Loop-Back" + "Stelle sicher, dass UnifiedPush-Verteiler verfügbar sind." + "Keine Push-Verteiler gefunden." + + "%1$d Verteiler gefunden: %2$s." + "%1$d Verteiler gefunden: %2$s." + + "UnifiedPush prüfen" 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 e3b6e3d0d9..9cd0d2715d 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -271,4 +271,55 @@ "Position" "Version : %1$s ( %2$s )" "Ang." + "Dépannage" + "Résoudre les problèmes liés aux notifications" + "Exécuter les tests" + "Relancer les tests" + "Certains tests ont échoué. Veuillez vérifier les détails." + "Exécuter les tests pour détecter tout problème dans votre configuration susceptible de provoquer un dysfonctionnement des notifications." + "Tenter de corriger" + "Tous les tests ont réussi." + "Dépanner les notifications" + "Certains tests nécessitent votre attention. Veuillez vérifier les détails." + "Vérifie que l’application peut afficher des notifications." + "Vérifier les autorisations" + "Obtenir le nom du fournisseur de Push actuel." + "Aucun fournisseur de Push n’est sélectionné." + "Fournisseur de Push actuel : %1$s." + "Fournisseur de Push actuel" + "Vérifier que l’application possède au moins un fournisseur de Push." + "Aucun fournisseur de Push n’a été trouvé." + + "%1$d fournisseur de Push détecté : %2$s" + "%1$d fournisseurs de Push détectés : %2$s" + + "Détecter les fournisseurs de Push" + "Vérifier que l’application peut afficher des notifications." + "Vous n’avez pas cliqué sur la notification." + "Impossible d’afficher la notification." + "Vous avez cliqué sur la notification!" + "Affichage des notifications" + "Veuillez cliquer sur la notification pour continuer le test." + "Vérification que Firebase est disponible." + "Firebase n’est pas disponible." + "Firebase est disponible." + "Vérification de Firebase" + "Vérifier que le jeton Firebase est disponible." + "Le jeton Firebase n’est pas connu." + "Jeton Firebase :%1$s." + "Vérifier le jeton Firebase" + "Vérifier que l’application reçoit les Push." + "Erreur : le Pusher a rejeté la demande." + "Erreur :%1$s." + "Erreur, impossible de tester les Push." + "Erreur, le délai d’attente du Push est dépassé." + "La demande d’envoi de Push et sa réception ont pris %1$d ms." + "Tester la réception des Push" + "Vérifier qu’au moins un distributeur UnifiedPush est disponible." + "Aucun distributeur UnifiedPush n’a été trouvé." + + "%1$d distributeur détecté :%2$s." + "%1$d distributeurs détectés :%2$s." + + "Vérifier UnifiedPush" 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 0d7886a21b..9c343ec200 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -55,8 +55,8 @@ "Szavazás szerkesztése" "Engedélyezés" "Szavazás lezárása" - "Add meg a PIN-kódot" - "Elfelejtetted a jelszavadat?" + "Adja meg a PIN-kódot" + "Elfelejtette a jelszót?" "Tovább" "Visszalépés" "Meghívás" @@ -95,17 +95,17 @@ "Üzenet küldése" "Megosztás" "Hivatkozás megosztása" - "Jelentkezz be újra" + "Jelentkezzen be újra" "Kijelentkezés" "Kijelentkezés mindenképp" "Kihagyás" "Indítás" "Csevegés indítása" "Ellenőrzés elindítása" - "Koppints a térkép betöltéséhez" + "Koppintson a térkép betöltéséhez" "Fénykép készítése" - "Koppints a beállításokért" - "Próbáld újra" + "Koppintson a beállításokért" + "Próbálja újra" "Forrás megtekintése" "Igen" "Névjegy" @@ -120,7 +120,7 @@ "Csevegés biztonsági mentése" "Szerzői jogok" "Szoba létrehozása…" - "Elhagytad a szobát" + "Elhagyta a szobát" "Sötét" "Visszafejtési hiba" "Fejlesztői beállítások" @@ -129,7 +129,7 @@ "Szerkesztés" "* %1$s %2$s" "Titkosítás engedélyezve" - "Add meg a PIN-kódodat" + "Adja meg a PIN-kódját" "Hiba" "Mindenki" "Sikertelen" @@ -184,7 +184,7 @@ "Formázott szöveges szerkesztő" "Szoba" "Szoba neve" - "például a projekted neve" + "például a projektje neve" "Mentett módosítások" "Mentés" "Képernyőzár" @@ -232,23 +232,25 @@ "A módosítások nem lettek mentve. Biztos, hogy visszalép?" "Menti a módosításokat?" "Nem sikerült létrehozni az állandó hivatkozást" - "A(z) %1$s nem tudta betölteni a térképet. Próbáld meg újra később." + "Az %1$s nem tudta betölteni a térképet. Próbálja meg újra később." "Nem sikerült betölteni az üzeneteket" - "A(z) %1$s nem tudta elérni a tartózkodási helyét. Próbáld meg újra később." + "Az %1$s nem tudta elérni a tartózkodási helyét. Próbálja újra később." "Nem sikerült feltölteni a hangüzenetét." - "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyedhez. Ezt a beállításokban engedélyezheted." + "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Ezt a beállításokban engedélyezheti." "Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Engedélyezze alább az elérését." - "Az %1$snek nincs engedélye, hogy hozzáférjen a mikrofonhoz. Engedélyezd, hogy tudjon hangüzenetet felvenni." + "Az %1$snek nincs engedélye, hogy hozzáférjen a mikrofonhoz. Engedélyezze, hogy tudjon hangüzenetet felvenni." "Néhány üzenet nem került elküldésre" "Elnézést, hiba történt" "🔐️ Csatlakozz hozzám itt: %1$s" - "Beszélgessünk a(z) %1$s: %2$s -n" + "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" "Az eszköz rázása a hibajelentéshez" "A biztonságos üzenetkezelés beállításához ellenőrizze ezt az eszközt." "Erősítse meg, hogy Ön az" "Mostantól biztonságosan olvashat vagy küldhet üzeneteket, és bármelyik csevegőpartnere megbízhat ebben az eszközben." "Eszköz ellenőrizve" + "Másik eszköz használata" + "Várakozás a másik eszközre…" "Nem sikerült kiválasztani a médiát, próbálja újra." "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." "Nem sikerült a média feltöltése, próbálja újra." @@ -269,4 +271,55 @@ "Hely" "Verzió: %1$s (%2$s)" "hu" + "Hibaelhárítás" + "Értesítések hibaelhárítása" + "Tesztek futtatása" + "Tesztek újbóli futtatása" + "Egyes tesztek sikertelenek voltak. Ellenőrizze a részleteket." + "A tesztek futtatása, hogy észlelje a konfigurációban felmerülő olyan problémákat, amelyek miatt az értesítések nem az elvárt módon viselkednek." + "Kísérlet a javításra" + "Minden teszt sikeresen lezajlott." + "Értesítések hibaelhárítása" + "Egyes tesztek a figyelmét igénylik. Ellenőrizze a részleteket." + "Ellenőrizze, hogy az alkalmazás képes-e értesítéseket megjeleníteni." + "Engedélyek ellenőrzése" + "A jelenlegi szolgáltató nevének lekérdezése." + "Nincs kiválasztva leküldéses értesítési szolgáltató." + "Jelenlegi leküldéses értesítési szolgáltató: %1$s." + "Jelenlegi leküldéses értesítési szolgáltató" + "Győződjön meg arról, hogy az alkalmazás legalább egy leküldéses értesítési szolgáltatóval rendelkezik." + "Nem található leküldéses értesítési szolgáltató." + + "%1$d leküldéses szolgáltató találva: %2$s" + "%1$d leküldéses szolgáltató találva: %2$s" + + "Leküldéses értesítési szolgáltatók észlelése" + "Ellenőrizze, hogy az alkalmazás képes-e megjeleníteni az értesítést." + "Az értesítésre nem kattintottak rá." + "Az értesítés nem jeleníthető meg." + "Az értesítésre rákattintottak!" + "Értesítés megjelenítése" + "A teszt folytatásához kattintson az értesítésre." + "Győződjön meg arról, hogy a Firebase elérhető-e." + "A Firebase nem érhető el." + "A Firebase elérhető." + "Ellenőrizze a Firebase-t" + "Győződjön meg arról, hogy a Firebase-token elérhető." + "A Firebase-token nem ismert." + "Firebase-token: %1$s." + "Ellenőrizze a Firebase-tokent" + "Győződjön meg arról, hogy az alkalmazás megkapja-e a leküldéses értesítést." + "Hiba: a leküldő elutasította a kérést." + "Hiba: %1$s." + "Hiba, nem lehet tesztelni a leküldéses értesítést." + "Hiba, időtúllépés a leküldéses értesítésre való várakozás során." + "A leküldéses értesítés folyamata %1$d ezredmásodpercig tartott." + "Tesztelje a leküldéses értesítés folyamatát" + "Győződjön meg arról, hogy a UnifiedPush forgalmazói elérhetők." + "Nem található forgalmazó a leküldéses értesítésekhez." + + "%1$d forgalmazó található: %2$s." + "%1$d forgalmazó található: %2$s." + + "Ellenőrizze a UnifiedPush szolgáltatást" 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 5540e7b084..a5a98b5d35 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -245,6 +245,8 @@ "Konfirmasi bahwa ini Anda" "Sekarang Anda dapat membaca atau mengirim pesan dengan aman, dan siapa pun yang mengobrol dengan Anda juga dapat mempercayai perangkat ini." "Perangkat terverifikasi" + "Gunakan perangkat lain" + "Menunggu di perangkat lain…" "Gagal memilih media, silakan coba lagi." "Gagal memproses media untuk diunggah, silakan coba lagi." "Gagal mengunggah media, silakan coba lagi." @@ -265,4 +267,53 @@ "Lokasi" "Versi: %1$s (%2$s)" "id" + "Pemecahan masalah" + "Pecahkan masalah notifikasi" + "Jalankan tes" + "Jalankan tes lagi" + "Beberapa tes gagal. Silakan periksa detailnya." + "Jalankan pengujian untuk mendeteksi masalah apa pun dalam konfigurasi Anda yang mungkin membuat notifikasi tidak berperilaku seperti yang diharapkan." + "Mencoba untuk memperbaiki" + "Semua tes berhasil dilalui." + "Pecahkan masalah notifikasi" + "Beberapa tes membutuhkan perhatian Anda. Silakan periksa detailnya." + "Pastikan aplikasi dapat menampilkan notifikasi." + "Periksa izin" + "Dapatkan nama penyedia saat ini." + "Tidak ada penyedia notifikasi dorongan yang dipilih." + "Penyedia notifikasi dorongan saat ini: %1$s." + "Penyedia notifikasi dorongan saat ini" + "Pastikan aplikasi memiliki setidaknya satu penyedia notifikasi dorongan." + "Tidak ada penyedia notifikasi dorongan yang ditemukan." + + "Ditemukan %1$d penyedia notifikasi dorongan: %2$s" + + "Deteksi penyedia notifikasi dorongan" + "Periksa apakah aplikasi dapat menampilkan notifikasi." + "Notifikasi belum diklik." + "Tidak dapat menampilkan notifikasi." + "Notifikasi telah diklik!" + "Tampilan notifikasi" + "Silakan klik pada notifikasi untuk melanjutkan tes." + "Pastikan bahwa Firebase tersedia." + "Firebase tidak tersedia." + "Firebase tersedia." + "Periksa Firebase" + "Pastikan token Firebase tersedia." + "Token Firebase tidak diketahui." + "Token Firebase: %1$s." + "Periksa token Firebase" + "Pastikan aplikasi menerima notifikasi dorongan." + "Kesalahan: pendorong telah menolak permintaan." + "Kesalahan: %1$s." + "Terjadi kesalahan, tidak dapat menguji notifikasi dorongan." + "Terjadi kesalahan, melebihi batas waktu menunggu notifikasi dorongan." + "Ulangan notifikasi dorongan membutuhkan %1$d ms." + "Uji ulangan notifikasi dorongan lagi" + "Pastikan distributor UnifiedPush tersedia." + "Tidak ada distributor notifikasi dorongan yang ditemukan." + + "%1$d distributor ditemukan: %2$s." + + "Periksa UnifiedPush" 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 7758a05eeb..ca4731f34f 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -253,6 +253,8 @@ "Подтвердите, что это вы" "Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству." "Устройство проверено" + "Используйте другое устройство" + "Ожидание на другом устройстве…" "Не удалось выбрать носитель, попробуйте еще раз." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось загрузить медиафайлы, попробуйте еще раз." @@ -273,4 +275,57 @@ "Местоположение" "Версия: %1$s (%2$s)" "ru" + "Устранение неполадок" + "Уведомления об устранении неполадок" + "Выполнение тестов" + "Повторное выполнение тестов" + "Некоторые тесты провалились. Пожалуйста, проверьте детали." + "Выполните тесты, чтобы обнаружить любую проблему в конфигурации, из-за которой уведомления могут работать не так, как ожидалось." + "Попытка исправить" + "Все тесты прошли успешно." + "Уведомления об устранении неполадок" + "Некоторые тесты требуют вашего внимания. Пожалуйста, проверьте детали." + "Убедитесь, что приложение может показывать уведомления." + "Проверка разрешений" + "Получение имени текущего поставщика." + "Поставщики push-уведомлений не выбраны." + "Текущий поставщик push-уведомлений: %1$s." + "Текущий поставщик push-уведомлений" + "Убедитесь, что у приложения есть хотя бы один поставщик push-сообщений." + "Поставщики push-уведомлений не найдены." + + "Найден %1$d push-провайдер: %2$s" + "Найдено %1$d push-провайдеров: %2$s" + "Найдено %1$d push-провайдеров: %2$s" + + "Обнаружение поставщиков push-уведомлений" + "Убедитесь, что приложение может отображать уведомление." + "Уведомление не было нажато." + "Невозможно отобразить уведомление." + "Уведомление было нажато!" + "Отобразить уведомление" + "Нажмите на уведомление, чтобы продолжить тест." + "Убедитесь, что Firebase доступен." + "Firebase недоступен." + "Firebase доступен." + "Проверить Firebase" + "Убедитесь, что токен Firebase доступен." + "Токен Firebase неизвестен." + "Токен Firebase: %1$s." + "Проверить токен Firebase" + "Убедитесь, что приложение получает push-сообщение." + "Ошибка: pusher отклонил запрос." + "Ошибка: %1$s." + "Ошибка, невозможно протестировать отправку." + "Ошибка, тайм-аут ожидания push-уведомления." + "Обратная отправка push-уведомления, заняла %1$d мс." + "Тест обратной отправки push-уведомления" + "Убедитесь, что дистрибьюторы UnifiedPush доступны." + "Поставщиков push-уведомлений не найдено." + + "%1$d провайдер найден: %2$s." + "%1$d провайдеров найдено: %2$s." + "%1$d провайдеров найдено: %2$s." + + "Проверка UnifiedPush" 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 f49201bda1..61e7b50cc0 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -274,4 +274,57 @@ "Poloha" "Verzia: %1$s (%2$s)" "sk" + "Riešenie problémov" + "Oznámenia riešení problémov" + "Spustiť testy" + "Spustiť testy znova" + "Niektoré testy zlyhali. Skontrolujte prosím podrobnosti." + "Spustite testy, aby ste zistili akýkoľvek problém vo vašej konfigurácii, ktorý môže spôsobiť, že sa upozornenia nebudú správať podľa očakávania." + "Pokus o opravu" + "Všetky testy prebehli úspešne." + "Oznámenia riešení problémov" + "Niektoré testy si vyžadujú vašu pozornosť. Prosím skontrolujte podrobnosti." + "Uistite sa, že aplikácia dokáže zobrazovať upozornenia." + "Skontrolovať povolenia" + "Získaťe názov aktuálneho poskytovateľa." + "Nie sú vybraní žiadni poskytovatelia push." + "Aktuálny poskytovateľ push: %1$s." + "Aktuálny poskytovateľ push" + "Uistite sa, že aplikácia má aspoň jedného poskytovateľa push." + "Nenašli sa žiadni poskytovatelia push." + + "Nájdený %1$d poskytovateľ služby push: %2$s" + "Nájdení %1$d poskytovatelia služby push: %2$s" + "Nájdených %1$d poskytovateľov služby push: %2$s" + + "Zistiť poskytovateľov push" + "Skontrolujte, či aplikácia dokáže zobraziť upozornenie." + "Na oznámenie nebolo kliknuté." + "Nie je možné zobraziť upozornenie." + "Na oznámenie bolo kliknuté!" + "Zobraziť upozornenie" + "Kliknite na upozornenie a pokračujte v teste." + "Uistite sa, že Firebase je k dispozícii." + "Firebase nie je k dispozícii." + "Firebase je k dispozícii." + "Skontrolovať Firebase" + "Uistite sa, že je k dispozícii token Firebase." + "Token Firebase nie je známy." + "Token Firebase: %1$s." + "Skontrolovať token Firebase" + "Uistite sa, že aplikácia prijíma push oznámenia." + "Chyba: pusher odmietol požiadavku." + "Chyba: %1$s." + "Chyba, nie je možné testovať push." + "Chyba, časový limit na push vypršal." + "Push loop back trvalo %1$d ms." + "Testovať Push loop back" + "Uistite sa, že sú dostupní distribútori UnifiedPush." + "Nenašli sa žiadni distribútori push." + + "%1$d nájdený distribútor: %2$s." + "%1$d nájdení distribútori: %2$s." + "%1$d nájdených distribútorov: %2$s." + + "Skontrolovať UnifiedPush" 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 a97f92401a..6dafa61fc9 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -51,7 +51,7 @@ "Відхилити" "Видалити опитування" "Вимкнути" - "Скасувати" + "Відкинути" "Готово" "Редагувати" "Редагувати опитування" @@ -60,7 +60,7 @@ "Введіть PIN-код" "Забули пароль?" "Переслати" - "Повернутися назад" + "Повернутися" "Запросити" "Запросити людей" "Запросити людей до %1$s" diff --git a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,de].png index a120bae4ac..f97428198e 100644 --- a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerNotSelected_null_PollAnswerEndedWinnerNotSelected-Day-4_5_null,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee2f07fb883d4fe98e4eb9b31b8f744bfb8ac7e4475e3e899417ff8921282218 -size 23326 +oid sha256:5a1ee722b6d18ad044447dbf05ff4b45c27c1c13696a4bf2d40bd61216e5f09e +size 23606 diff --git a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,de].png index e60fbfd469..1b3a9012f0 100644 --- a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollAnswerEndedWinnerSelected_null_PollAnswerEndedWinnerSelected-Day-5_6_null,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebafcfc81cdef83c70ac4f19bbcfe2a98c7aed9e9756ca6543cd352868a8cce2 -size 23335 +oid sha256:2b9c46336519829da81df6b3e1e31edc1fd8e5bda5d5c610f252525605f6826b +size 23611 diff --git a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,de].png index d7c69a27b8..73dd0d0276 100644 --- a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentCreatorEnded_null_PollContentCreatorEnded-Day-12_13_null,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c145620dcad2c1c006447617b6dd255c8b480d43923b678074299a3774db3588 -size 50269 +oid sha256:b55eb3b44d7f133c381d98a1c29dfaae5913523e8d0edb618ffa429d52b656fd +size 50717 diff --git a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,de].png index d7c69a27b8..73dd0d0276 100644 --- a/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.poll.api.pollcontent_PollContentEnded_null_PollContentEnded-Day-9_10_null,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c145620dcad2c1c006447617b6dd255c8b480d43923b678074299a3774db3588 -size 50269 +oid sha256:b55eb3b44d7f133c381d98a1c29dfaae5913523e8d0edb618ffa429d52b656fd +size 50717 diff --git a/screenshots/de/ui_T_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-4_5_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-4_5_null_3,NEXUS_5,1.0,de].png index 6fbf0729df..2d34e5cf5b 100644 --- a/screenshots/de/ui_T_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-4_5_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomdetails.impl.members.moderation_RoomMembersModerationView_null_RoomMembersModerationView-Day-4_5_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:871bbfeac8a04eb9ca756f660c3fa4cc235fd293ce10be4e0168cb0ab3b271e8 -size 10931 +oid sha256:574b5719f3a8a6c6c146e911912f8dc0e49d0321c6c9a7cd49b2a416c2bbc706 +size 10966 diff --git a/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..3ee3be0d41 --- /dev/null +++ b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b6f0ea121612aa4023e9a0c6e14fe19615f07c93d94cfe1539d1d967dd81f72 +size 15061 diff --git a/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..c7c601331a --- /dev/null +++ b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:931dec4c0491a88b28186878bda2093d1d51be8d19147308ba6a54dbf6445339 +size 31059 diff --git a/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..f49c79ff2d --- /dev/null +++ b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:584e37736a964a4a1b009859e0a660d0615e13241a470fee8406a53b93c9f01c +size 32912 diff --git a/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..99ca038987 --- /dev/null +++ b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f40d8cb6f0b73e30ecef7c354db23a3c9b4054e7e8cdd51e2a4287401a255f76 +size 31968 diff --git a/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..8f4560115b --- /dev/null +++ b/screenshots/de/ui_T_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ed001056babb81e004f455b45645c8b4a88c094d4cf2c189304ae42c5beffdf +size 36169 diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..cf0e5444de --- /dev/null +++ b/screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4f23c0a8a77a1d0e4fa4278c5cec60b53e4b337d9076d450bdbbff7f6dba470 +size 11778 diff --git a/screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..fc8e76dc89 --- /dev/null +++ b/screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01404e25f56b60dcc84b0bbdd6711f0e7ed6b58078743bf7e919d6766842c92f +size 81164 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index 267730e8a0..d6d5573390 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -100,6 +100,9 @@ export const screenshots = [ ["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_58,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_59,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_6,NEXUS_5,1.0,en]","",0,], +["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_60,NEXUS_5,1.0,en]","",0,], +["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_61,NEXUS_5,1.0,en]","",0,], +["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_62,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_7,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_8,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.components.avatar_Avatar_null_Avatars_Avatar_0_null_9,NEXUS_5,1.0,en]","",0,], @@ -219,7 +222,7 @@ export const screenshots = [ ["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_3,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_4,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-11_13_null_0,NEXUS_5,1.0,en]",1,], -["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Night_1_null,NEXUS_5,1.0,en]",0,], +["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Night_1_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLarge_null_ElementLogoAtomLarge-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLarge_null_ElementLogoAtomLarge-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_null_ElementLogoAtomMediumNoBlurShadow-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_null_ElementLogoAtomMediumNoBlurShadow-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMedium_null_ElementLogoAtomMedium-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMedium_null_ElementLogoAtomMedium-Night_1_null,NEXUS_5,1.0,en]",0,], @@ -569,6 +572,11 @@ export const screenshots = [ ["ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_7,NEXUS_5,1.0,en]","",1,], ["ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_8,NEXUS_5,1.0,en]","",1,], ["ui_S_t[f.roomdetails.impl_RoomDetails_null_RoomDetails--0_0_null_9,NEXUS_5,1.0,en]","",1,], +["ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_0,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_1,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_2,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_3,NEXUS_5,1.0,en]","ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_3,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Day-0_1_null_4,NEXUS_5,1.0,en]","ui_S_t[f.roomdirectory.impl.root_RoomDirectoryView_null_RoomDirectoryView-Night-0_2_null_4,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_2,NEXUS_5,1.0,en]",1,], @@ -587,7 +595,8 @@ export const screenshots = [ ["ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_2_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_3_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_0,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_0,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en]",0,], From e98bb97e1453dc6688aa6fc0b16971de4005d017 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:05:00 +0200 Subject: [PATCH 037/124] Update dependency gradle to v8.7 (#2597) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew.bat | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch delta 34118 zcmY(qRX`kF)3u#IAjsf0xCD212@LM;?(PINyAue(f;$XO2=4Cg1P$=#e%|lo zKk1`B>Q#GH)wNd-&cJofz}3=WfYndTeo)CyX{fOHsQjGa<{e=jamMNwjdatD={CN3>GNchOE9OGPIqr)3v>RcKWR3Z zF-guIMjE2UF0Wqk1)21791y#}ciBI*bAenY*BMW_)AeSuM5}vz_~`+1i!Lo?XAEq{TlK5-efNFgHr6o zD>^vB&%3ZGEWMS>`?tu!@66|uiDvS5`?bF=gIq3rkK(j<_TybyoaDHg8;Y#`;>tXI z=tXo~e9{U!*hqTe#nZjW4z0mP8A9UUv1}C#R*@yu9G3k;`Me0-BA2&Aw6f`{Ozan2 z8c8Cs#dA-7V)ZwcGKH}jW!Ja&VaUc@mu5a@CObzNot?b{f+~+212lwF;!QKI16FDS zodx>XN$sk9;t;)maB^s6sr^L32EbMV(uvW%or=|0@U6cUkE`_!<=LHLlRGJx@gQI=B(nn z-GEjDE}*8>3U$n(t^(b^C$qSTI;}6q&ypp?-2rGpqg7b}pyT zOARu2x>0HB{&D(d3sp`+}ka+Pca5glh|c=M)Ujn_$ly^X6&u z%Q4Y*LtB_>i6(YR!?{Os-(^J`(70lZ&Hp1I^?t@~SFL1!m0x6j|NM!-JTDk)%Q^R< z@e?23FD&9_W{Bgtr&CG&*Oer3Z(Bu2EbV3T9FeQ|-vo5pwzwQ%g&=zFS7b{n6T2ZQ z*!H(=z<{D9@c`KmHO&DbUIzpg`+r5207}4D=_P$ONIc5lsFgn)UB-oUE#{r+|uHc^hzv_df zV`n8&qry%jXQ33}Bjqcim~BY1?KZ}x453Oh7G@fA(}+m(f$)TY%7n=MeLi{jJ7LMB zt(mE*vFnep?YpkT_&WPV9*f>uSi#n#@STJmV&SLZnlLsWYI@y+Bs=gzcqche=&cBH2WL)dkR!a95*Ri)JH_4c*- zl4pPLl^as5_y&6RDE@@7342DNyF&GLJez#eMJjI}#pZN{Y8io{l*D+|f_Y&RQPia@ zNDL;SBERA|B#cjlNC@VU{2csOvB8$HzU$01Q?y)KEfos>W46VMh>P~oQC8k=26-Ku)@C|n^zDP!hO}Y z_tF}0@*Ds!JMt>?4y|l3?`v#5*oV-=vL7}zehMON^=s1%q+n=^^Z{^mTs7}*->#YL z)x-~SWE{e?YCarwU$=cS>VzmUh?Q&7?#Xrcce+jeZ|%0!l|H_=D_`77hBfd4Zqk&! zq-Dnt_?5*$Wsw8zGd@?woEtfYZ2|9L8b>TO6>oMh%`B7iBb)-aCefM~q|S2Cc0t9T zlu-ZXmM0wd$!gd-dTtik{bqyx32%f;`XUvbUWWJmpHfk8^PQIEsByJm+@+-aj4J#D z4#Br3pO6z1eIC>X^yKk|PeVwX_4B+IYJyJyc3B`4 zPrM#raacGIzVOexcVB;fcsxS=s1e&V;Xe$tw&KQ`YaCkHTKe*Al#velxV{3wxx}`7@isG zp6{+s)CG%HF#JBAQ_jM%zCX5X;J%-*%&jVI?6KpYyzGbq7qf;&hFprh?E5Wyo=bZ) z8YNycvMNGp1836!-?nihm6jI`^C`EeGryoNZO1AFTQhzFJOA%Q{X(sMYlzABt!&f{ zoDENSuoJQIg5Q#@BUsNJX2h>jkdx4<+ipUymWKFr;w+s>$laIIkfP6nU}r+?J9bZg zUIxz>RX$kX=C4m(zh-Eg$BsJ4OL&_J38PbHW&7JmR27%efAkqqdvf)Am)VF$+U3WR z-E#I9H6^)zHLKCs7|Zs<7Bo9VCS3@CDQ;{UTczoEprCKL3ZZW!ffmZFkcWU-V|_M2 zUA9~8tE9<5`59W-UgUmDFp11YlORl3mS3*2#ZHjv{*-1#uMV_oVTy{PY(}AqZv#wF zJVks)%N6LaHF$$<6p8S8Lqn+5&t}DmLKiC~lE{jPZ39oj{wR&fe*LX-z0m}9ZnZ{U z>3-5Bh{KKN^n5i!M79Aw5eY=`6fG#aW1_ZG;fw7JM69qk^*(rmO{|Z6rXy?l=K=#_ zE-zd*P|(sskasO(cZ5L~_{Mz&Y@@@Q)5_8l<6vB$@226O+pDvkFaK8b>%2 zfMtgJ@+cN@w>3)(_uR;s8$sGONbYvoEZ3-)zZk4!`tNzd<0lwt{RAgplo*f@Z)uO` zzd`ljSqKfHJOLxya4_}T`k5Ok1Mpo#MSqf~&ia3uIy{zyuaF}pV6 z)@$ZG5LYh8Gge*LqM_|GiT1*J*uKes=Oku_gMj&;FS`*sfpM+ygN&yOla-^WtIU#$ zuw(_-?DS?6DY7IbON7J)p^IM?N>7x^3)(7wR4PZJu(teex%l>zKAUSNL@~{czc}bR z)I{XzXqZBU3a;7UQ~PvAx8g-3q-9AEd}1JrlfS8NdPc+!=HJ6Bs( zCG!0;e0z-22(Uzw>hkEmC&xj?{0p|kc zM}MMXCF%RLLa#5jG`+}{pDL3M&|%3BlwOi?dq!)KUdv5__zR>u^o|QkYiqr(m3HxF z6J*DyN#Jpooc$ok=b7{UAVM@nwGsr6kozSddwulf5g1{B=0#2)zv!zLXQup^BZ4sv*sEsn)+MA?t zEL)}3*R?4(J~CpeSJPM!oZ~8;8s_=@6o`IA%{aEA9!GELRvOuncE`s7sH91 zmF=+T!Q6%){?lJn3`5}oW31(^Of|$r%`~gT{eimT7R~*Mg@x+tWM3KE>=Q>nkMG$U za7r>Yz2LEaA|PsMafvJ(Y>Xzha?=>#B!sYfVob4k5Orb$INFdL@U0(J8Hj&kgWUlO zPm+R07E+oq^4f4#HvEPANGWLL_!uF{nkHYE&BCH%l1FL_r(Nj@M)*VOD5S42Gk-yT z^23oAMvpA57H(fkDGMx86Z}rtQhR^L!T2iS!788E z+^${W1V}J_NwdwdxpXAW8}#6o1(Uu|vhJvubFvQIH1bDl4J4iDJ+181KuDuHwvM?` z%1@Tnq+7>p{O&p=@QT}4wT;HCb@i)&7int<0#bj8j0sfN3s6|a(l7Bj#7$hxX@~iP z1HF8RFH}irky&eCN4T94VyKqGywEGY{Gt0Xl-`|dOU&{Q;Ao;sL>C6N zXx1y^RZSaL-pG|JN;j9ADjo^XR}gce#seM4QB1?S`L*aB&QlbBIRegMnTkTCks7JU z<0(b+^Q?HN1&$M1l&I@>HMS;!&bb()a}hhJzsmB?I`poqTrSoO>m_JE5U4=?o;OV6 zBZjt;*%1P>%2{UL=;a4(aI>PRk|mr&F^=v6Fr&xMj8fRCXE5Z2qdre&;$_RNid5!S zm^XiLK25G6_j4dWkFqjtU7#s;b8h?BYFxV?OE?c~&ME`n`$ix_`mb^AWr+{M9{^^Rl;~KREplwy2q;&xe zUR0SjHzKVYzuqQ84w$NKVPGVHL_4I)Uw<$uL2-Ml#+5r2X{LLqc*p13{;w#E*Kwb*1D|v?e;(<>vl@VjnFB^^Y;;b3 z=R@(uRj6D}-h6CCOxAdqn~_SG=bN%^9(Ac?zfRkO5x2VM0+@_qk?MDXvf=@q_* z3IM@)er6-OXyE1Z4sU3{8$Y$>8NcnU-nkyWD&2ZaqX1JF_JYL8y}>@V8A5%lX#U3E zet5PJM`z79q9u5v(OE~{by|Jzlw2<0h`hKpOefhw=fgLTY9M8h+?37k@TWpzAb2Fc zQMf^aVf!yXlK?@5d-re}!fuAWu0t57ZKSSacwRGJ$0uC}ZgxCTw>cjRk*xCt%w&hh zoeiIgdz__&u~8s|_TZsGvJ7sjvBW<(C@}Y%#l_ID2&C`0;Eg2Z+pk;IK}4T@W6X5H z`s?ayU-iF+aNr5--T-^~K~p;}D(*GWOAYDV9JEw!w8ZYzS3;W6*_`#aZw&9J ziXhBKU3~zd$kKzCAP-=t&cFDeQR*_e*(excIUxKuD@;-twSlP6>wWQU)$|H3Cy+`= z-#7OW!ZlYzZxkdQpfqVDFU3V2B_-eJS)Fi{fLtRz!K{~7TR~XilNCu=Z;{GIf9KYz zf3h=Jo+1#_s>z$lc~e)l93h&RqW1VHYN;Yjwg#Qi0yzjN^M4cuL>Ew`_-_wRhi*!f zLK6vTpgo^Bz?8AsU%#n}^EGigkG3FXen3M;hm#C38P@Zs4{!QZPAU=m7ZV&xKI_HWNt90Ef zxClm)ZY?S|n**2cNYy-xBlLAVZ=~+!|7y`(fh+M$#4zl&T^gV8ZaG(RBD!`3?9xcK zp2+aD(T%QIgrLx5au&TjG1AazI;`8m{K7^!@m>uGCSR;Ut{&?t%3AsF{>0Cm(Kf)2 z?4?|J+!BUg*P~C{?mwPQ#)gDMmro20YVNsVx5oWQMkzQ? zsQ%Y>%7_wkJqnSMuZjB9lBM(o zWut|B7w48cn}4buUBbdPBW_J@H7g=szrKEpb|aE>!4rLm+sO9K%iI75y~2HkUo^iw zJ3se$8$|W>3}?JU@3h@M^HEFNmvCp|+$-0M?RQ8SMoZ@38%!tz8f8-Ptb@106heiJ z^Bx!`0=Im z1!NUhO=9ICM*+||b3a7w*Y#5*Q}K^ar+oMMtekF0JnO>hzHqZKH0&PZ^^M(j;vwf_ z@^|VMBpcw8;4E-9J{(u7sHSyZpQbS&N{VQ%ZCh{c1UA5;?R} z+52*X_tkDQ(s~#-6`z4|Y}3N#a&dgP4S_^tsV=oZr4A1 zaSoPN1czE(UIBrC_r$0HM?RyBGe#lTBL4~JW#A`P^#0wuK)C-2$B6TvMi@@%K@JAT_IB^T7Zfqc8?{wHcSVG_?{(wUG%zhCm=%qP~EqeqKI$9UivF zv+5IUOs|%@ypo6b+i=xsZ=^G1yeWe)z6IX-EC`F=(|_GCNbHbNp(CZ*lpSu5n`FRA zhnrc4w+Vh?r>her@Ba_jv0Omp#-H7avZb=j_A~B%V0&FNi#!S8cwn0(Gg-Gi_LMI{ zCg=g@m{W@u?GQ|yp^yENd;M=W2s-k7Gw2Z(tsD5fTGF{iZ%Ccgjy6O!AB4x z%&=6jB7^}pyftW2YQpOY1w@%wZy%}-l0qJlOSKZXnN2wo3|hujU+-U~blRF!^;Tan z0w;Srh0|Q~6*tXf!5-rCD)OYE(%S|^WTpa1KHtpHZ{!;KdcM^#g8Z^+LkbiBHt85m z;2xv#83lWB(kplfgqv@ZNDcHizwi4-8+WHA$U-HBNqsZ`hKcUI3zV3d1ngJP-AMRET*A{> zb2A>Fk|L|WYV;Eu4>{a6ESi2r3aZL7x}eRc?cf|~bP)6b7%BnsR{Sa>K^0obn?yiJ zCVvaZ&;d_6WEk${F1SN0{_`(#TuOOH1as&#&xN~+JDzX(D-WU_nLEI}T_VaeLA=bc zl_UZS$nu#C1yH}YV>N2^9^zye{rDrn(rS99>Fh&jtNY7PP15q%g=RGnxACdCov47= zwf^9zfJaL{y`R#~tvVL#*<`=`Qe zj_@Me$6sIK=LMFbBrJps7vdaf_HeX?eC+P^{AgSvbEn?n<}NDWiQGQG4^ZOc|GskK z$Ve2_n8gQ-KZ=s(f`_X!+vM5)4+QmOP()2Fe#IL2toZBf+)8gTVgDSTN1CkP<}!j7 z0SEl>PBg{MnPHkj4wj$mZ?m5x!1ePVEYI(L_sb0OZ*=M%yQb?L{UL(2_*CTVbRxBe z@{)COwTK1}!*CK0Vi4~AB;HF(MmQf|dsoy(eiQ>WTKcEQlnKOri5xYsqi61Y=I4kzAjn5~{IWrz_l))|Ls zvq7xgQs?Xx@`N?f7+3XKLyD~6DRJw*uj*j?yvT3}a;(j_?YOe%hUFcPGWRVBXzpMJ zM43g6DLFqS9tcTLSg=^&N-y0dXL816v&-nqC0iXdg7kV|PY+js`F8dm z2PuHw&k+8*&9SPQ6f!^5q0&AH(i+z3I7a?8O+S5`g)>}fG|BM&ZnmL;rk)|u{1!aZ zEZHpAMmK_v$GbrrWNP|^2^s*!0waLW=-h5PZa-4jWYUt(Hr@EA(m3Mc3^uDxwt-me^55FMA9^>hpp26MhqjLg#^Y7OIJ5%ZLdNx&uDgIIqc zZRZl|n6TyV)0^DDyVtw*jlWkDY&Gw4q;k!UwqSL6&sW$B*5Rc?&)dt29bDB*b6IBY z6SY6Unsf6AOQdEf=P1inu6(6hVZ0~v-<>;LAlcQ2u?wRWj5VczBT$Op#8IhppP-1t zfz5H59Aa~yh7EN;BXJsLyjkjqARS5iIhDVPj<=4AJb}m6M@n{xYj3qsR*Q8;hVxDyC4vLI;;?^eENOb5QARj#nII5l$MtBCI@5u~(ylFi$ zw6-+$$XQ}Ca>FWT>q{k)g{Ml(Yv=6aDfe?m|5|kbGtWS}fKWI+})F6`x@||0oJ^(g|+xi zqlPdy5;`g*i*C=Q(aGeDw!eQg&w>UUj^{o?PrlFI=34qAU2u@BgwrBiaM8zoDTFJ< zh7nWpv>dr?q;4ZA?}V}|7qWz4W?6#S&m>hs4IwvCBe@-C>+oohsQZ^JC*RfDRm!?y zS4$7oxcI|##ga*y5hV>J4a%HHl^t$pjY%caL%-FlRb<$A$E!ws?8hf0@(4HdgQ!@> zds{&g$ocr9W4I84TMa9-(&^_B*&R%^=@?Ntxi|Ejnh;z=!|uVj&3fiTngDPg=0=P2 zB)3#%HetD84ayj??qrxsd9nqrBem(8^_u_UY{1@R_vK-0H9N7lBX5K(^O2=0#TtUUGSz{ z%g>qU8#a$DyZ~EMa|8*@`GOhCW3%DN%xuS91T7~iXRr)SG`%=Lfu%U~Z_`1b=lSi?qpD4$vLh$?HU6t0MydaowUpb zQr{>_${AMesCEffZo`}K0^~x>RY_ZIG{(r39MP>@=aiM@C;K)jUcfQV8#?SDvq>9D zI{XeKM%$$XP5`7p3K0T}x;qn)VMo>2t}Ib(6zui;k}<<~KibAb%p)**e>ln<=qyWU zrRDy|UXFi9y~PdEFIAXejLA{K)6<)Q`?;Q5!KsuEw({!#Rl8*5_F{TP?u|5(Hijv( ztAA^I5+$A*+*e0V0R~fc{ET-RAS3suZ}TRk3r)xqj~g_hxB`qIK5z(5wxYboz%46G zq{izIz^5xW1Vq#%lhXaZL&)FJWp0VZNO%2&ADd?+J%K$fM#T_Eke1{dQsx48dUPUY zLS+DWMJeUSjYL453f@HpRGU6Dv)rw+-c6xB>(=p4U%}_p>z^I@Ow9`nkUG21?cMIh9}hN?R-d)*6%pr6d@mcb*ixr7 z)>Lo<&2F}~>WT1ybm^9UO{6P9;m+fU^06_$o9gBWL9_}EMZFD=rLJ~&e?fhDnJNBI zKM=-WR6g7HY5tHf=V~6~QIQ~rakNvcsamU8m28YE=z8+G7K=h%)l6k zmCpiDInKL6*e#)#Pt;ANmjf`8h-nEt&d}(SBZMI_A{BI#ck-_V7nx)K9_D9K-p@?Zh81#b@{wS?wCcJ%og)8RF*-0z+~)6f#T` zWqF7_CBcnn=S-1QykC*F0YTsKMVG49BuKQBH%WuDkEy%E?*x&tt%0m>>5^HCOq|ux zuvFB)JPR-W|%$24eEC^AtG3Gp4qdK%pjRijF5Sg3X}uaKEE z-L5p5aVR!NTM8T`4|2QA@hXiLXRcJveWZ%YeFfV%mO5q#($TJ`*U>hicS+CMj%Ip# zivoL;dd*araeJK9EA<(tihD50FHWbITBgF9E<33A+eMr2;cgI3Gg6<-2o|_g9|> zv5}i932( zYfTE9?4#nQhP@a|zm#9FST2 z!y+p3B;p>KkUzH!K;GkBW}bWssz)9b>Ulg^)EDca;jDl+q=243BddS$hY^fC6lbpM z(q_bo4V8~eVeA?0LFD6ZtKcmOH^75#q$Eo%a&qvE8Zsqg=$p}u^|>DSWUP5i{6)LAYF4E2DfGZuMJ zMwxxmkxQf}Q$V3&2w|$`9_SQS^2NVbTHh;atB>=A%!}k-f4*i$X8m}Ni^ppZXk5_oYF>Gq(& z0wy{LjJOu}69}~#UFPc;$7ka+=gl(FZCy4xEsk);+he>Nnl>hb5Ud-lj!CNicgd^2 z_Qgr_-&S7*#nLAI7r()P$`x~fy)+y=W~6aNh_humoZr7MWGSWJPLk}$#w_1n%(@? z3FnHf1lbxKJbQ9c&i<$(wd{tUTX6DAKs@cXIOBv~!9i{wD@*|kwfX~sjKASrNFGvN zrFc=!0Bb^OhR2f`%hrp2ibv#KUxl)Np1aixD9{^o=)*U%n%rTHX?FSWL^UGpHpY@7 z74U}KoIRwxI#>)Pn4($A`nw1%-D}`sGRZD8Z#lF$6 zOeA5)+W2qvA%m^|$WluUU-O+KtMqd;Pd58?qZj})MbxYGO<{z9U&t4D{S2G>e+J9K ztFZ?}ya>SVOLp9hpW)}G%kTrg*KXXXsLkGdgHb+R-ZXqdkdQC0_)`?6mqo8(EU#d( zy;u&aVPe6C=YgCRPV!mJ6R6kdY*`e+VGM~`VtC>{k27!9vAZT)x2~AiX5|m1Rq}_= z;A9LX^nd$l-9&2%4s~p5r6ad-siV`HtxKF}l&xGSYJmP=z!?Mlwmwef$EQq~7;#OE z)U5eS6dB~~1pkj#9(}T3j!((8Uf%!W49FfUAozijoxInUE7z`~U3Y^}xc3xp){#9D z<^Tz2xw}@o@fdUZ@hnW#dX6gDOj4R8dV}Dw`u!h@*K)-NrxT8%2`T}EvOImNF_N1S zy?uo6_ZS>Qga4Xme3j#aX+1qdFFE{NT0Wfusa$^;eL5xGE_66!5_N8!Z~jCAH2=${ z*goHjl|z|kbmIE{cl-PloSTtD+2=CDm~ZHRgXJ8~1(g4W=1c3=2eF#3tah7ho`zm4 z05P&?nyqq$nC?iJ-nK_iBo=u5l#|Ka3H7{UZ&O`~t-=triw=SE7ynzMAE{Mv-{7E_ zViZtA(0^wD{iCCcg@c{54Ro@U5p1QZq_XlEGtdBAQ9@nT?(zLO0#)q55G8_Ug~Xnu zR-^1~hp|cy&52iogG@o?-^AD8Jb^;@&Ea5jEicDlze6%>?u$-eE};bQ`T6@(bED0J zKYtdc?%9*<<$2LCBzVx9CA4YV|q-qg*-{yQ;|0=KIgI6~z0DKTtajw2Oms3L zn{C%{P`duw!(F@*P)lFy11|Z&x`E2<=$Ln38>UR~z6~za(3r;45kQK_^QTX%!s zNzoIFFH8|Y>YVrUL5#mgA-Jh>j7)n)5}iVM4%_@^GSwEIBA2g-;43* z*)i7u*xc8jo2z8&=8t7qo|B-rsGw)b8UXnu`RgE4u!(J8yIJi(5m3~aYsADcfZ!GG zzqa7p=sg`V_KjiqI*LA-=T;uiNRB;BZZ)~88 z`C%p8%hIev2rxS12@doqsrjgMg3{A&N8A?%Ui5vSHh7!iC^ltF&HqG~;=16=h0{ygy^@HxixUb1XYcR36SB}}o3nxu z_IpEmGh_CK<+sUh@2zbK9MqO!S5cao=8LSQg0Zv4?ju%ww^mvc0WU$q@!oo#2bv24 z+?c}14L2vlDn%Y0!t*z=$*a!`*|uAVu&NO!z_arim$=btpUPR5XGCG0U3YU`v>yMr z^zmTdcEa!APX zYF>^Q-TP11;{VgtMqC}7>B^2gN-3KYl33gS-p%f!X<_Hr?`rG8{jb9jmuQA9U;BeG zHj6Pk(UB5c6zwX%SNi*Py*)gk^?+729$bAN-EUd*RKN7{CM4`Q65a1qF*-QWACA&m zrT)B(M}yih{2r!Tiv5Y&O&=H_OtaHUz96Npo_k0eN|!*s2mLe!Zkuv>^E8Xa43ZwH zOI058AZznYGrRJ+`*GmZzMi6yliFmGMge6^j?|PN%ARns!Eg$ufpcLc#1Ns!1@1 zvC7N8M$mRgnixwEtX{ypBS^n`k@t2cCh#_6L6WtQb8E~*Vu+Rr)YsKZRX~hzLG*BE zaeU#LPo?RLm(Wzltk79Jd1Y$|6aWz1)wf1K1RtqS;qyQMy@H@B805vQ%wfSJB?m&&=^m4i* zYVH`zTTFbFtNFkAI`Khe4e^CdGZw;O0 zqkQe2|NG_y6D%h(|EZNf&77_!NU%0y={^E=*gKGQ=)LdKPM3zUlM@otH2X07Awv8o zY8Y7a1^&Yy%b%m{mNQ5sWNMTIq96Wtr>a(hL>Qi&F(ckgKkyvM0IH<_}v~Fv-GqDapig=3*ZMOx!%cYY)SKzo7ECyem z9Mj3C)tCYM?C9YIlt1?zTJXNOo&oVxu&uXKJs7i+j8p*Qvu2PAnY}b`KStdpi`trk ztAO}T8eOC%x)mu+4ps8sYZ=vYJp16SVWEEgQyFKSfWQ@O5id6GfL`|2<}hMXLPszS zgK>NWOoR zBRyKeUPevpqKKShD|MZ`R;~#PdNMB3LWjqFKNvH9k+;(`;-pyXM55?qaji#nl~K8m z_MifoM*W*X9CQiXAOH{cZcP0;Bn10E1)T@62Um>et2ci!J2$5-_HPy(AGif+BJpJ^ ziHWynC_%-NlrFY+(f7HyVvbDIM$5ci_i3?22ZkF>Y8RPBhgx-7k3M2>6m5R24C|~I z&RPh9xpMGzhN4bii*ryWaN^d(`0 zTOADlU)g`1p+SVMNLztd)c+;XjXox(VHQwqzu>FROvf0`s&|NEv26}(TAe;@=FpZq zaVs6mp>W0rM3Qg*6x5f_bPJd!6dQGmh?&v0rpBNfS$DW-{4L7#_~-eA@7<2BsZV=X zow){3aATmLZOQrs>uzDkXOD=IiX;Ue*B(^4RF%H zeaZ^*MWn4tBDj(wj114r(`)P96EHq4th-;tWiHhkp2rDlrklX}I@ib-nel0slFoQO zOeTc;Rh7sMIebO`1%u)=GlEj+7HU;c|Nj>2j)J-kpR)s3#+9AiB zd$hAk6;3pu9(GCR#)#>aCGPYq%r&i02$0L9=7AlIGYdlUO5%eH&M!ZWD&6^NBAj0Y9ZDcPg@r@8Y&-}e!aq0S(`}NuQ({;aigCPnq75U9cBH&Y7 ze)W0aD>muAepOKgm7uPg3Dz7G%)nEqTUm_&^^3(>+eEI;$ia`m>m0QHEkTt^=cx^JsBC68#H(3zc~Z$E9I)oSrF$3 zUClHXhMBZ|^1ikm3nL$Z@v|JRhud*IhOvx!6X<(YSX(9LG#yYuZeB{=7-MyPF;?_8 zy2i3iVKG2q!=JHN>~!#Bl{cwa6-yB@b<;8LSj}`f9pw7#x3yTD>C=>1S@H)~(n_K4 z2-yr{2?|1b#lS`qG@+823j;&UE5|2+EdU4nVw5=m>o_gj#K>>(*t=xI7{R)lJhLU{ z4IO6!x@1f$aDVIE@1a0lraN9!(j~_uGlks)!&davUFRNYHflp<|ENwAxsp~4Hun$Q z$w>@YzXp#VX~)ZP8`_b_sTg(Gt7?oXJW%^Pf0UW%YM+OGjKS}X`yO~{7WH6nX8S6Z ztl!5AnM2Lo*_}ZLvo%?iV;D2z>#qdpMx*xY2*GGlRzmHCom`VedAoR=(A1nO)Y>;5 zCK-~a;#g5yDgf7_phlkM@)C8s!xOu)N2UnQhif-v5kL$*t=X}L9EyBRq$V(sI{90> z=ghTPGswRVbTW@dS2H|)QYTY&I$ljbpNPTc_T|FEJkSW7MV!JM4I(ksRqQ8)V5>}v z2Sf^Z9_v;dKSp_orZm09jb8;C(vzFFJgoYuWRc|Tt_&3k({wPKiD|*m!+za$(l*!gNRo{xtmqjy1=kGzFkTH=Nc>EL@1Um0BiN1)wBO$i z6rG={bRcT|%A3s3xh!Bw?=L&_-X+6}L9i~xRj2}-)7fsoq0|;;PS%mcn%_#oV#kAp zGw^23c8_0~ ze}v9(p};6HM0+qF5^^>BBEI3d=2DW&O#|(;wg}?3?uO=w+{*)+^l_-gE zSw8GV=4_%U4*OU^hibDV38{Qb7P#Y8zh@BM9pEM_o2FuFc2LWrW2jRRB<+IE)G=Vx zuu?cp2-`hgqlsn|$nx@I%TC!`>bX^G00_oKboOGGXLgyLKXoo$^@L7v;GWqfUFw3< zekKMWo0LR;TaFY}Tt4!O$3MU@pqcw!0w0 zA}SnJ6Lb597|P5W8$OsEHTku2Kw9y4V=hx*K%iSn!#LW9W#~OiWf^dXEP$^2 zaok=UyGwy3GRp)bm6Gqr>8-4h@3=2`Eto2|JE6Sufh?%U6;ut1v1d@#EfcQP2chCt z+mB{Bk5~()7G>wM3KYf7Xh?LGbwg1uWLotmc_}Z_o;XOUDyfU?{9atAT$={v82^w9 z(MW$gINHt4xB3{bdbhRR%T}L?McK?!zkLK3(e>zKyei(yq%Nsijm~LV|9mll-XHavFcc$teX7v);H>=oN-+E_Q{c|! zp
    JV~-9AH}jxf6IF!PxrB9is{_9s@PYth^`pb%DkwghLdAyDREz(csf9)HcVRq z+2Vn~>{(S&_;bq_qA{v7XbU?yR7;~JrLfo;g$Lkm#ufO1P`QW_`zWW+4+7xzQZnO$ z5&GyJs4-VGb5MEDBc5=zxZh9xEVoY(|2yRv&!T7LAlIs@tw+4n?v1T8M>;hBv}2n) zcqi+>M*U@uY>4N3eDSAH2Rg@dsl!1py>kO39GMP#qOHipL~*cCac2_vH^6x@xmO|E zkWeyvl@P$2Iy*mCgVF+b{&|FY*5Ygi8237i)9YW#Fp& z?TJTQW+7U)xCE*`Nsx^yaiJ0KSW}}jc-ub)8Z8x(|K7G>`&l{Y&~W=q#^4Gf{}aJ%6kLXsmv6cr=Hi*uB`V26;dr4C$WrPnHO>g zg1@A%DvIWPDtXzll39kY6#%j;aN7grYJP9AlJgs3FnC?crv$wC7S4_Z?<_s0j;MmE z75yQGul2=bY%`l__1X3jxju2$Ws%hNv75ywfAqjgFO7wFsFDOW^)q2%VIF~WhwEW0 z45z^+r+}sJ{q+>X-w(}OiD(!*&cy4X&yM`!L0Fe+_RUfs@=J{AH#K~gArqT=#DcGE z!FwY(h&+&811rVCVoOuK)Z<-$EX zp`TzcUQC256@YWZ*GkE@P_et4D@qpM92fWA6c$MV=^qTu7&g)U?O~-fUR&xFqNiY1 zRd=|zUs_rmFZhKI|H}dcKhy%Okl(#y#QuMi81zsY56Y@757xBQqDNkd+XhLQhp2BB zBF^aJ__D676wLu|yYo6jNJNw^B+Ce;DYK!f$!dNs1*?D^97u^jKS++7S z5qE%zG#HY-SMUn^_yru=T6v`)CM%K<>_Z>tPe|js`c<|y7?qol&)C=>uLWkg5 zmzNcSAG_sL)E9or;i+O}tY^70@h7+=bG1;YDlX{<4zF_?{)K5B&?^tKZ6<$SD%@>F zY0cl2H7)%zKeDX%Eo7`ky^mzS)s;842cP{_;dzFuyd~Npb4u!bwkkhf8-^C2e3`q8>MuPhgiv0VxHxvrN9_`rJv&GX0fWz-L-Jg^B zrTsm>)-~j0F1sV=^V?UUi{L2cp%YwpvHwwLaSsCIrGI#({{QfbgDxLKsUC6w@m?y} zg?l=7aMX-RnMxvLn_4oSB|9t;)Qf2%m-GKo_07?N1l^ahJ+Wf8C>h5~=-o1BJzV@5HBTB-ACNpsHnGt6_ku37M z{vIEB^tR=--4SEg{jfF=gEogtGwi&A$mwk7E+SV$$ZuU}#F3Y7t}o{!w4LJh8v4PW%8HfUK@dta#l*z@w*9Xzz(i)r#WXi`r1D#oBPtNM7M?Hkq zhhS1)ea5(6VY45|)tCTr*@yc$^Zc!zQzsNXU?aRN6mh7zVu~i=qTrX^>de+f6HYfDsW@6PBlw0CsDBcOWUmt&st>Z zYNJEsRCP1#g0+Htb=wITvexBY@fOpAmR7?szQNR~nM)?sPWIj)0)jG-EF8U@nnBaQZy z)ImpVYQL>lBejMDjlxA$#G4%y+^_>N;}r@Zoe2|u-9-x@vvD^ZWnV>Gm=pZa7REAf zOnomhCxBaGZgT+4kiE%aS&lH2sI1mSCM<%)Cr*Sli;#!aXcUb&@Z|Hj{VPsJyClqD%>hy`Y7z(GASs8Mqas3!D zSQE83*%uctlD|p%4)v`arra4y>yP5m25V*_+n)Ry1v>z_Fz!TV6t+N?x?#iH$q=m= z8&X{uW%LVRO87dVl=$Y*>dabJVq{o|Kx`7(D2$5DVX&}XGbg|Ua(*5b=;5qzW9;|w>m{hIO(Tu-z(ey8H=EMluJNyK4BJmGpX~ZM2O61 zk*O7js{-MBqwq>Urf0igN+6soGGc!Y?SP6hiXuJzZ1V4WZqE*?h;PG84gvG~dds6~484!kPM zMP87IP?dhdc;%|cS&LxY*Ib6P3%p|9)E3IgRmhhwtUR3eRK6iZ_6fiGW}jnL4(I|t ze`2yLvmuY42lNwO6>I#Son3$R4NOoP*WUm1R4jl#agtSLE}fSu-Z>{+*?pQIn7`s3LAzF#1pSxCAo?clr9 z9PUj#REq28*ZkJnxs$aK%8^5?P<_Q!#Z?%JH0FKVF;&zH3F#J^fz|ahl$Ycs~kFij_XP;U<`FcaDYyXYPM~&jEe1Xj1n;wyRdD;lmnq&FEro=;+Z$=v-&fYM9eK*S_D&oTXFW#b0 zRY}Y7R#bLzTfg9i7{s?=P9~qjA?$-U2p5;0?gPPu`1JY|*?*8IPO!eX>oiX=O#F!A zl`S%e5Y(csR1f)I(iKMf-;5%_rPP7h&}5Fc(8byKUH1*d7?9%QC|4aADj3L8yuo6GOv#%HDgU3bN(UHw1+(99&Om%f!DY(RYSf4&Uny% zH}*&rEXc$W5+eyeEg|I|E-HnkIO0!$1sV7Z&NXxiCZJ@`kH4eEi5}q~!Vv5qQq{MI zi4^`GYoUN-7Q(jy^SKXL4$G4K+FQXR)B}ee=pS0RyK=YC8c2bGnMA~rrOh&jd3_AT zxVaq37w^-;OU3+C`Kko-Z%l_2FC^maa=Ae0Fm@PEtXEg@cX*oka1Lt&h@jES<6?o1Oi1C9>}7+U(Ve zQ$=8RlzcnfCd59CsJ=gG^A!2Bb_PY~K2sSau{)?Ge03G7US&qrgV!3NUi>UHWZ*lo zS;~0--vn{ot+7UWMV{a(X3rZ8Z06Ps3$-sd|CWE(Y#l`swvcDbMjuReGsoA`rmZ`^ z=AaArdbeU0EtwnOuzq@u5P1rlZjH#gNgh6HIhG(>dX%4m{_!&DNTQE)8= zXD-vcpcSi|DSm3aUMnrV;DQY?svz?9*#GT$NXb~Hem=24iy>7xj367(!#RjnrHtrP-Q`T2W*PEvAR-=j ztY2|#<|JvHNVnM-tNdoS_yRSo=yFqukTZmB$|>Vclj)o=YzC9!ph8)ZOH5X=%Aq|9gNgc}^KFVLht!Lyw54v5u&D zW%vT%z`H{Ax>Ry+bD&QjHQke_wEA;oj(&E!s4|OURButQKSc7Ar-PzIiFa8F@ezkaY2J9&PH+VI1!G+{JgsQ7%da*_Gr!exT*OgJld)b-?cd)xI+|v_C`h(Cg`N~oj0`SQPTma z{@vc8L^D-rBXwS#00jT#@=-n1H-C3hvg61r2jx#ok&cr#BV~9JdPaVihyrGq*lb>bm$H6rIoc}ifaSn6mTD9% z$FRJxbNozOo6y}!OUci1VBv-7{TYZ4GkOM@46Y9?8%mSH9?l&lU59)T#Fjg(h%6I} z?ib zZ(xb8Rwr+vv>@$h{WglT2lL`#V=-9tP^c)cjvnz(g|VL^h8^CPVv12dE(o}WQ@0OP z^2-&ssBXP^#Oh`X5@F+~$PCB6kK-T7sFUK|>$lNDSkvAy%{y2qgq-&v zv}^&gm`wiYztWgMS<{^qQKYNV=>CQaOeglAY~EZvr}n~tW=yg)_+fzqF%~+*V_$3h z2hDW`e$qR;QMg?(wKE>%H_6ASS@6bkOi-m- zg6B7AzD;gBS1%OD7|47a%3BykN{w}P!Wn-nQOfpKUpx8Mk{$IO62D!%U9$kr!e%T> zlqQih?3(U&5%r!KZFZPdbwZ0laAJCj!c&pEFVzrH&_&i5m68Y_*J+-Qjlnz}Q{3oAD)`d14H zKUGmbwC|beC9Mtp>SbL~NVrlctU3WBpHz(UeIa~_{u^_4OaHs_LQt>bUwcyD`_Bbh zC=x|1vSjL)JvVHLw|xKynEvq2m)7O-6qdmjht7pZ*z|o%NA17v$9H*(5D5(MXiNo1 z72Tv}QASqr$!mY58s_Q{hHa9MY+QZ`2zX-FT@Kd?`8pczcV^9IeOKDG4WKqiP7N|S z+O977=VQTk8k5dafK`vd(4?_3pBdB?YG9*Z=R@y|$S+d%1sJf-Ka++I&v9hH)h#}} zw-MjQWJ?ME<7PR(G<1#*Z-&M?%=yzhQw$Lki(R+Pq$X~Q!9BO=fP9FyCIS8zE3n04 z8ScD%XmJnIv=pMTgt6VSxBXOZucndRE@7^aU0wefJYueY(Cb%?%0rz)zWEnsNsKhQ z+&o6d^x=R;Pt7fUa_`JVb1HPHYbXg{Jvux|atQ^bV#_|>7QZNC~P^IKUThB6{kvz2pr2*Cyxj zy37Nri8za8J!@Iw9rbt~#^<9zOaM8LOi$kPBcAGqPq-DB^-93Qeup{9@9&=zV6KQN zL)ic5S%n1!F(7b>MQ973$~<0|9MY-G!?wk?j-cQhMQlM2n{&7JoTBGsP;=fC6CBJn zxlpk^%x=B16rfb-W9pYV#9IRHQL9VG4?Uh>pN>2}0-MST2AB2pQjf*rT+TLCX-+&m z9I{ic2ogXoh=HwdI#igr(JC>>NUP|M>SA?-ux<2&>Jyx>Iko!B<3vS}{g*dKqxYW7 z0i`&U#*v)jot+keO#G&wowD!VvD(j`Z9a*-_RALKn0b(KnZ37d#Db7royLhBW~*7o zRa`=1fo9C4dgq;;R)JpP++a9^{xd)8``^fPW9!a%MCDYJc;3yicPs8IiQM>DhUX*; zeIrxE#JRrr|D$@bKgOm4C9D+e!_hQKj3LC`Js)|Aijx=J!rlgnpKeF>b+QlKhI^4* zf%Of^RmkW|xU|p#Lad44Y5LvIUIR>VGH8G zz7ZEIREG%UOy4)C!$muX6StM4@Fsh&Goa}cj10RL(#>oGtr6h~7tZDDQ_J>h)VmYlKK>9ns8w4tdx6LdN5xJQ9t-ABtTf_ zf1dKVv!mhhQFSN=ggf(#$)FtN-okyT&o6Ms+*u72Uf$5?4)78EErTECzweDUbbU)) zc*tt+9J~Pt%!M352Y5b`Mwrjn^Orp+)L_U1ORHJ}OUsB78YPcIRh4p5jzoDB7B*fb z4v`bouQeCAW#z9b1?4(M3dcwNn2F2plwC^RVHl#h&b-8n#5^o+Ll20OlJ^gOYiK2< z;MQuR!t!>`i}CAOa4a+Rh5IL|@kh4EdEL*O=3oGx4asg?XCTcUOQnmHs^6nLu6WcI zSt9q7nl*?2TIikKNb?3JZBo$cW6)b#;ZKzi+(~D-%0Ec+QW=bZZm@w|prGiThO3dy zU#TQ;RYQ+xU~*@Zj;Rf~z~iL8Da`RT!Z)b3ILBhnIl@VX9K0PSj5owH#*FJXX3vZ= zg_Zyn^G&l!WR6wN9GWvt)sM?g2^CA8&F#&t2z3_MiluRqvNbV{Me6yZ&X-_ zd6#Xdh%+6tCmSNTdCBusVkRwJ_A~<^Nd6~MNOvS;YDixM43`|8e_bmc*UWi7TLA})`T_F ztk&Nd=dgFUss#Ol$LXTRzP9l1JOSvAws~^X%(`ct$?2Im?UNpXjBec_-+8YK%rq#P zT9=h8&gCtgx?=Oj$Yr2jI3`VVuZ`lH>*N+*K11CD&>>F)?(`yr~54vHJftY*z?EorK zm`euBK<$(!XO%6-1=m>qqp6F`S@Pe3;pK5URT$8!Dd|;`eOWdmn916Ut5;iXWQoXE z0qtwxlH=m_NONP3EY2eW{Qwr-X1V3;5tV;g7tlL4BRilT#Y&~o_!f;*hWxWmvA;Pg zRb^Y$#PipnVlLXQIzKCuQP9IER0Ai4jZp+STb1Xq0w(nVn<3j(<#!vuc?7eJEZC<- zPhM7ObhgabN2`pm($tu^MaBkRLzx&jdh;>BP|^$TyD1UHt9Qvr{ZcBs^l!JI4~d-Py$P5QOYO&8eQOFe)&G zZm+?jOJioGs7MkkQBCzJSFJV6DiCav#kmdxc@IJ9j5m#&1)dhJt`y8{T!uxpBZ>&z zD^V~%GEaODak5qGj|@cA7HSH{#jHW;Q0KRdTp@PJO#Q1gGI=((a1o%X*{knz&_`ym zkRLikN^fQ%Gy1|~6%h^vx>ToJ(#aJDxoD8qyOD{CPbSvR*bC>Nm+mkw>6mD0mlD0X zGepCcS_x7+6X7dH;%e`aIfPr-NXSqlu&?$Br1R}3lSF2 zWOXDtG;v#EVLSQ!>4323VX-|E#qb+x%IxzUBDI~N23x? zXUHfTTV#_f9T$-2FPG@t)rpc9u9!@h^!4=fL^kg9 zVv%&KY3!?bU*V4X)wNT%Chr;YK()=~lc%$auOB_|oH`H)Xot@1cmk{^qdt&1C55>k zYnIkdoiAYW41zrRBfqR?9r^cpWIEqfS;|R#bIs4$cqA zoq~$yl8h{IXTSdSdH?;`ky6i%+Oc?HvwH+IS`%_a!d#CqQob9OTNIuhUnOQsX;nl_ z;1w99qO9lAb|guQ9?p4*9TmIZ5{su!h?v-jpOuShq!{AuHUYtmZ%brpgHl$BKLK_L z6q5vZodM$)RE^NNO>{ZWPb%Ce111V4wIX}?DHA=uzTu0$1h8zy!SID~m5t)(ov$!6 zB^@fP#vpx3enbrbX=vzol zj^Bg7V$Qa53#3Lptz<6Dz=!f+FvUBVIBtYPN{(%t(EcveSuxi3DI>XQ*$HX~O{KLK5Dh{H2ir87E^!(ye{9H&2U4kFxtKHkw zZPOTIa*29KbXx-U4hj&iH<9Z@0wh8B6+>qQJn{>F0mGnrj|0_{nwN}Vw_C!rm0!dC z>iRlEf}<+z&?Z4o3?C>QrLBhXP!MV0L#CgF{>;ydIBd5A{bd-S+VFn zLqq4a*HD%65IqQ5BxNz~vOGU=JJv|NG{OcW%2PU~MEfy6(bl#^TfT7+az5M-I`i&l z#g!HUfN}j#adA-21x7jbP6F;`99c8Qt|`_@u@fbhZF+Wkmr;IdVHj+F=pDb4MY?fU znDe##Hn){D}<>vVhYL#)+6p9eAT3T$?;-~bZU%l7MpPNh_mPc(h@79 z;LPOXk>e3nmIxl9lno5cI5G@Q!pE&hQ`s{$Ae4JhTebeTsj*|!6%0;g=wH?B1-p{P z`In#EP12q6=xXU)LiD+mLidPrYGHaKbe5%|vzApq9(PI6I5XjlGf<_uyy59iw8W;k zdLZ|8R8RWDc`#)n2?~}@5)vvksY9UaLW`FM=2s|vyg>Remm=QGthdNL87$nR&TKB*LB%*B}|HkG64 zZ|O4=Yq?Zwl>_KgIG@<8i{Zw#P3q_CVT7Dt zoMwoI)BkpQj8u(m!>1dfOwin(50}VNiLA>A2OG&TBXcP=H(3I;!WdPFe?r_e{%>bc6(Zk?6~Ew&;#ZxBJ| zAd1(sAHqlo_*rP;nTk)kAORe3cF&tj>m&LsvB)`-y9#$4XU=Dd^+CzvoAz%9216#f0cS`;kERxrtjbl^7pmO;_y zYBGOL7R1ne7%F9M2~0a7Srciz=MeaMU~ zV%Y#m_KV$XReYHtsraWLrdJItLtRiRo98T3J|x~(a>~)#>JHDJ z|4j!VO^qWQfCm9-$N29SpHUqvz62%#%98;2FNIF*?c9hZ7GAu$q>=0 zX_igPSK8Et(fmD)V=CvbtA-V(wS?z6WV|RX2`g=w=4D)+H|F_N(^ON!jHf72<2nCJ z^$hEygTAq7URR{Vq$)BsmFKTZ+i1i(D@SJuTGBN3W8{JpJ^J zkF=gBTz|P;Xxo1NIypGzJq8GK^#4tl)S%8$PP6E8c|GkkQ)vZ1OiB%mH#@hO1Z%Hp zv%2~Mlar^}7TRN-SscvQ*xVv+i1g8CwybQHCi3k;o$K@bmB%^-U8dILX)7b~#iPu@ z&D&W7YY2M3v`s(lNm2#^dCRFd;UYMUw1Rh2mto8laH1m`n0u;>okp5XmbsShOhQwo z@EYOehg-KNab)Rieib?m&NXls+&31)MB&H-zj_WmJsGjc1sCSOz0!2Cm1vV?y@kkQ z<1k6O$hvTQnGD*esux*aD3lEm$mUi0td0NiOtz3?7}h;Bt*vIC{tDBr@D)9rjhP^< zY*uKu^BiuSO%)&FL>C?Ng!HYZHLy`R>`rgq+lJhdXfo|df zmkzpQf{6o9%^|7Yb5v{Tu& zsP*Y~<#jK$S_}uEisRC;=y{zbq`4Owc@JyvB->nPzb#&vcMKi5n66PVV{Aub>*>q8 z=@u7jYA4Ziw2{fSED#t4QLD7Rt`au^y(Ggp3y(UcwIKtI(OMi@GHxs!bj$v~j(FZK zbdcP^gExtXQqQ8^Q#rHy1&W8q!@^aL>g1v2R45T(KErWB)1rB@rU`#n&-?g2Ti~xXCrexrLgajgzNy=N9|A6K=RZ zc3yk>w5sz1zsg~tO~-Ie?%Aplh#)l3`s632mi#CCl^75%i6IY;dzpuxu+2fliEjQn z&=~U+@fV4>{Fp=kk0oQIvBdqS#yY`Z+>Z|T&K{d;v3}=JqzKx05XU3M&@D5!uPTGydasyeZ5=1~IX-?HlM@AGB9|Mzb{{Dt@bUU8{KUPU@EX zv0fpQNvG~nD2WiOe{Vn=hE^rQD(5m+!$rs%s{w9;yg9oxRhqi0)rwsd245)igLmv* zJb@Xlet$+)oS1Ra#qTB@U|lix{Y4lGW-$5*4xOLY{9v9&RK<|K!fTd0wCKYZ)h&2f zEMcTCd+bj&YVmc#>&|?F!3?br3ChoMPTA{RH@NF(jmGMB2fMyW(<0jUT=8QFYD7-% zS0ydgp%;?W=>{V9>BOf=p$q5U511~Q0-|C!85)W0ov7eb35%XV;3mdUI@f5|x5C)R z$t?xLFZOv}A(ZjjSbF+8&%@RChpRvo>)sy>-IO8A@>i1A+8bZd^5J#(lgNH&A=V4V z*HUa0{zT{u-_FF$978RziwA@@*XkV{<-CE1N=Z!_!7;wq*xt3t((m+^$SZKaPim3K zO|Gq*w5r&7iqiQ!03SY{@*LKDkzhkHe*TzQaYAkz&jNxf^&A_-40(aGs53&}$dlKz zsel3=FvHqdeIf!UYwL&Mg3w_H?utbE_(PL9B|VAyaOo8k4qb>EvNYHrVmj^ocJQTf zL%4vl{qgmJf#@uWL@)WiB>Lm>?ivwB%uO|)i~;#--nFx4Kr6{TruZU0N_t_zqkg`? zwPFK|WiC4sI%o1H%$!1ANyq6_0OSPQJybh^vFriV=`S;kSsYkExZwB{68$dTODWJQ z@N57kBhwN(y~OHW_M}rX2W13cl@*i_tjW`TMfa~Y;I}1hzApXgWqag@(*@(|EMOg- z^qMk(s~dL#ps>>`oWZD=i1XI3(;gs7q#^Uj&L`gVu#4zn$i!BIHMoOZG!YoPO^=Gu z5`X-(KoSsHL77c<7^Y*IM2bI!dzg5j>;I@2-EeB$LgW|;csQTM&Z|R)q>yEjk@Sw% z6FQk*&zHWzcXalUJSoa&pgH24n`wKkg=2^ta$b1`(BBpBT2Ah9yQF&Kh+3jTaSE|=vChGz2_R^{$C;D`Ua(_=|OO11uLm;+3k%kO19EA`U065i;fRBoH z{Hq$cgHKRFPf0#%L?$*KeS@FDD;_TfJ#dwP7zzO5F>xntH(ONK{4)#jYUDQr6N(N< zp+fAS9l9)^c4Ss8628Zq5AzMq4zc(In_yJSXAT57Dtl}@= zvZoD7iq0cx7*#I{{r9m{%~g6@Hdr|*njKBb_5}mobCv=&X^`D9?;x6cHwRcwnlO^h zl;MiKr#LaoB*PELm8+8%btnC)b^E12!^ zMmVA!z>59e7n+^!P{PA?f9M^2FjKVw1%x~<`RY5FcXJE)AE}MTopGFDkyEjGiE|C6 z(ad%<3?v*?p;LJGopSEY18HPu2*}U!Nm|rfewc6(&y(&}B#j85d-5PeQ{}zg>>Rvl zDQ3H4E%q_P&kjuAQ>!0bqgAj){vzHpnn+h(AjQ6GO9v**l0|aCsCyXVE@uh?DU;Em zE*+7EU9tDH````D`|rM6WUlzBf1e{ht8$62#ilA6Dcw)qAzSRwu{czZJAcKv8w(Q6 zx)b$aq*=E=b5(UH-5*u)3iFlD;XQyklZrwHy}+=h6=aKtTriguHP@Inf+H@q32_LL z2tX|+X}4dMYB;*EW9~^5bydv)_!<%q#%Ocyh=1>FwL{rtZ?#2Scp{Q55%Fd-LgLU$ zM2u#|F{%vi%+O2^~uK3)?$6>9cc7_}F zWU72eFrzZ~x3ZIBH;~EMtD%51o*bnW;&QuzwWd$ds=O>Ev807cu%>Ac^ZK&7bCN;Ftk#eeQL4pG0p!W{Ri@tGw>nhIo`rC zi!Z6?70nYrNf92V{Y_i(a4DG=5>RktP=?%GcHEx?aKN$@{w{uj#Cqev$bXefo?yC6KI%Rol z%~$974WCymg;BBhd9Mv}_MeNro_8IB4!evgo*je4h?B-CAkEW-Wr-Q_V9~ef(znU& z{f-OHnj>@lZH(EcUb2TpOkc70@1BPiY0B#++1EPY5|UU?&^Vpw|C`k4ZWiB-3oAQM zgmG%M`2qDw5BMY|tG++34My2fE|^kvMSp(d+~P(Vk*d+RW1833i_bX^RYbg9tDtX` zox?y^YYfs-#fX|y7i(FN7js)66jN!`p9^r7oildEU#6J1(415H3h>W*p(p9@dI|c7 z&c*Aqzksg}o`D@i+o@WIw&jjvL!(`)JglV5zwMn)praO2M05H&CDeps0Wq8(8AkuE zPm|8MB6f0kOzg(gw}k>rzhQyo#<#sVdht~Wdk`y`=%0!jbd1&>Kxed8lS{Xq?Zw>* zU5;dM1tt``JH+A9@>H%-9f=EnW)UkRJe0+e^iqm0C5Z5?iEn#lbp}Xso ztleC}hl&*yPFcoCZ@sgvvjBA_Ew6msFml$cfLQY_(=h03WS_z+Leeh$M3#-?f9YT^Q($z z+pgaEv$rIa*9wST`WHASQio=9IaVS7l<87%;83~X*`{BX#@>>p=k`@FYo ze!K5_h8hOc`m0mK0p}LxsguM}w=9vw6Ku8y@RNrXSRPh&S`t4UQY=e-B8~3YCt1Fc zU$CtRW%hbcy{6K{>v0F*X<`rXVM3a{!muAeG$zBf`a(^l${EA9w3>J{aPwJT?mKVN2ba+v)Mp*~gQ_+Ws6= zy@D?85!U@VY0z9T=E9LMbe$?7_KIg)-R$tD)9NqIt84fb{B;f7C)n+B8)Cvo*F0t! zva6LeeC}AK4gL#d#N_HvvD& z0;mdU3@7%d5>h(xX-NBmJAOChtb(pX-qUtRLF5f$ z`X?Kpu?ENMc88>O&ym_$Jc7LZ> z#73|xJ|aa@l}PawS4Mpt9n)38w#q^P1w2N|rYKdcG;nb!_nHMZA_09L!j)pBK~e+j?tb-_A`wF8 zIyh>&%v=|n?+~h}%i1#^9UqZ?E9W!qJ0d0EHmioSt@%v7FzF`eM$X==#oaPESHBm@ zYzTXVo*y|C0~l_)|NF|F(If~YWJVkQAEMf5IbH{}#>PZpbXZU;+b^P8LWmlmDJ%Zu)4CajvRL!g_Faph`g0hpA2)D0|h zYy0h5+@4T81(s0D=crojdj|dYa{Y=<2zKp@xl&{sHO;#|!uTHtTey25f1U z#=Nyz{rJy#@SPk3_U|aALcg%vEjwIqSO$LZI59^;Mu~Swb53L+>oxWiN7J{;P*(2b@ao*aU~}-_j10 z@fQiaWnb}fRrHhNKrxKmi{aC#34BRP(a#0K>-J8D+v_2!~(V-6J%M@L{s?fU5ChwFfqn)2$siOUKw z?SmIRlbE8ot5P^z0J&G+rQ5}H=JE{FNsg`^jab7g-c}o`s{JS{-#}CRdW@hO`HfEp z1eR0DsN! zt5xmsYt{Uu;ZM`CgW)VYk=!$}N;w+Ct$Wf!*Z-7}@pA62F^1e$Ojz9O5H;TyT&rV( zr#IBM8te~-2t2;kv2xm&z%tt3pyt|s#vg2EOx1XkfsB*RM;D>ab$W-D6#Jdf zJ3{yD;P4=pFNk2GL$g~+5x;f9m*U2!ovWMK^U5`mAgBRhGpu)e`?#4vsE1aofu)iT zDm;aQIK6pNd8MMt@}h|t9c$)FT7PLDvu3e)y`otVe1SU4U=o@d!gn(DB9kC>Ac1wJ z?`{Hq$Q!rGb9h&VL#z+BKsLciCttdLJe9EmZF)J)c1MdVCrxg~EM80_b3k{ur=jVjrVhDK1GTjd3&t#ORvC0Q_&m|n>&TF1C_>k^8&ylR7oz#rG?mE%V| zepj0BlD|o?p8~LK_to`GINhGyW{{jZ{xqaO*SPvH)BYy1eH22DL_Kkn28N!0z3fzj z_+xZ3{ph_Tgkd)D$OjREak$O{F~mODA_D`5VsoobVnpxI zV0F_79%JB!?@jPs=cY73FhGuT!?fpVX1W=Wm zK5}i7(Pfh4o|Z{Ur=Y>bM1BDo2OdXBB(4Y#Z!61A8C6;7`6v-(P{ou1mAETEV?Nt< zMY&?ucJcJ$NyK0Zf@b;U#3ad?#dp`>zmNn=H1&-H`Y+)ai-TfyZJX@O&nRB*7j$ zDQF!q#a7VHL3z#Hc?Ca!MRbgL`daF zW#;L$yiQP|5VvgvRLluk3>-1cS+7MQ1)DC&DpYyS9j;!Rt$HdXK1}tG3G_)ZwXvGH zG;PB^f@CFrbEK4>3gTVj73~Tny+~k_pEHt|^eLw{?6NbG&`Ng9diB9XsMr(ztNC!{FhW8Hi!)TI`(Q|F*b z-z;#*c1T~kN67omP(l7)ZuTlxaC_XI(K8$VPfAzj?R**AMb0*p@$^PsN!LB@RYQ4U zA^xYY9sX4+;7gY%$i%ddfvneGfzbE4ZTJT5Vk3&1`?ULTy28&D#A&{dr5ZlZH&NTz zdfZr%Rw*Ukmgu@$C5$}QLOyb|PMA5syQns?iN@F|VFEvFPK321mTW^uv?GGNH6rnM zR9a2vB`}Y++T3Wumy$6`W)_c0PS*L;;0J^(T7<)`s{}lZVp`e)fM^?{$ zLbNw>N&6aw5Hlf_M)h8=)x0$*)V-w-Pw5Kh+EY{^$?#{v)_Y{9p5K{DjLnJ(ZUcyk*y(6D8wHB8=>Y)fb_Pw0v)Xybk`Sw@hNEaHP$-n`DtYP ziJyiauEXtuMpWyQjg$gdJR?e+=8w+=5GO-OT8pRaVFP1k^vI|I&agGjN-O*bJEK!M z`kt^POhUexh+PA&@And|vk-*MirW?>qB(f%y{ux z*d44UXxQOs+C`e-x4KSWhPg-!gO~kavIL8X3?!Ac2ih-dkK~Ua2qlcs1b-AIWg*8u z0QvL~51vS$LnmJSOnV4JUCUzg&4;bSsR5r_=FD@y|)Y2R_--e zMWJ;~*r=vJssF5_*n?wF0DO_>Mja=g+HvT=Yd^uBU|aw zRixHUQJX0Pgt-nFV+8&|;-n>!jNUj!8Y_YzH*%M!-_uWt6& z|Ec+lAD``i^do;u_?<(RpzsYZVJ8~}|NjUFgXltofbjhf!v&208g^#0h-x?`z8cInq!9kfVwJ|HQ;VK>p_-fn@(3q?e51Keq(=U-7C0#as-q z8Or}Ps07>O2@AAXz_%3bTOh{tKm#uRe}Sqr=w6-Wz$FCdfF3qNabEaj`-OfipxaL- zPh2R*l&%ZbcV?lv4C3+t2DAVSFaRo20^W_n4|0t(_*`?KmmUHG2sNZ*CRZlCFIyZbJqLdBCj)~%if)g|4NJr(8!R!E0iBbm$;`m;1n2@(8*E%B zH!g{hK|WK?1jUfM9zX?hlV#l%!6^p$$P+~rg}OdKg|d^Ed4WTY1$1J@WWHr$Os_(L z;-Zu1FJqhR4LrCUl)C~E7gA!^wtA6YIh10In9rX@LGSjnTPtLp+gPGp6u z3}{?J1!yT~?FwqT;O_-1%37f#4ek&DL){N}MX3RbNfRb-T;U^wXhx#De&QssA$lu~ mWkA_K7-+yz9tH*t6hj_Qg(_m7JaeTomk=)l!_+yTk^le-`GmOu delta 34176 zcmX7vV`H6d(}mmEwr$(CZQE$vU^m*aZQE(=WXEZ2+l}qF_w)XN>&rEBu9;)4>7EB0 zo(HR^Mh47P)@z^^pH!4#b(O8!;$>N+S+v5K5f8RrQ+Qv0_oH#e!pI2>yt4ij>fI9l zW&-hsVAQg%dpn3NRy$kb_vbM2sr`>bZ48b35m{D=OqX;p8A${^Dp|W&J5mXvUl#_I zN!~GCBUzj~C%K?<7+UZ_q|L)EGG#_*2Zzko-&Kck)Qd2%CpS3{P1co1?$|Sj1?E;PO z7alI9$X(MDly9AIEZ-vDLhpAKd1x4U#w$OvBtaA{fW9)iD#|AkMrsSaNz(69;h1iM1#_ z?u?O_aKa>vk=j;AR&*V-p3SY`CI}Uo%eRO(Dr-Te<99WQhi>y&l%UiS%W2m(d#woD zW?alFl75!1NiUzVqgqY98fSQNjhX3uZ&orB08Y*DFD;sjIddWoJF;S_@{Lx#SQk+9 zvSQ-620z0D7cy8-u_7u?PqYt?R0m2k%PWj%V(L|MCO(@3%l&pzEy7ijNv(VXU9byn z@6=4zL|qk*7!@QWd9imT9i%y}1#6+%w=s%WmsHbw@{UVc^?nL*GsnACaLnTbr9A>B zK)H-$tB`>jt9LSwaY+4!F1q(YO!E7@?SX3X-Ug4r($QrmJnM8m#;#LN`kE>?<{vbCZbhKOrMpux zTU=02hy${;n&ikcP8PqufhT9nJU>s;dyl;&~|Cs+o{9pCu{cRF+0{iyuH~6=tIZXVd zR~pJBC3Hf-g%Y|bhTuGyd~3-sm}kaX5=T?p$V?48h4{h2;_u{b}8s~Jar{39PnL7DsXpxcX#3zx@f9K zkkrw9s2*>)&=fLY{=xeIYVICff2Id5cc*~l7ztSsU@xuXYdV1(lLGZ5)?mXyIDf1- zA7j3P{C5s?$Y-kg60&XML*y93zrir8CNq*EMx)Kw)XA(N({9t-XAdX;rjxk`OF%4-0x?ne@LlBQMJe5+$Ir{Oj`@#qe+_-z!g5qQ2SxKQy1ex_x^Huj%u+S@EfEPP-70KeL@7@PBfadCUBt%`huTknOCj{ z;v?wZ2&wsL@-iBa(iFd)7duJTY8z-q5^HR-R9d*ex2m^A-~uCvz9B-1C$2xXL#>ow z!O<5&jhbM&@m=l_aW3F>vjJyy27gY}!9PSU3kITbrbs#Gm0gD?~Tub8ZFFK$X?pdv-%EeopaGB#$rDQHELW!8bVt`%?&>0 zrZUQ0!yP(uzVK?jWJ8^n915hO$v1SLV_&$-2y(iDIg}GDFRo!JzQF#gJoWu^UW0#? z*OC-SPMEY!LYY*OO95!sv{#-t!3Z!CfomqgzFJld>~CTFKGcr^sUai5s-y^vI5K={ z)cmQthQuKS07e8nLfaIYQ5f}PJQqcmokx?%yzFH*`%k}RyXCt1Chfv5KAeMWbq^2MNft;@`hMyhWg50(!jdAn;Jyx4Yt)^^DVCSu?xRu^$*&&=O6#JVShU_N3?D)|$5pyP8A!f)`| z>t0k&S66T*es5(_cs>0F=twYJUrQMqYa2HQvy)d+XW&rai?m;8nW9tL9Ivp9qi2-` zOQM<}D*g`28wJ54H~1U!+)vQh)(cpuf^&8uteU$G{9BUhOL| zBX{5E1**;hlc0ZAi(r@)IK{Y*ro_UL8Ztf8n{Xnwn=s=qH;fxkK+uL zY)0pvf6-iHfX+{F8&6LzG;&d%^5g`_&GEEx0GU=cJM*}RecV-AqHSK@{TMir1jaFf&R{@?|ieOUnmb?lQxCN!GnAqcii9$ z{a!Y{Vfz)xD!m2VfPH=`bk5m6dG{LfgtA4ITT?Sckn<92rt@pG+sk>3UhTQx9ywF3 z=$|RgTN<=6-B4+UbYWxfQUOe8cmEDY3QL$;mOw&X2;q9x9qNz3J97)3^jb zdlzkDYLKm^5?3IV>t3fdWwNpq3qY;hsj=pk9;P!wVmjP|6Dw^ez7_&DH9X33$T=Q{>Nl zv*a*QMM1-2XQ)O=3n@X+RO~S`N13QM81^ZzljPJIFBh%x<~No?@z_&LAl)ap!AflS zb{yFXU(Uw(dw%NR_l7%eN2VVX;^Ln{I1G+yPQr1AY+0MapBnJ3k1>Zdrw^3aUig*! z?xQe8C0LW;EDY(qe_P!Z#Q^jP3u$Z3hQpy^w7?jI;~XTz0ju$DQNc4LUyX}+S5zh> zGkB%~XU+L?3pw&j!i|x6C+RyP+_XYNm9`rtHpqxvoCdV_MXg847oHhYJqO+{t!xxdbsw4Ugn($Cwkm^+36&goy$vkaFs zrH6F29eMPXyoBha7X^b+N*a!>VZ<&Gf3eeE+Bgz7PB-6X7 z_%2M~{sTwC^iQVjH9#fVa3IO6E4b*S%M;#WhHa^L+=DP%arD_`eW5G0<9Tk=Ci?P@ z6tJXhej{ZWF=idj32x7dp{zmQY;;D2*11&-(~wifGXLmD6C-XR=K3c>S^_+x!3OuB z%D&!EOk;V4Sq6eQcE{UEDsPMtED*;qgcJU^UwLwjE-Ww54d73fQ`9Sv%^H>juEKmxN+*aD=0Q+ZFH1_J(*$~9&JyUJ6!>(Nj zi3Z6zWC%Yz0ZjX>thi~rH+lqv<9nkI3?Ghn7@!u3Ef){G(0Pvwnxc&(YeC=Kg2-7z zr>a^@b_QClXs?Obplq@Lq-l5>W);Y^JbCYk^n8G`8PzCH^rnY5Zk-AN6|7Pn=oF(H zxE#8LkI;;}K7I^UK55Z)c=zn7OX_XVgFlEGSO}~H^y|wd7piw*b1$kA!0*X*DQ~O` z*vFvc5Jy7(fFMRq>XA8Tq`E>EF35{?(_;yAdbO8rrmrlb&LceV%;U3haVV}Koh9C| zTZnR0a(*yN^Hp9u*h+eAdn)d}vPCo3k?GCz1w>OOeme(Mbo*A7)*nEmmUt?eN_vA; z=~2}K_}BtDXJM-y5fn^v>QQo+%*FdZQFNz^j&rYhmZHgDA-TH47#Wjn_@iH4?6R{J z%+C8LYIy>{3~A@|y4kN8YZZp72F8F@dOZWp>N0-DyVb4UQd_t^`P)zsCoygL_>>x| z2Hyu7;n(4G&?wCB4YVUIVg0K!CALjRsb}&4aLS|}0t`C}orYqhFe7N~h9XQ_bIW*f zGlDCIE`&wwyFX1U>}g#P0xRRn2q9%FPRfm{-M7;}6cS(V6;kn@6!$y06lO>8AE_!O z{|W{HEAbI0eD$z9tQvWth7y>qpTKQ0$EDsJkQxAaV2+gE28Al8W%t`Pbh zPl#%_S@a^6Y;lH6BfUfZNRKwS#x_keQ`;Rjg@qj zZRwQXZd-rWngbYC}r6X)VCJ-=D54A+81%(L*8?+&r7(wOxDSNn!t(U}!;5|sjq zc5yF5$V!;%C#T+T3*AD+A({T)#p$H_<$nDd#M)KOLbd*KoW~9E19BBd-UwBX1<0h9 z8lNI&7Z_r4bx;`%5&;ky+y7PD9F^;Qk{`J@z!jJKyJ|s@lY^y!r9p^75D)_TJ6S*T zLA7AA*m}Y|5~)-`cyB+lUE9CS_`iB;MM&0fX**f;$n($fQ1_Zo=u>|n~r$HvkOUK(gv_L&@DE0b4#ya{HN)8bNQMl9hCva zi~j0v&plRsp?_zR zA}uI4n;^_Ko5`N-HCw_1BMLd#OAmmIY#ol4M^UjLL-UAat+xA+zxrFqKc@V5Zqan_ z+LoVX-Ub2mT7Dk_ z<+_3?XWBEM84@J_F}FDe-hl@}x@v-s1AR{_YD!_fMgagH6s9uyi6pW3gdhauG>+H? zi<5^{dp*5-9v`|m*ceT&`Hqv77oBQ+Da!=?dDO&9jo;=JkzrQKx^o$RqAgzL{ zjK@n)JW~lzxB>(o(21ibI}i|r3e;17zTjdEl5c`Cn-KAlR7EPp84M@!8~CywES-`mxKJ@Dsf6B18_!XMIq$Q3rTDeIgJ3X zB1)voa#V{iY^ju>*Cdg&UCbx?d3UMArPRHZauE}c@Fdk;z85OcA&Th>ZN%}=VU%3b9={Q(@M4QaeuGE(BbZ{U z?WPDG+sjJSz1OYFpdImKYHUa@ELn%n&PR9&I7B$<-c3e|{tPH*u@hs)Ci>Z@5$M?lP(#d#QIz}~()P7mt`<2PT4oHH}R&#dIx4uq943D8gVbaa2&FygrSk3*whGr~Jn zR4QnS@83UZ_BUGw;?@T zo5jA#potERcBv+dd8V$xTh)COur`TQ^^Yb&cdBcesjHlA3O8SBeKrVj!-D3+_p6%P zP@e{|^-G-C(}g+=bAuAy8)wcS{$XB?I=|r=&=TvbqeyXiuG43RR>R72Ry7d6RS;n^ zO5J-QIc@)sz_l6%Lg5zA8cgNK^GK_b-Z+M{RLYk5=O|6c%!1u6YMm3jJg{TfS*L%2 zA<*7$@wgJ(M*gyTzz8+7{iRP_e~(CCbGB}FN-#`&1ntct@`5gB-u6oUp3#QDxyF8v zOjxr}pS{5RpK1l7+l(bC)0>M;%7L?@6t}S&a zx0gP8^sXi(g2_g8+8-1~hKO;9Nn%_S%9djd*;nCLadHpVx(S0tixw2{Q}vOPCWvZg zjYc6LQ~nIZ*b0m_uN~l{&2df2*ZmBU8dv`#o+^5p>D5l%9@(Y-g%`|$%nQ|SSRm0c zLZV)45DS8d#v(z6gj&6|ay@MP23leodS8-GWIMH8_YCScX#Xr)mbuvXqSHo*)cY9g z#Ea+NvHIA)@`L+)T|f$Etx;-vrE3;Gk^O@IN@1{lpg&XzU5Eh3!w;6l=Q$k|%7nj^ z|HGu}c59-Ilzu^w<93il$cRf@C(4Cr2S!!E&7#)GgUH@py?O;Vl&joXrep=2A|3Vn zH+e$Ctmdy3B^fh%12D$nQk^j|v=>_3JAdKPt2YVusbNW&CL?M*?`K1mK*!&-9Ecp~>V1w{EK(429OT>DJAV21fG z=XP=%m+0vV4LdIi#(~XpaUY$~fQ=xA#5?V%xGRr_|5WWV=uoG_Z&{fae)`2~u{6-p zG>E>8j({w7njU-5Lai|2HhDPntQ(X@yB z9l?NGoKB5N98fWrkdN3g8ox7Vic|gfTF~jIfXkm|9Yuu-p>v3d{5&hC+ZD%mh|_=* zD5v*u(SuLxzX~owH!mJQi%Z=ALvdjyt9U6baVY<88B>{HApAJ~>`buHVGQd%KUu(d z5#{NEKk6Vy08_8*E(?hqZe2L?P2$>!0~26N(rVzB9KbF&JQOIaU{SumX!TsYzR%wB z<5EgJXDJ=1L_SNCNZcBWBNeN+Y`)B%R(wEA?}Wi@mp(jcw9&^1EMSM58?68gwnXF` zzT0_7>)ep%6hid-*DZ42eU)tFcFz7@bo=<~CrLXpNDM}tv*-B(ZF`(9^RiM9W4xC%@ZHv=>w(&~$Wta%)Z;d!{J;e@z zX1Gkw^XrHOfYHR#hAU=G`v43E$Iq}*gwqm@-mPac0HOZ0 zVtfu7>CQYS_F@n6n#CGcC5R%4{+P4m7uVlg3axX}B(_kf((>W?EhIO&rQ{iUO$16X zv{Abj3ZApUrcar7Ck}B1%RvnR%uocMlKsRxV9Qqe^Y_5C$xQW@9QdCcF%W#!zj;!xWc+0#VQ*}u&rJ7)zc+{vpw+nV?{tdd&Xs`NV zKUp|dV98WbWl*_MoyzM0xv8tTNJChwifP!9WM^GD|Mkc75$F;j$K%Y8K@7?uJjq-w zz*|>EH5jH&oTKlIzueAN2926Uo1OryC|CmkyoQZABt#FtHz)QmQvSX35o`f z<^*5XXxexj+Q-a#2h4(?_*|!5Pjph@?Na8Z>K%AAjNr3T!7RN;7c)1SqAJfHY|xAV z1f;p%lSdE8I}E4~tRH(l*rK?OZ>mB4C{3e%E-bUng2ymerg8?M$rXC!D?3O}_mka? zm*Y~JMu+_F7O4T;#nFv)?Ru6 z92r|old*4ZB$*6M40B;V&2w->#>4DEu0;#vHSgXdEzm{+VS48 z7U1tVn#AnQ3z#gP26$!dmS5&JsXsrR>~rWA}%qd{92+j zu+wYAqrJYOA%WC9nZ>BKH&;9vMSW_59z5LtzS4Q@o5vcrWjg+28#&$*8SMYP z!l5=|p@x6YnmNq>23sQ(^du5K)TB&K8t{P`@T4J5cEFL@qwtsCmn~p>>*b=37y!kB zn6x{#KjM{S9O_otGQub*K)iIjtE2NfiV~zD2x{4r)IUD(Y8%r`n;#)ujIrl8Sa+L{ z>ixGoZJ1K@;wTUbRRFgnltN_U*^EOJS zRo4Y+S`cP}e-zNtdl^S5#%oN#HLjmq$W^(Y6=5tM#RBK-M14RO7X(8Gliy3+&9fO; zXn{60%0sWh1_g1Z2r0MuGwSGUE;l4TI*M!$5dm&v9pO7@KlW@j_QboeDd1k9!7S)jIwBza-V#1)(7ht|sjY}a19sO!T z2VEW7nB0!zP=Sx17-6S$r=A)MZikCjlQHE)%_Ka|OY4+jgGOw=I3CM`3ui^=o0p7u z?xujpg#dRVZCg|{%!^DvoR*~;QBH8ia6%4pOh<#t+e_u!8gjuk_Aic=|*H24Yq~Wup1dTRQs0nlZOy+30f16;f7EYh*^*i9hTZ`h`015%{i|4 z?$7qC3&kt#(jI#<76Biz=bl=k=&qyaH>foM#zA7}N`Ji~)-f-t&tR4^do)-5t?Hz_Q+X~S2bZx{t+MEjwy3kGfbv(ij^@;=?H_^FIIu*HP_7mpV)NS{MY-Rr7&rvWo@Wd~{Lt!8|66rq`GdGu% z@<(<7bYcZKCt%_RmTpAjx=TNvdh+ZiLkMN+hT;=tC?%vQQGc7WrCPIYZwYTW`;x|N zrlEz1yf95FiloUU^(onr3A3>+96;;6aL?($@!JwiQ2hO|^i)b4pCJ7-y&a~B#J`#FO!3uBp{5GBvM2U@K85&o0q~6#LtppE&cVY z3Bv{xQ-;i}LN-60B2*1suMd=Fi%Y|7@52axZ|b=Wiwk^5eg{9X4}(q%4D5N5_Gm)` zg~VyFCwfkIKW(@@ZGAlTra6CO$RA_b*yz#){B82N7AYpQ9)sLQfhOAOMUV7$0|d$=_y&jl>va$3u-H z_+H*|UXBPLe%N2Ukwu1*)kt!$Y>(IH3`YbEt; znb1uB*{UgwG{pQnh>h@vyCE!6B~!k}NxEai#iY{$!_w54s5!6jG9%pr=S~3Km^EEA z)sCnnau+ZY)(}IK#(3jGGADw8V7#v~<&y5cF=5_Ypkrs3&7{}%(4KM7) zuSHVqo~g#1kzNwXc39%hL8atpa1Wd#V^uL=W^&E)fvGivt)B!M)?)Y#Ze&zU6O_I?1wj)*M;b*dE zqlcwgX#eVuZj2GKgBu@QB(#LHMd`qk<08i$hG1@g1;zD*#(9PHjVWl*5!;ER{Q#A9 zyQ%fu<$U?dOW=&_#~{nrq{RRyD8upRi}c-m!n)DZw9P>WGs>o1vefI}ujt_`O@l#Z z%xnOt4&e}LlM1-0*dd?|EvrAO-$fX8i{aTP^2wsmSDd!Xc9DxJB=x1}6|yM~QQPbl z0xrJcQNtWHgt*MdGmtj%x6SWYd?uGnrx4{m{6A9bYx`m z$*UAs@9?3s;@Jl19%$!3TxPlCkawEk12FADYJClt0N@O@Pxxhj+Kk(1jK~laR0*KGAc7%C4nI^v2NShTc4#?!p{0@p0T#HSIRndH;#Ts0YECtlSR}~{Uck+keoJq6iH)(Zc~C!fBe2~4(Wd> zR<4I1zMeW$<0xww(@09!l?;oDiq zk8qjS9Lxv$<5m#j(?4VLDgLz;8b$B%XO|9i7^1M;V{aGC#JT)c+L=BgCfO5k>CTlI zOlf~DzcopV29Dajzt*OcYvaUH{UJPaD$;spv%>{y8goE+bDD$~HQbON>W*~JD`;`- zZEcCPSdlCvANe z=?|+e{6AW$f(H;BND>uy1MvQ`pri>SafK5bK!YAE>0URAW9RS8#LWUHBOc&BNQ9T+ zJpg~Eky!u!9WBk)!$Z?!^3M~o_VPERYnk1NmzVYaGH;1h+;st==-;jzF~2LTn+x*k zvywHZg7~=aiJe=OhS@U>1fYGvT1+jsAaiaM;) zay2xsMKhO+FIeK?|K{G4SJOEt*eX?!>K8jpsZWW8c!X|JR#v(1+Ey5NM^TB1n|_40 z@Db2gH}PNT+3YEyqXP8U@)`E|Xat<{K5K;eK7O0yV72m|b!o43!e-!P>iW>7-9HN7 zmmc7)JX0^lPzF#>$#D~nU^3f!~Q zQWly&oZEb1847&czU;dg?=dS>z3lJkADL1innNtE(f?~OxM`%A_PBp?Lj;zDDomf$ z;|P=FTmqX|!sHO6uIfCmh4Fbgw@`DOn#`qAPEsYUiBvUlw zevH{)YWQu>FPXU$%1!h*2rtk_J}qNkkq+StX8Wc*KgG$yH#p-kcD&)%>)Yctb^JDB zJe>=!)5nc~?6hrE_3n^_BE<^;2{}&Z>Dr)bX>H{?kK{@R)`R5lnlO6yU&UmWy=d03 z*(jJIwU3l0HRW1PvReOb|MyZT^700rg8eFp#p<3Et%9msiCxR+jefK%x81+iN0=hG z;<`^RUVU+S)Iv-*5y^MqD@=cp{_cP4`s=z)Ti3!Bf@zCmfpZTwf|>|0t^E8R^s`ad z5~tA?0x7OM{*D;zb6bvPu|F5XpF11`U5;b*$p zNAq7E6c=aUnq>}$JAYsO&=L^`M|DdSSp5O4LA{|tO5^8%Hf1lqqo)sj=!aLNKn9(3 zvKk($N`p`f&u+8e^Z-?uc2GZ_6-HDQs@l%+pWh!|S9+y3!jrr3V%cr{FNe&U6(tYs zLto$0D+2}K_9kuxgFSeQ!EOXjJtZ$Pyl_|$mPQ9#fES=Sw8L% zO7Jij9cscU)@W+$jeGpx&vWP9ZN3fLDTp zaYM$gJD8ccf&g>n?a56X=y zec%nLN`(dVCpSl9&pJLf2BN;cR5F0Nn{(LjGe7RjFe7efp3R_2JmHOY#nWEc2TMhMSj5tBf-L zlxP3sV`!?@!mRnDTac{35I7h@WTfRjRiFw*Q*aD8)n)jdkJC@)jD-&mzAdK6Kqdct8P}~dqixq;n zjnX!pb^;5*Rr?5ycT7>AB9)RED^x+DVDmIbHKjcDv2lHK;apZOc=O@`4nJ;k|iikKk66v4{zN#lmSn$lh z_-Y3FC)iV$rFJH!#mNqWHF-DtSNbI)84+VLDWg$ph_tkKn_6+M1RZ!)EKaRhY={el zG-i@H!fvpH&4~$5Q+zHU(Ub=;Lzcrc3;4Cqqbr$O`c5M#UMtslK$3r+Cuz>xKl+xW?`t2o=q`1djXC=Q6`3C${*>dm~I{ z(aQH&Qd{{X+&+-4{epSL;q%n$)NOQ7kM}ea9bA++*F+t$2$%F!U!U}(&y7Sd0jQMV zkOhuJ$+g7^kb<`jqFiq(y1-~JjP13J&uB=hfjH5yAArMZx?VzW1~>tln~d5pt$uWR~TM!lIg+D)prR zocU0N2}_WTYpU`@Bsi1z{$le`dO{-pHFQr{M}%iEkX@0fv!AGCTcB90@e|slf#unz z*w4Cf>(^XI64l|MmWih1g!kwMJiifdt4C<5BHtaS%Ra>~3IFwjdu;_v*7BL|fPu+c zNp687`{}e@|%)5g4U*i=0zlSWXzz=YcZ*&Bg zr$r(SH0V5a%oHh*t&0y%R8&jDI=6VTWS_kJ!^WN!ET@XfEHYG-T1jJsDd`yEgh!^* z+!P62=v`R2=TBVjt=h}|JIg7N^RevZuyxyS+jsk>=iLA52Ak+7L?2$ZDUaWdi1PgB z_;*Uae_n&7o27ewV*y(wwK~8~tU<#Np6UUIx}zW6fR&dKiPq|$A{BwG_-wVfkm+EP zxHU@m`im3cD#fH63>_X`Il-HjZN_hqOVMG;(#7RmI13D-s_>41l|vDH1BglPsNJ+p zTniY{Hwoief+h%C^|@Syep#722=wmcTR7awIzimAcye?@F~f|n<$%=rM+Jkz9m>PF70$)AK@|h_^(zn?!;={;9Zo7{ zBI7O?6!J2Ixxk;XzS~ScO9{K1U9swGvR_d+SkromF040|Slk%$)M;9O_8h0@WPe4= z%iWM^ust8w$(NhO)7*8uq+9CycO$3m-l}O70sBi<4=j0CeE_&3iRUWJkDM$FIfrkR zHG2|hVh3?Nt$fdI$W?<|Qq@#hjDijk@7eUr1&JHYI>(_Q4^3$+Zz&R)Z`WqhBIvjo zX#EbA8P0Qla-yACvt)%oAVHa#kZi3Y8|(IOp_Z6J-t{)98*OXQ#8^>vTENsV@(M}^ z(>8BXw`{+)BfyZB!&85hT0!$>7$uLgp9hP9M7v=5@H`atsri1^{1VDxDqizj46-2^ z?&eA9udH#BD|QY2B7Zr$l;NJ-$L!u8G{MZoX)~bua5J=0p_JnM`$(D4S!uF}4smWq zVo%kQ~C~X?cWCH zo4s#FqJ)k|D{c_ok+sZ8`m2#-Uk8*o)io`B+WTD0PDA!G`DjtibftJXhPVjLZj~g& z=MM9nF$7}xvILx}BhM;J-Xnz0=^m1N2`Mhn6@ct+-!ijIcgi6FZ*oIPH(tGYJ2EQ0 z{;cjcc>_GkAlWEZ2zZLA_oa-(vYBp7XLPbHCBcGH$K9AK6nx}}ya%QB2=r$A;11*~ z_wfru1SkIQ0&QUqd)%eAY^FL!G;t@7-prQ|drDn#yDf%Uz8&kGtrPxKv?*TqkC(}g zUx10<;3Vhnx{gpWXM8H zKc0kkM~gIAts$E!X-?3DWG&^knj4h(q5(L;V81VWyC@_71oIpXfsb0S(^Js#N_0E} zJ%|XX&EeVPyu}? zz~(%slTw+tcY3ZMG$+diC8zed=CTN}1fB`RXD_v2;{evY z@MCG$l9Az+F()8*SqFyrg3jrN7k^x3?;A?L&>y{ZUi$T8!F7Dv8s}}4r9+Wo0h^m= zAob@CnJ;IR-{|_D;_w)? zcH@~&V^(}Ag}%A90);X2AhDj(-YB>$>GrW1F4C*1S5`u@N{T|;pYX1;E?gtBbPvS* zlv3r#rw2KCmLqX0kGT8&%#A6Sc(S>apOHtfn+UdYiN4qPawcL{Sb$>&I)Ie>Xs~ej z7)a=-92!sv-A{-7sqiG-ysG0k&beq6^nX1L!Fs$JU#fsV*CbsZqBQ|y z{)}zvtEwO%(&mIG|L?qs2Ou1rqTZHV@H+sm8Nth(+#dp0DW4VXG;;tCh`{BpY)THY z_10NNWpJuzCG%Q@#Aj>!v7Eq8eI6_JK3g2CsB2jz)2^bWiM{&U8clnV7<2?Qx5*k_ zl9B$P@LV7Sani>Xum{^yJ6uYxM4UHnw4zbPdM|PeppudXe}+OcX z!nr!xaUA|xYtA~jE|436iL&L={H3e}H`M1;2|pLG)Z~~Ug9X%_#D!DW>w}Es!D{=4 zxRPBf5UWm2{}D>Em;v43miQ~2{>%>O*`wA{7j;yh;*DV=C-bs;3p{AD;>VPcn>E;V zLgtw|Y{|Beo+_ABz`lofH+cdf33LjIf!RdcW~wWgmsE%2yCQGbst4TS_t%6nS8a+m zFEr<|9TQzQC@<(yNN9GR4S$H-SA?xiLIK2O2>*w-?cdzNPsG4D3&%$QOK{w)@Dk}W z|3_Z>U`XBu7j6Vc=es(tz}c7k4al1$cqDW4a~|xgE9zPX(C`IsN(QwNomzsBOHqjd zi{D|jYSv5 zC>6#uB~%#!!*?zXW`!yHWjbjwm!#eo3hm;>nJ!<`ZkJamE6i>>WqkoTpbm(~b%G_v z`t3Z#ERips;EoA_0c?r@WjEP|ulD+hue5r8946Sd0kuBD$A!=dxigTZn)u3>U;Y8l zX9j(R*(;;i&HrB&M|Xnitzf@><3#)aKy=bFCf5Hz@_);{nlL?J!U>%fL$Fk~Ocs3& zB@-Ek%W>h9#$QIYg07&lS_CG3d~LrygXclO!Ws-|PxMsn@n{?77wCaq?uj`dd7lllDCGd?ed&%5k{RqUhiN1u&?uz@Fq zNkv_4xmFcl?vs>;emR1R<$tg;*Ayp@rl=ik z=x2Hk zJqsM%++e|*+#camAiem6f;3-khtIgjYmNL0x|Mz|y{r{6<@_&a7^1XDyE>v*uo!qF zBq^I8PiF#w<-lFvFx9xKoi&0j)4LX~rWsK$%3hr@ebDv^($$T^4m4h#Q-(u*Mbt6F zE%y0Fvozv=WAaTj6EWZ)cX{|9=AZDvPQuq>2fUkU(!j1GmdgeYLX`B0BbGK(331ME zu3yZ3jQ@2)WW5!C#~y}=q5Av=_;+hNi!%gmY;}~~e!S&&^{4eJuNQ2kud%Olf8TRI zW-Dze987Il<^!hCO{AR5tLW{F1WLuZ>nhPjke@CSnN zzoW{m!+PSCb7byUf-1b;`{0GU^zg7b9c!7ueJF`>L;|akVzb&IzoLNNEfxp7b7xMN zKs9QG6v@t7X)yYN9}3d4>*ROMiK-Ig8(Do$3UI&E}z!vcH2t(VIk-cLyC-Y%`)~>Ce23A=dQsc<( ziy;8MmHki+5-(CR8$=lRt{(9B9W59Pz|z0^;`C!q<^PyE$KXt!KibFH*xcB9V%xTD zn;YlZ*tTukwr$(mWMka@|8CW-J8!zCXI{P1-&=wSvZf&%9SZ7m`1&2^nV#D z6T*)`Mz3wGUC69Fg0Xk!hwY}ykk!TE%mr57TLX*U4ygwvM^!#G`HYKLIN>gT;?mo% zAxGgzSnm{}vRG}K)8n(XjG#d+IyAFnozhk|uwiey(p@ zu>j#n4C|Mhtd=0G?Qn5OGh{{^MWR)V*geNY8d)py)@5a85G&_&OSCx4ASW8g&AEXa zC}^ET`eORgG*$$Q1L=9_8MCUO4Mr^1IA{^nsB$>#Bi(vN$l8+p(U^0dvN_{Cu-UUm zQyJc!8>RWp;C3*2dGp49QVW`CRR@no(t+D|@nl138lu@%c1VCy3|v4VoKZ4AwnnjF z__8f$usTzF)TQ$sQ^|#(M}-#0^3Ag%A0%5vA=KK$37I`RY({kF-z$(P50pf3_20YTr%G@w+bxE_V+Tt^YHgrlu$#wjp7igF!=o8e2rqCs|>XM9+M7~TqI&fcx z=pcX6_MQQ{TIR6a0*~xdgFvs<2!yaA1F*4IZgI!)xnzJCwsG&EElg_IpFbrT}nr)UQy}GiK;( zDlG$cksync34R3J^FqJ=={_y9x_pcd%$B*u&vr7^ItxqWFIAkJgaAQiA)pioK1JQ| zYB_6IUKc$UM*~f9{Xzw*tY$pUglV*?BDQuhsca*Fx!sm`9y`V&?lVTH%%1eJ74#D_ z7W+@8@7LAu{aq)sPys{MM~;`k>T%-wPA)E2QH7(Z4XEUrQ5YstG`Uf@w{n_Oc!wem z7=8z;k$N{T74B*zVyJI~4d60M09FYG`33;Wxh=^Ixhs69U_SG_deO~_OUO1s9K-8p z5{HmcXAaKqHrQ@(t?d@;63;Pnj2Kk<;Hx=kr>*Ko`F*l){%GVDj5nkohSU)B&5Vrc zo0u%|b%|VITSB)BXTRPQC=Bv=qplloSI#iKV#~z#t#q*jcS`3s&w-z^m--CYDI7n2 z%{LHFZ*(1u4DvhES|Dc*n%JL8%8?h7boNf|qxl8D)np@5t~VORwQn)TuSI07b-T=_ zo8qh+0yf|-6=x;Ra$w&WeVZhUO%3v6Ni*}i&sby3s_(?l5Er{K9%0_dE<`7^>8mLr zZ|~l#Bi@5}8{iZ$(d9)!`}@2~#sA~?uH|EbrJQcTw|ssG)MSJJIF96-_gf&* zy~I&$m6e0nnLz^M2;G|IeUk?s+afSZ){10*P~9W%RtYeSg{Nv5FG<2QaWpj?d`;}<4( z>V1i|wNTpH`jJtvTD0C3CTws410U9HS_%Ti2HaB~%^h6{+$@5`K9}T=eQL;dMZ?=Y zX^z?B3ZU_!E^OW%Z*-+t&B-(kLmDwikb9+F9bj;NFq-XHRB=+L)Rew{w|7p~7ph{#fRT}}K zWA)F7;kJBCk^aFILnkV^EMs=B~#qh*RG2&@F|x2$?7QTX_T6qL?i$c6J*-cNQC~E6dro zR)CGIoz;~V?=>;(NF4dihkz~Koqu}VNPE9^R{L@e6WkL{fK84H?C*uvKkO(!H-&y( zq|@B~juu*x#J_i3gBrS0*5U*%NDg+Ur9euL*5QaF^?-pxxieMM6k_xAP;S}sfKmIa zj(T6o{4RfARHz25YWzv=QaJ4P!O$LHE(L~6fB89$`6+olZR!#%y?_v+Cf+g)5#!ZM zkabT-y%v|ihYuV}Y%-B%pxL264?K%CXlbd_s<GY5BG*`kYQjao$QHiC_qPk5uE~AO+F=eOtTWJ1vm*cU(D5kvs3kity z$IYG{$L<8|&I>|WwpCWo5K3!On`)9PIx(uWAq>bSQTvSW`NqgprBIuV^V>C~?+d(w$ZXb39Vs`R=BX;4HISfN^qW!{4 z^amy@Nqw6oqqobiNlxzxU*z2>2Q;9$Cr{K;*&l!;Y??vi^)G|tefJG9utf|~4xh=r3UjmRlADyLC*i`r+m;$7?7*bL!oR4=yU<8<-3XVA z%sAb`xe&4RV(2vj+1*ktLs<&m~mGJ@RuJ)1c zLxZyjg~*PfOeAm8R>7e&#FXBsfU_?azU=uxBm=E6z7FSr7J>{XY z1qUT>dh`X(zHRML_H-7He^P_?148AkDqrb>;~1M-k+xHVy>;D7p!z=XBgxMGQX2{* z-xMCOwS33&K^~3%#k`eIjKWvNe1f3y#}U4;J+#-{;=Xne^6+eH@eGJK#i|`~dgV5S zdn%`RHBsC!=9Q=&=wNbV#pDv6rgl?k1wM03*mN`dQBT4K%uRoyoH{e=ZL5E*`~X|T zbKG9aWI}7NGTQtjc3BYDTY3LbkgBNSHG$5xVx8gc@dEuJqT~QPBD=Scf53#kZzZ6W zM^$vkvMx+-0$6R^{{hZ2qLju~e85Em>1nDcRN3-Mm7x;87W#@RSIW9G>TT6Q{4e~b z8DN%n83FvXWdpr|I_8TaMv~MCqq0TA{AXYO-(~l=ug42gpMUvOjG_pWSEdDJ2Bxqz z!em;9=7y3HW*XUtK+M^)fycd8A6Q@B<4biGAR)r%gQf>lWI%WmMbij;un)qhk$bff zQxb{&L;`-1uvaCE7Fm*83^0;!QA5-zeSvKY}WjbwE68)jqnOmj^CTBHaD zvK6}Mc$a39b~Y(AoS|$%ePoHgMjIIux?;*;=Y|3zyfo)^fM=1GBbn7NCuKSxp1J|z zC>n4!X_w*R8es1ofcPrD>%e=E*@^)7gc?+JC@mJAYsXP;10~gZv0!Egi~){3mjVzs z^PrgddFewu>Ax_G&tj-!L=TuRl0FAh#X0gtQE#~}(dSyPO=@7yd zNC6l_?zs_u5&x8O zQ|_JvKf!WHf43F0R%NQwGQi-Dy7~PGZ@KRKMp?kxlaLAV=X{UkKgaTu2!qzPi8aJ z-;n$}unR?%uzCkMHwb56T%IUV)h>qS(XiuRLh3fdlr!Cri|{fZf0x9GVYUOlsKgxLA7vHrkpQddcSsg4JfibzpB zwR!vYiL)7%u8JG7^x@^px(t-c_Xt|9Dm)C@_zGeW_3nMLZBA*9*!fLTV$Uf1a0rDt zJI@Z6pdB9J(a|&T_&AocM2WLNB;fpLnlOFtC9yE6cb39?*1@wy8UgruTtX?@=<6YW zF%82|(F7ANWQ`#HPyPqG6~ggFlhJW#R>%p@fzrpL^K)Kbwj(@#7s97r`)iJ{&-ToR z$7(mQI@~;lwY+8dSKP~0G|#sjL2lS0LQP3Oe=>#NZ|JKKYd6s6qwe#_6Xz_^L4PJ5TM_|#&~zy= zabr|kkr3Osj;bPz`B0s;c&kzzQ2C8|tC9tz;es~zr{hom8bT?t$c|t;M0t2F{xI;G z`0`ADc_nJSdT`#PYCWu4R0Rmbk#PARx(NBfdU>8wxzE(`jA}atMEsaG6zy8^^nCu| z9_tLj90r-&Xc~+p%1vyt>=q_hQsDYB&-hPj(-OGxFpesWm;A(Lh>UWy4SH9&+mB(A z2jkTQ2C&o(Q4wC_>|c()M8_kF?qKhNB+PW6__;U+?ZUoDp2GNr<|*j(CC*#v0{L2E zgVBw6|3c(~V4N*WgJsO(I3o>8)EO5;p7Xg8yU&%rZ3QSRB6Ig6MK7Wn5r+xo2V}fM z0QpfDB9^xJEi}W*Fv6>=p4%@eP`K5k%kCE0YF2Eu5L!DM1ZY7wh`kghC^NwxrL}90dRXjQx=H>8 zOWP@<+C!tcw8EL8aCt9{|4aT+x|70i6m*LP*lhp;kGr5f#OwRy`(60LK@rd=to5yk^%N z6MTSk)7)#!cGDV@pbQ>$N8i2rAD$f{8T{QM+|gaj^sBt%24UJGF4ufrG1_Ag$Rn?c zzICg9`ICT>9N_2vqvVG#_lf9IEd%G5gJ_!j)1X#d^KUJBkE9?|K03AEe zo>5Rql|WuUU=LhLRkd&0rH4#!!>sMg@4Wr=z2|}dpOa`4c;_DqN{3Pj`AgSnc;h%# z{ny1lK%7?@rwZO(ZACq#8mL)|vy8tO0d1^4l;^e?hU+zuH%-8Y^5YqM9}sRzr-XC0 zPzY1l($LC-yyy*1@eoEANoTLQAZ2lVto2r7$|?;PPQX`}rbxPDH-a$8ez@J#v0R5n z7P*qT3aHj02*cK)WzZmoXkw?e3XNu&DkElGZ0Nk~wBti%yLh+l2DYx&U1lD_NW_Yt zGN>yOF?u%ksMW?^+~2&p@NoPzk`T)8qifG_owD>@iwI3@u^Y;Mqaa!2DGUKi{?U3d z|Efe=CBc!_ZDoa~LzZr}%;J|I$dntN24m4|1(#&Tw0R}lP`a`?uT;>szf^0mDJx3u z6IJvpeOpS$OV!Xw21p>Xu~MZ(Nas5Iim-#QSLIYSNhYgx1V!AR>b zf5b7O`ITTvW5z%X8|7>&BeEs8~J1i47l;`7Y#MUMReQ4z!IL1rh8UauKNPG?7rV_;#Y zG*6Vrt^SsTMOpV7mkui}l_S8UNOBcYi+DzcMF>YKrs3*(q5fwVCr;_zO?gpGx*@%O zl`KOwYMSUs4e&}eM#FhB3(RIDJ9ZRn6NN{2Nf+ z2jcz%-u6IPq{n7N3wLH{9c+}4G(NyZa`UmDr5c-SPgj0Sy$VN#Vxxr;kF>-P;5k!w zuAdrP(H+v{Dybn78xM6^*Ym@UGxx?L)m}WY#R>6M2zXnPL_M9#h($ECz^+(4HmKN7 zA>E;`AEqouHJd7pegrq4zkk>kHh`TEb`^(_ea;v{?MW3Sr^FXegkqAQPM-h^)$#Jn z?bKbnXR@k~%*?q`TPL=sD8C+n^I#08(}d$H(@Y;3*{~nv4RLZLw`v=1M0-%j>CtT( zTp#U03GAv{RFAtj4vln4#E4eLOvt zs;=`m&{S@AJbcl1q^39VOtmN^Zm(*x(`(SUgF(=6#&^7oA8T_ojX>V5sJx@*cV|29 z)6_%P6}e}`58Sd;LY2cWv~w}fer&_c1&mlY0`YNNk9q=TRg@Khc5E$N`aYng=!afD z@ewAv^jl$`U5;q4OxFM4ab%X_Jv>V!98w$8ZN*`D-)0S7Y^6xW$pQ%g3_lEmW9Ef^ zGmFsQw`E!ATjDvy@%mdcqrD-uiKB}!)ZRwpZRmyu+x|RUXS+oQ*_jIZKAD~U=3B|t zz>9QQr91qJihg9j9rWHww{v@+SYBzCfc0kI=4Gr{ZLcC~mft^EkJ`CMl?8fZ z3G4ix71=2dQ`5QuTOYA0(}f`@`@U<#K?1TI(XO9c*()q!Hf}JUCaUmg#y?ffT9w1g zc)e=JcF-9J`hK{0##K#A>m^@ZFx!$g09WSBdc8O^IdP&JE@O{i0&G!Ztvt{L4q%x& zGE2s!RVi6ZN9)E*(c33HuMf7#X2*VPVThdmrVz-Fyqxcs&aI4DvP#bfW={h$9>K0HsBTUf z2&!G;( z^oOVIYJv~OM=-i`6=r4Z1*hC8Fcf3rI9?;a_rL*nr@zxwKNlxf(-#Kgn@C~4?BdKk zYvL?QcQeDwwR5_S(`sn&{PL6FYxwb-qSh_rUUo{Yi-GZz5rZotG4R<+!PfsGg`MVtomw z5kzOZJrh(#rMR_87KeP0Q=#^5~r_?y1*kN?3Fq% zvnzHw$r!w|Soxz8Nbx2d&{!#w$^Hua%fx!xUbc2SI-<{h>e2I;$rJL)4)hnT5cx^* zIq#+{3;Leun3Xo=C(XVjt_z)F#PIoAw%SqJ=~DMQeB zNWQ={d|1qtlDS3xFik}#j*8%DG0<^6fW~|NGL#P_weHnJ(cYEdJtI9#1-Pa8M}(r{ zwnPJB_qB?IqZw5h!hRwW2WIEb?&F<52Ruxpr77O2K>=t*3&Z@=5(c^Uy&JSph}{Q^ z0Tl|}gt=&vK;Rb9Tx{{jUvhtmF>;~k$8T7kp;EV`C!~FKW|r$n^d6=thh`)^uYgBd zydgnY9&mm$?B@pKK+_QreOm?wnl5l}-wA$RZCZukfC$slxbqv9uKq0o^QeSID96{Rm^084kZ)*`P zk))V~+<4-_7d6<~)PL%!+%JP`Dn23vUpH47h~xnA=B_a}rLy|7U-f0W+fH`{wnyh2 zD$JYdXuygeP5&OAqpl2)BZ|X){~G;E|7{liYf%AZFmXXyA@32qLA)tuuQz`n^iH1Y z=)pAzxK$jw0Xq?7`M`=kN2WeQFhz)p;QhjbKg#SB zP~_Vqo0SGbc5Q;v4Q7vm6_#iT+p9B>%{s`8H}r|hAL5I8Q|ceJAL*eruzD8~_m>fg26HvLpik&#{3Zd#|1C_>l&-RW2nBBzSO zQ3%G{nI*T}jBjr%3fjG*&G#ruH^ioDM>0 zb0vSM8ML?tPU*y%aoCq;V%x%~!W*HaebuDn9qeT*vk0%X>fq-4zrrQf{Uq5zI1rEy zjQ@V|Cp~$AoBu=VgnVl@Yiro>ZF{uB=5)~i1rZzmDTIzLBy`8Too!#Z4nE$Z{~uB( z_=o=gKuhVpy&`}-c&f%**M&(|;2iy+nZy2Su}GOAH_GT9z`!ogwn$+Bi&1ZhtPF zVS&LO5#Bq}cew$kvE7*t8W^{{7&7WaF{upy0mj*K&xbnXvSP9V$6m6cesHGC!&Us36ld9f*Pn8gbJb3`PPT|ZG zri2?uIu09i>6Y-0-8sREOU?WaGke0+rHPb^sp;*E{Z5P7kFJ@RiLZTO`cN2mRR#Nz zxjJ##Nk+Uy-2N-8K_@576L(kJ>$UhP+)|w!SQHkkz+e62*hpzyfmY4eQLZtZUhEdG zIZluDOoPDlt5#iw+2epC3vEATfok^?SDT`TzBwtgKjY z>ZImbO)i~T=IYAfw$3j2mF1Cj*_yqK(qw(U^r-!gcUKvWQrDG@E{lEyWDWOPtA9v{ z5($&mxw{nZWo_Ov??S#Bo1;+YwVfx%M23|o$24Hdf^&4hQeV=Cffa5MMYOu2NZLSC zQ4UxWvn+8%YVGDg(Y*1iHbUyT^=gP*COcE~QkU|&6_3h z-GOS6-@o9+Vd(D7x#NYt{Bvx2`P&ZuCx#^l0bR89Hr6Vm<||c3Waq(KO0eZ zH(|B;X}{FaZ8_4yyWLdK!G_q9AYZcoOY}Jlf3R;%oR5dwR(rk7NqyF%{r>F4s^>li z`R~-fh>YIAC1?%!O?mxLx!dq*=%IRCj;vXX628aZ;+^M0CDFUY0Rc<1P5e(OVX8n- z*1UOrX{J}b2N)6m5&_xw^WSN=Lp$I$T>f8K6|J_bj%ZsIYKNs1$TFt!RuCWF48;98`7D(XPVnk+~~i=U$} zR#;!ZRo4eVqlDxjDeE^3+8)bzG_o~VRwdxqvD^HNh#@o>1My$0*Y_`wfQ$y}az|Uz zM47oEaYNTH?J^w9EVNnvfmmbV+GHDe)Kf;$^@6?9DrSHnk@*{PuJ>ra|9KO!qQ-Fp zNNcZB4ZdAI>jEh@3Mt(E1Fy!^gH-Zx6&lr8%=duIgI^~gC{Q;4yoe;#F7B`w9daIe z{(I;y)=)anc;C;)#P`8H6~iAG_q-4rPJb(6rn4pjclGi6$_L79sFAj#CTv;t@94S6 zz`Id7?k!#3JItckcwOf?sj=Xr6oKvAyt1=jiWN@XBFoW6dw_+c9O9x2i4or?*~8f& zm<>yzc6Aw_E-gsGAa`6`cjK~k^TJt(^`E1^_h)5(8)1kzAsBxjd4+!hJ&&T!qklDN z`?j#za=(^wRCvEI75uE^K#IBe5!5g2XW}|lUqAmdmIQb7xJtP}G9^(=!V`ZS_7#RZ zjXq#Cekw>fE*YS-?Qea|7~H?)bbLK;G&(~%!B@H`o#LYAuu6;-c~jFfjY7GKZ|9~{ zE!`!d@@rhY_@5fDbuQ8gRI~R_vs4%fR5$?yot4hDPJ28k_Wzmc^0yzwMr#*(OXq@g zRUgQmJA?E>3GO=5N8iWIfBP{&QM%!Oa*iwTlbd0Fbm*QCX>oRb*2XfG-=Bz1Qz0$v zn#X!2C!LqE601LEMq;X7`P*5nurdKZAmmsI-zZ|rTH;AFxNDyZ_#hN2m4W(|YB64E z470#yh$;8QzsdA;6vbNvc95HLvZvyT4{C>F(fwy&izvNDuvfO1Z;`Ss#4a_c6pm*{0t|_i9z{@84^lffQa5zG4<{(+p5-S z^>lG-^GJR#V>;5f3~y%n=`U_jBp~WgB0cp;Lx5VZYPYCH&(evw#}AYRlGJ>vcoeVr z3%#-QUBgeH!GB>XLw;rT&oMI9ynP;leDwh4O2uM!oIWo&Qxk{^9#nX&^3GJ z(U~5{S9aw@yHH^yuQGso=~*JOC9Zdi6(TFP+IddkfK5Eu9q;+F9?PPNAe-O;;P_Aa zPJ{Dqa1gQb%dZ|0I{#B0(z|r(qq!A4CxlW92-LwXFjYfOzAT1DDK`9rm4AB~l&oVv zi6_{)M9L1%JP}i52y@`!T9RB~!CRel53wl?amNHqcuElq%hn)|#BPvW5_m51RVb|? zXQ&B*eAD}}QamG>o{?i~usG5X6IDa3+Xkb8w%7;C8|Cln70biA+ZH}fxkH^Wei$vZPnuqIT!Mmy26;mLfU z3Bbv4M^vvMlz-I+46=g>0^wWkmA!hlYj*I!%it^x9Kx(d{L|+L{rW?Y#hLHWJfd5X z>B=Swk8=;mRtIz}Hr3NE_garb5W*!7fnNM{+m2_>!cHZZlNEeof~7M#FBEQ+f&gJ3 z^zv*t?XV)jQi%0-Ra|ISiW-fx)DsK-> zI}Fv%uee$#-1PKJwr=lU89eh=M{>Nk7IlJ)U33U)lLW+OOU%A|9-Lf;`@c*+vX{W2 z{{?0QoP!#?8=5%yL=fP%iF+?n$0#iHz`P;1{Ra6iwr=V7v^8;NoLJ5)QxIyIx>ur?lMwV=mBo0BA?28kMow8SX=Ax5L%S~x4+EQi#Ig`(ht%)D(F#Pa!)SiHy&PvUp32=VtAsR|6|NZR@jkad zX^aEgojf9(-)rNOZ=NVA&a;6Cljkb=H-bY9m^_I)`pBHB16QW)sU27zF13ypefeATJc1Wzy39GrKF{UntHsIU59AdXp?j{eh2R)IbU&omd zk6(qzvE@hve1yM6dgkbz>5HDR&MD~yi$yymQ}?b;RfL$N-#l7(u?T^Wlu+Q;fo|jd zBe^jzGMHY(2=5l?bEIh+zgE$1TEQ&!p3fH;AW`P?W5Hkj3eJnT>dqg! zf~}A*SZU5HHDCbdywQ^l_PqssHRlrySYN=`hAv2sVrtcF!`kyEu%XeeRUTJU7vB%h zY0*)N$mLo6d=tJfe}IPIeiH~>AKwCpkn&WEfYgl?3anq5#-F$6$v-(G_j0*S9mdsn zg@ek_ut4(?+JP_9-n`YqoD(gAz+Ttm1#t za96D}oQR(o=e8wwes19_(p4g(A1vSGwPAp~Hh3hh!fc>u{1E^+^}AzwilFVf6^vbL zc&NnRs`u)N-P|Cu4()yTiuE{j_V&=K?iP!IUBf~ei2}~_KBvUAlXa;R#Wl`gOBtJ$Y5(L))@`riLB)v*r>9*8VfmQt<72?+fdwP{BA@?_qo>mN7yzICUCaeG(+>Rb~8wg~6U(P)NlDLuhQgjbC}=)HuZgC}0Z-qLX4lJ7^)8~!!*qP0=~`Y_(A z{@15*ZevZSI^s|OnpCeCwLXf#tgbq8y~R*GB5anmZ;_N!+-3>!wu@NBFCNJ$#y?{? zMI!?s*=_xA;V&aX)ROxzVW8*de+&P#2zucA|8mksdgCXBsZ*TM=%{L1Tk5LB_*^@&S?O=ot{h)1xRVSn27&Tk8>rF|6ruzYb;Nq) z;qvlmrP^SL$mhe4Ai)xpl6Wx&y;z8o!7-+6$qj;ZLXvfR71I@w(R|6lyuP6v-lP&r z@KK-TEmGQfMmk1c0^fd7!^si}T%b5a2%>T-Drh|^Cf z$}qxIv@zxbmJ#qjK6Q_aGDe{ciVT20V1lW52Xs!}x(4_j)sUXYdm4 zwYC9FOa;X*c*LxL;xE5ov?|?^7gWXyALy_D2GvDo-8%0-Y%9TkkO_Tcr2qIUg3(OC z%3wt?hyn*+e^z%(~2#!2dvMFa$mzgwk1I1X;naFMjXSbnmZ!zd%7u)=cgi z*0&@Scrl&BDfU(9Pks8#;!~v~r7~DN{G6WE&_;7i{{a*?oiCao(l%2ruxX0fAt69e2vLgL%Mf_)!*(Tz zNKW>sW@YB2vBfP>C&L|-pq)Uq^PsG_THu;8iEcqafO?0k$IQp1KyWyOoTxwmKvlc^ zO9$%Tt8;%qQxwy5;CsJ)V}a7I6}SvQ%0_H53Kcqx=m83fIzpLSGgfVe^SPdc*xPdciI5dg}#{Etv$e<)gGD=qm0v=!aN@*?$s zLhzD%4w{vf-g6FHQjG9XyC+4=bewb?Mz%!u8%oP{G9{UJFTLTcCi3R(=Nm&t&Sl(? zr>pj?=ECdDVa}-g%`LF^1EY@>7d}%VhYpKFSDPH)D(zB+gPe1m7E}W>TiW=8L0&(D&YG=0<&7G4Bu{;-#Ud;-1%Ta9V}U6fyK1YX z`Rq|i-X(loPZ)M$H%m@j7bGx>uj~y=0)!t#dc|c}+hT%~Sq>fefez0Ul|jOJHta~u zx7*mV6~Jpt(FkY(pQN91>aFk7VS%Sa^oLaq$*)W?fy`xuFJgH<2s=!Rz}_(qdmdF~ zlr2f=)q_vpi8X;Jq>5^$GweJ{iS`Khw2f)fsvKpgh;U~13a+9 zfaw}UuGiBy;q10pI^Avb#X3D=k_r(T{N;-xA)OM}2Py5L##<96NU*Sr7GQqhfrPej z?;B$Bt_sTxuSAPXfTSC{zr?@$$0iHxC@z*5F52j*PG87hh`0w3At8jPf*rjNE~_Gj z2)fjeUFJ(#l9uWuw&5#@13|AQ1;pdA?EL4YKq0JDR5T8I?aWGxI=J9}vdyH;gQ@iE z>+UnC2iwT0f80-VuE^bY!N@(}9?bOXyy%rTqSNDN4rO4Zt#(kZwcGgTp&3((F+nsd ze~B)%K6oP4WX_w1>|QImC;9q zy}4p+s%^Too2(gE>yo%+yY#F{)phtmNqsJPVQQ0lGR|H9q>aA&AtU4M+EZ%`xvQLb zbigBOc`dL}&j3er?EOI`!W)N#>+uwp_!h^5FspaEylq!e(FPY-6T3~WeNmZ<$?Y6y z-!bM1kD7ZF8xl+Pi6fiv1?)q%`aNxn#pK%)ct||L&Xnf8Gu&3g;Of{B8Pt=u`e+Mn zA(DmU#3cF#Nr7W;X0V4ksFHMcNDAf4G&D8VjLeZ^|5-f$>_|71>P3xuu)?4NJed*w z6GR_RB5HQLzT(h+`Y?-3esxeue{-Q%b+!&o>IJ!#=}#_&q+hwJga>fkt(*(WdoN5vSta z#$mMN6}YzYRpaBZ)j)EL91-oL1(|d(>%UclsTUOyXyWM&(hNqLwqtn`!E>HJM{ zh>M~xa1@*U^cwx-k5QjePr5=B6u*jpJ)C0{C?f7Yga+I^4$TleyX$x&jm9z@c!?cC z<2kY7)p^+W{AXd@l1C09_yB*TG|yzb96BYk z8Wpj81vB>zcR+qM4m~A44w1n7$fxB$-?MV}S?Fh}c_|2FXg`cZ?750i;Cdl-_nGK# zta)h)6!*AsQ-z8caSh)%5JY>_yCeJs~FpAzdY8 zF@SU_hN#~ip5I;UACFzx1v0yf{j97l&)e-=`d#1Kp6A(Kj&HC!%vK!wEdK3HFJ?|6 za;WwUczZ+&<$g!Td^48@lJtfW@doXL#jY6)dK_RDCQAZ}l&OdD+?Yl5-bqpsHZR^( zF{u_cR(x>u(c4i5f(^8!h6CV0#ZxRFhLlunWiGDLO6yoRb(wV<(P^8=fOU7Hp{AHE z;Yg%kg@6&tL3Z*IrbkDeQ$%rbalVP39D@LVrC2xSavnTp%PorXPf1DVzHyqjDsDnS zL=mv0a2s60bHKGQM)ue>npH0SCp;XtZFUzm?R-x7D*(PxMmuJ4J*K2eY&ebe0yQHe zVG&*qe{pot{PM^xQv`H_rn2FcYOrEN+I#uX^1`Id%J$;Hi2cNCU!0Hlc0TjxLzkss zHxmC;hQBu5U4J0XflWM;{uH`_47Sg)QyZ{8D&T0;bdc3{^^<=q7P?C_2E-}PQn>*= z2T5q^J|Q_2+x%Qt`i3m6=6V$)BxIx{2KAFkMb#q`iMCD|L>+}_dYVA$wBr1Zr}YOF z^MMGO@PHGGh>g|^yF`PvvtDwN@kxt?ClLcG<+murHMz1Asj!$l=b)4{d}SqOJ}>Y< zSeAyP@ZEcpx`ayIdp>{--UVLYC_cZZURh_!4u2(*#x@Tk(QJa}4BqqZ$6%LhF-HB~ zAcc?$I6KP}IxANcAteEBX$Ys?T=JB|Fnd3*UAO0mYAXCgWf~?7Z_G7G5`H4;S^QKK zG*2l75vI@DHQC*es>6&|r^#RHKRQ5rwv_l4`!(!I3%)Z$P1fnZ8N@27zyg}54ElO%SjQ_4uujX)4ta@Gz2)_>4b~vX|rhRIH-eqdD zL)xaEpW3K|a>daQRRR*_$W>rWOsW-IE4VQl3L$3}=-PFU)s@XG&9+DFivH-;2&w~$ES_nJZJH!?1mO!CnP)Jb{mW9=f`bDpo^PI6i4|YurK)Q1 z^Ys1oHRdr!$X4RuyR%kgp!a*Lz*_AAoJ$EVAdsNCoPA^VZE1pGO@D3UStACE+%vs6 z$io@E>DmB|3VV~GbOt2oc+K;t zdn3gaFvYz;vRN-+2+Qk{8|O}e86nVck)fZn3sg$j#dLVham{yGkc$I#!HF7mRS%f* z!+NdzG49K(qaO^SBlp@K@D?|^rAq;8{*@kRc4sYSNQmoy7@_RS_ksWl2T_38h2A)# ziU2WXWD03(NqS&Mu*?0-iK8X_Z3w`}c7MPv0qZ7iM|L3xdTnR{y!7{#82$}uJCiGT zqa=8<9L05hu6 z1N+2n7OzT{NEf?gS@eq7@buCDFe9mAxY%THo^b@BHckKK>jg6{@)>n z43cPs%$Qi0iwyZ+{C491>FRu5+6baJ{&XXXC@Sp+b!QE|{7_d?lm5K=B z)myKEcxjFm74+drF|JCYcxdY%ASig#YoRBRUV7An7f-%rqj%PHECbxh#5476cEq@NQL?dI6gUqvS@w zq!WmD(aR0{NxItAZCKDCVw=Zu{9WGDu^i?2g zLerPiOU*HSaXg^3CdOX^F6c9MiHINP339N%)a96`^Z-c#&EogcxMSYo0Cb4{-}q1( zRrJine`P|6WRkm8u4Ja1QRYq$AR>b7tugd#EsT-VmXN-t!TYjZy}i!uKi6$u>EJ?w zvdHZg+hp+5ree?>fdJAX)5#Wtm#2M-{~2jfX2{G`)?D6UD1MevdeeU;;HCi}AtJr( SGW6ptSs!X7{rG*o_g?|vpSEZK diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 865f1ba80d..fcbbad6dd6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=85719317abd2112f021d4f41f09ec370534ba288432065f4b477b6a3b652910d -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip +distributionSha256Sum=194717442575a6f96e1c1befa2c30e9a4fc90f701d7aee33eb879b79e7ff05c0 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From d741ff684594f08723254f8249d1833ec6fc3836 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 10:05:28 +0200 Subject: [PATCH 038/124] Update wysiwyg to v2.35.0 (#2611) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2e2993ef6..b34a82dacb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,7 +38,7 @@ serialization_json = "1.6.3" showkase = "1.0.2" appyx = "1.4.0" sqldelight = "2.0.1" -wysiwyg = "2.34.0" +wysiwyg = "2.35.0" telephoto = "0.9.0" # DI From 61edf282a4ccb5b1c0341ded5f83349ba658b7be Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:52:05 +0200 Subject: [PATCH 039/124] Update dagger to v2.51.1 (#2626) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b34a82dacb..2be4f8dbcb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,7 +42,7 @@ wysiwyg = "2.35.0" telephoto = "0.9.0" # DI -dagger = "2.51" +dagger = "2.51.1" anvil = "2.4.9" # Auto service From 1e5a61a49c9b8d97ad34885c23e1b02823bd2614 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 12:35:35 +0200 Subject: [PATCH 040/124] Move PermalinkBuilder, MatrixToConverter and PermalinkParser content to the `impl` project in order to remove projects.appconfig dependency from matrix.api module. --- .../features/messages/impl/MessagesNode.kt | 28 ++++ .../features/messages/impl/MessagesView.kt | 5 + .../MessageComposerPresenter.kt | 6 +- .../messagecomposer/MessageComposerState.kt | 2 + .../MessageComposerStateProvider.kt | 7 + .../messagecomposer/MessageComposerView.kt | 1 + .../timeline/DefaultHtmlConverterProvider.kt | 10 +- .../messages/impl/timeline/TimelineView.kt | 3 + .../components/ATimelineItemEventRow.kt | 1 + .../components/TimelineItemEventRow.kt | 40 +---- .../TimelineItemGroupedEventsRow.kt | 6 + .../timeline/components/TimelineItemRow.kt | 3 + .../TimelineItemContentMessageFactory.kt | 11 +- .../event/TimelineItemEventFactory.kt | 4 +- .../impl/timeline/model/InReplyToDetails.kt | 7 +- .../typing/MessagesViewWithTypingPreview.kt | 1 + .../messages/impl/MessagesPresenterTest.kt | 4 + .../messages/impl/MessagesViewTest.kt | 2 + .../fixtures/TimelineItemsFactoryFixtures.kt | 3 + .../MessageComposerPresenterTest.kt | 15 +- .../DefaultHtmlConverterProviderTest.kt | 9 +- .../impl/timeline/TimelineViewTest.kt | 2 + .../TimelineItemContentMessageFactoryTest.kt | 2 + .../timeline/model/InReplyToDetailTest.kt | 35 +++- .../roomdetails/impl/RoomDetailsNode.kt | 7 +- .../members/details/RoomMemberDetailsNode.kt | 3 +- .../deeplink/usecase/InviteFriendsUseCase.kt | 3 +- .../impl/DefaultRoomLastMessageFormatter.kt | 4 +- .../DefaultRoomLastMessageFormatterTest.kt | 4 +- libraries/matrix/api/build.gradle.kts | 3 - .../matrix/api/permalink/MatrixToConverter.kt | 28 +--- .../matrix/api/permalink/PermalinkBuilder.kt | 65 +------ .../matrix/api/permalink/PermalinkParser.kt | 117 +------------ libraries/matrix/impl/build.gradle.kts | 3 + .../permalink/DefaultMatrixToConverter.kt | 61 +++++++ .../impl/permalink/DefaultPermalinkBuilder.kt | 92 ++++++++++ .../impl/permalink/DefaultPermalinkParser.kt | 158 ++++++++++++++++++ .../DefaultMatrixToConverterTest.kt} | 16 +- .../permalink/DefaultPermalinkBuilderTest.kt} | 22 +-- .../permalink/DefaultPermalinkParserTest.kt} | 67 ++++++-- .../test/permalink/FakePermalinkBuilder.kt | 37 ++++ .../test/permalink/FakePermalinkParser.kt | 37 ++++ libraries/matrixui/build.gradle.kts | 1 + .../matrix/ui/messages/ToHtmlDocument.kt | 15 +- .../matrix/ui/messages/ToPlainText.kt | 16 +- .../matrixui/messages/ToHtmlDocumentTest.kt | 37 +++- .../matrixui/messages/ToPlainTextTest.kt | 9 +- .../notifications/NotifiableEventResolver.kt | 4 +- .../NotifiableEventResolverTest.kt | 2 + .../libraries/textcomposer/TextComposer.kt | 12 +- .../mentions/MentionSpanProvider.kt | 36 +++- .../impl/mentions/MentionSpanProviderTest.kt | 23 +++ 52 files changed, 771 insertions(+), 318 deletions(-) create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt rename libraries/matrix/{api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt => impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt} (71%) rename libraries/matrix/{api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt => impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilderTest.kt} (70%) rename libraries/matrix/{api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt => impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParserTest.kt} (70%) create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt 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 c81cbebaae..ea3584e4f5 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 @@ -16,9 +16,12 @@ package io.element.android.features.messages.impl +import android.content.Context +import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -31,11 +34,14 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.RoomScope 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.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.mediaplayer.api.MediaPlayer @@ -52,6 +58,7 @@ class MessagesNode @AssistedInject constructor( presenterFactory: MessagesPresenter.Factory, private val timelineItemPresenterFactories: TimelineItemPresenterFactories, private val mediaPlayer: MediaPlayer, + private val permalinkParser: PermalinkParser, ) : Node(buildContext, plugins = plugins), MessagesNavigator { private val presenter = presenterFactory.create(this) private val callback = plugins().firstOrNull() @@ -96,6 +103,25 @@ class MessagesNode @AssistedInject constructor( private fun onUserDataClicked(userId: UserId) { callback?.onUserDataClicked(userId) } + + private fun onLinkClicked( + context: Context, + url: String, + ) { + when (val permalink = permalinkParser.parse(Uri.parse(url))) { + is PermalinkData.UserLink -> { + onUserDataClicked(UserId(permalink.userId)) + } + is PermalinkData.RoomLink -> { + // TODO: Implement room link handling + } + is PermalinkData.FallbackLink, + is PermalinkData.RoomEmailInviteLink -> { + context.openUrlInExternalApp(url) + } + } + } + override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { callback?.onShowEventDebugInfoClicked(eventId, debugInfo) } @@ -126,6 +152,7 @@ class MessagesNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { + val context = LocalContext.current CompositionLocalProvider( LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories, ) { @@ -137,6 +164,7 @@ class MessagesNode @AssistedInject constructor( onEventClicked = this::onEventClicked, onPreviewAttachments = this::onPreviewAttachments, onUserDataClicked = this::onUserDataClicked, + onLinkClicked = { onLinkClicked(context, it) }, onSendLocationClicked = this::onSendLocationClicked, onCreatePollClicked = this::onCreatePollClicked, onJoinCallClicked = this::onJoinCallClicked, 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 91cccd88e8..9cf4375769 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 @@ -119,6 +119,7 @@ fun MessagesView( onRoomDetailsClicked: () -> Unit, onEventClicked: (event: TimelineItem.Event) -> Boolean, onUserDataClicked: (UserId) -> Unit, + onLinkClicked: (String) -> Unit, onPreviewAttachments: (ImmutableList) -> Unit, onSendLocationClicked: () -> Unit, onCreatePollClicked: () -> Unit, @@ -213,6 +214,7 @@ fun MessagesView( onMessageClicked = ::onMessageClicked, onMessageLongClicked = ::onMessageLongClicked, onUserDataClicked = onUserDataClicked, + onLinkClicked = onLinkClicked, onTimestampClicked = { event -> if (event.localSendState is LocalEventSendState.SendingFailed) { state.retrySendMenuState.eventSink(RetrySendMenuEvents.EventSelected(event)) @@ -313,6 +315,7 @@ private fun MessagesViewContent( state: MessagesState, onMessageClicked: (TimelineItem.Event) -> Unit, onUserDataClicked: (UserId) -> Unit, + onLinkClicked: (String) -> Unit, onReactionClicked: (key: String, TimelineItem.Event) -> Unit, onReactionLongClicked: (key: String, TimelineItem.Event) -> Unit, onMoreReactionsClicked: (TimelineItem.Event) -> Unit, @@ -386,6 +389,7 @@ private fun MessagesViewContent( onMessageClicked = onMessageClicked, onMessageLongClicked = onMessageLongClicked, onUserDataClicked = onUserDataClicked, + onLinkClicked = onLinkClicked, onTimestampClicked = onTimestampClicked, onReactionClicked = onReactionClicked, onReactionLongClicked = onReactionLongClicked, @@ -570,6 +574,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) onEventClicked = { false }, onPreviewAttachments = {}, onUserDataClicked = {}, + onLinkClicked = {}, onSendLocationClicked = {}, onCreatePollClicked = {}, onJoinCallClicked = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 37e404d314..04c182a566 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -49,6 +49,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder @@ -96,6 +97,8 @@ class MessageComposerPresenter @Inject constructor( private val messageComposerContext: MessageComposerContextImpl, private val richTextEditorStateFactory: RichTextEditorStateFactory, private val currentSessionIdHolder: CurrentSessionIdHolder, + private val permalinkParser: PermalinkParser, + private val permalinkBuilder: PermalinkBuilder, permissionsPresenterFactory: PermissionsPresenter.Factory, ) : Presenter { private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) @@ -334,7 +337,7 @@ class MessageComposerPresenter @Inject constructor( } is MentionSuggestion.Member -> { val text = mention.roomMember.displayName?.prependIndent("@") ?: mention.roomMember.userId.value - val link = PermalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch + val link = permalinkBuilder.permalinkForUser(mention.roomMember.userId).getOrNull() ?: return@launch richTextEditorState.insertMentionAtSuggestion(text = text, link = link) } } @@ -345,6 +348,7 @@ class MessageComposerPresenter @Inject constructor( return MessageComposerState( richTextEditorState = richTextEditorState, + permalinkParser = permalinkParser, isFullScreen = isFullScreen.value, mode = messageComposerContext.composerMode, showAttachmentSourcePicker = showAttachmentSourcePicker, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index 09eb51477f..194ce1914c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Stable import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.mentions.MentionSuggestion import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.wysiwyg.compose.RichTextEditorState import kotlinx.collections.immutable.ImmutableList @@ -28,6 +29,7 @@ import kotlinx.collections.immutable.ImmutableList @Stable data class MessageComposerState( val richTextEditorState: RichTextEditorState, + val permalinkParser: PermalinkParser, val isFullScreen: Boolean, val mode: MessageComposerMode, val showAttachmentSourcePicker: Boolean, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index da3c0c8af7..fb7f616e12 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt @@ -16,9 +16,12 @@ package io.element.android.features.messages.impl.messagecomposer +import android.net.Uri import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.messages.impl.mentions.MentionSuggestion 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.permalink.PermalinkParser import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.wysiwyg.compose.RichTextEditorState import kotlinx.collections.immutable.ImmutableList @@ -43,6 +46,10 @@ fun aMessageComposerState( memberSuggestions: ImmutableList = persistentListOf(), ) = MessageComposerState( richTextEditorState = richTextEditorState, + permalinkParser = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData = TODO() + override fun parse(uri: Uri): PermalinkData = TODO() + }, isFullScreen = isFullScreen, mode = mode, showTextFormatting = showTextFormatting, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index 6cb2900a20..e68cf29844 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -109,6 +109,7 @@ internal fun MessageComposerView( modifier = modifier, state = state.richTextEditorState, voiceMessageState = voiceMessageState.voiceMessageState, + permalinkParser = state.permalinkParser, subcomposing = subcomposing, onRequestFocus = ::onRequestFocus, onSendMessage = ::sendMessage, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt index aefadcbb25..26ad72dcf9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle import io.element.android.libraries.textcomposer.mentions.rememberMentionSpanProvider import io.element.android.wysiwyg.compose.StyledHtmlConverter @@ -39,7 +40,9 @@ import javax.inject.Inject @ContributesBinding(SessionScope::class) @SingleIn(SessionScope::class) -class DefaultHtmlConverterProvider @Inject constructor() : HtmlConverterProvider { +class DefaultHtmlConverterProvider @Inject constructor( + private val permalinkParser: PermalinkParser, +) : HtmlConverterProvider { private val htmlConverter: MutableState = mutableStateOf(null) @Composable @@ -50,7 +53,10 @@ class DefaultHtmlConverterProvider @Inject constructor() : HtmlConverterProvider } val editorStyle = ElementRichTextEditorStyle.textStyle() - val mentionSpanProvider = rememberMentionSpanProvider(currentUserId = currentUserId) + val mentionSpanProvider = rememberMentionSpanProvider( + currentUserId = currentUserId, + permalinkParser = permalinkParser, + ) val context = LocalContext.current 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 094ecb6c3b..5869238753 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 @@ -81,6 +81,7 @@ fun TimelineView( typingNotificationState: TypingNotificationState, roomName: String?, onUserDataClicked: (UserId) -> Unit, + onLinkClicked: (String) -> Unit, onMessageClicked: (TimelineItem.Event) -> Unit, onMessageLongClicked: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, @@ -140,6 +141,7 @@ fun TimelineView( onClick = onMessageClicked, onLongClick = onMessageLongClicked, onUserDataClick = onUserDataClicked, + onLinkClicked = onLinkClicked, inReplyToClick = ::inReplyToClicked, onReactionClick = onReactionClicked, onReactionLongClick = onReactionLongClicked, @@ -276,6 +278,7 @@ internal fun TimelineViewPreview( onMessageClicked = {}, onTimestampClicked = {}, onUserDataClicked = {}, + onLinkClicked = {}, onMessageLongClicked = {}, onReactionClicked = { _, _ -> }, onReactionLongClicked = { _, _ -> }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt index a86095b3fc..92b12b2dc8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt @@ -38,6 +38,7 @@ internal fun ATimelineItemEventRow( onClick = {}, onLongClick = {}, onUserDataClick = {}, + onLinkClicked = {}, inReplyToClick = {}, onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, 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 81c10ad7d8..22bce0e873 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 @@ -17,7 +17,6 @@ package io.element.android.features.messages.impl.timeline.components import android.annotation.SuppressLint -import android.net.Uri import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -50,7 +49,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.res.stringResource @@ -94,7 +92,6 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.canBeRepliedTo import io.element.android.features.messages.impl.timeline.model.metadata -import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.components.EqualWidthColumn import io.element.android.libraries.designsystem.components.avatar.Avatar @@ -109,9 +106,6 @@ 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.EventId 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.permalink.PermalinkParser -import io.element.android.libraries.matrix.api.room.Mention import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings @@ -128,6 +122,7 @@ fun TimelineItemEventRow( isHighlighted: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, + onLinkClicked: (String) -> Unit, onUserDataClick: (UserId) -> Unit, inReplyToClick: (EventId) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, @@ -151,13 +146,6 @@ fun TimelineItemEventRow( inReplyToClick(inReplyToEventId) } - fun onMentionClicked(mention: Mention) { - when (mention) { - is Mention.User -> onUserDataClick(mention.userId) - else -> Unit // TODO implement actions for other mentions being clicked - } - } - Column(modifier = modifier.fillMaxWidth()) { if (event.groupPosition.isNew()) { Spacer(modifier = Modifier.height(16.dp)) @@ -203,7 +191,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, - onMentionClicked = ::onMentionClicked, + onLinkClicked = onLinkClicked, eventSink = eventSink, ) } @@ -222,7 +210,7 @@ fun TimelineItemEventRow( onReactionClicked = { emoji -> onReactionClick(emoji, event) }, onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) }, onMoreReactionsClicked = { onMoreReactionsClick(event) }, - onMentionClicked = ::onMentionClicked, + onLinkClicked = onLinkClicked, eventSink = eventSink, ) } @@ -278,7 +266,7 @@ private fun TimelineItemEventRowContent( onReactionClicked: (emoji: String) -> Unit, onReactionLongClicked: (emoji: String) -> Unit, onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, - onMentionClicked: (Mention) -> Unit, + onLinkClicked: (String) -> Unit, eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, modifier: Modifier = Modifier, ) { @@ -346,7 +334,7 @@ private fun TimelineItemEventRowContent( onTimestampClicked = { onTimestampClicked(event) }, - onMentionClicked = onMentionClicked, + onLinkClicked = onLinkClicked, eventSink = eventSink, ) } @@ -429,7 +417,7 @@ private fun MessageEventBubbleContent( @Suppress("UNUSED_PARAMETER") inReplyToClick: () -> Unit, onTimestampClicked: () -> Unit, - onMentionClicked: (Mention) -> Unit, + onLinkClicked: (String) -> Unit, eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, @SuppressLint("ModifierParameter") // need to rename this modifier to prevent linter false positives @@ -530,7 +518,6 @@ private fun MessageEventBubbleContent( modifier: Modifier = Modifier, canShrinkContent: Boolean = false, ) { - val context = LocalContext.current val timestampLayoutModifier: Modifier val contentModifier: Modifier when { @@ -566,20 +553,7 @@ private fun MessageEventBubbleContent( ) { onContentLayoutChanged -> TimelineItemEventContentView( content = event.content, - onLinkClicked = { url -> - when (val permalink = PermalinkParser.parse(Uri.parse(url))) { - is PermalinkData.UserLink -> { - onMentionClicked(Mention.User(UserId(permalink.userId))) - } - is PermalinkData.RoomLink -> { - onMentionClicked(Mention.Room(permalink.getRoomId(), permalink.getRoomAlias())) - } - is PermalinkData.FallbackLink, - is PermalinkData.RoomEmailInviteLink -> { - context.openUrlInExternalApp(url) - } - } - }, + onLinkClicked = onLinkClicked, eventSink = eventSink, onContentLayoutChanged = onContentLayoutChanged, modifier = contentModifier 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 74998afb89..bc05882f1b 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 @@ -51,6 +51,7 @@ fun TimelineItemGroupedEventsRow( onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, onUserDataClick: (UserId) -> Unit, + onLinkClicked: (String) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, @@ -78,6 +79,7 @@ fun TimelineItemGroupedEventsRow( onLongClick = onLongClick, inReplyToClick = inReplyToClick, onUserDataClick = onUserDataClick, + onLinkClicked = onLinkClicked, onTimestampClicked = onTimestampClicked, onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, @@ -102,6 +104,7 @@ private fun TimelineItemGroupedEventsRowContent( onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, onUserDataClick: (UserId) -> Unit, + onLinkClicked: (String) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, @@ -135,6 +138,7 @@ private fun TimelineItemGroupedEventsRowContent( onLongClick = onLongClick, inReplyToClick = inReplyToClick, onUserDataClick = onUserDataClick, + onLinkClicked = onLinkClicked, onTimestampClicked = onTimestampClicked, onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, @@ -175,6 +179,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi onLongClick = {}, inReplyToClick = {}, onUserDataClick = {}, + onLinkClicked = {}, onTimestampClicked = {}, onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, @@ -200,6 +205,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi onLongClick = {}, inReplyToClick = {}, onUserDataClick = {}, + onLinkClicked = {}, onTimestampClicked = {}, onReactionClick = { _, _ -> }, onReactionLongClick = { _, _ -> }, 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 6acddd179d..5146c87df6 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 @@ -36,6 +36,7 @@ internal fun TimelineItemRow( highlightedItem: String?, sessionState: SessionState, onUserDataClick: (UserId) -> Unit, + onLinkClicked: (String) -> Unit, onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, @@ -79,6 +80,7 @@ internal fun TimelineItemRow( onClick = { onClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) }, onUserDataClick = onUserDataClick, + onLinkClicked = onLinkClicked, inReplyToClick = inReplyToClick, onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, @@ -103,6 +105,7 @@ internal fun TimelineItemRow( onLongClick = onLongClick, inReplyToClick = inReplyToClick, onUserDataClick = onUserDataClick, + onLinkClicked = onLinkClicked, onTimestampClicked = onTimestampClicked, onReactionClick = onReactionClick, onReactionLongClick = onReactionLongClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index af1ef0bc3a..acd99d3704 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -41,6 +41,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.permalink.PermalinkParser 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.FileMessageType @@ -67,6 +68,7 @@ class TimelineItemContentMessageFactory @Inject constructor( private val fileExtensionExtractor: FileExtensionExtractor, private val featureFlagService: FeatureFlagService, private val htmlConverterProvider: HtmlConverterProvider, + private val permalinkParser: PermalinkParser, ) { suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent { return when (val messageType = content.type) { @@ -74,7 +76,10 @@ class TimelineItemContentMessageFactory @Inject constructor( val emoteBody = "* $senderDisplayName ${messageType.body.trimEnd()}" TimelineItemEmoteContent( body = emoteBody, - htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"), + htmlDocument = messageType.formatted?.toHtmlDocument( + permalinkParser = permalinkParser, + prefix = "* $senderDisplayName", + ), formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisplayName") ?: emoteBody.withLinks(), isEdited = content.isEdited, ) @@ -197,7 +202,7 @@ class TimelineItemContentMessageFactory @Inject constructor( val body = messageType.body.trimEnd() TimelineItemNoticeContent( body = body, - htmlDocument = messageType.formatted?.toHtmlDocument(), + htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser), formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(), isEdited = content.isEdited, ) @@ -206,7 +211,7 @@ class TimelineItemContentMessageFactory @Inject constructor( val body = messageType.body.trimEnd() TimelineItemTextContent( body = body, - htmlDocument = messageType.formatted?.toHtmlDocument(), + htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser), formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(), isEdited = content.isEdited, ) 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 38125dcef7..0522379f72 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 @@ -30,6 +30,7 @@ import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormat 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.MatrixClient +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails @@ -43,6 +44,7 @@ class TimelineItemEventFactory @Inject constructor( private val contentFactory: TimelineItemContentFactory, private val matrixClient: MatrixClient, private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, + private val permalinkParser: PermalinkParser, ) { suspend fun create( currentTimelineItem: MatrixTimelineItem.Event, @@ -80,7 +82,7 @@ class TimelineItemEventFactory @Inject constructor( reactionsState = currentTimelineItem.computeReactionsState(), readReceiptState = currentTimelineItem.computeReadReceiptState(roomMembers), localSendState = currentTimelineItem.event.localSendState, - inReplyTo = currentTimelineItem.event.inReplyTo()?.map(), + inReplyTo = currentTimelineItem.event.inReplyTo()?.map(permalinkParser = permalinkParser), isThreaded = currentTimelineItem.event.isThreaded(), debugInfo = currentTimelineItem.event.debugInfo, origin = currentTimelineItem.event.origin, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt index 759ead5b61..3c629f23fc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetails.kt @@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.model import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent @@ -34,7 +35,9 @@ data class InReplyToDetails( val textContent: String?, ) -fun InReplyTo.map() = when (this) { +fun InReplyTo.map( + permalinkParser: PermalinkParser, +) = when (this) { is InReplyTo.Ready -> InReplyToDetails( eventId = eventId, senderId = senderId, @@ -44,7 +47,7 @@ fun InReplyTo.map() = when (this) { textContent = when (content) { is MessageContent -> { val messageContent = content as MessageContent - (messageContent.type as? TextMessageType)?.toPlainText() ?: messageContent.body + (messageContent.type as? TextMessageType)?.toPlainText(permalinkParser = permalinkParser) ?: messageContent.body } is StickerContent -> { val stickerContent = content as StickerContent diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt index 2fea39eeb7..3b09afb7fc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt @@ -35,6 +35,7 @@ internal fun MessagesViewWithTypingPreview( onEventClicked = { false }, onPreviewAttachments = {}, onUserDataClicked = {}, + onLinkClicked = {}, onSendLocationClicked = {}, onCreatePollClicked = {}, onJoinCallClicked = {}, 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 30413d1b9f..6cceb6b5b5 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 @@ -77,6 +77,8 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService +import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember @@ -725,6 +727,8 @@ class MessagesPresenterTest { richTextEditorStateFactory = TestRichTextEditorStateFactory(), permissionsPresenterFactory = permissionsPresenterFactory, currentSessionIdHolder = CurrentSessionIdHolder(FakeMatrixClient(A_SESSION_ID)), + permalinkParser = FakePermalinkParser(), + permalinkBuilder = FakePermalinkBuilder(), ) val voiceMessageComposerPresenter = VoiceMessageComposerPresenter( this, 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 b931dc9860..e176e20805 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 @@ -478,6 +478,7 @@ private fun AndroidComposeTestRule.setMessa onRoomDetailsClicked: () -> Unit = EnsureNeverCalled(), onEventClicked: (event: TimelineItem.Event) -> Boolean = EnsureNeverCalledWithParamAndResult(), onUserDataClicked: (UserId) -> Unit = EnsureNeverCalledWithParam(), + onLinkClicked: (String) -> Unit = EnsureNeverCalledWithParam(), onPreviewAttachments: (ImmutableList) -> Unit = EnsureNeverCalledWithParam(), onSendLocationClicked: () -> Unit = EnsureNeverCalled(), onCreatePollClicked: () -> Unit = EnsureNeverCalled(), @@ -492,6 +493,7 @@ private fun AndroidComposeTestRule.setMessa onRoomDetailsClicked = onRoomDetailsClicked, onEventClicked = onEventClicked, onUserDataClicked = onUserDataClicked, + onLinkClicked = onLinkClicked, onPreviewAttachments = onPreviewAttachments, onSendLocationClicked = onSendLocationClicked, onCreatePollClicked = onCreatePollClicked, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt index 562424474c..db056dea19 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt @@ -41,6 +41,7 @@ import io.element.android.libraries.eventformatter.api.TimelineEventFormatter import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope @@ -57,6 +58,7 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), featureFlagService = FakeFeatureFlagService(), htmlConverterProvider = FakeHtmlConverterProvider(), + permalinkParser = FakePermalinkParser(), ), redactedMessageFactory = TimelineItemContentRedactedFactory(), stickerFactory = TimelineItemContentStickerFactory( @@ -73,6 +75,7 @@ internal fun TestScope.aTimelineItemsFactory(): TimelineItemsFactory { ), matrixClient = matrixClient, lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(), + permalinkParser = FakePermalinkParser(), ), virtualItemFactory = TimelineItemVirtualFactory( daySeparatorFactory = TimelineItemDaySeparatorFactory( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index 5e980514cb..290f297117 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -43,6 +43,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.VideoInfo +import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.Mention @@ -60,6 +61,8 @@ import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.A_USER_ID_4 import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.mediapickers.api.PickerProvider @@ -805,7 +808,14 @@ class MessageComposerPresenterTest { @Test fun `present - insertMention`() = runTest { - val presenter = createPresenter(this) + val presenter = createPresenter( + coroutineScope = this, + permalinkBuilder = FakePermalinkBuilder( + result = { + Result.success("https://matrix.to/#/${A_USER_ID_2.value}") + } + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -941,6 +951,7 @@ class MessageComposerPresenterTest { mediaPreProcessor: MediaPreProcessor = this.mediaPreProcessor, snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher, permissionPresenter: PermissionsPresenter = FakePermissionsPresenter(), + permalinkBuilder: PermalinkBuilder = FakePermalinkBuilder() ) = MessageComposerPresenter( coroutineScope, room, @@ -955,6 +966,8 @@ class MessageComposerPresenterTest { TestRichTextEditorStateFactory(), currentSessionIdHolder = CurrentSessionIdHolder(FakeMatrixClient(A_SESSION_ID)), permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter), + permalinkParser = FakePermalinkParser(), + permalinkBuilder = permalinkBuilder, ) private suspend fun ReceiveTurbine.awaitFirstItem(): T { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt index 276544a057..7411ceda1a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.test.junit4.createComposeRule import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -32,7 +33,9 @@ class DefaultHtmlConverterProviderTest { @Test fun `calling provide without calling Update first should throw an exception`() { - val provider = DefaultHtmlConverterProvider() + val provider = DefaultHtmlConverterProvider( + permalinkParser = FakePermalinkParser(), + ) val exception = runCatching { provider.provide() }.exceptionOrNull() @@ -41,7 +44,9 @@ class DefaultHtmlConverterProviderTest { @Test fun `calling provide after calling Update first should return an HtmlConverter`() { - val provider = DefaultHtmlConverterProvider() + val provider = DefaultHtmlConverterProvider( + permalinkParser = FakePermalinkParser(), + ) composeTestRule.setContent { CompositionLocalProvider(LocalInspectionMode provides true) { provider.Update(currentUserId = A_USER_ID) 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 78cd671e3f..44fb6270ae 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 @@ -45,6 +45,7 @@ class TimelineViewTest { typingNotificationState = aTypingNotificationState(), roomName = null, onUserDataClicked = EnsureNeverCalledWithParam(), + onLinkClicked = EnsureNeverCalledWithParam(), onMessageClicked = EnsureNeverCalledWithParam(), onMessageLongClicked = EnsureNeverCalledWithParam(), onTimestampClicked = EnsureNeverCalledWithParam(), @@ -72,6 +73,7 @@ class TimelineViewTest { typingNotificationState = aTypingNotificationState(), roomName = null, onUserDataClicked = EnsureNeverCalledWithParam(), + onLinkClicked = EnsureNeverCalledWithParam(), onMessageClicked = EnsureNeverCalledWithParam(), onMessageLongClicked = EnsureNeverCalledWithParam(), onTimestampClicked = EnsureNeverCalledWithParam(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index 98953f4827..6e475707ad 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation import kotlinx.collections.immutable.persistentListOf @@ -664,6 +665,7 @@ class TimelineItemContentMessageFactoryTest { fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), featureFlagService = featureFlagService, htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform), + permalinkParser = FakePermalinkParser(), ) private fun createStickerContent( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt index f07a73fd84..bf287341ad 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/InReplyToDetailTest.kt @@ -26,14 +26,27 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershi import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import org.junit.Test class InReplyToDetailTest { @Test fun `map - with a not ready InReplyTo does not work`() { - assertThat(InReplyTo.Pending.map()).isNull() - assertThat(InReplyTo.NotLoaded(AN_EVENT_ID).map()).isNull() - assertThat(InReplyTo.Error.map()).isNull() + assertThat( + InReplyTo.Pending.map( + permalinkParser = FakePermalinkParser() + ) + ).isNull() + assertThat( + InReplyTo.NotLoaded(AN_EVENT_ID).map( + permalinkParser = FakePermalinkParser() + ) + ).isNull() + assertThat( + InReplyTo.Error.map( + permalinkParser = FakePermalinkParser() + ) + ).isNull() } @Test @@ -48,7 +61,9 @@ class InReplyToDetailTest { change = MembershipChange.INVITED, ) ) - val inReplyToDetails = inReplyTo.map() + val inReplyToDetails = inReplyTo.map( + permalinkParser = FakePermalinkParser() + ) assertThat(inReplyToDetails).isNotNull() assertThat(inReplyToDetails?.textContent).isNull() } @@ -74,7 +89,11 @@ class InReplyToDetailTest { ) ) ) - assertThat(inReplyTo.map()?.textContent).isEqualTo("Hello!") + assertThat( + inReplyTo.map( + permalinkParser = FakePermalinkParser() + )?.textContent + ).isEqualTo("Hello!") } @Test @@ -95,6 +114,10 @@ class InReplyToDetailTest { ) ) ) - assertThat(inReplyTo.map()?.textContent).isEqualTo("**Hello!**") + assertThat( + inReplyTo.map( + permalinkParser = FakePermalinkParser() + )?.textContent + ).isEqualTo("**Hello!**") } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index 63cc745ecc..6bf8b00dd7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -45,6 +45,7 @@ class RoomDetailsNode @AssistedInject constructor( private val presenter: RoomDetailsPresenter, private val room: MatrixRoom, private val analyticsService: AnalyticsService, + private val permalinkBuilder: PermalinkBuilder, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun openRoomMemberList() @@ -84,8 +85,8 @@ class RoomDetailsNode @AssistedInject constructor( private fun onShareRoom(context: Context) { val alias = room.alias ?: room.alternativeAliases.firstOrNull() - val permalinkResult = alias?.let { PermalinkBuilder.permalinkForRoomAlias(it) } - ?: PermalinkBuilder.permalinkForRoomId(room.roomId) + val permalinkResult = alias?.let { permalinkBuilder.permalinkForRoomAlias(it) } + ?: permalinkBuilder.permalinkForRoomId(room.roomId) permalinkResult.onSuccess { permalink -> context.startSharePlainTextIntent( activityResultLauncher = null, @@ -99,7 +100,7 @@ class RoomDetailsNode @AssistedInject constructor( } private fun onShareMember(context: Context, member: RoomMember) { - val permalinkResult = PermalinkBuilder.permalinkForUser(member.userId) + val permalinkResult = permalinkBuilder.permalinkForUser(member.userId) permalinkResult.onSuccess { permalink -> context.startSharePlainTextIntent( activityResultLauncher = null, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index bdf7385661..71cd975e18 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -46,6 +46,7 @@ class RoomMemberDetailsNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val analyticsService: AnalyticsService, + private val permalinkBuilder: PermalinkBuilder, presenterFactory: RoomMemberDetailsPresenter.Factory, ) : Node(buildContext, plugins = plugins) { interface Callback : NodeInputs { @@ -74,7 +75,7 @@ class RoomMemberDetailsNode @AssistedInject constructor( val context = LocalContext.current fun onShareUser() { - val permalinkResult = PermalinkBuilder.permalinkForUser(inputs.roomMemberId) + val permalinkResult = permalinkBuilder.permalinkForUser(inputs.roomMemberId) permalinkResult.onSuccess { permalink -> context.startSharePlainTextIntent( activityResultLauncher = null, diff --git a/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt index 24296f8798..b0d3f3a339 100644 --- a/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt +++ b/libraries/deeplink/src/main/kotlin/io/element/android/libraries/deeplink/usecase/InviteFriendsUseCase.kt @@ -31,9 +31,10 @@ class InviteFriendsUseCase @Inject constructor( private val stringProvider: StringProvider, private val matrixClient: MatrixClient, private val buildMeta: BuildMeta, + private val permalinkBuilder: PermalinkBuilder, ) { fun execute(activity: Activity) { - val permalinkResult = PermalinkBuilder.permalinkForUser(matrixClient.sessionId) + val permalinkResult = permalinkBuilder.permalinkForUser(matrixClient.sessionId) permalinkResult.fold( onSuccess = { permalink -> val appName = buildMeta.applicationName diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt index e60840768e..ffaabb45ea 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatter.kt @@ -25,6 +25,7 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.SessionScope import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.eventformatter.impl.mode.RenderingMode +import io.element.android.libraries.matrix.api.permalink.PermalinkParser 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.EventTimelineItem @@ -62,6 +63,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( private val roomMembershipContentFormatter: RoomMembershipContentFormatter, private val profileChangeContentFormatter: ProfileChangeContentFormatter, private val stateContentFormatter: StateContentFormatter, + private val permalinkParser: PermalinkParser ) : RoomLastMessageFormatter { companion object { // Max characters to display in the last message. This works around https://github.com/element-hq/element-x-android/issues/2105 @@ -121,7 +123,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor( return "* $senderDisplayName ${messageType.body}" } is TextMessageType -> { - messageType.toPlainText() + messageType.toPlainText(permalinkParser) } is VideoMessageType -> { sp.getString(CommonStrings.common_video) diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt index a0a17b3465..158eeec20d 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTest.kt @@ -51,6 +51,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageT import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.timeline.aPollContent import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem @@ -78,7 +79,8 @@ class DefaultRoomLastMessageFormatterTest { sp = AndroidStringProvider(context.resources), roomMembershipContentFormatter = RoomMembershipContentFormatter(fakeMatrixClient, stringProvider), profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider), - stateContentFormatter = StateContentFormatter(stringProvider) + stateContentFormatter = StateContentFormatter(stringProvider), + permalinkParser = FakePermalinkParser(), ) } diff --git a/libraries/matrix/api/build.gradle.kts b/libraries/matrix/api/build.gradle.kts index 9304634be2..6219296732 100644 --- a/libraries/matrix/api/build.gradle.kts +++ b/libraries/matrix/api/build.gradle.kts @@ -34,7 +34,6 @@ anvil { } dependencies { - implementation(projects.appconfig) implementation(projects.libraries.di) implementation(libs.dagger) implementation(projects.libraries.androidutils) @@ -45,7 +44,5 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.test.truth) - testImplementation(libs.test.robolectric) - testImplementation(projects.tests.testutils) testImplementation(projects.libraries.matrix.test) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt index 4ae7b7dc94..a29a992aca 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt @@ -17,40 +17,18 @@ package io.element.android.libraries.matrix.api.permalink import android.net.Uri -import io.element.android.appconfig.MatrixConfiguration /** * Mapping of an input URI to a matrix.to compliant URI. */ -object MatrixToConverter { +interface MatrixToConverter { /** * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. - * To be successfully converted, URL path should contain one of the [SUPPORTED_PATHS]. + * To be successfully converted, URL path should contain one of the [DefaultMatrixToConverter.SUPPORTED_PATHS]. * Examples: * - https://riot.im/develop/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org * - https://app.element.io/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org * - https://www.example.org/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org */ - fun convert(uri: Uri): Uri? { - val uriString = uri.toString() - val baseUrl = MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL - - return when { - // URL is already a matrix.to - uriString.startsWith(baseUrl) -> uri - // Web or client url - SUPPORTED_PATHS.any { it in uriString } -> { - val path = SUPPORTED_PATHS.first { it in uriString } - Uri.parse(baseUrl + uriString.substringAfter(path)) - } - // URL is not supported - else -> null - } - } - - private val SUPPORTED_PATHS = listOf( - "/#/room/", - "/#/user/", - "/#/group/" - ) + fun convert(uri: Uri): Uri? } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt index c46e15db3b..14c29f2de5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt @@ -16,70 +16,13 @@ package io.element.android.libraries.matrix.api.permalink -import io.element.android.appconfig.MatrixConfiguration -import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId -object PermalinkBuilder { - private const val ROOM_PATH = "room/" - private const val USER_PATH = "user/" - - private val permalinkBaseUrl get() = (MatrixConfiguration.clientPermalinkBaseUrl ?: MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL).also { - var baseUrl = it - if (!baseUrl.endsWith("/")) { - baseUrl += "/" - } - if (!baseUrl.endsWith("/#/")) { - baseUrl += "/#/" - } - } - - fun permalinkForUser(userId: UserId): Result { - return if (MatrixPatterns.isUserId(userId.value)) { - val url = buildString { - append(permalinkBaseUrl) - if (!isMatrixTo()) { - append(USER_PATH) - } - append(userId.value) - } - Result.success(url) - } else { - Result.failure(PermalinkBuilderError.InvalidUserId) - } - } - - fun permalinkForRoomAlias(roomAlias: String): Result { - return if (MatrixPatterns.isRoomAlias(roomAlias)) { - Result.success(permalinkForRoomAliasOrId(roomAlias)) - } else { - Result.failure(PermalinkBuilderError.InvalidRoomAlias) - } - } - - fun permalinkForRoomId(roomId: RoomId): Result { - return if (MatrixPatterns.isRoomId(roomId.value)) { - Result.success(permalinkForRoomAliasOrId(roomId.value)) - } else { - Result.failure(PermalinkBuilderError.InvalidRoomId) - } - } - - private fun permalinkForRoomAliasOrId(value: String): String { - val id = escapeId(value) - return buildString { - append(permalinkBaseUrl) - if (!isMatrixTo()) { - append(ROOM_PATH) - } - append(id) - } - } - - private fun escapeId(value: String) = value.replace("/", "%2F") - - private fun isMatrixTo(): Boolean = permalinkBaseUrl.startsWith(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL) +interface PermalinkBuilder { + fun permalinkForUser(userId: UserId): Result + fun permalinkForRoomAlias(roomAlias: String): Result + fun permalinkForRoomId(roomId: RoomId): Result } sealed class PermalinkBuilderError : Throwable() { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt index 3b90aee1be..463f8fb32d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt @@ -17,11 +17,6 @@ package io.element.android.libraries.matrix.api.permalink import android.net.Uri -import android.net.UrlQuerySanitizer -import io.element.android.libraries.matrix.api.core.MatrixPatterns -import kotlinx.collections.immutable.toImmutableList -import timber.log.Timber -import java.net.URLDecoder /** * This class turns a uri to a [PermalinkData]. @@ -29,121 +24,15 @@ import java.net.URLDecoder * or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org) * or client permalinks (e.g. user/@chagai95:matrix.org) */ -object PermalinkParser { +interface PermalinkParser { /** * Turns a uri string to a [PermalinkData]. */ - fun parse(uriString: String): PermalinkData { - val uri = Uri.parse(uriString) - return parse(uri) - } + fun parse(uriString: String): PermalinkData /** * Turns a uri to a [PermalinkData]. * https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md */ - fun parse(uri: Uri): PermalinkData { - // the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the - // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid - // so convert URI to matrix.to to simplify parsing process - val matrixToUri = MatrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri) - - // We can't use uri.fragment as it is decoding to early and it will break the parsing - // of parameters that represents url (like signurl) - val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment - if (fragment.isEmpty()) { - return PermalinkData.FallbackLink(uri) - } - val safeFragment = fragment.substringBefore('?') - val viaQueryParameters = fragment.getViaParameters() - - // we are limiting to 2 params - val params = safeFragment - .split(MatrixPatterns.SEP_REGEX) - .filter { it.isNotEmpty() } - .take(2) - - val decodedParams = params - .map { URLDecoder.decode(it, "UTF-8") } - - val identifier = params.getOrNull(0) - val decodedIdentifier = decodedParams.getOrNull(0) - val extraParameter = decodedParams.getOrNull(1) - return when { - identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) - MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier) - MatrixPatterns.isRoomId(decodedIdentifier) -> { - handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters) - } - MatrixPatterns.isRoomAlias(decodedIdentifier) -> { - PermalinkData.RoomLink( - roomIdOrAlias = decodedIdentifier, - isRoomAlias = true, - eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, - viaParameters = viaQueryParameters.toImmutableList() - ) - } - else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier)) - } - } - - private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List): PermalinkData { - // Can't rely on built in parsing because it's messing around the signurl - val paramList = safeExtractParams(fragment) - val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second - val email = paramList.firstOrNull { it.first == "email" }?.second - return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) { - try { - val signValidUri = Uri.parse(signUrl) - val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException("missing `authority`") - val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException("missing `token`") - val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException("missing `private_key`") - PermalinkData.RoomEmailInviteLink( - roomId = identifier, - email = email!!, - signUrl = signUrl!!, - roomName = paramList.firstOrNull { it.first == "room_name" }?.second, - inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second, - roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second, - roomType = paramList.firstOrNull { it.first == "room_type" }?.second, - identityServer = identityServerHost, - token = token, - privateKey = privateKey - ) - } catch (failure: Throwable) { - Timber.i("## Permalink: Failed to parse permalink $signUrl") - PermalinkData.FallbackLink(uri) - } - } else { - PermalinkData.RoomLink( - roomIdOrAlias = identifier, - isRoomAlias = false, - eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, - viaParameters = viaQueryParameters.toImmutableList() - ) - } - } - - private fun safeExtractParams(fragment: String) = - fragment.substringAfter("?").split('&').mapNotNull { - val splitNameValue = it.split("=") - if (splitNameValue.size == 2) { - Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8")) - } else { - null - } - } - - private fun String.getViaParameters(): List { - return runCatching { - UrlQuerySanitizer(this) - .parameterList - .filter { - it.mParameter == "via" - } - .map { - URLDecoder.decode(it.mValue, "UTF-8") - } - }.getOrDefault(emptyList()) - } + fun parse(uri: Uri): PermalinkData } diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index d50bad6696..523ea2fda1 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { } else { debugImplementation(libs.matrix.sdk) } + implementation(projects.appconfig) implementation(projects.libraries.di) implementation(projects.libraries.androidutils) implementation(projects.libraries.network) @@ -52,8 +53,10 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.test.truth) + testImplementation(libs.test.robolectric) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.analytics.test) + testImplementation(projects.tests.testutils) testImplementation(libs.coroutines.test) testImplementation(libs.test.turbine) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt new file mode 100644 index 0000000000..002b311600 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.permalink + +import android.net.Uri +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appconfig.MatrixConfiguration +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.permalink.MatrixToConverter +import javax.inject.Inject + +/** + * Mapping of an input URI to a matrix.to compliant URI. + */ +@ContributesBinding(AppScope::class) +class DefaultMatrixToConverter @Inject constructor() : MatrixToConverter { + /** + * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. + * To be successfully converted, URL path should contain one of the [SUPPORTED_PATHS]. + * Examples: + * - https://riot.im/develop/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org + * - https://app.element.io/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org + * - https://www.example.org/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org + */ + override fun convert(uri: Uri): Uri? { + val uriString = uri.toString() + val baseUrl = MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL + + return when { + // URL is already a matrix.to + uriString.startsWith(baseUrl) -> uri + // Web or client url + SUPPORTED_PATHS.any { it in uriString } -> { + val path = SUPPORTED_PATHS.first { it in uriString } + Uri.parse(baseUrl + uriString.substringAfter(path)) + } + // URL is not supported + else -> null + } + } + + private val SUPPORTED_PATHS = listOf( + "/#/room/", + "/#/user/", + "/#/group/" + ) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt new file mode 100644 index 0000000000..ae30c94c9c --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.permalink + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.appconfig.MatrixConfiguration +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.MatrixPatterns +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.permalink.PermalinkBuilder +import io.element.android.libraries.matrix.api.permalink.PermalinkBuilderError +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder { + private val permalinkBaseUrl + get() = (MatrixConfiguration.clientPermalinkBaseUrl ?: MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL).also { + var baseUrl = it + if (!baseUrl.endsWith("/")) { + baseUrl += "/" + } + if (!baseUrl.endsWith("/#/")) { + baseUrl += "/#/" + } + } + + override fun permalinkForUser(userId: UserId): Result { + return if (MatrixPatterns.isUserId(userId.value)) { + val url = buildString { + append(permalinkBaseUrl) + if (!isMatrixTo()) { + append(USER_PATH) + } + append(userId.value) + } + Result.success(url) + } else { + Result.failure(PermalinkBuilderError.InvalidUserId) + } + } + + override fun permalinkForRoomAlias(roomAlias: String): Result { + return if (MatrixPatterns.isRoomAlias(roomAlias)) { + Result.success(permalinkForRoomAliasOrId(roomAlias)) + } else { + Result.failure(PermalinkBuilderError.InvalidRoomAlias) + } + } + + override fun permalinkForRoomId(roomId: RoomId): Result { + return if (MatrixPatterns.isRoomId(roomId.value)) { + Result.success(permalinkForRoomAliasOrId(roomId.value)) + } else { + Result.failure(PermalinkBuilderError.InvalidRoomId) + } + } + + private fun permalinkForRoomAliasOrId(value: String): String { + val id = escapeId(value) + return buildString { + append(permalinkBaseUrl) + if (!isMatrixTo()) { + append(ROOM_PATH) + } + append(id) + } + } + + private fun escapeId(value: String) = value.replace("/", "%2F") + + private fun isMatrixTo(): Boolean = permalinkBaseUrl.startsWith(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL) + + companion object { + private const val ROOM_PATH = "room/" + private const val USER_PATH = "user/" + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt new file mode 100644 index 0000000000..ab91a89af9 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.permalink + +import android.net.Uri +import android.net.UrlQuerySanitizer +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.core.MatrixPatterns +import io.element.android.libraries.matrix.api.permalink.MatrixToConverter +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import kotlinx.collections.immutable.toImmutableList +import timber.log.Timber +import java.net.URLDecoder +import javax.inject.Inject + +/** + * This class turns a uri to a [PermalinkData]. + * element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks + * or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org) + * or client permalinks (e.g. user/@chagai95:matrix.org) + */ +@ContributesBinding(AppScope::class) +class DefaultPermalinkParser @Inject constructor( + private val matrixToConverter: MatrixToConverter +) : PermalinkParser { + /** + * Turns a uri string to a [PermalinkData]. + */ + override fun parse(uriString: String): PermalinkData { + val uri = Uri.parse(uriString) + return parse(uri) + } + + /** + * Turns a uri to a [PermalinkData]. + * https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md + */ + override fun parse(uri: Uri): PermalinkData { + // the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the + // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid + // so convert URI to matrix.to to simplify parsing process + val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri) + + // We can't use uri.fragment as it is decoding to early and it will break the parsing + // of parameters that represents url (like signurl) + val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment + if (fragment.isEmpty()) { + return PermalinkData.FallbackLink(uri) + } + val safeFragment = fragment.substringBefore('?') + val viaQueryParameters = fragment.getViaParameters() + + // we are limiting to 2 params + val params = safeFragment + .split(MatrixPatterns.SEP_REGEX) + .filter { it.isNotEmpty() } + .take(2) + + val decodedParams = params + .map { URLDecoder.decode(it, "UTF-8") } + + val identifier = params.getOrNull(0) + val decodedIdentifier = decodedParams.getOrNull(0) + val extraParameter = decodedParams.getOrNull(1) + return when { + identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) + MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier) + MatrixPatterns.isRoomId(decodedIdentifier) -> { + handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters) + } + MatrixPatterns.isRoomAlias(decodedIdentifier) -> { + PermalinkData.RoomLink( + roomIdOrAlias = decodedIdentifier, + isRoomAlias = true, + eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, + viaParameters = viaQueryParameters.toImmutableList() + ) + } + else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier)) + } + } + + private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List): PermalinkData { + // Can't rely on built in parsing because it's messing around the signurl + val paramList = safeExtractParams(fragment) + val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second + val email = paramList.firstOrNull { it.first == "email" }?.second + return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) { + try { + val signValidUri = Uri.parse(signUrl) + val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException("missing `authority`") + val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException("missing `token`") + val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException("missing `private_key`") + PermalinkData.RoomEmailInviteLink( + roomId = identifier, + email = email!!, + signUrl = signUrl!!, + roomName = paramList.firstOrNull { it.first == "room_name" }?.second, + inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second, + roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second, + roomType = paramList.firstOrNull { it.first == "room_type" }?.second, + identityServer = identityServerHost, + token = token, + privateKey = privateKey + ) + } catch (failure: Throwable) { + Timber.i("## Permalink: Failed to parse permalink $signUrl") + PermalinkData.FallbackLink(uri) + } + } else { + PermalinkData.RoomLink( + roomIdOrAlias = identifier, + isRoomAlias = false, + eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, + viaParameters = viaQueryParameters.toImmutableList() + ) + } + } + + private fun safeExtractParams(fragment: String) = + fragment.substringAfter("?").split('&').mapNotNull { + val splitNameValue = it.split("=") + if (splitNameValue.size == 2) { + Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8")) + } else { + null + } + } + + private fun String.getViaParameters(): List { + return runCatching { + UrlQuerySanitizer(this) + .parameterList + .filter { + it.mParameter == "via" + } + .map { + URLDecoder.decode(it.mValue, "UTF-8") + } + }.getOrDefault(emptyList()) + } +} diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt similarity index 71% rename from libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt index cbf1bffc88..7401ac7c2e 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverterTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 New Vector Ltd + * Copyright (c) 2024 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.api.permalink +package io.element.android.libraries.matrix.impl.permalink import android.net.Uri import com.google.common.truth.Truth.assertThat @@ -23,34 +23,34 @@ import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) -class MatrixToConverterTest { +class DefaultMatrixToConverterTest { @Test fun `converting a matrix-to url does nothing`() { val url = Uri.parse("https://matrix.to/#/#element-android:matrix.org") - assertThat(MatrixToConverter.convert(url)).isEqualTo(url) + assertThat(DefaultMatrixToConverter().convert(url)).isEqualTo(url) } @Test fun `converting a url with a supported room path returns a matrix-to url`() { val url = Uri.parse("https://riot.im/develop/#/room/#element-android:matrix.org") - assertThat(MatrixToConverter.convert(url)).isEqualTo(Uri.parse("https://matrix.to/#/#element-android:matrix.org")) + assertThat(DefaultMatrixToConverter().convert(url)).isEqualTo(Uri.parse("https://matrix.to/#/#element-android:matrix.org")) } @Test fun `converting a url with a supported user path returns a matrix-to url`() { val url = Uri.parse("https://riot.im/develop/#/user/@test:matrix.org") - assertThat(MatrixToConverter.convert(url)).isEqualTo(Uri.parse("https://matrix.to/#/@test:matrix.org")) + assertThat(DefaultMatrixToConverter().convert(url)).isEqualTo(Uri.parse("https://matrix.to/#/@test:matrix.org")) } @Test fun `converting a url with a supported group path returns a matrix-to url`() { val url = Uri.parse("https://riot.im/develop/#/group/+group:matrix.org") - assertThat(MatrixToConverter.convert(url)).isEqualTo(Uri.parse("https://matrix.to/#/+group:matrix.org")) + assertThat(DefaultMatrixToConverter().convert(url)).isEqualTo(Uri.parse("https://matrix.to/#/+group:matrix.org")) } @Test fun `converting an unsupported url returns null`() { val url = Uri.parse("https://element.io/") - assertThat(MatrixToConverter.convert(url)).isNull() + assertThat(DefaultMatrixToConverter().convert(url)).isNull() } } diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilderTest.kt similarity index 70% rename from libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilderTest.kt index 2b5b29e084..c861b3105c 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilderTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilderTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 New Vector Ltd + * Copyright (c) 2024 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.api.permalink +package io.element.android.libraries.matrix.impl.permalink import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.metadata.withReleaseBehavior @@ -23,18 +23,18 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.tests.testutils.assertThrowsInDebug import org.junit.Test -class PermalinkBuilderTest { +class DefaultPermalinkBuilderTest { fun `building a permalink for an invalid user id throws when verifying the id`() { assertThrowsInDebug { val userId = UserId("some invalid user id") - PermalinkBuilder.permalinkForUser(userId) + DefaultPermalinkBuilder().permalinkForUser(userId) } } fun `building a permalink for an invalid room id throws when verifying the id`() { assertThrowsInDebug { val roomId = RoomId("some invalid room id") - PermalinkBuilder.permalinkForRoomId(roomId) + DefaultPermalinkBuilder().permalinkForRoomId(roomId) } } @@ -42,7 +42,7 @@ class PermalinkBuilderTest { fun `building a permalink for an invalid user id returns failure when not verifying the id`() { withReleaseBehavior { val userId = UserId("some invalid user id") - assertThat(PermalinkBuilder.permalinkForUser(userId).isFailure).isTrue() + assertThat(DefaultPermalinkBuilder().permalinkForUser(userId).isFailure).isTrue() } } @@ -50,31 +50,31 @@ class PermalinkBuilderTest { fun `building a permalink for an invalid room id returns failure when not verifying the id`() { withReleaseBehavior { val roomId = RoomId("some invalid room id") - assertThat(PermalinkBuilder.permalinkForRoomId(roomId).isFailure).isTrue() + assertThat(DefaultPermalinkBuilder().permalinkForRoomId(roomId).isFailure).isTrue() } } @Test fun `building a permalink for an invalid room alias returns failure`() { val roomAlias = "an invalid room alias" - assertThat(PermalinkBuilder.permalinkForRoomAlias(roomAlias).isFailure).isTrue() + assertThat(DefaultPermalinkBuilder().permalinkForRoomAlias(roomAlias).isFailure).isTrue() } @Test fun `building a permalink for a valid user id returns a matrix-to url`() { val userId = UserId("@user:matrix.org") - assertThat(PermalinkBuilder.permalinkForUser(userId).getOrNull()).isEqualTo("https://matrix.to/#/@user:matrix.org") + assertThat(DefaultPermalinkBuilder().permalinkForUser(userId).getOrNull()).isEqualTo("https://matrix.to/#/@user:matrix.org") } @Test fun `building a permalink for a valid room id returns a matrix-to url`() { val roomId = RoomId("!aBCdEFG1234:matrix.org") - assertThat(PermalinkBuilder.permalinkForRoomId(roomId).getOrNull()).isEqualTo("https://matrix.to/#/!aBCdEFG1234:matrix.org") + assertThat(DefaultPermalinkBuilder().permalinkForRoomId(roomId).getOrNull()).isEqualTo("https://matrix.to/#/!aBCdEFG1234:matrix.org") } @Test fun `building a permalink for a valid room alias returns a matrix-to url`() { val roomAlias = "#room:matrix.org" - assertThat(PermalinkBuilder.permalinkForRoomAlias(roomAlias).getOrNull()).isEqualTo("https://matrix.to/#/#room:matrix.org") + assertThat(DefaultPermalinkBuilder().permalinkForRoomAlias(roomAlias).getOrNull()).isEqualTo("https://matrix.to/#/#room:matrix.org") } } diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParserTest.kt similarity index 70% rename from libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParserTest.kt index 66cdaf88ee..1e9e3bc2dc 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParserTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 New Vector Ltd + * Copyright (c) 2024 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,44 +14,60 @@ * limitations under the License. */ -package io.element.android.libraries.matrix.api.permalink +package io.element.android.libraries.matrix.impl.permalink import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.permalink.PermalinkData import kotlinx.collections.immutable.persistentListOf import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) -class PermalinkParserTest { +class DefaultPermalinkParserTest { @Test fun `parsing an invalid url returns a fallback link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://element.io" - assertThat(PermalinkParser.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) + assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) } @Test fun `parsing an invalid url with the right path but no content returns a fallback link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/user" - assertThat(PermalinkParser.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) + assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) } @Test fun `parsing an invalid url with the right path but empty content returns a fallback link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/user/" - assertThat(PermalinkParser.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) + assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) } @Test fun `parsing an invalid url with the right path but invalid content returns a fallback link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/user/some%20user!" - assertThat(PermalinkParser.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) + assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) } @Test fun `parsing a valid user url returns a user link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/user/@test:matrix.org" - assertThat(PermalinkParser.parse(url)).isEqualTo( + assertThat(sut.parse(url)).isEqualTo( PermalinkData.UserLink( userId = "@test:matrix.org" ) @@ -60,8 +76,11 @@ class PermalinkParserTest { @Test fun `parsing a valid room id url returns a room link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/room/!aBCD1234:matrix.org" - assertThat(PermalinkParser.parse(url)).isEqualTo( + assertThat(sut.parse(url)).isEqualTo( PermalinkData.RoomLink( roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, @@ -73,8 +92,11 @@ class PermalinkParserTest { @Test fun `parsing a valid room id with event id url returns a room link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/$1234567890abcdef:matrix.org" - assertThat(PermalinkParser.parse(url)).isEqualTo( + assertThat(sut.parse(url)).isEqualTo( PermalinkData.RoomLink( roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, @@ -86,8 +108,11 @@ class PermalinkParserTest { @Test fun `parsing a valid room id with and invalid event id url returns a room link with no event id`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/1234567890abcdef:matrix.org" - assertThat(PermalinkParser.parse(url)).isEqualTo( + assertThat(sut.parse(url)).isEqualTo( PermalinkData.RoomLink( roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, @@ -99,8 +124,11 @@ class PermalinkParserTest { @Test fun `parsing a valid room id with event id and via parameters url returns a room link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/$1234567890abcdef:matrix.org?via=matrix.org&via=matrix.com" - assertThat(PermalinkParser.parse(url)).isEqualTo( + assertThat(sut.parse(url)).isEqualTo( PermalinkData.RoomLink( roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, @@ -112,8 +140,11 @@ class PermalinkParserTest { @Test fun `parsing a valid room alias url returns a room link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/room/#element-android:matrix.org" - assertThat(PermalinkParser.parse(url)).isEqualTo( + assertThat(sut.parse(url)).isEqualTo( PermalinkData.RoomLink( roomIdOrAlias = "#element-android:matrix.org", isRoomAlias = true, @@ -125,6 +156,9 @@ class PermalinkParserTest { @Test fun `parsing a url with an invalid signurl returns a fallback link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) // This url has no private key val url = "https://app.element.io/#/room/%21aBCDEF12345%3Amatrix.org" + "?email=testuser%40element.io" + @@ -135,11 +169,14 @@ class PermalinkParserTest { "&guest_access_token=" + "&guest_user_id=" + "&room_type=" - assertThat(PermalinkParser.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) + assertThat(sut.parse(url)).isInstanceOf(PermalinkData.FallbackLink::class.java) } @Test fun `parsing a url with signurl returns a room email invite link`() { + val sut = DefaultPermalinkParser( + matrixToConverter = DefaultMatrixToConverter(), + ) val url = "https://app.element.io/#/room/%21aBCDEF12345%3Amatrix.org" + "?email=testuser%40element.io" + "&signurl=https%3A%2F%2Fvector.im%2F_matrix%2Fidentity%2Fapi%2Fv1%2Fsign-ed25519%3Ftoken%3Da_token%26private_key%3Da_private_key" + @@ -149,7 +186,7 @@ class PermalinkParserTest { "&guest_access_token=" + "&guest_user_id=" + "&room_type=" - assertThat(PermalinkParser.parse(url)).isEqualTo( + assertThat(sut.parse(url)).isEqualTo( PermalinkData.RoomEmailInviteLink( roomId = "!aBCDEF12345:matrix.org", email = "testuser@element.io", diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt new file mode 100644 index 0000000000..4e6d3375e1 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.test.permalink + +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.permalink.PermalinkBuilder + +class FakePermalinkBuilder( + private val result: () -> Result = { Result.failure(Exception("Not implemented")) } +) : PermalinkBuilder { + override fun permalinkForUser(userId: UserId): Result { + return result() + } + + override fun permalinkForRoomAlias(roomAlias: String): Result { + return result() + } + + override fun permalinkForRoomId(roomId: RoomId): Result { + return result() + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt new file mode 100644 index 0000000000..5c6935d76d --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.test.permalink + +import android.net.Uri +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser + +class FakePermalinkParser( + private var result: () -> PermalinkData = { throw Exception("Not implemented") } +) : PermalinkParser { + fun givenResult(result: PermalinkData) { + this.result = { result } + } + + override fun parse(uriString: String): PermalinkData { + return result() + } + + override fun parse(uri: Uri): PermalinkData { + TODO("Not yet implemented") + } +} diff --git a/libraries/matrixui/build.gradle.kts b/libraries/matrixui/build.gradle.kts index 669f9c9634..fa7a946959 100644 --- a/libraries/matrixui/build.gradle.kts +++ b/libraries/matrixui/build.gradle.kts @@ -47,4 +47,5 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.test.truth) testImplementation(libs.test.robolectric) + testImplementation(projects.libraries.matrix.test) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt index b3c6a0bf86..36ae94d7f7 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt @@ -29,9 +29,13 @@ import org.jsoup.nodes.Document * * This will also make sure mentions are prefixed with `@`. * + * @param permalinkParser the parser to use to parse the mentions. * @param prefix if not null, the prefix will be inserted at the beginning of the message. */ -fun FormattedBody.toHtmlDocument(prefix: String? = null): Document? { +fun FormattedBody.toHtmlDocument( + permalinkParser: PermalinkParser, + prefix: String? = null, +): Document? { return takeIf { it.format == MessageFormat.HTML }?.body // Trim whitespace at the end to avoid having wrong rendering of the message. // We don't trim the start in case it's used as indentation. @@ -44,17 +48,20 @@ fun FormattedBody.toHtmlDocument(prefix: String? = null): Document? { } // Prepend `@` to mentions - fixMentions(dom) + fixMentions(dom, permalinkParser) dom } } -private fun fixMentions(dom: Document) { +private fun fixMentions( + dom: Document, + permalinkParser: PermalinkParser, +) { val links = dom.getElementsByTag("a") links.forEach { if (it.hasAttr("href")) { - val link = PermalinkParser.parse(it.attr("href")) + val link = permalinkParser.parse(it.attr("href")) if (link is PermalinkData.UserLink && !it.text().startsWith("@")) { it.prependText("@") } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt index 0cea2d7ae2..1eb581af65 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.ui.messages +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType @@ -29,15 +30,24 @@ import org.jsoup.select.NodeVisitor * Converts the HTML string in [TextMessageType.formatted] to a plain text representation by parsing it and removing all formatting. * If the message is not formatted or the format is not [MessageFormat.HTML], the [TextMessageType.body] is returned instead. */ -fun TextMessageType.toPlainText() = formatted?.toPlainText() ?: body +fun TextMessageType.toPlainText( + permalinkParser: PermalinkParser, +) = formatted?.toPlainText(permalinkParser) ?: body /** * Converts the HTML string in [FormattedBody.body] to a plain text representation by parsing it and removing all formatting. * If the message is not formatted or the format is not [MessageFormat.HTML] we return `null`. + * @param permalinkParser the parser to use to parse the mentions. * @param prefix if not null, the prefix will be inserted at the beginning of the message. */ -fun FormattedBody.toPlainText(prefix: String? = null): String? { - return this.toHtmlDocument(prefix)?.toPlainText() +fun FormattedBody.toPlainText( + permalinkParser: PermalinkParser, + prefix: String? = null, +): String? { + return this.toHtmlDocument( + permalinkParser = permalinkParser, + prefix = prefix, + )?.toPlainText() } /** diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt index 704b87c593..ab83f77e66 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToHtmlDocumentTest.kt @@ -16,9 +16,13 @@ package io.element.android.libraries.matrixui.messages +import android.net.Uri import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.ui.messages.toHtmlDocument import org.junit.Test import org.junit.runner.RunWith @@ -33,7 +37,7 @@ class ToHtmlDocumentTest { body = "Hello world" ) - val document = body.toHtmlDocument() + val document = body.toHtmlDocument(permalinkParser = FakePermalinkParser()) assertThat(document).isNull() } @@ -45,7 +49,7 @@ class ToHtmlDocumentTest { body = "

    Hello world

    " ) - val document = body.toHtmlDocument() + val document = body.toHtmlDocument(permalinkParser = FakePermalinkParser()) assertThat(document).isNotNull() assertThat(document?.text()).isEqualTo("Hello world") } @@ -57,7 +61,10 @@ class ToHtmlDocumentTest { body = "

    Hello world

    " ) - val document = body.toHtmlDocument(prefix = "@Jorge:") + val document = body.toHtmlDocument( + permalinkParser = FakePermalinkParser(), + prefix = "@Jorge:" + ) assertThat(document).isNotNull() assertThat(document?.text()).isEqualTo("@Jorge: Hello world") } @@ -69,7 +76,13 @@ class ToHtmlDocumentTest { body = "Hey Alice!" ) - val document = body.toHtmlDocument() + val document = body.toHtmlDocument(permalinkParser = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData { + return PermalinkData.UserLink("@alice:matrix.org") + } + + override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented") + }) assertThat(document?.text()).isEqualTo("Hey @Alice!") } @@ -80,7 +93,13 @@ class ToHtmlDocumentTest { body = "Hey @Alice!" ) - val document = body.toHtmlDocument() + val document = body.toHtmlDocument(permalinkParser = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData { + return PermalinkData.UserLink("@alice:matrix.org") + } + + override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented") + }) assertThat(document?.text()).isEqualTo("Hey @Alice!") } @@ -91,7 +110,13 @@ class ToHtmlDocumentTest { body = "Hey Alice!" ) - val document = body.toHtmlDocument() + val document = body.toHtmlDocument(permalinkParser = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData { + return PermalinkData.FallbackLink(uri = Uri.parse("https://matrix.org")) + } + + override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented") + }) assertThat(document?.text()).isEqualTo("Hey Alice!") } } diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt index 7c61075584..74e43a9e39 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrixui/messages/ToPlainTextTest.kt @@ -20,6 +20,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.ui.messages.toPlainText import org.jsoup.Jsoup import org.junit.Test @@ -59,7 +60,7 @@ class ToPlainTextTest {
    """.trimIndent() ) - assertThat(formattedBody.toPlainText()).isEqualTo( + assertThat(formattedBody.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo( """ Hello world • This is an unordered list. @@ -79,7 +80,7 @@ class ToPlainTextTest {
    """.trimIndent() ) - assertThat(formattedBody.toPlainText()).isNull() + assertThat(formattedBody.toPlainText(permalinkParser = FakePermalinkParser())).isNull() } @Test @@ -96,7 +97,7 @@ class ToPlainTextTest { """.trimIndent() ) ) - assertThat(messageType.toPlainText()).isEqualTo( + assertThat(messageType.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo( """ Hello world • This is an unordered list. @@ -119,6 +120,6 @@ class ToPlainTextTest { """.trimIndent() ) ) - assertThat(messageType.toPlainText()).isEqualTo("This is the fallback text") + assertThat(messageType.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo("This is the fallback text") } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index 3b2ac19313..8a285e3b32 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -30,6 +30,7 @@ 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.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.RoomMembershipState import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType @@ -68,6 +69,7 @@ class NotifiableEventResolver @Inject constructor( private val matrixClientProvider: MatrixClientProvider, private val notificationMediaRepoFactory: NotificationMediaRepo.Factory, @ApplicationContext private val context: Context, + private val permalinkParser: PermalinkParser, ) { suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? { // Restore session @@ -252,7 +254,7 @@ class NotifiableEventResolver @Inject constructor( is ImageMessageType -> messageType.body is StickerMessageType -> messageType.body is NoticeMessageType -> messageType.body - is TextMessageType -> messageType.toPlainText() + is TextMessageType -> messageType.toPlainText(permalinkParser = permalinkParser) is VideoMessageType -> messageType.body is LocationMessageType -> messageType.body is OtherMessageType -> messageType.body diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt index 4e491a0def..b2f954091e 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolverTest.kt @@ -43,6 +43,7 @@ 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.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.notification.FakeNotificationService +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationMediaRepo import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -549,6 +550,7 @@ class NotifiableEventResolverTest { matrixClientProvider = matrixClientProvider, notificationMediaRepoFactory = notificationMediaRepoFactory, context = context, + permalinkParser = FakePermalinkParser(), ) } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 91c8bd9666..caa5e9f96b 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -63,6 +63,8 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo @@ -98,6 +100,7 @@ import kotlin.time.Duration.Companion.seconds fun TextComposer( state: RichTextEditorState, voiceMessageState: VoiceMessageState, + permalinkParser: PermalinkParser, composerMode: MessageComposerMode, enableTextFormatting: Boolean, enableVoiceMessages: Boolean, @@ -152,7 +155,10 @@ fun TextComposer( val textInput: @Composable () -> Unit = remember(state, subcomposing, composerMode, onResetComposerMode, onError) { @Composable { - val mentionSpanProvider = rememberMentionSpanProvider(currentUserId) + val mentionSpanProvider = rememberMentionSpanProvider( + currentUserId = currentUserId, + permalinkParser = permalinkParser, + ) TextInput( state = state, subcomposing = subcomposing, @@ -907,6 +913,10 @@ private fun ATextComposer( state = richTextEditorState, showTextFormatting = showTextFormatting, voiceMessageState = voiceMessageState, + permalinkParser = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData = TODO("Not yet implemented") + override fun parse(uri: Uri): PermalinkData = TODO("Not yet implemented") + }, composerMode = composerMode, enableTextFormatting = enableTextFormatting, enableVoiceMessages = enableVoiceMessages, diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt index f7b28d65e8..e50b0fc16a 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.textcomposer.mentions import android.graphics.Color import android.graphics.Typeface +import android.net.Uri import android.view.ViewGroup import android.widget.TextView import androidx.compose.foundation.layout.PaddingValues @@ -42,10 +43,12 @@ import io.element.android.libraries.designsystem.theme.mentionPillText import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser +import kotlinx.collections.immutable.persistentListOf @Stable class MentionSpanProvider( private val currentSessionId: SessionId, + private val permalinkParser: PermalinkParser, private var currentUserTextColor: Int = 0, private var currentUserBackgroundColor: Int = Color.WHITE, private var otherTextColor: Int = 0, @@ -73,7 +76,7 @@ class MentionSpanProvider( } fun getMentionSpanFor(text: String, url: String): MentionSpan { - val permalinkData = PermalinkParser.parse(url) + val permalinkData = permalinkParser.parse(url) val (startPaddingPx, endPaddingPx) = paddingValuesPx.value return when { permalinkData is PermalinkData.UserLink -> { @@ -112,9 +115,15 @@ class MentionSpanProvider( } @Composable -fun rememberMentionSpanProvider(currentUserId: SessionId): MentionSpanProvider { +fun rememberMentionSpanProvider( + currentUserId: SessionId, + permalinkParser: PermalinkParser, +): MentionSpanProvider { val provider = remember(currentUserId) { - MentionSpanProvider(currentUserId) + MentionSpanProvider( + currentSessionId = currentUserId, + permalinkParser = permalinkParser, + ) } provider.setup() return provider @@ -123,7 +132,26 @@ fun rememberMentionSpanProvider(currentUserId: SessionId): MentionSpanProvider { @PreviewsDayNight @Composable internal fun MentionSpanPreview() { - val provider = rememberMentionSpanProvider(SessionId("@me:matrix.org")) + val provider = rememberMentionSpanProvider( + currentUserId = SessionId("@me:matrix.org"), + permalinkParser = object : PermalinkParser { + override fun parse(uriString: String): PermalinkData { + return when (uriString) { + "https://matrix.to/#/@me:matrix.org" -> PermalinkData.UserLink("@me:matrix.org") + "https://matrix.to/#/@other:matrix.org" -> PermalinkData.UserLink("@other:matrix.org") + "https://matrix.to/#/#room:matrix.org" -> PermalinkData.RoomLink( + roomIdOrAlias = "#room:matrix.org", + isRoomAlias = true, + eventId = null, + viaParameters = persistentListOf(), + ) + else -> TODO() + } + } + + override fun parse(uri: Uri): PermalinkData = TODO() + }, + ) ElementPreview { provider.setup() diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt index c8281cca0e..475b879895 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderTest.kt @@ -18,9 +18,12 @@ package io.element.android.libraries.textcomposer.impl.mentions import android.graphics.Color import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider import io.element.android.tests.testutils.WarmUpRule +import kotlinx.collections.immutable.persistentListOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -35,8 +38,10 @@ class MentionSpanProviderTest { private val otherColor = Color.BLUE private val currentUserId = A_SESSION_ID + private val permalinkParser = FakePermalinkParser() private val mentionSpanProvider = MentionSpanProvider( currentSessionId = currentUserId, + permalinkParser = permalinkParser, currentUserBackgroundColor = myUserColor, currentUserTextColor = myUserColor, otherBackgroundColor = otherColor, @@ -45,6 +50,7 @@ class MentionSpanProviderTest { @Test fun `getting mention span for current user should return a MentionSpan with custom colors`() { + permalinkParser.givenResult(PermalinkData.UserLink(currentUserId.value)) val mentionSpan = mentionSpanProvider.getMentionSpanFor("@me:matrix.org", "https://matrix.to/#/${currentUserId.value}") assertThat(mentionSpan.backgroundColor).isEqualTo(myUserColor) assertThat(mentionSpan.textColor).isEqualTo(myUserColor) @@ -52,6 +58,7 @@ class MentionSpanProviderTest { @Test fun `getting mention span for other user should return a MentionSpan with normal colors`() { + permalinkParser.givenResult(PermalinkData.UserLink("@other:matrix.org")) val mentionSpan = mentionSpanProvider.getMentionSpanFor("@other:matrix.org", "https://matrix.to/#/@other:matrix.org") assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) assertThat(mentionSpan.textColor).isEqualTo(otherColor) @@ -59,6 +66,14 @@ class MentionSpanProviderTest { @Test fun `getting mention span for a room should return a MentionSpan with normal colors`() { + permalinkParser.givenResult( + PermalinkData.RoomLink( + roomIdOrAlias = "#room:matrix.org", + isRoomAlias = true, + eventId = null, + viaParameters = persistentListOf(), + ) + ) val mentionSpan = mentionSpanProvider.getMentionSpanFor("#room:matrix.org", "https://matrix.to/#/#room:matrix.org") assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) assertThat(mentionSpan.textColor).isEqualTo(otherColor) @@ -66,6 +81,14 @@ class MentionSpanProviderTest { @Test fun `getting mention span for @room should return a MentionSpan with normal colors`() { + permalinkParser.givenResult( + PermalinkData.RoomLink( + roomIdOrAlias = "#", + isRoomAlias = true, + eventId = null, + viaParameters = persistentListOf(), + ) + ) val mentionSpan = mentionSpanProvider.getMentionSpanFor("@room", "#") assertThat(mentionSpan.backgroundColor).isEqualTo(otherColor) assertThat(mentionSpan.textColor).isEqualTo(otherColor) From 2a248417faef2681c2950adae97c5df57c741a0f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 14:35:58 +0200 Subject: [PATCH 041/124] Move Event exclusion to the module appconfig. It has to become an android module to be able to add a dependency on : libraries:matrix:api. --- appconfig/build.gradle.kts | 10 +++---- .../android/appconfig/TimelineConfig.kt | 22 ++++++++++++++ .../libraries/matrix/impl/RustMatrixClient.kt | 30 +++++++------------ 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/appconfig/build.gradle.kts b/appconfig/build.gradle.kts index 853a775b45..e59799d685 100644 --- a/appconfig/build.gradle.kts +++ b/appconfig/build.gradle.kts @@ -14,16 +14,13 @@ * limitations under the License. */ plugins { - id("java-library") - id("com.android.lint") - alias(libs.plugins.kotlin.jvm) + id("io.element.android-library") alias(libs.plugins.anvil) alias(libs.plugins.ksp) } -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 +android { + namespace = "io.element.android.appconfig" } anvil { @@ -33,4 +30,5 @@ anvil { dependencies { implementation(libs.dagger) implementation(projects.libraries.di) + implementation(projects.libraries.matrix.api) } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt index c309e01277..da116d3b27 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt @@ -16,6 +16,28 @@ package io.element.android.appconfig +import io.element.android.libraries.matrix.api.room.StateEventType + object TimelineConfig { const val MAX_READ_RECEIPT_TO_DISPLAY = 3 + + /** + * Event types that will be filtered out from the timeline (i.e. not displayed). + */ + val excludedEvents = listOf( + StateEventType.ROOM_ALIASES, + StateEventType.ROOM_CANONICAL_ALIAS, + StateEventType.ROOM_GUEST_ACCESS, + StateEventType.ROOM_HISTORY_VISIBILITY, + StateEventType.ROOM_JOIN_RULES, + StateEventType.ROOM_PINNED_EVENTS, + StateEventType.ROOM_POWER_LEVELS, + StateEventType.ROOM_SERVER_ACL, + StateEventType.ROOM_TOMBSTONE, + StateEventType.SPACE_CHILD, + StateEventType.SPACE_PARENT, + StateEventType.POLICY_RULE_ROOM, + StateEventType.POLICY_RULE_SERVER, + StateEventType.POLICY_RULE_USER, + ) } 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 62691e8cde..0212a5293a 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 @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl +import io.element.android.appconfig.TimelineConfig import io.element.android.libraries.androidutils.file.getSizeOfFiles import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -54,6 +55,7 @@ import io.element.android.libraries.matrix.impl.room.MatrixRoomInfoMapper import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber import io.element.android.libraries.matrix.impl.room.RustMatrixRoom +import io.element.android.libraries.matrix.impl.room.map import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory import io.element.android.libraries.matrix.impl.roomlist.RustRoomListService @@ -97,7 +99,6 @@ import org.matrix.rustcomponents.sdk.NotificationProcessSetup import org.matrix.rustcomponents.sdk.PowerLevels import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem -import org.matrix.rustcomponents.sdk.StateEventType import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter import org.matrix.rustcomponents.sdk.use @@ -229,24 +230,15 @@ class RustMatrixClient( ), ) - private val eventFilters = TimelineEventTypeFilter.exclude( - listOf( - StateEventType.ROOM_ALIASES, - StateEventType.ROOM_CANONICAL_ALIAS, - StateEventType.ROOM_GUEST_ACCESS, - StateEventType.ROOM_HISTORY_VISIBILITY, - StateEventType.ROOM_JOIN_RULES, - StateEventType.ROOM_PINNED_EVENTS, - StateEventType.ROOM_POWER_LEVELS, - StateEventType.ROOM_SERVER_ACL, - StateEventType.ROOM_TOMBSTONE, - StateEventType.SPACE_CHILD, - StateEventType.SPACE_PARENT, - StateEventType.POLICY_RULE_ROOM, - StateEventType.POLICY_RULE_SERVER, - StateEventType.POLICY_RULE_USER, - ).map(FilterTimelineEventType::State) - ) + private val eventFilters = TimelineConfig.excludedEvents + .takeIf { it.isNotEmpty() } + ?.let { listStateEventType -> + TimelineEventTypeFilter.exclude( + listStateEventType.map { stateEventType -> + FilterTimelineEventType.State(stateEventType.map()) + } + ) + } override val mediaLoader: MatrixMediaLoader = RustMediaLoader( baseCacheDirectory = baseCacheDirectory, From 71b8a1df90b6f0c783785569dcc4430bce84b28d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 14:36:44 +0200 Subject: [PATCH 042/124] Exlude CallMember state event from the timeline (Fixes #2625) --- .../main/kotlin/io/element/android/appconfig/TimelineConfig.kt | 1 + changelog.d/2625.bugfix | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/2625.bugfix diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt index da116d3b27..2ad2916550 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt @@ -25,6 +25,7 @@ object TimelineConfig { * Event types that will be filtered out from the timeline (i.e. not displayed). */ val excludedEvents = listOf( + StateEventType.CALL_MEMBER, StateEventType.ROOM_ALIASES, StateEventType.ROOM_CANONICAL_ALIAS, StateEventType.ROOM_GUEST_ACCESS, diff --git a/changelog.d/2625.bugfix b/changelog.d/2625.bugfix new file mode 100644 index 0000000000..48fea40258 --- /dev/null +++ b/changelog.d/2625.bugfix @@ -0,0 +1 @@ +Hide Event org.matrix.msc3401.call.member on the timeline. From ef7553a6f67f0866135cd5e3fa7d2c1c8f698b63 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 15:06:59 +0200 Subject: [PATCH 043/124] Fix compilation of minimal app. --- .../minimal/OnlyFallbackPermalinkParser.kt | 31 +++++++++++++++++++ .../android/samples/minimal/RoomListScreen.kt | 1 + 2 files changed, 32 insertions(+) create mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt new file mode 100644 index 0000000000..50903a910d --- /dev/null +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.samples.minimal + +import android.net.Uri +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.permalink.PermalinkParser + +class OnlyFallbackPermalinkParser : PermalinkParser { + override fun parse(uriString: String): PermalinkData { + return PermalinkData.FallbackLink(Uri.parse(uriString)) + } + + override fun parse(uri: Uri): PermalinkData { + return PermalinkData.FallbackLink(uri) + } +} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index 827038785c..7bf3261371 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -89,6 +89,7 @@ class RoomListScreen( ), profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider), stateContentFormatter = StateContentFormatter(stringProvider), + permalinkParser = OnlyFallbackPermalinkParser(), ), ) private val presenter = RoomListPresenter( From 8e7fdc859c106ded5805a8b56c07f0ad947f9b8d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 15:51:59 +0200 Subject: [PATCH 044/124] Code quality and remove reference to `DefaultMatrixToConverter.SUPPORTED_PATHS` in `MatrixToConverter` --- .../android/features/messages/impl/MessagesNode.kt | 4 ++-- .../matrix/api/permalink/MatrixToConverter.kt | 1 - .../impl/permalink/DefaultMatrixToConverter.kt | 12 +++++++----- .../matrix/test/permalink/FakePermalinkParser.kt | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) 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 ea3584e4f5..aca840236e 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 @@ -110,10 +110,10 @@ class MessagesNode @AssistedInject constructor( ) { when (val permalink = permalinkParser.parse(Uri.parse(url))) { is PermalinkData.UserLink -> { - onUserDataClicked(UserId(permalink.userId)) + callback?.onUserDataClicked(UserId(permalink.userId)) } is PermalinkData.RoomLink -> { - // TODO: Implement room link handling + // TODO Implement room link handling } is PermalinkData.FallbackLink, is PermalinkData.RoomEmailInviteLink -> { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt index a29a992aca..c525bd4c00 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt @@ -24,7 +24,6 @@ import android.net.Uri interface MatrixToConverter { /** * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. - * To be successfully converted, URL path should contain one of the [DefaultMatrixToConverter.SUPPORTED_PATHS]. * Examples: * - https://riot.im/develop/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org * - https://app.element.io/#/room/#element-android:matrix.org -> https://matrix.to/#/#element-android:matrix.org diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt index 002b311600..a5271c0b22 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt @@ -53,9 +53,11 @@ class DefaultMatrixToConverter @Inject constructor() : MatrixToConverter { } } - private val SUPPORTED_PATHS = listOf( - "/#/room/", - "/#/user/", - "/#/group/" - ) + companion object { + val SUPPORTED_PATHS = listOf( + "/#/room/", + "/#/user/", + "/#/group/" + ) + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt index 5c6935d76d..f046eadf93 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser class FakePermalinkParser( - private var result: () -> PermalinkData = { throw Exception("Not implemented") } + private var result: () -> PermalinkData = { TODO("Not implemented") } ) : PermalinkParser { fun givenResult(result: PermalinkData) { this.result = { result } From 6c9ea2b920187541609495a730cdca0f2f605bbb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 25 Mar 2024 10:47:18 +0100 Subject: [PATCH 045/124] Create FakePushService. --- appnav/build.gradle.kts | 1 + .../appnav/loggedin/LoggedInPresenterTest.kt | 20 +--------- libraries/push/test/build.gradle.kts | 1 + .../libraries/push/test/FakePushService.kt | 37 +++++++++++++++++++ 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 4e436ec718..ab215f8394 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.push.test) testImplementation(projects.features.networkmonitor.test) testImplementation(projects.features.login.impl) testImplementation(projects.tests.testutils) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index 079ccab17a..0da579aa76 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -22,13 +22,10 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.test.FakeNetworkMonitor -import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService -import io.element.android.libraries.push.api.PushService -import io.element.android.libraries.pushproviders.api.Distributor -import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.libraries.push.test.FakePushService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest @@ -73,20 +70,7 @@ class LoggedInPresenterTest { return LoggedInPresenter( matrixClient = FakeMatrixClient(roomListService = roomListService), networkMonitor = FakeNetworkMonitor(networkStatus), - pushService = object : PushService { - override fun notificationStyleChanged() { - } - - override fun getAvailablePushProviders(): List { - return emptyList() - } - - override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) { - } - - override suspend fun testPush() { - } - } + pushService = FakePushService(), ) } } diff --git a/libraries/push/test/build.gradle.kts b/libraries/push/test/build.gradle.kts index 9fccadb9be..7826c3072b 100644 --- a/libraries/push/test/build.gradle.kts +++ b/libraries/push/test/build.gradle.kts @@ -25,5 +25,6 @@ android { dependencies { api(projects.libraries.push.api) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.pushproviders.api) implementation(projects.tests.testutils) } diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt new file mode 100644 index 0000000000..7b6422cd1b --- /dev/null +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.test + +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider + +class FakePushService : PushService { + override fun notificationStyleChanged() { + } + + override fun getAvailablePushProviders(): List { + return emptyList() + } + + override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) { + } + + override suspend fun testPush() { + } +} From 2bfe125a773e034247f3bb7897d67a84dde48fc3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 26 Mar 2024 11:36:31 +0100 Subject: [PATCH 046/124] Troubleshoot notifications screen --- features/preferences/impl/build.gradle.kts | 10 + .../preferences/impl/PreferencesFlowNode.kt | 11 + .../notifications/NotificationSettingsNode.kt | 6 + .../notifications/NotificationSettingsView.kt | 11 + .../TroubleshootNotificationsEvents.kt | 23 ++ .../TroubleshootNotificationsNode.kt | 44 ++++ .../TroubleshootNotificationsPresenter.kt | 58 +++++ .../TroubleshootNotificationsState.kt | 24 ++ .../TroubleshootNotificationsStateProvider.kt | 119 ++++++++++ .../TroubleshootNotificationsView.kt | 220 ++++++++++++++++++ .../troubleshoot/TroubleshootTestSuite.kt | 109 +++++++++ .../TroubleshootTestSuiteState.kt | 26 +++ .../FakeNotificationTroubleshootTest.kt | 82 +++++++ ...TroubleshootNotificationsPresenterTests.kt | 126 ++++++++++ .../TroubleshootNotificationsViewTest.kt | 115 +++++++++ .../androidutils/system/SystemUtils.kt | 8 + .../NotificationTroubleshootTest.kt | 29 +++ .../NotificationTroubleshootTestDelegate.kt | 81 +++++++ .../NotificationTroubleshootTestState.kt | 31 +++ .../core/notifications/TestFilterData.kt | 21 ++ libraries/permissions/api/build.gradle.kts | 1 - libraries/permissions/impl/build.gradle.kts | 2 + .../impl/DefaultPermissionStateProvider.kt | 7 +- .../impl/action/AndroidPermissionActions.kt | 4 +- ...ficationTroubleshootCheckPermissionTest.kt | 66 ++++++ .../impl/action/FakePermissionActions.kt | 5 +- ...tionTroubleshootCheckPermissionTestTest.kt | 100 ++++++++ .../push/api/GetCurrentPushProvider.kt | 21 ++ .../android/libraries/push/api/PushService.kt | 6 +- .../push/api/gateway/PushGatewayFailure.kt | 4 +- libraries/push/impl/build.gradle.kts | 2 + .../impl/DefaultGetCurrentPushProvider.kt | 41 ++++ .../libraries/push/impl/DefaultPushService.kt | 10 +- .../libraries/push/impl/PushersManager.kt | 15 +- .../notifications/NotificationDisplayer.kt | 18 +- .../notifications/TestNotificationReceiver.kt | 8 +- .../TestNotificationReceiverBinding.kt | 25 ++ .../factories/NotificationCreator.kt | 4 +- .../push/impl/push/DefaultPushHandler.kt | 5 +- .../pushgateway/PushGatewayNotification.kt | 2 + .../pushgateway/PushGatewayNotifyRequest.kt | 7 +- .../troubleshoot/CurrentPushProviderTest.kt | 58 +++++ .../troubleshoot/DiagnosticPushHandler.kt | 33 +++ .../troubleshoot/NotificationClickHandler.kt | 33 +++ .../impl/troubleshoot/NotificationTest.kt | 94 ++++++++ .../impl/troubleshoot/PushLoopbackTest.kt | 102 ++++++++ .../impl/troubleshoot/PushProvidersTest.kt | 59 +++++ .../DefaultNotificationDrawerManagerTest.kt | 4 +- .../notifications/NotificationFactoryTest.kt | 4 +- ...nFactory.kt => FakeNotificationCreator.kt} | 8 +- .../fake/FakeNotificationDisplayer.kt | 5 + .../CurrentPushProviderTestTest.kt | 60 +++++ .../impl/troubleshoot/NotificationTestTest.kt | 88 +++++++ .../impl/troubleshoot/PushLoopbackTestTest.kt | 143 ++++++++++++ .../troubleshoot/PushProvidersTestTest.kt | 64 +++++ .../push/test/FakeGetCurrentPushProvider.kt | 25 ++ .../libraries/push/test/FakePushService.kt | 8 +- .../api/CurrentUserPushConfig.kt | 22 ++ .../pushproviders/api/PushProvider.kt | 5 +- .../pushproviders/firebase/build.gradle.kts | 2 + .../firebase/FirebasePushProvider.kt | 28 +-- .../pushproviders/firebase/FirebaseStore.kt | 16 +- .../firebase/FirebaseTroubleshooter.kt | 32 +-- .../firebase/IsPlayServiceAvailable.kt | 47 ++++ .../troubleshoot/FirebaseAvailabilityTest.kt | 65 ++++++ .../troubleshoot/FirebaseTokenTest.kt | 73 ++++++ .../firebase/FakeFirebaseTroubleshooter.kt | 27 +++ .../firebase/InMemoryFirebaseStore.kt | 27 +++ .../FirebaseAvailabilityTestTest.kt | 67 ++++++ .../troubleshoot/FirebaseTokenTestTest.kt | 78 +++++++ libraries/pushproviders/test/build.gradle.kts | 27 +++ .../pushproviders/test/FakePushProvider.kt | 45 ++++ .../unifiedpush/build.gradle.kts | 3 + .../UnifiedPushDistributorProvider.kt | 47 ++++ .../unifiedpush/UnifiedPushProvider.kt | 32 +-- .../OpenDistributorWebPageAction.kt | 41 ++++ .../troubleshoot/UnifiedPushTest.kt | 70 ++++++ .../FakeOpenDistributorWebPageAction.kt | 23 ++ .../FakeUnifiedPushDistributorProvider.kt | 32 +++ .../troubleshoot/UnifiedPushTestTest.kt | 81 +++++++ 80 files changed, 3086 insertions(+), 99 deletions(-) create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsEvents.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenter.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsState.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsStateProvider.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt create mode 100644 features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuiteState.kt create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsViewTest.kt create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestState.kt create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/TestFilterData.kt create mode 100644 libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt create mode 100644 libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt create mode 100644 libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt create mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt rename libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/{FakeAndroidNotificationFactory.kt => FakeNotificationCreator.kt} (85%) create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt create mode 100644 libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt create mode 100644 libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt create mode 100644 libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt create mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt create mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt create mode 100644 libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt create mode 100644 libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt create mode 100644 libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt create mode 100644 libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt create mode 100644 libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt create mode 100644 libraries/pushproviders/test/build.gradle.kts create mode 100644 libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt create mode 100644 libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushDistributorProvider.kt create mode 100644 libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt create mode 100644 libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt create mode 100644 libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt create mode 100644 libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt create mode 100644 libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index ee0658ddc6..415f0e36b4 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -23,6 +23,11 @@ plugins { android { namespace = "io.element.android.features.preferences.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -50,6 +55,7 @@ dependencies { implementation(projects.libraries.mediapickers.api) implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.permissions.api) + implementation(projects.libraries.push.api) implementation(projects.features.rageshake.api) implementation(projects.features.lockscreen.api) implementation(projects.features.analytics.api) @@ -71,12 +77,14 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(libs.test.mockk) + testImplementation(libs.test.robolectric) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.mediapickers.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) @@ -86,4 +94,6 @@ dependencies { testImplementation(projects.services.toolbox.test) testImplementation(projects.features.analytics.impl) testImplementation(projects.tests.testutils) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index c27a5ec14e..5f57ad8b5e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -39,6 +39,7 @@ import io.element.android.features.preferences.impl.developer.DeveloperSettingsN import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode import io.element.android.features.preferences.impl.notifications.NotificationSettingsNode import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingNode +import io.element.android.features.preferences.impl.notifications.troubleshoot.TroubleshootNotificationsNode import io.element.android.features.preferences.impl.root.PreferencesRootNode import io.element.android.features.preferences.impl.user.editprofile.EditUserProfileNode import io.element.android.libraries.architecture.BackstackView @@ -85,6 +86,9 @@ class PreferencesFlowNode @AssistedInject constructor( @Parcelize data object NotificationSettings : NavTarget + @Parcelize + data object TroubleshootNotifications : NavTarget + @Parcelize data object LockScreenSettings : NavTarget @@ -177,9 +181,16 @@ class PreferencesFlowNode @AssistedInject constructor( override fun editDefaultNotificationMode(isOneToOne: Boolean) { backstack.push(NavTarget.EditDefaultNotificationSetting(isOneToOne)) } + + override fun onTroubleshootNotificationsClicked() { + backstack.push(NavTarget.TroubleshootNotifications) + } } createNode(buildContext, listOf(notificationSettingsCallback)) } + NavTarget.TroubleshootNotifications -> { + createNode(buildContext) + } is NavTarget.EditDefaultNotificationSetting -> { val callback = object : EditDefaultNotificationSettingNode.Callback { override fun openRoomNotificationSettings(roomId: RoomId) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt index 122d13a817..621b0ed8b1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt @@ -35,6 +35,7 @@ class NotificationSettingsNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun editDefaultNotificationMode(isOneToOne: Boolean) + fun onTroubleshootNotificationsClicked() } private val callbacks = plugins() @@ -43,6 +44,10 @@ class NotificationSettingsNode @AssistedInject constructor( callbacks.forEach { it.editDefaultNotificationMode(isOneToOne) } } + private fun onTroubleshootNotificationsClicked() { + callbacks.forEach { it.onTroubleshootNotificationsClicked() } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -50,6 +55,7 @@ class NotificationSettingsNode @AssistedInject constructor( state = state, onOpenEditDefault = { openEditDefault(isOneToOne = it) }, onBackPressed = ::navigateUp, + onTroubleshootNotificationsClicked = ::onTroubleshootNotificationsClicked, modifier = modifier, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index e68b9fc8b5..8f59ffcdba 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -46,6 +46,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun NotificationSettingsView( state: NotificationSettingsState, onOpenEditDefault: (isOneToOne: Boolean) -> Unit, + onTroubleshootNotificationsClicked: () -> Unit, onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { @@ -77,6 +78,7 @@ fun NotificationSettingsView( // TODO We are removing the call notification toggle until support for call notifications has been added // onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) }, onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) }, + onTroubleshootNotificationsClicked = onTroubleshootNotificationsClicked, ) } AsyncActionView( @@ -99,6 +101,7 @@ private fun NotificationSettingsContentView( // TODO We are removing the call notification toggle until support for call notifications has been added // onCallsNotificationsChanged: (Boolean) -> Unit, onInviteForMeNotificationsChanged: (Boolean) -> Unit, + onTroubleshootNotificationsClicked: () -> Unit, ) { val context = LocalContext.current if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) { @@ -163,6 +166,13 @@ private fun NotificationSettingsContentView( onCheckedChange = onInviteForMeNotificationsChanged ) } + PreferenceCategory(title = "Troubleshoot") { + PreferenceText( + modifier = Modifier, + title = "Troubleshoot notifications", + onClick = onTroubleshootNotificationsClicked + ) + } } } @@ -204,6 +214,7 @@ internal fun NotificationSettingsViewPreview(@PreviewParameter(NotificationSetti state = state, onBackPressed = {}, onOpenEditDefault = {}, + onTroubleshootNotificationsClicked = {}, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsEvents.kt new file mode 100644 index 0000000000..a6b361de13 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsEvents.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +sealed interface TroubleshootNotificationsEvents { + data object StartTests : TroubleshootNotificationsEvents + data object RetryFailedTests : TroubleshootNotificationsEvents + data class QuickFix(val testIndex: Int) : TroubleshootNotificationsEvents +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt new file mode 100644 index 0000000000..1ac7c0c079 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +import androidx.compose.runtime.Composable +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 dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class TroubleshootNotificationsNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: TroubleshootNotificationsPresenter, +) : Node(buildContext, plugins = plugins) { + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + TroubleshootNotificationsView( + state = state, + onBackPressed = ::navigateUp, + modifier = modifier, + ) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenter.kt new file mode 100644 index 0000000000..1ccbf86ce9 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenter.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +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 io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.launch +import javax.inject.Inject + +class TroubleshootNotificationsPresenter @Inject constructor( + private val troubleshootTestSuite: TroubleshootTestSuite, +) : Presenter { + @Composable + override fun present(): TroubleshootNotificationsState { + val coroutineScope = rememberCoroutineScope() + LaunchedEffect(Unit) { + troubleshootTestSuite.start(this) + } + + val testSuiteState by troubleshootTestSuite.state.collectAsState() + fun handleEvents(event: TroubleshootNotificationsEvents) { + when (event) { + TroubleshootNotificationsEvents.StartTests -> coroutineScope.launch { + troubleshootTestSuite.runTestSuite(this) + } + is TroubleshootNotificationsEvents.QuickFix -> coroutineScope.launch { + troubleshootTestSuite.quickFix(event.testIndex, this) + } + TroubleshootNotificationsEvents.RetryFailedTests -> coroutineScope.launch { + troubleshootTestSuite.retryFailedTest(this) + } + } + } + + return TroubleshootNotificationsState( + testSuiteState = testSuiteState, + eventSink = ::handleEvents + ) + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsState.kt new file mode 100644 index 0000000000..55032b2fe9 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsState.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +data class TroubleshootNotificationsState( + val testSuiteState: TroubleshootTestSuiteState, + val eventSink: (TroubleshootNotificationsEvents) -> Unit, +) { + val hasFailedTests: Boolean = testSuiteState.mainState.isFailure() +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsStateProvider.kt new file mode 100644 index 0000000000..316bd72ad7 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsStateProvider.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import kotlinx.collections.immutable.toImmutableList + +open class TroubleshootNotificationsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateIdle(), + aTroubleshootTestStateIdle(), + aTroubleshootTestStateIdle(visible = false), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateInProgress(), + aTroubleshootTestStateIdle(), + aTroubleshootTestStateIdle(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateInProgress(), + aTroubleshootTestStateIdle(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateWaitingForUser(), + aTroubleshootTestStateIdle(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateFailure(hasQuickFix = true), + aTroubleshootTestStateInProgress(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateFailure(hasQuickFix = true), + aTroubleshootTestStateFailure(hasQuickFix = false), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateSuccess(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateWaitingForUser(), + ) + ), + ) +} + +fun aTroubleshootNotificationsState( + tests: List = emptyList(), + eventSink: (TroubleshootNotificationsEvents) -> Unit = {}, +) = TroubleshootNotificationsState( + eventSink = eventSink, + testSuiteState = TroubleshootTestSuiteState( + mainState = tests.computeMainState(), + tests = tests.toImmutableList(), + ), +) + +fun aTroubleshootTestState( + status: NotificationTroubleshootTestState.Status, + name: String = "Test", + description: String = "Description", +): NotificationTroubleshootTestState { + return NotificationTroubleshootTestState( + name = name, + description = description, + status = status, + ) +} + +fun aTroubleshootTestStateIdle(visible: Boolean = true) = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Idle(visible = visible)) + +fun aTroubleshootTestStateInProgress() = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.InProgress) + +fun aTroubleshootTestStateWaitingForUser() = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.WaitingForUser) + +fun aTroubleshootTestStateSuccess() = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Success) + +fun aTroubleshootTestStateFailure(hasQuickFix: Boolean) = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = hasQuickFix)) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt new file mode 100644 index 0000000000..cadffa1ba0 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.progressSemantics +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState.Status +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.components.preferences.PreferencePage +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.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.OnLifecycleEvent + +/** + * A view that allows a user edit their global notification settings. + */ +@Composable +fun TroubleshootNotificationsView( + state: TroubleshootNotificationsState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + OnLifecycleEvent { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> { + if (state.hasFailedTests) { + state.eventSink(TroubleshootNotificationsEvents.RetryFailedTests) + } + } + else -> Unit + } + } + + PreferencePage( + modifier = modifier, + onBackPressed = onBackPressed, + title = "Troubleshoot notifications", + ) { + TroubleshootNotificationsContent(state) + } +} + +@Composable +private fun TroubleshootTestView( + testState: NotificationTroubleshootTestState, + onQuickFixClicked: () -> Unit, +) { + if ((testState.status as? Status.Idle)?.visible == false) return + ListItem( + headlineContent = { Text(text = testState.name) }, + supportingContent = { Text(text = testState.description) }, + trailingContent = when (testState.status) { + is Status.Idle -> null + Status.InProgress -> ListItemContent.Custom { + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(20.dp), + strokeWidth = 2.dp + ) + } + Status.WaitingForUser -> ListItemContent.Custom { + Icon( + contentDescription = null, + modifier = Modifier.size(24.dp), + imageVector = CompoundIcons.Info(), + tint = ElementTheme.colors.iconAccentTertiary + ) + } + Status.Success -> ListItemContent.Custom { + Icon( + contentDescription = null, + modifier = Modifier.size(24.dp), + imageVector = CompoundIcons.Check(), + tint = ElementTheme.colors.iconAccentTertiary + ) + } + is Status.Failure -> ListItemContent.Custom { + Icon( + contentDescription = null, + modifier = Modifier.size(24.dp), + imageVector = CompoundIcons.Error(), + tint = ElementTheme.colors.textCriticalPrimary + ) + } + } + ) + if ((testState.status as? Status.Failure)?.hasQuickFix == true) { + ListItem( + headlineContent = { + }, + trailingContent = ListItemContent.Custom { + Button( + text = "Attempt to fix", + onClick = onQuickFixClicked + ) + } + ) + } +} + +@Composable +private fun TroubleshootNotificationsContent(state: TroubleshootNotificationsState) { + when (state.testSuiteState.mainState) { + AsyncAction.Loading, + AsyncAction.Confirming, + is AsyncAction.Success, + is AsyncAction.Failure -> { + TestSuiteView( + testSuiteState = state.testSuiteState, + onQuickFixClicked = { + state.eventSink(TroubleshootNotificationsEvents.QuickFix(it)) + } + ) + } + AsyncAction.Uninitialized -> Unit + } + when (state.testSuiteState.mainState) { + AsyncAction.Uninitialized -> { + ListItem(headlineContent = { + Text( + text = "Run the tests to detect any issue in your configuration " + + "that may make notifications not behave as expected." + ) + }) + RunTestButton(state = state) + } + AsyncAction.Loading -> Unit + is AsyncAction.Failure -> { + ListItem(headlineContent = { + Text(text = "Some tests failed, please check the details.") + }) + RunTestButton(state = state) + } + AsyncAction.Confirming -> { + ListItem(headlineContent = { + Text( + text = "Some tests require your attention. Please check the details." + ) + }) + } + is AsyncAction.Success -> { + ListItem(headlineContent = { + Text( + text = "All tests passed successfully." + ) + }) + } + } +} + +@Composable +private fun RunTestButton(state: TroubleshootNotificationsState) { + ListItem( + headlineContent = { + Button( + text = if (state.testSuiteState.mainState is AsyncAction.Failure) "Run tests again" else "Run tests", + onClick = { + state.eventSink(TroubleshootNotificationsEvents.StartTests) + }, + modifier = Modifier.fillMaxWidth(), + ) + } + ) +} + +@Composable +private fun TestSuiteView( + testSuiteState: TroubleshootTestSuiteState, + onQuickFixClicked: (Int) -> Unit, +) { + testSuiteState.tests.forEachIndexed { index, testState -> + TroubleshootTestView( + testState = testState, + onQuickFixClicked = { + onQuickFixClicked(index) + }, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun TroubleshootNotificationsViewPreview( + @PreviewParameter(TroubleshootNotificationsStateProvider::class) state: TroubleshootNotificationsState, +) = ElementPreview { + TroubleshootNotificationsView( + state = state, + onBackPressed = {}, + ) +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt new file mode 100644 index 0000000000..4996d278de --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.core.notifications.TestFilterData +import io.element.android.libraries.push.api.GetCurrentPushProvider +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +class TroubleshootTestSuite @Inject constructor( + private val notificationTroubleshootTests: Set<@JvmSuppressWildcards NotificationTroubleshootTest>, + private val getCurrentPushProvider: GetCurrentPushProvider, +) { + lateinit var tests: List + + private val _state: MutableStateFlow = MutableStateFlow( + TroubleshootTestSuiteState( + mainState = AsyncAction.Uninitialized, + tests = emptyList().toImmutableList() + ) + ) + val state: StateFlow = _state + + suspend fun start(coroutineScope: CoroutineScope) { + val testFilterData = TestFilterData( + currentPushProviderName = getCurrentPushProvider.getCurrentPushProvider() + ) + tests = notificationTroubleshootTests + .filter { it.isRelevant(testFilterData) } + .sortedBy { it.order } + tests.forEach { + // Observe the state of the tests + it.state.onEach { + emitState() + }.launchIn(coroutineScope) + } + } + + suspend fun runTestSuite(coroutineScope: CoroutineScope) { + tests.forEach { + it.reset() + } + tests.forEach { + it.run(coroutineScope) + } + } + + suspend fun retryFailedTest(coroutineScope: CoroutineScope) { + tests + .filter { it.state.value.status is NotificationTroubleshootTestState.Status.Failure } + .forEach { + it.run(coroutineScope) + } + } + + private fun emitState() { + val states = tests.map { it.state.value } + _state.tryEmit( + TroubleshootTestSuiteState( + mainState = states.computeMainState(), + tests = states.toImmutableList() + ) + ) + } + + suspend fun quickFix(testIndex: Int, coroutineScope: CoroutineScope) { + tests[testIndex].quickFix(coroutineScope) + } +} + +fun List.computeMainState(): AsyncAction { + val isIdle = all { it.status is NotificationTroubleshootTestState.Status.Idle } + val isRunning = any { it.status is NotificationTroubleshootTestState.Status.InProgress } + return when { + isIdle -> AsyncAction.Uninitialized + isRunning -> AsyncAction.Loading + else -> { + if (any { it.status is NotificationTroubleshootTestState.Status.WaitingForUser }) { + AsyncAction.Confirming + } else if (any { it.status is NotificationTroubleshootTestState.Status.Failure }) { + AsyncAction.Failure(Exception("Some tests failed")) + } else { + AsyncAction.Success(Unit) + } + } + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuiteState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuiteState.kt new file mode 100644 index 0000000000..e516b65109 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuiteState.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import kotlinx.collections.immutable.ImmutableList + +data class TroubleshootTestSuiteState( + val mainState: AsyncAction, + val tests: ImmutableList, +) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt new file mode 100644 index 0000000000..e619a62972 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeNotificationTroubleshootTest( + override val order: Int = 0, + private val defaultName: String = "test name", + private val defaultDescription: String = "test description", + private val firstStatus: NotificationTroubleshootTestState.Status = NotificationTroubleshootTestState.Status.Idle(visible = true), + private val runAction: () -> NotificationTroubleshootTestState? = { null }, + private val resetAction: () -> NotificationTroubleshootTestState? = { null }, + private val quickFixAction: () -> NotificationTroubleshootTestState? = { null }, +) : NotificationTroubleshootTest { + private val _state = MutableStateFlow( + NotificationTroubleshootTestState( + name = defaultName, + description = defaultDescription, + status = firstStatus + ) + ) + override val state: StateFlow = _state.asStateFlow() + + override suspend fun run(coroutineScope: CoroutineScope) { + updateState(NotificationTroubleshootTestState.Status.InProgress) + runAction()?.let { + _state.tryEmit(it) + } + } + + override fun reset() { + updateState( + name = defaultName, + description = defaultDescription, + status = firstStatus, + ) + resetAction()?.let { + _state.tryEmit(it) + } + } + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + updateState(NotificationTroubleshootTestState.Status.InProgress) + quickFixAction()?.let { + _state.tryEmit(it) + } + } + + fun updateState( + status: NotificationTroubleshootTestState.Status, + name: String = defaultName, + description: String = defaultDescription, + ) { + _state.tryEmit( + NotificationTroubleshootTestState( + name = name, + description = description, + status = status, + ) + ) + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt new file mode 100644 index 0000000000..e6e712b1b8 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +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.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class TroubleshootNotificationsPresenterTests { + @Test + fun `present - initial state`() = runTest { + val presenter = createTroubleshootNotificationsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.testSuiteState.tests).isEmpty() + assertThat(initialState.testSuiteState.mainState).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - start test`() = runTest { + val troubleshootTestSuite = createTroubleshootTestSuite( + tests = setOf(FakeNotificationTroubleshootTest()) + ) + val presenter = createTroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(TroubleshootNotificationsEvents.StartTests) + skipItems(1) + val stateAfterStart = awaitItem() + assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) + } + } + + @Test + fun `present - start failed test`() = runTest { + val troubleshootTestSuite = createTroubleshootTestSuite( + tests = setOf( + FakeNotificationTroubleshootTest( + firstStatus = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = false) + ) + ) + ) + val presenter = createTroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(TroubleshootNotificationsEvents.RetryFailedTests) + skipItems(1) + val stateAfterStart = awaitItem() + assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) + } + } + + @Test + fun `present - quick fix test`() = runTest { + val troubleshootTestSuite = createTroubleshootTestSuite( + tests = setOf( + FakeNotificationTroubleshootTest( + firstStatus = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = false) + ) + ) + ) + val presenter = createTroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.testSuiteState.mainState).isInstanceOf(AsyncAction.Failure::class.java) + initialState.eventSink(TroubleshootNotificationsEvents.QuickFix(0)) + val stateAfterStart = awaitItem() + assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) + } + } + + private fun createTroubleshootTestSuite( + tests: Set = emptySet(), + currentPushProvider: String? = null, + ): TroubleshootTestSuite { + return TroubleshootTestSuite( + notificationTroubleshootTests = tests, + getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider), + ) + } + + private fun createTroubleshootNotificationsPresenter( + troubleshootTestSuite: TroubleshootTestSuite = createTroubleshootTestSuite(), + ): TroubleshootNotificationsPresenter { + return TroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsViewTest.kt new file mode 100644 index 0000000000..af2b37d7c0 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsViewTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications.troubleshoot + +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.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +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 TroubleshootNotificationsViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `press menu back invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setTroubleshootNotificationsView( + state = aTroubleshootNotificationsState( + eventSink = eventsRecorder + ), + onBackPressed = it, + ) + rule.pressBack() + } + } + + @Test + fun `clicking on run test emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setTroubleshootNotificationsView( + aTroubleshootNotificationsState( + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Run tests").performClick() + eventsRecorder.assertSingle(TroubleshootNotificationsEvents.StartTests) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on run test again emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setTroubleshootNotificationsView( + aTroubleshootNotificationsState( + tests = listOf(aTroubleshootTestStateFailure(hasQuickFix = false)), + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Run tests again").performClick() + eventsRecorder.assertList( + listOf( + TroubleshootNotificationsEvents.RetryFailedTests, + TroubleshootNotificationsEvents.StartTests, + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on quick fix emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setTroubleshootNotificationsView( + aTroubleshootNotificationsState( + tests = listOf(aTroubleshootTestStateFailure(hasQuickFix = true)), + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Attempt to fix").performClick() + eventsRecorder.assertList( + listOf( + TroubleshootNotificationsEvents.RetryFailedTests, + TroubleshootNotificationsEvents.QuickFix(0), + ) + ) + } +} + +private fun AndroidComposeTestRule.setTroubleshootNotificationsView( + state: TroubleshootNotificationsState, + onBackPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + TroubleshootNotificationsView( + state = state, + onBackPressed = onBackPressed, + ) + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt index 422307ffd9..9f473a7089 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.androidutils.system +import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -73,6 +74,9 @@ fun Context.startNotificationSettingsIntent(activityResultLauncher: ActivityResu val intent = Intent() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + if (this !is Activity && activityResultLauncher == null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName) } else { intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS @@ -152,8 +156,12 @@ fun Context.startSharePlainTextIntent( fun Context.openUrlInExternalApp( url: String, errorMessage: String = getString(R.string.error_no_compatible_app_found), + inNewTask: Boolean = false, ) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + if (inNewTask) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } try { startActivity(intent) } catch (activityNotFoundException: ActivityNotFoundException) { diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt new file mode 100644 index 0000000000..755e186e8b --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.core.notifications + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow + +interface NotificationTroubleshootTest { + val order: Int + val state: StateFlow + fun isRelevant(data: TestFilterData): Boolean = true + suspend fun run(coroutineScope: CoroutineScope) + fun reset() + suspend fun quickFix(coroutineScope: CoroutineScope) {} +} diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt new file mode 100644 index 0000000000..9711677253 --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.core.notifications + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * A NotificationTroubleshootTest delegate, with common pattern for running and resetting. + */ +class NotificationTroubleshootTestDelegate( + private val defaultName: String, + private val defaultDescription: String, + private val visibleWhenIdle: Boolean = true, + private val hasQuickFix: Boolean = false, + private val fakeDelay: Long = 0L, +) { + private val _state: MutableStateFlow = MutableStateFlow( + NotificationTroubleshootTestState( + name = defaultName, + description = defaultDescription, + status = NotificationTroubleshootTestState.Status.Idle(visibleWhenIdle), + ) + ) + + val state: StateFlow = _state.asStateFlow() + + fun updateState( + status: NotificationTroubleshootTestState.Status, + name: String = defaultName, + description: String = defaultDescription, + ) { + _state.tryEmit( + NotificationTroubleshootTestState( + name = name, + description = description, + status = status, + ) + ) + } + + fun reset() { + updateState(NotificationTroubleshootTestState.Status.Idle(visibleWhenIdle)) + } + + suspend fun start() { + updateState(NotificationTroubleshootTestState.Status.InProgress) + delay(fakeDelay) + } + + fun done(isSuccess: Boolean = true) { + updateState( + if (isSuccess) { + NotificationTroubleshootTestState.Status.Success + } else { + NotificationTroubleshootTestState.Status.Failure(hasQuickFix) + } + ) + } + + companion object { + const val SHORT_DELAY = 300L + const val LONG_DELAY = 500L + } +} diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestState.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestState.kt new file mode 100644 index 0000000000..9657be4b3b --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestState.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.core.notifications + +data class NotificationTroubleshootTestState( + val name: String, + val description: String, + val status: Status, +) { + sealed interface Status { + data class Idle(val visible: Boolean) : Status + data object InProgress : Status + data object WaitingForUser : Status + data object Success : Status + data class Failure(val hasQuickFix: Boolean) : Status + } +} diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/TestFilterData.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/TestFilterData.kt new file mode 100644 index 0000000000..e22fefb7f1 --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/TestFilterData.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.core.notifications + +data class TestFilterData( + val currentPushProviderName: String?, +) diff --git a/libraries/permissions/api/build.gradle.kts b/libraries/permissions/api/build.gradle.kts index 32d3776419..cb60c6c0a7 100644 --- a/libraries/permissions/api/build.gradle.kts +++ b/libraries/permissions/api/build.gradle.kts @@ -24,7 +24,6 @@ android { dependencies { implementation(projects.libraries.architecture) - implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) diff --git a/libraries/permissions/impl/build.gradle.kts b/libraries/permissions/impl/build.gradle.kts index 7d05d9a1d7..82904bc750 100644 --- a/libraries/permissions/impl/build.gradle.kts +++ b/libraries/permissions/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) + implementation(projects.services.toolbox.api) api(projects.libraries.permissions.api) testImplementation(libs.test.junit) @@ -57,6 +58,7 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.permissions.test) + testImplementation(projects.services.toolbox.test) testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt index c05df3de46..61a82aafff 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt @@ -17,6 +17,8 @@ package io.element.android.libraries.permissions.impl import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext @@ -33,7 +35,10 @@ class DefaultPermissionStateProvider @Inject constructor( private val permissionsStore: PermissionsStore, ) : PermissionStateProvider { override fun isPermissionGranted(permission: String): Boolean { - return context.checkSelfPermission(permission) == android.content.pm.PackageManager.PERMISSION_GRANTED + return ContextCompat.checkSelfPermission( + context, + permission, + ) == PackageManager.PERMISSION_GRANTED } override suspend fun setPermissionDenied(permission: String, value: Boolean) = permissionsStore.setPermissionDenied(permission, value) diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt index 6370e839a3..405b61a809 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt @@ -18,7 +18,7 @@ package io.element.android.libraries.permissions.impl.action import android.content.Context import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.androidutils.system.openAppSettingsPage +import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import javax.inject.Inject @@ -28,6 +28,6 @@ class AndroidPermissionActions @Inject constructor( @ApplicationContext private val context: Context ) : PermissionActions { override fun openSettings() { - context.openAppSettingsPage() + context.startNotificationSettingsIntent() } } 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 new file mode 100644 index 0000000000..501b00e14a --- /dev/null +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.permissions.impl.troubleshoot + +import android.Manifest +import android.os.Build +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.permissions.api.PermissionStateProvider +import io.element.android.libraries.permissions.impl.action.PermissionActions +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class NotificationTroubleshootCheckPermissionTest @Inject constructor( + private val permissionStateProvider: PermissionStateProvider, + private val sdkVersionProvider: BuildVersionSdkIntProvider, + private val permissionActions: PermissionActions, +) : NotificationTroubleshootTest { + override val order: Int = 0 + + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Check permissions", + defaultDescription = "Ensure that the application can show notifications.", + hasQuickFix = true, + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val result = if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { + permissionStateProvider.isPermissionGranted(Manifest.permission.POST_NOTIFICATIONS) + } else { + true + } + delegate.done(result) + } + + override fun reset() = delegate.reset() + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + // Do not bother about asking the permission inline, just lead the user to the settings + permissionActions.openSettings() + } +} diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt index fa17329900..f6709a7f3d 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt @@ -16,11 +16,14 @@ package io.element.android.libraries.permissions.impl.action -class FakePermissionActions : PermissionActions { +class FakePermissionActions( + val openSettingsAction: () -> Unit = {} +) : PermissionActions { var openSettingsCalled = false private set override fun openSettings() { + openSettingsAction() openSettingsCalled = true } } diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt new file mode 100644 index 0000000000..80c07e2ce0 --- /dev/null +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.permissions.impl.troubleshoot + +import android.os.Build +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.permissions.impl.FakePermissionStateProvider +import io.element.android.libraries.permissions.impl.action.FakePermissionActions +import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NotificationTroubleshootCheckPermissionTestTest { + @Test + fun `test NotificationTroubleshootCheckPermissionTest below TIRAMISU success`() = runTest { + val sut = NotificationTroubleshootCheckPermissionTest( + permissionStateProvider = FakePermissionStateProvider(), + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU - 1), + permissionActions = FakePermissionActions() + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + @Test + fun `test NotificationTroubleshootCheckPermissionTest TIRAMISU success`() = runTest { + val sut = NotificationTroubleshootCheckPermissionTest( + permissionStateProvider = FakePermissionStateProvider(), + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU), + permissionActions = FakePermissionActions() + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + @Test + fun `test NotificationTroubleshootCheckPermissionTest TIRAMISU error`() = runTest { + val permissionStateProvider = FakePermissionStateProvider( + permissionGranted = false + ) + val actions = FakePermissionActions( + openSettingsAction = { + permissionStateProvider.setPermissionGranted() + } + ) + val sut = NotificationTroubleshootCheckPermissionTest( + permissionStateProvider = permissionStateProvider, + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU), + permissionActions = actions + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) + // Quick fix + launch { + sut.quickFix(this) + // Run the test again (IRL it will be done thanks to the resuming of the application) + sut.run(this) + } + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } +} diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt new file mode 100644 index 0000000000..0c6ed41929 --- /dev/null +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.api + +interface GetCurrentPushProvider { + suspend fun getCurrentPushProvider(): String? +} diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt index 5f4736e5ab..abfc328e9f 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt @@ -37,6 +37,8 @@ interface PushService { */ suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) - // TODO Move away - suspend fun testPush() + /** + * Return false in case of early error. + */ + suspend fun testPush(): Boolean } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt index c7814a1796..07cf9acf52 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt @@ -16,6 +16,6 @@ package io.element.android.libraries.push.api.gateway -sealed class PushGatewayFailure : Throwable(cause = null) { - data object PusherRejected : PushGatewayFailure() +sealed class PushGatewayFailure : Exception() { + class PusherRejected : PushGatewayFailure() } diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index 7df972c3c3..f16f7f23f0 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -69,6 +69,8 @@ dependencies { testImplementation(libs.coil.test) testImplementation(libs.coroutines.test) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.push.test) + testImplementation(projects.libraries.pushproviders.test) testImplementation(projects.tests.testutils) testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.toolbox.impl) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt new file mode 100644 index 0000000000..bdf36b7f31 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.pushstore.api.UserPushStoreFactory +import io.element.android.services.appnavstate.api.AppNavigationStateService +import io.element.android.services.appnavstate.api.currentSessionId +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultGetCurrentPushProvider @Inject constructor( + private val pushStoreFactory: UserPushStoreFactory, + private val appNavigationStateService: AppNavigationStateService, +) : GetCurrentPushProvider { + override suspend fun getCurrentPushProvider(): String? { + return appNavigationStateService + .appNavigationState + .value + .navigationState + .currentSessionId() + ?.let { pushStoreFactory.create(it) } + ?.getPushProviderName() + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt index e60cd8b014..5a4df57b47 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.push.impl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.push.api.GetCurrentPushProvider import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager import io.element.android.libraries.pushproviders.api.Distributor @@ -32,6 +33,7 @@ class DefaultPushService @Inject constructor( private val pushersManager: PushersManager, private val userPushStoreFactory: UserPushStoreFactory, private val pushProviders: Set<@JvmSuppressWildcards PushProvider>, + private val getCurrentPushProvider: GetCurrentPushProvider, ) : PushService { override fun notificationStyleChanged() { defaultNotificationDrawerManager.notificationStyleChanged() @@ -58,7 +60,11 @@ class DefaultPushService @Inject constructor( userPushStore.setPushProviderName(pushProvider.name) } - override suspend fun testPush() { - pushersManager.testPush() + override suspend fun testPush(): Boolean { + val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider() + val pushProvider = pushProviders.find { it.name == currentPushProvider } ?: return false + val config = pushProvider.getCurrentUserPushConfig() ?: return false + pushersManager.testPush(config) + return true } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index d4424da492..dbefc03126 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -23,9 +23,11 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient 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.SessionId import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest +import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig import io.element.android.libraries.pushproviders.api.PusherSubscriber import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret @@ -45,16 +47,14 @@ class PushersManager @Inject constructor( private val pushClientSecret: PushClientSecret, private val userPushStoreFactory: UserPushStoreFactory, ) : PusherSubscriber { - // TODO Move this to the PushProvider API - suspend fun testPush() { + suspend fun testPush(config: CurrentUserPushConfig) { pushGatewayNotifyRequest.execute( PushGatewayNotifyRequest.Params( - // unifiedPushHelper.getPushGateway() ?: return - url = "TODO", + url = config.url, appId = PushConfig.PUSHER_APP_ID, - // unifiedPushHelper.getEndpointOrToken().orEmpty() - pushKey = "TODO", - eventId = TEST_EVENT_ID + pushKey = config.pushKey, + eventId = TEST_EVENT_ID, + roomId = TEST_ROOM_ID, ) ) } @@ -112,5 +112,6 @@ class PushersManager @Inject constructor( companion object { val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID") + val TEST_ROOM_ID = RoomId("!room:domain") } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt index 2cb01ba2f7..04202bbb2f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.push.impl.notifications import android.Manifest -import android.annotation.SuppressLint import android.app.Notification import android.content.Context import android.content.pm.PackageManager @@ -32,12 +31,13 @@ class NotificationDisplayer @Inject constructor( ) { private val notificationManager = NotificationManagerCompat.from(context) - fun showNotificationMessage(tag: String?, id: Int, notification: Notification) { + fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Timber.w("Not allowed to notify.") - return + return false } notificationManager.notify(tag, id, notification) + return true } fun cancelNotificationMessage(tag: String?, id: Int) { @@ -53,15 +53,21 @@ class NotificationDisplayer @Inject constructor( } } - @SuppressLint("LaunchActivityFromNotification") - fun displayDiagnosticNotification(notification: Notification) { - showNotificationMessage( + fun displayDiagnosticNotification(notification: Notification): Boolean { + return showNotificationMessage( tag = "DIAGNOSTIC", id = NOTIFICATION_ID_DIAGNOSTIC, notification = notification ) } + fun dismissDiagnosticNotification() { + cancelNotificationMessage( + tag = "DIAGNOSTIC", + id = NOTIFICATION_ID_DIAGNOSTIC + ) + } + /** * Cancel the foreground notification service. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt index 152ed0a03e..0bc3e69bfb 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt @@ -19,9 +19,15 @@ package io.element.android.libraries.push.impl.notifications import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.push.impl.troubleshoot.NotificationClickHandler +import javax.inject.Inject class TestNotificationReceiver : BroadcastReceiver() { + @Inject lateinit var notificationClickHandler: NotificationClickHandler + override fun onReceive(context: Context, intent: Intent) { - // TODO The test notification has been clicked, notify the ui + context.bindings().inject(this) + notificationClickHandler.handleNotificationClick() } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt new file mode 100644 index 0000000000..6390bff885 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.notifications + +import com.squareup.anvil.annotations.ContributesTo +import io.element.android.libraries.di.AppScope + +@ContributesTo(AppScope::class) +interface TestNotificationReceiverBinding { + fun inject(service: TestNotificationReceiver) +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index 2beddc53f9..67c8973d13 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -299,6 +299,7 @@ class NotificationCreator @Inject constructor( } fun createDiagnosticNotification(): Notification { + val intent = pendingIntentFactory.createTestPendingIntent() return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest()) .setContentTitle(buildMeta.applicationName) .setContentText(stringProvider.getString(R.string.notification_test_push_notification_content)) @@ -308,7 +309,8 @@ class NotificationCreator @Inject constructor( .setPriority(NotificationCompat.PRIORITY_MAX) .setCategory(NotificationCompat.CATEGORY_STATUS) .setAutoCancel(true) - .setContentIntent(pendingIntentFactory.createTestPendingIntent()) + .setContentIntent(intent) + .setDeleteIntent(intent) .build() } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index a930db2708..5de0620eca 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.push.impl.PushersManager import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver import io.element.android.libraries.push.impl.store.DefaultPushDataStore +import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushproviders.api.PushHandler import io.element.android.libraries.pushstore.api.UserPushStoreFactory @@ -51,6 +52,7 @@ class DefaultPushHandler @Inject constructor( // private val actionIds: NotificationActionIds, private val buildMeta: BuildMeta, private val matrixAuthenticationService: MatrixAuthenticationService, + private val diagnosticPushHandler: DiagnosticPushHandler, ) : PushHandler { private val coroutineScope = CoroutineScope(SupervisorJob()) @@ -75,8 +77,7 @@ class DefaultPushHandler @Inject constructor( // Diagnostic Push if (pushData.eventId == PushersManager.TEST_EVENT_ID) { - // val intent = Intent(actionIds.push) - // TODO The test push has been received, notify the ui + diagnosticPushHandler.handlePush() return } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt index 9e52d94049..5e341e3286 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt @@ -23,6 +23,8 @@ import kotlinx.serialization.Serializable internal data class PushGatewayNotification( @SerialName("event_id") val eventId: String, + @SerialName("room_id") + val roomId: String, /** * Required. This is an array of devices that the notification should be sent to. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt index 7130e38d6e..e8c01493ab 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.push.impl.pushgateway import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.network.RetrofitFactory import io.element.android.libraries.push.api.gateway.PushGatewayFailure import javax.inject.Inject @@ -27,7 +28,8 @@ class PushGatewayNotifyRequest @Inject constructor( val url: String, val appId: String, val pushKey: String, - val eventId: EventId + val eventId: EventId, + val roomId: RoomId, ) suspend fun execute(params: Params) { @@ -40,6 +42,7 @@ class PushGatewayNotifyRequest @Inject constructor( PushGatewayNotifyBody( PushGatewayNotification( eventId = params.eventId.value, + roomId = params.roomId.value, devices = listOf( PushGatewayDevice( params.appId, @@ -51,7 +54,7 @@ class PushGatewayNotifyRequest @Inject constructor( ) if (response.rejectedPushKeys.contains(params.pushKey)) { - throw PushGatewayFailure.PusherRejected + throw PushGatewayFailure.PusherRejected() } } } 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 new file mode 100644 index 0000000000..6b8f69e8db --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.api.GetCurrentPushProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class CurrentPushProviderTest @Inject constructor( + private val getCurrentPushProvider: GetCurrentPushProvider, +) : NotificationTroubleshootTest { + override val order = 110 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Current push provider", + defaultDescription = "Get the name of the current provider.", + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val provider = getCurrentPushProvider.getCurrentPushProvider() + if (provider != null) { + delegate.updateState( + description = "Current push provider: $provider", + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = "No push providers selected", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + override fun reset() = delegate.reset() +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt new file mode 100644 index 0000000000..21b78161d9 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +class DiagnosticPushHandler @Inject constructor() { + private val _state = MutableSharedFlow() + val state: SharedFlow = _state + + suspend fun handlePush() { + _state.emit(Unit) + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt new file mode 100644 index 0000000000..29f5fe0b9f --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +class NotificationClickHandler @Inject constructor() { + private val _state = MutableSharedFlow(extraBufferCapacity = 1) + val state: SharedFlow = _state + + fun handleNotificationClick() { + _state.tryEmit(Unit) + } +} 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 new file mode 100644 index 0000000000..b2e7ef223c --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.impl.notifications.NotificationDisplayer +import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull +import timber.log.Timber +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds + +@ContributesMultibinding(AppScope::class) +class NotificationTest @Inject constructor( + private val notificationCreator: NotificationCreator, + private val notificationDisplayer: NotificationDisplayer, + private val notificationClickHandler: NotificationClickHandler +) : NotificationTroubleshootTest { + override val order = 50 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Display notification", + defaultDescription = "Check that the application can display notification", + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val notification = notificationCreator.createDiagnosticNotification() + val result = notificationDisplayer.displayDiagnosticNotification(notification) + if (result) { + coroutineScope.listenToNotificationClick() + delegate.updateState( + description = "Please click on the notification to continue the test.", + status = NotificationTroubleshootTestState.Status.WaitingForUser + ) + } else { + delegate.updateState( + description = "Cannot display the notification.", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + private fun CoroutineScope.listenToNotificationClick() = launch { + val job = launch { + notificationClickHandler.state.first() + Timber.d("Notification clicked!") + } + val s = withTimeoutOrNull(30.seconds) { + job.join() + } + job.cancel() + if (s == null) { + notificationDisplayer.dismissDiagnosticNotification() + delegate.updateState( + description = "The notification has not been clicked.", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } else { + delegate.updateState( + description = "The notification has been clicked!", + status = NotificationTroubleshootTestState.Status.Success + ) + } + }.invokeOnCompletion { + // Ensure that the notification is cancelled when the screen is left + notificationDisplayer.dismissDiagnosticNotification() + } + + override fun reset() = delegate.reset() +} 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 new file mode 100644 index 0000000000..86d7b437a7 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.push.api.gateway.PushGatewayFailure +import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull +import timber.log.Timber +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds + +@ContributesMultibinding(AppScope::class) +class PushLoopbackTest @Inject constructor( + private val pushService: PushService, + private val diagnosticPushHandler: DiagnosticPushHandler, + private val clock: SystemClock, +) : NotificationTroubleshootTest { + override val order = 500 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Test Push loopback", + defaultDescription = "Ensure that the application is receiving push.", + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val startTime = clock.epochMillis() + val completable = CompletableDeferred() + val job = coroutineScope.launch { + diagnosticPushHandler.state.first() + completable.complete(clock.epochMillis() - startTime) + } + val testPushResult = try { + pushService.testPush() + } catch (pusherRejected: PushGatewayFailure.PusherRejected) { + delegate.updateState( + description = "Error: pusher has rejected the request.", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + job.cancel() + return + } catch (e: Exception) { + Timber.e(e, "Failed to test push") + delegate.updateState( + description = "Error: ${e.message}.", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + job.cancel() + return + } + if (!testPushResult) { + delegate.updateState( + description = "Error, cannot test push.", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + job.cancel() + return + } + val result = withTimeoutOrNull(10.seconds) { + completable.await() + } + job.cancel() + if (result == null) { + delegate.updateState( + description = "Error, timeout waiting for push.", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } else { + delegate.updateState( + description = "Push loopback took $result ms", + status = NotificationTroubleshootTestState.Status.Success + ) + } + } + + override fun reset() = delegate.reset() +} 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 new file mode 100644 index 0000000000..7beca906b9 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.pushproviders.api.PushProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class PushProvidersTest @Inject constructor( + pushProviders: Set<@JvmSuppressWildcards PushProvider>, +) : NotificationTroubleshootTest { + private val sortedPushProvider = pushProviders.sortedBy { it.index } + override val order = 100 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Detect push providers", + defaultDescription = "Ensure that the application has at least one push provider.", + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val result = sortedPushProvider.isNotEmpty() + if (result) { + delegate.updateState( + description = "Found ${sortedPushProvider.size} push providers: ${sortedPushProvider.joinToString { it.name }}", + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = "No push providers found", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + override fun reset() = delegate.reset() +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt index e641029c0d..a8058fbcc5 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.test.A_SPACE_ID import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.core.aBuildMeta -import io.element.android.libraries.push.impl.notifications.fake.FakeAndroidNotificationFactory +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoaderHolder import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator @@ -118,7 +118,7 @@ class DefaultNotificationDrawerManagerTest { NotificationIdProvider(), NotificationDisplayer(context), NotificationFactory( - FakeAndroidNotificationFactory().instance, + FakeNotificationCreator().instance, FakeRoomGroupMessageCreator().instance, FakeSummaryGroupMessageCreator().instance, ) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt index 50ee91f448..cd1d19b3a7 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt @@ -22,7 +22,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser 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_SESSION_ID -import io.element.android.libraries.push.impl.notifications.fake.FakeAndroidNotificationFactory +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator @@ -41,7 +41,7 @@ private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roo @RunWith(RobolectricTestRunner::class) class NotificationFactoryTest { - private val androidNotificationFactory = FakeAndroidNotificationFactory() + private val androidNotificationFactory = FakeNotificationCreator() private val roomGroupMessageCreator = FakeRoomGroupMessageCreator() private val summaryGroupMessageCreator = FakeSummaryGroupMessageCreator() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeAndroidNotificationFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt similarity index 85% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeAndroidNotificationFactory.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt index 6637b64979..5029166f06 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeAndroidNotificationFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiab import io.mockk.every import io.mockk.mockk -class FakeAndroidNotificationFactory { +class FakeNotificationCreator { val instance = mockk() fun givenCreateRoomInvitationNotificationFor(event: InviteNotifiableEvent): Notification { @@ -37,4 +37,10 @@ class FakeAndroidNotificationFactory { every { instance.createSimpleEventNotification(event) } returns mockNotification return mockNotification } + + fun givenCreateDiagnosticNotification(): Notification { + val mockNotification = mockk() + every { instance.createDiagnosticNotification() } returns mockNotification + return mockNotification + } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt index 9af681490a..d737c8eae2 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.push.impl.notifications.NotificationDisplayer import io.element.android.libraries.push.impl.notifications.NotificationIdProvider import io.mockk.confirmVerified +import io.mockk.every import io.mockk.mockk import io.mockk.verify import io.mockk.verifyOrder @@ -27,6 +28,10 @@ import io.mockk.verifyOrder class FakeNotificationDisplayer { val instance = mockk(relaxed = true) + fun givenDisplayDiagnosticNotificationResult(result: Boolean) { + every { instance.displayDiagnosticNotification(any()) } returns result + } + fun verifySummaryCancelled() { verify { instance.cancelNotificationMessage(tag = null, NotificationIdProvider().getSummaryNotificationId(A_SESSION_ID)) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt new file mode 100644 index 0000000000..8506d826b5 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class CurrentPushProviderTestTest { + @Test + fun `test CurrentPushProviderTest with a push provider`() = runTest { + val sut = CurrentPushProviderTest( + getCurrentPushProvider = FakeGetCurrentPushProvider("foo") + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + assertThat(lastItem.description).contains("foo") + } + } + + @Test + fun `test CurrentPushProviderTest without push provider`() = runTest { + val sut = CurrentPushProviderTest( + getCurrentPushProvider = FakeGetCurrentPushProvider(null) + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt new file mode 100644 index 0000000000..fcef1dcbab --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NotificationTestTest { + private val fakeNotificationCreator = FakeNotificationCreator().apply { + givenCreateDiagnosticNotification() + } + private val fakeNotificationDisplayer = FakeNotificationDisplayer().apply { + givenDisplayDiagnosticNotificationResult(true) + } + + private val notificationClickHandler = NotificationClickHandler() + + @Test + fun `test NotificationTest notification cannot be displayed`() = runTest { + fakeNotificationDisplayer.givenDisplayDiagnosticNotificationResult(false) + val sut = createNotificationTest() + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isInstanceOf(NotificationTroubleshootTestState.Status.Failure::class.java) + } + } + + @Test + fun `test NotificationTest user does not click on notification`() = runTest { + val sut = createNotificationTest() + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.WaitingForUser) + assertThat(awaitItem().status).isInstanceOf(NotificationTroubleshootTestState.Status.Failure::class.java) + } + } + + @Test + fun `test NotificationTest user clicks on notification`() = runTest { + val sut = createNotificationTest() + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.WaitingForUser) + notificationClickHandler.handleNotificationClick() + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + private fun createNotificationTest(): NotificationTest { + return NotificationTest( + notificationCreator = fakeNotificationCreator.instance, + notificationDisplayer = fakeNotificationDisplayer.instance, + notificationClickHandler = notificationClickHandler + ) + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt new file mode 100644 index 0000000000..354ec60bb9 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.libraries.push.api.gateway.PushGatewayFailure +import io.element.android.libraries.push.test.FakePushService +import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class PushLoopbackTestTest { + @Test + fun `test PushLoopbackTest timeout - push is not received`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService(), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock() + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + assertThat(lastItem.description).contains("timeout") + } + } + + @Test + fun `test PushLoopbackTest PusherRejected error`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService( + testPushBlock = { + throw PushGatewayFailure.PusherRejected() + } + ), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock() + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + assertThat(lastItem.description).contains("rejected") + } + } + + @Test + fun `test PushLoopbackTest setup error`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService( + testPushBlock = { false } + ), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock() + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + assertThat(lastItem.description).contains("cannot test push") + } + } + + @Test + fun `test PushLoopbackTest other error`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService( + testPushBlock = { + throw AN_EXCEPTION + } + ), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock() + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + assertThat(lastItem.description).contains(A_FAILURE_REASON) + } + } + + @Test + fun `test PushLoopbackTest push is received`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService(testPushBlock = { + diagnosticPushHandler.handlePush() + true + }), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock() + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt new file mode 100644 index 0000000000..644392c11b --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.pushproviders.test.FakePushProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class PushProvidersTestTest { + @Test + fun `test PushProvidersTest with empty list`() = runTest { + val sut = PushProvidersTest( + pushProviders = emptySet(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } + + @Test + fun `test PushProvidersTest with 2 push providers`() = runTest { + val sut = PushProvidersTest( + pushProviders = setOf( + FakePushProvider(name = "foo"), + FakePushProvider(name = "bar"), + ), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + assertThat(lastItem.description).contains("foo") + assertThat(lastItem.description).contains("bar") + } + } +} diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt new file mode 100644 index 0000000000..76363c9d99 --- /dev/null +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.test + +import io.element.android.libraries.push.api.GetCurrentPushProvider + +class FakeGetCurrentPushProvider( + private val currentPushProvider: String? +) : GetCurrentPushProvider { + override suspend fun getCurrentPushProvider(): String? = currentPushProvider +} diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt index 7b6422cd1b..969815ec66 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt @@ -20,8 +20,11 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.push.api.PushService import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.tests.testutils.simulateLongTask -class FakePushService : PushService { +class FakePushService( + private val testPushBlock: suspend () -> Boolean = { true } +) : PushService { override fun notificationStyleChanged() { } @@ -32,6 +35,7 @@ class FakePushService : PushService { override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) { } - override suspend fun testPush() { + override suspend fun testPush(): Boolean = simulateLongTask { + testPushBlock() } } diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt new file mode 100644 index 0000000000..bfd6488904 --- /dev/null +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.api + +data class CurrentUserPushConfig( + val url: String, + val pushKey: String, +) diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt index 3d8349a117..4e9b818dd4 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt @@ -49,8 +49,5 @@ interface PushProvider { */ suspend fun unregister(matrixClient: MatrixClient) - /** - * Attempt to troubleshoot the push provider. - */ - suspend fun troubleshoot(): Result + suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? } diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index 186ab121fd..55dca139b0 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -51,8 +51,10 @@ dependencies { exclude(group = "com.google.firebase", module = "firebase-measurement-connector") } + testImplementation(libs.coroutines.test) testImplementation(libs.test.junit) testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) } 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 4c3d6d3a20..317d49f3b6 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 @@ -16,14 +16,11 @@ package io.element.android.libraries.pushproviders.firebase -import android.content.Context -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.squareup.anvil.annotations.ContributesMultibinding import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.AppScope -import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushproviders.api.PusherSubscriber @@ -34,25 +31,15 @@ private val loggerTag = LoggerTag("FirebasePushProvider", LoggerTag.PushLoggerTa @ContributesMultibinding(AppScope::class) class FirebasePushProvider @Inject constructor( - @ApplicationContext private val context: Context, private val firebaseStore: FirebaseStore, - private val firebaseTroubleshooter: FirebaseTroubleshooter, private val pusherSubscriber: PusherSubscriber, + private val isPlayServiceAvailable: IsPlayServiceAvailable, ) : PushProvider { override val index = FirebaseConfig.INDEX override val name = FirebaseConfig.NAME override fun isAvailable(): Boolean { - // The PlayServices has to be available - val apiAvailability = GoogleApiAvailability.getInstance() - val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) - return if (resultCode == ConnectionResult.SUCCESS) { - Timber.tag(loggerTag.value).d("Google Play Services is available") - true - } else { - Timber.tag(loggerTag.value).w("Google Play Services is not available") - false - } + return isPlayServiceAvailable.isAvailable() } override fun getDistributors(): List { @@ -73,7 +60,12 @@ class FirebasePushProvider @Inject constructor( pusherSubscriber.unregisterPusher(matrixClient, pushKey, FirebaseConfig.PUSHER_HTTP_URL) } - override suspend fun troubleshoot(): Result { - return firebaseTroubleshooter.troubleshoot() + override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { + return firebaseStore.getFcmToken()?.let { fcmToken -> + CurrentUserPushConfig( + url = FirebaseConfig.PUSHER_HTTP_URL, + pushKey = fcmToken + ) + } } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt index 0342c67462..0614e2065c 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt @@ -18,20 +18,28 @@ package io.element.android.libraries.pushproviders.firebase import android.content.SharedPreferences import androidx.core.content.edit +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.DefaultPreferences import javax.inject.Inject /** * This class store the Firebase token in SharedPrefs. */ -class FirebaseStore @Inject constructor( +interface FirebaseStore { + fun getFcmToken(): String? + fun storeFcmToken(token: String?) +} + +@ContributesBinding(AppScope::class) +class DefaultFirebaseStore @Inject constructor( @DefaultPreferences private val sharedPrefs: SharedPreferences, -) { - fun getFcmToken(): String? { +) : FirebaseStore { + override fun getFcmToken(): String? { return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) } - fun storeFcmToken(token: String?) { + override fun storeFcmToken(token: String?) { sharedPrefs.edit { putString(PREFS_KEY_FCM_TOKEN, token) } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt index f3efba1a16..6d205c42ae 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt @@ -16,25 +16,28 @@ package io.element.android.libraries.pushproviders.firebase -import android.content.Context -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.google.firebase.messaging.FirebaseMessaging -import io.element.android.libraries.di.ApplicationContext +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope import timber.log.Timber import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine +interface FirebaseTroubleshooter { + suspend fun troubleshoot(): Result +} + /** * This class force retrieving and storage of the Firebase token. */ -class FirebaseTroubleshooter @Inject constructor( - @ApplicationContext private val context: Context, +@ContributesBinding(AppScope::class) +class DefaultFirebaseTroubleshooter @Inject constructor( private val newTokenHandler: FirebaseNewTokenHandler, -) { - suspend fun troubleshoot(): Result { + private val isPlayServiceAvailable: IsPlayServiceAvailable, +) : FirebaseTroubleshooter { + override suspend fun troubleshoot(): Result { return runCatching { val token = retrievedFirebaseToken() newTokenHandler.handle(token) @@ -44,7 +47,7 @@ class FirebaseTroubleshooter @Inject constructor( private suspend fun retrievedFirebaseToken(): String { return suspendCoroutine { continuation -> // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' - if (checkPlayServices(context)) { + if (isPlayServiceAvailable.isAvailable()) { try { FirebaseMessaging.getInstance().token .addOnSuccessListener { token -> @@ -65,15 +68,4 @@ class FirebaseTroubleshooter @Inject constructor( } } } - - /** - * Check the device to make sure it has the Google Play Services APK. If - * it doesn't, display a dialog that allows users to download the APK from - * the Google Play Store or enable it in the device's system settings. - */ - private fun checkPlayServices(context: Context): Boolean { - val apiAvailability = GoogleApiAvailability.getInstance() - val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) - return resultCode == ConnectionResult.SUCCESS - } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt new file mode 100644 index 0000000000..50d9d4fe6f --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.firebase + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import timber.log.Timber +import javax.inject.Inject + +interface IsPlayServiceAvailable { + fun isAvailable(): Boolean +} + +@ContributesBinding(AppScope::class) +class DefaultIsPlayServiceAvailable @Inject constructor( + @ApplicationContext private val context: Context, +) : IsPlayServiceAvailable { + override fun isAvailable(): Boolean { + val apiAvailability = GoogleApiAvailability.getInstance() + val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) + return if (resultCode == ConnectionResult.SUCCESS) { + Timber.d("Google Play Services is available") + true + } else { + Timber.w("Google Play Services is not available") + false + } + } +} 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 new file mode 100644 index 0000000000..e0891d6081 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.firebase.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.core.notifications.TestFilterData +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.pushproviders.firebase.FirebaseConfig +import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class FirebaseAvailabilityTest @Inject constructor( + private val isPlayServiceAvailable: IsPlayServiceAvailable, +) : NotificationTroubleshootTest { + override val order = 300 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Check Firebase", + defaultDescription = "Ensure that Firebase is available.", + visibleWhenIdle = false, + fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY, + ) + override val state: StateFlow = delegate.state + + override fun isRelevant(data: TestFilterData): Boolean { + return data.currentPushProviderName == FirebaseConfig.NAME + } + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val result = isPlayServiceAvailable.isAvailable() + if (result) { + delegate.updateState( + description = "Firebase is available", + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = "Firebase is not available", + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + override fun reset() = delegate.reset() +} 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 new file mode 100644 index 0000000000..7c8ce5dfef --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.firebase.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.core.notifications.TestFilterData +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.pushproviders.firebase.FirebaseConfig +import io.element.android.libraries.pushproviders.firebase.FirebaseStore +import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class FirebaseTokenTest @Inject constructor( + private val firebaseStore: FirebaseStore, + private val firebaseTroubleshooter: FirebaseTroubleshooter, +) : NotificationTroubleshootTest { + override val order = 310 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Check Firebase token", + defaultDescription = "Ensure that Firebase token is available.", + visibleWhenIdle = false, + fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY, + ) + override val state: StateFlow = delegate.state + + override fun isRelevant(data: TestFilterData): Boolean { + return data.currentPushProviderName == FirebaseConfig.NAME + } + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val token = firebaseStore.getFcmToken() + if (token != null) { + delegate.updateState( + description = "Firebase token: ${token.take(8)}*****", + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = "Firebase token is not known", + status = NotificationTroubleshootTestState.Status.Failure(true) + ) + } + } + + override fun reset() = delegate.reset() + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + delegate.start() + firebaseTroubleshooter.troubleshoot() + run(coroutineScope) + } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt new file mode 100644 index 0000000000..b0dae793cd --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.firebase + +import io.element.android.tests.testutils.simulateLongTask + +class FakeFirebaseTroubleshooter( + private val troubleShootResult: () -> Result = { Result.success(Unit) } +) : FirebaseTroubleshooter { + override suspend fun troubleshoot(): Result = simulateLongTask { + troubleShootResult() + } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt new file mode 100644 index 0000000000..f298b9f30e --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.firebase + +class InMemoryFirebaseStore( + private var token: String? = null +) : FirebaseStore { + override fun getFcmToken(): String? = token + + override fun storeFcmToken(token: String?) { + this.token = token + } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt new file mode 100644 index 0000000000..eea622849b --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.firebase.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class FirebaseAvailabilityTestTest { + @Test + fun `test FirebaseAvailabilityTest success`() = runTest { + val sut = FirebaseAvailabilityTest( + isPlayServiceAvailable = object : IsPlayServiceAvailable { + override fun isAvailable(): Boolean { + return true + } + } + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + @Test + fun `test FirebaseAvailabilityTest failure`() = runTest { + val sut = FirebaseAvailabilityTest( + isPlayServiceAvailable = object : IsPlayServiceAvailable { + override fun isAvailable(): Boolean { + return false + } + } + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt new file mode 100644 index 0000000000..0463939234 --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.firebase.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter +import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class FirebaseTokenTestTest { + @Test + fun `test FirebaseTokenTest success`() = runTest { + val sut = FirebaseTokenTest( + firebaseStore = InMemoryFirebaseStore(FAKE_TOKEN), + firebaseTroubleshooter = FakeFirebaseTroubleshooter(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + assertThat(lastItem.description).contains(FAKE_TOKEN.take(8)) + assertThat(lastItem.description).doesNotContain(FAKE_TOKEN) + } + } + + @Test + fun `test FirebaseTokenTest error`() = runTest { + val firebaseStore = InMemoryFirebaseStore(null) + val sut = FirebaseTokenTest( + firebaseStore = firebaseStore, + firebaseTroubleshooter = FakeFirebaseTroubleshooter( + troubleShootResult = { + firebaseStore.storeFcmToken(FAKE_TOKEN) + Result.success(Unit) + } + ), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) + // Quick fix + sut.quickFix(this) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + companion object { + private const val FAKE_TOKEN = "abcdefghijk" + } +} diff --git a/libraries/pushproviders/test/build.gradle.kts b/libraries/pushproviders/test/build.gradle.kts new file mode 100644 index 0000000000..ddb68ed43f --- /dev/null +++ b/libraries/pushproviders/test/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.pushproviders.test" +} + +dependencies { + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.pushproviders.api) +} diff --git a/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt new file mode 100644 index 0000000000..8d8b94ec19 --- /dev/null +++ b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.test + +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider + +class FakePushProvider( + override val index: Int = 0, + override val name: String = "aFakePushProvider", + private val isAvailable: Boolean = true, + private val distributors: List = emptyList() +) : PushProvider { + override fun isAvailable(): Boolean = isAvailable + + override fun getDistributors(): List = distributors + + override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor) { + // No-op + } + + override suspend fun unregister(matrixClient: MatrixClient) { + // No-op + } + + override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { + return null + } +} diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index a3968f4ecc..ecceed6026 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { implementation(projects.libraries.pushproviders.api) implementation(projects.libraries.architecture) implementation(projects.libraries.core) + implementation(projects.services.appnavstate.api) implementation(projects.services.toolbox.api) implementation(projects.libraries.network) @@ -50,8 +51,10 @@ dependencies { // UnifiedPush library api(libs.unifiedpush) + testImplementation(libs.coroutines.test) testImplementation(libs.test.junit) testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushDistributorProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushDistributorProvider.kt new file mode 100644 index 0000000000..5c9249e8f4 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushDistributorProvider.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush + +import android.content.Context +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.system.getApplicationLabel +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.pushproviders.api.Distributor +import org.unifiedpush.android.connector.UnifiedPush +import javax.inject.Inject + +interface UnifiedPushDistributorProvider { + fun getDistributors(): List +} + +@ContributesBinding(AppScope::class) +class DefaultUnifiedPushDistributorProvider @Inject constructor( + @ApplicationContext private val context: Context, +) : UnifiedPushDistributorProvider { + override fun getDistributors(): List { + val distributors = UnifiedPush.getDistributors(context) + return distributors.mapNotNull { + if (it == context.packageName) { + // Exclude self + null + } else { + Distributor(it, context.getApplicationLabel(it)) + } + } + } +} 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 5d0a0da7a1..e7ea1841c5 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 @@ -16,17 +16,16 @@ package io.element.android.libraries.pushproviders.unifiedpush -import android.content.Context import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.androidutils.system.getApplicationLabel import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.AppScope -import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret -import org.unifiedpush.android.connector.UnifiedPush +import io.element.android.services.appnavstate.api.AppNavigationStateService +import io.element.android.services.appnavstate.api.currentSessionId import timber.log.Timber import javax.inject.Inject @@ -34,10 +33,12 @@ private val loggerTag = LoggerTag("UnifiedPushProvider", LoggerTag.PushLoggerTag @ContributesMultibinding(AppScope::class) class UnifiedPushProvider @Inject constructor( - @ApplicationContext private val context: Context, + private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider, private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, private val unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val pushClientSecret: PushClientSecret, + private val unifiedPushStore: UnifiedPushStore, + private val appNavigationStateService: AppNavigationStateService, ) : PushProvider { override val index = UnifiedPushConfig.INDEX override val name = UnifiedPushConfig.NAME @@ -54,15 +55,7 @@ class UnifiedPushProvider @Inject constructor( } override fun getDistributors(): List { - val distributors = UnifiedPush.getDistributors(context) - return distributors.mapNotNull { - if (it == context.packageName) { - // Exclude self - null - } else { - Distributor(it, context.getApplicationLabel(it)) - } - } + return unifiedPushDistributorProvider.getDistributors() } override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor) { @@ -75,7 +68,14 @@ class UnifiedPushProvider @Inject constructor( unRegisterUnifiedPushUseCase.execute(clientSecret) } - override suspend fun troubleshoot(): Result { - TODO("Not yet implemented") + override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { + val currentSession = appNavigationStateService.appNavigationState.value.navigationState.currentSessionId() ?: return null + val clientSecret = pushClientSecret.getSecretForUser(currentSession) + val url = unifiedPushStore.getPushGateway(clientSecret) ?: return null + val pushKey = unifiedPushStore.getEndpoint(clientSecret) ?: return null + return CurrentUserPushConfig( + url = url, + pushKey = pushKey, + ) } } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt new file mode 100644 index 0000000000..daad9afda3 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot + +import android.content.Context +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.androidutils.system.openUrlInExternalApp +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import javax.inject.Inject + +interface OpenDistributorWebPageAction { + fun execute() +} + +@ContributesBinding(AppScope::class) +class DefaultOpenDistributorWebPageAction @Inject constructor( + @ApplicationContext private val context: Context, +) : OpenDistributorWebPageAction { + override fun execute() { + // Open the distributor download page + context.openUrlInExternalApp( + url = "https://unifiedpush.org/users/distributors/", + inNewTask = true + ) + } +} 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 new file mode 100644 index 0000000000..3d612b0613 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.notifications.NotificationTroubleshootTest +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.core.notifications.TestFilterData +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class UnifiedPushTest @Inject constructor( + private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider, + private val openDistributorWebPageAction: OpenDistributorWebPageAction, +) : NotificationTroubleshootTest { + override val order = 400 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = "Check UnifiedPush", + defaultDescription = "Ensure that UnifiedPush distributors are available.", + visibleWhenIdle = false, + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override fun isRelevant(data: TestFilterData): Boolean { + return data.currentPushProviderName == UnifiedPushConfig.NAME + } + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val distributors = unifiedPushDistributorProvider.getDistributors() + if (distributors.isNotEmpty()) { + delegate.updateState( + description = "Distributors found: ${distributors.joinToString { it.name }}", + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = "No push distributors found", + status = NotificationTroubleshootTestState.Status.Failure(true) + ) + } + } + + override fun reset() = delegate.reset() + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + openDistributorWebPageAction.execute() + } +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt new file mode 100644 index 0000000000..ca91807fb9 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot + +class FakeOpenDistributorWebPageAction( + private val executeAction: () -> Unit = {} +) : OpenDistributorWebPageAction { + override fun execute() = executeAction() +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt new file mode 100644 index 0000000000..e9734956d7 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot + +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider + +class FakeUnifiedPushDistributorProvider( + private var getDistributorsResult: List = emptyList() +) : UnifiedPushDistributorProvider { + override fun getDistributors(): List { + return getDistributorsResult + } + + fun setDistributorsResult(list: List) { + getDistributorsResult = list + } +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt new file mode 100644 index 0000000000..e7a8ccc680 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.pushproviders.api.Distributor +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class UnifiedPushTestTest { + @Test + fun `test UnifiedPushTest success`() = runTest { + val sut = UnifiedPushTest( + unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider( + getDistributorsResult = listOf( + Distributor("value", "Name"), + ) + ), + openDistributorWebPageAction = FakeOpenDistributorWebPageAction(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + @Test + fun `test UnifiedPushTest error`() = runTest { + val providers = FakeUnifiedPushDistributorProvider() + val sut = UnifiedPushTest( + unifiedPushDistributorProvider = providers, + openDistributorWebPageAction = FakeOpenDistributorWebPageAction( + executeAction = { + providers.setDistributorsResult( + listOf( + Distributor("value", "Name"), + ) + ) + } + ), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) + // Quick fix + launch { + sut.quickFix(this) + sut.run(this) + } + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } +} From a83952814f698f7b93d4514ae3133187bb420ad6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 26 Mar 2024 11:49:57 +0100 Subject: [PATCH 047/124] Rename Fake class with `Mockk` prefix for clarity --- .../DefaultNotificationDrawerManagerTest.kt | 18 +++++----- .../NotifiableEventProcessorTest.kt | 20 +++++------ .../notifications/NotificationFactoryTest.kt | 26 +++++++------- .../notifications/NotificationRendererTest.kt | 36 +++++++++---------- ...Creator.kt => MockkNotificationCreator.kt} | 2 +- ...layer.kt => MockkNotificationDisplayer.kt} | 2 +- ...Factory.kt => MockkNotificationFactory.kt} | 2 +- ...ector.kt => MockkOutdatedEventDetector.kt} | 2 +- ...tor.kt => MockkRoomGroupMessageCreator.kt} | 2 +- ....kt => MockkSummaryGroupMessageCreator.kt} | 2 +- .../impl/troubleshoot/NotificationTestTest.kt | 14 ++++---- 11 files changed, 63 insertions(+), 63 deletions(-) rename libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/{FakeNotificationCreator.kt => MockkNotificationCreator.kt} (98%) rename libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/{FakeNotificationDisplayer.kt => MockkNotificationDisplayer.kt} (97%) rename libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/{FakeNotificationFactory.kt => MockkNotificationFactory.kt} (98%) rename libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/{FakeOutdatedEventDetector.kt => MockkOutdatedEventDetector.kt} (97%) rename libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/{FakeRoomGroupMessageCreator.kt => MockkRoomGroupMessageCreator.kt} (97%) rename libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/{FakeSummaryGroupMessageCreator.kt => MockkSummaryGroupMessageCreator.kt} (95%) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt index a8058fbcc5..b84e7b4be3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt @@ -23,10 +23,10 @@ import io.element.android.libraries.matrix.test.A_SPACE_ID import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.core.aBuildMeta -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoaderHolder -import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator -import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkRoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.services.appnavstate.api.AppNavigationState @@ -115,12 +115,12 @@ class DefaultNotificationDrawerManagerTest { appNavigationStateService = appNavigationStateService ), notificationRenderer = NotificationRenderer( - NotificationIdProvider(), - NotificationDisplayer(context), - NotificationFactory( - FakeNotificationCreator().instance, - FakeRoomGroupMessageCreator().instance, - FakeSummaryGroupMessageCreator().instance, + notificationIdProvider = NotificationIdProvider(), + notificationDisplayer = NotificationDisplayer(context), + notificationFactory = NotificationFactory( + notificationCreator = MockkNotificationCreator().instance, + roomGroupMessageCreator = MockkRoomGroupMessageCreator().instance, + summaryGroupMessageCreator = MockkSummaryGroupMessageCreator().instance, ) ), notificationEventPersistence = InMemoryNotificationEventPersistence(initialData = initialData), diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt index 02da10a351..a8626766e5 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt @@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SPACE_ID import io.element.android.libraries.matrix.test.A_THREAD_ID -import io.element.android.libraries.push.impl.notifications.fake.FakeOutdatedEventDetector +import io.element.android.libraries.push.impl.notifications.fake.MockkOutdatedEventDetector import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent @@ -42,7 +42,7 @@ private val VIEWING_A_ROOM = aNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_I private val VIEWING_A_THREAD = aNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_ID, A_THREAD_ID) class NotifiableEventProcessorTest { - private val outdatedDetector = FakeOutdatedEventDetector() + private val mockkOutdatedDetector = MockkOutdatedEventDetector() @Test fun `given simple events when processing then keep simple events`() { @@ -97,7 +97,7 @@ class NotifiableEventProcessorTest { @Test fun `given out of date message event when processing then removes message event`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) - outdatedDetector.givenEventIsOutOfDate(events[0]) + mockkOutdatedDetector.givenEventIsOutOfDate(events[0]) val eventProcessor = createProcessor(navigationState = NOT_VIEWING_A_ROOM) @@ -113,7 +113,7 @@ class NotifiableEventProcessorTest { @Test fun `given in date message event when processing then keep message event`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) - outdatedDetector.givenEventIsInDate(events[0]) + mockkOutdatedDetector.givenEventIsInDate(events[0]) val eventProcessor = createProcessor(navigationState = NOT_VIEWING_A_ROOM) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -128,7 +128,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing the same room main timeline when processing main timeline message event then removes message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = null)) - events.forEach { outdatedDetector.givenEventIsOutOfDate(it) } + events.forEach { mockkOutdatedDetector.givenEventIsOutOfDate(it) } val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_ROOM) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -143,7 +143,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing the same thread timeline when processing thread message event then removes message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID)) - events.forEach { outdatedDetector.givenEventIsOutOfDate(it) } + events.forEach { mockkOutdatedDetector.givenEventIsOutOfDate(it) } val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_THREAD) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -158,7 +158,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing main timeline of the same room when processing thread timeline message event then keep message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID)) - outdatedDetector.givenEventIsInDate(events[0]) + mockkOutdatedDetector.givenEventIsInDate(events[0]) val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_ROOM) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -173,7 +173,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing thread timeline of the same room when processing main timeline message event then keep message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) - outdatedDetector.givenEventIsInDate(events[0]) + mockkOutdatedDetector.givenEventIsInDate(events[0]) val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_THREAD) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -213,8 +213,8 @@ class NotifiableEventProcessorTest { navigationState: NavigationState ): NotifiableEventProcessor { return NotifiableEventProcessor( - outdatedDetector.instance, - FakeAppNavigationStateService(MutableStateFlow(AppNavigationState(navigationState, isInForeground))), + outdatedDetector = mockkOutdatedDetector.instance, + appNavigationStateService = FakeAppNavigationStateService(MutableStateFlow(AppNavigationState(navigationState, isInForeground))), ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt index cd1d19b3a7..6a211fc446 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt @@ -22,10 +22,10 @@ import io.element.android.libraries.matrix.api.user.MatrixUser 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_SESSION_ID -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader -import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator -import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkRoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent @@ -41,19 +41,19 @@ private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roo @RunWith(RobolectricTestRunner::class) class NotificationFactoryTest { - private val androidNotificationFactory = FakeNotificationCreator() - private val roomGroupMessageCreator = FakeRoomGroupMessageCreator() - private val summaryGroupMessageCreator = FakeSummaryGroupMessageCreator() + private val mockkNotificationCreator = MockkNotificationCreator() + private val mockkRoomGroupMessageCreator = MockkRoomGroupMessageCreator() + private val mockkSummaryGroupMessageCreator = MockkSummaryGroupMessageCreator() private val notificationFactory = NotificationFactory( - androidNotificationFactory.instance, - roomGroupMessageCreator.instance, - summaryGroupMessageCreator.instance + notificationCreator = mockkNotificationCreator.instance, + roomGroupMessageCreator = mockkRoomGroupMessageCreator.instance, + summaryGroupMessageCreator = mockkSummaryGroupMessageCreator.instance ) @Test fun `given a room invitation when mapping to notification then is Append`() = testWith(notificationFactory) { - val expectedNotification = androidNotificationFactory.givenCreateRoomInvitationNotificationFor(AN_INVITATION_EVENT) + val expectedNotification = mockkNotificationCreator.givenCreateRoomInvitationNotificationFor(AN_INVITATION_EVENT) val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, AN_INVITATION_EVENT)) val result = roomInvitation.toNotifications() @@ -90,7 +90,7 @@ class NotificationFactoryTest { @Test fun `given a simple event when mapping to notification then is Append`() = testWith(notificationFactory) { - val expectedNotification = androidNotificationFactory.givenCreateSimpleInvitationNotificationFor(A_SIMPLE_EVENT) + val expectedNotification = mockkNotificationCreator.givenCreateSimpleInvitationNotificationFor(A_SIMPLE_EVENT) val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_SIMPLE_EVENT)) val result = roomInvitation.toNotifications() @@ -128,7 +128,7 @@ class NotificationFactoryTest { @Test fun `given room with message when mapping to notification then delegates to room group message creator`() = testWith(notificationFactory) { val events = listOf(A_MESSAGE_EVENT) - val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor( + val expectedNotification = mockkRoomGroupMessageCreator.givenCreatesRoomMessageFor( MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), events, A_ROOM_ID @@ -197,7 +197,7 @@ class NotificationFactoryTest { ) ) val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("\$not-redacted"))) - val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor( + val expectedNotification = mockkRoomGroupMessageCreator.givenCreatesRoomMessageFor( MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), withRedactedRemoved, A_ROOM_ID, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt index 4780ae3914..21fc1b4fca 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt @@ -22,8 +22,8 @@ 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_SESSION_ID import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationFactory +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationDisplayer +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationFactory import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.mockk.mockk import kotlinx.coroutines.test.runTest @@ -51,14 +51,14 @@ private val ONE_SHOT_META = OneShotNotification.Append.Meta(key = "ignored", sum @RunWith(RobolectricTestRunner::class) class NotificationRendererTest { - private val notificationDisplayer = FakeNotificationDisplayer() - private val notificationFactory = FakeNotificationFactory() + private val mockkNotificationDisplayer = MockkNotificationDisplayer() + private val mockkNotificationFactory = MockkNotificationFactory() private val notificationIdProvider = NotificationIdProvider() private val notificationRenderer = NotificationRenderer( notificationIdProvider = notificationIdProvider, - notificationDisplayer = notificationDisplayer.instance, - notificationFactory = notificationFactory.instance, + notificationDisplayer = mockkNotificationDisplayer.instance, + notificationFactory = mockkNotificationFactory.instance, ) @Test @@ -67,8 +67,8 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifySummaryCancelled() - notificationDisplayer.verifyNoOtherInteractions() + mockkNotificationDisplayer.verifySummaryCancelled() + mockkNotificationDisplayer.verifyNoOtherInteractions() } @Test @@ -77,7 +77,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)) } @@ -89,7 +89,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -108,7 +108,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), A_NOTIFICATION) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -120,7 +120,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)) } @@ -132,7 +132,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -151,7 +151,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { showNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -163,7 +163,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)) } @@ -175,7 +175,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -194,7 +194,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -221,7 +221,7 @@ class NotificationRendererTest { useCompleteNotificationFormat: Boolean = USE_COMPLETE_NOTIFICATION_FORMAT, summaryNotification: SummaryNotification = A_SUMMARY_NOTIFICATION ) { - notificationFactory.givenNotificationsFor( + mockkNotificationFactory.givenNotificationsFor( groupedEvents = A_PROCESSED_EVENTS, matrixUser = MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL), useCompleteNotificationFormat = useCompleteNotificationFormat, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationCreator.kt similarity index 98% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationCreator.kt index 5029166f06..205ba058e6 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationCreator.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiab import io.mockk.every import io.mockk.mockk -class FakeNotificationCreator { +class MockkNotificationCreator { val instance = mockk() fun givenCreateRoomInvitationNotificationFor(event: InviteNotifiableEvent): Notification { diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationDisplayer.kt similarity index 97% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationDisplayer.kt index d737c8eae2..dc55cecfac 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationDisplayer.kt @@ -25,7 +25,7 @@ import io.mockk.mockk import io.mockk.verify import io.mockk.verifyOrder -class FakeNotificationDisplayer { +class MockkNotificationDisplayer { val instance = mockk(relaxed = true) fun givenDisplayDiagnosticNotificationResult(result: Boolean) { diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationFactory.kt similarity index 98% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationFactory.kt index 9c7755aa9d..6a8410d2cb 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationFactory.kt @@ -26,7 +26,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.mockk -class FakeNotificationFactory { +class MockkNotificationFactory { val instance = mockk() fun givenNotificationsFor( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkOutdatedEventDetector.kt similarity index 97% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkOutdatedEventDetector.kt index 03bf7e8491..414f7ae652 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkOutdatedEventDetector.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import io.mockk.every import io.mockk.mockk -class FakeOutdatedEventDetector { +class MockkOutdatedEventDetector { val instance = mockk() fun givenEventIsOutOfDate(notifiableEvent: NotifiableEvent) { diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkRoomGroupMessageCreator.kt similarity index 97% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkRoomGroupMessageCreator.kt index a41a4aadc2..389a4f441d 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkRoomGroupMessageCreator.kt @@ -24,7 +24,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableMess import io.mockk.coEvery import io.mockk.mockk -class FakeRoomGroupMessageCreator { +class MockkRoomGroupMessageCreator { val instance = mockk() fun givenCreatesRoomMessageFor( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkSummaryGroupMessageCreator.kt similarity index 95% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkSummaryGroupMessageCreator.kt index 546cb1e054..8f99651c89 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkSummaryGroupMessageCreator.kt @@ -19,6 +19,6 @@ package io.element.android.libraries.push.impl.notifications.fake import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator import io.mockk.mockk -class FakeSummaryGroupMessageCreator { +class MockkSummaryGroupMessageCreator { val instance = mockk() } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt index fcef1dcbab..0a4ac2d066 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt @@ -19,17 +19,17 @@ package io.element.android.libraries.push.impl.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationDisplayer import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test class NotificationTestTest { - private val fakeNotificationCreator = FakeNotificationCreator().apply { + private val mockkNotificationCreator = MockkNotificationCreator().apply { givenCreateDiagnosticNotification() } - private val fakeNotificationDisplayer = FakeNotificationDisplayer().apply { + private val mockkNotificationDisplayer = MockkNotificationDisplayer().apply { givenDisplayDiagnosticNotificationResult(true) } @@ -37,7 +37,7 @@ class NotificationTestTest { @Test fun `test NotificationTest notification cannot be displayed`() = runTest { - fakeNotificationDisplayer.givenDisplayDiagnosticNotificationResult(false) + mockkNotificationDisplayer.givenDisplayDiagnosticNotificationResult(false) val sut = createNotificationTest() launch { sut.run(this) @@ -80,8 +80,8 @@ class NotificationTestTest { private fun createNotificationTest(): NotificationTest { return NotificationTest( - notificationCreator = fakeNotificationCreator.instance, - notificationDisplayer = fakeNotificationDisplayer.instance, + notificationCreator = mockkNotificationCreator.instance, + notificationDisplayer = mockkNotificationDisplayer.instance, notificationClickHandler = notificationClickHandler ) } From 74048cca8f084aa8633411f17b019fb04abcee0b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 26 Mar 2024 15:29:12 +0100 Subject: [PATCH 048/124] Avoid tryEmit(), use emit() --- .../troubleshoot/FakeNotificationTroubleshootTest.kt | 10 +++++----- .../core/notifications/NotificationTroubleshootTest.kt | 6 ++++-- .../NotificationTroubleshootTestDelegate.kt | 8 ++++---- .../NotificationTroubleshootCheckPermissionTest.kt | 2 +- .../push/impl/troubleshoot/CurrentPushProviderTest.kt | 2 +- .../push/impl/troubleshoot/NotificationTest.kt | 2 +- .../push/impl/troubleshoot/PushLoopbackTest.kt | 2 +- .../push/impl/troubleshoot/PushProvidersTest.kt | 2 +- .../firebase/troubleshoot/FirebaseAvailabilityTest.kt | 2 +- .../firebase/troubleshoot/FirebaseTokenTest.kt | 2 +- .../unifiedpush/troubleshoot/UnifiedPushTest.kt | 2 +- 11 files changed, 21 insertions(+), 19 deletions(-) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt index e619a62972..361c59cae7 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt @@ -48,30 +48,30 @@ class FakeNotificationTroubleshootTest( } } - override fun reset() { + override suspend fun reset() { updateState( name = defaultName, description = defaultDescription, status = firstStatus, ) resetAction()?.let { - _state.tryEmit(it) + _state.emit(it) } } override suspend fun quickFix(coroutineScope: CoroutineScope) { updateState(NotificationTroubleshootTestState.Status.InProgress) quickFixAction()?.let { - _state.tryEmit(it) + _state.emit(it) } } - fun updateState( + suspend fun updateState( status: NotificationTroubleshootTestState.Status, name: String = defaultName, description: String = defaultDescription, ) { - _state.tryEmit( + _state.emit( NotificationTroubleshootTestState( name = name, description = description, diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt index 755e186e8b..7af0b85ffb 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt @@ -24,6 +24,8 @@ interface NotificationTroubleshootTest { val state: StateFlow fun isRelevant(data: TestFilterData): Boolean = true suspend fun run(coroutineScope: CoroutineScope) - fun reset() - suspend fun quickFix(coroutineScope: CoroutineScope) {} + suspend fun reset() + suspend fun quickFix(coroutineScope: CoroutineScope) { + error("Quick fix not implemented, you need to override this method in your test") + } } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt index 9711677253..88aaae7281 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt @@ -41,12 +41,12 @@ class NotificationTroubleshootTestDelegate( val state: StateFlow = _state.asStateFlow() - fun updateState( + suspend fun updateState( status: NotificationTroubleshootTestState.Status, name: String = defaultName, description: String = defaultDescription, ) { - _state.tryEmit( + _state.emit( NotificationTroubleshootTestState( name = name, description = description, @@ -55,7 +55,7 @@ class NotificationTroubleshootTestDelegate( ) } - fun reset() { + suspend fun reset() { updateState(NotificationTroubleshootTestState.Status.Idle(visibleWhenIdle)) } @@ -64,7 +64,7 @@ class NotificationTroubleshootTestDelegate( delay(fakeDelay) } - fun done(isSuccess: Boolean = true) { + suspend fun done(isSuccess: Boolean = true) { updateState( if (isSuccess) { NotificationTroubleshootTestState.Status.Success 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 501b00e14a..f158ccbabd 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 @@ -57,7 +57,7 @@ class NotificationTroubleshootCheckPermissionTest @Inject constructor( delegate.done(result) } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() override suspend fun quickFix(coroutineScope: CoroutineScope) { // Do not bother about asking the permission inline, just lead the user to the settings 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 6b8f69e8db..32f99edd13 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 @@ -54,5 +54,5 @@ class CurrentPushProviderTest @Inject constructor( } } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() } 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 b2e7ef223c..d2bf0b160d 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 @@ -90,5 +90,5 @@ class NotificationTest @Inject constructor( notificationDisplayer.dismissDiagnosticNotification() } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() } 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 86d7b437a7..b4323b612f 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 @@ -98,5 +98,5 @@ class PushLoopbackTest @Inject constructor( } } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() } 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 7beca906b9..bbcee50a54 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 @@ -55,5 +55,5 @@ class PushProvidersTest @Inject constructor( } } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() } 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 e0891d6081..acd907fb0a 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 @@ -61,5 +61,5 @@ class FirebaseAvailabilityTest @Inject constructor( } } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() } 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 7c8ce5dfef..e501ce1e9d 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 @@ -63,7 +63,7 @@ class FirebaseTokenTest @Inject constructor( } } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() override suspend fun quickFix(coroutineScope: CoroutineScope) { delegate.start() 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 3d612b0613..df79030c44 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 @@ -62,7 +62,7 @@ class UnifiedPushTest @Inject constructor( } } - override fun reset() = delegate.reset() + override suspend fun reset() = delegate.reset() override suspend fun quickFix(coroutineScope: CoroutineScope) { openDistributorWebPageAction.execute() From 201944890e724e62469888931ee6cd0b7b3a224f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 15:08:42 +0100 Subject: [PATCH 049/124] Update analytics event lib. --- gradle/libs.versions.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2be4f8dbcb..cb7b6ac50c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -177,7 +177,9 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0" # Analytics posthog = "com.posthog:posthog-android:3.1.16" sentry = "io.sentry:sentry-android:7.6.0" -matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.14.0" +# Note: only 0.19.0 will compile properly +# main branch can be tested replacing the version with main-SNAPSHOT +matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.15.0" # Emojibase matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3" From ef48c2b5a31f022897db308a204047d823cff51b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 16:11:24 +0100 Subject: [PATCH 050/124] Track NotificationTroubleshoot screen --- .../TroubleshootNotificationsNode.kt | 4 ++ services/analytics/api/build.gradle.kts | 4 +- .../services/analytics/api/ScreenTracker.kt | 29 ++++++++++ .../analytics/impl/DefaultScreenTracker.kt | 58 +++++++++++++++++++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt create mode 100644 services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt index 1ac7c0c079..833f512c81 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt @@ -23,17 +23,21 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.SessionScope +import io.element.android.services.analytics.api.ScreenTracker @ContributesNode(SessionScope::class) class TroubleshootNotificationsNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val presenter: TroubleshootNotificationsPresenter, + private val screenTracker: ScreenTracker, ) : Node(buildContext, plugins = plugins) { @Composable override fun View(modifier: Modifier) { + screenTracker.TrackScreen(this, MobileScreen.ScreenName.NotificationTroubleshoot) val state = presenter.present() TroubleshootNotificationsView( state = state, diff --git a/services/analytics/api/build.gradle.kts b/services/analytics/api/build.gradle.kts index 28b871a659..361ad09162 100644 --- a/services/analytics/api/build.gradle.kts +++ b/services/analytics/api/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { @@ -23,6 +23,8 @@ android { dependencies { api(projects.services.analyticsproviders.api) + api(projects.services.toolbox.api) + api(libs.appyx.core) implementation(libs.coroutines.core) implementation(projects.libraries.matrix.api) implementation(projects.libraries.core) diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt new file mode 100644 index 0000000000..b2f1e3ab6d --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.services.analytics.api + +import androidx.compose.runtime.Composable +import com.bumble.appyx.core.node.Node +import im.vector.app.features.analytics.plan.MobileScreen + +interface ScreenTracker { + @Composable + fun TrackScreen( + node: Node, + screen: MobileScreen.ScreenName, + ) +} diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt new file mode 100644 index 0000000000..970fcd0c95 --- /dev/null +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.services.analytics.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.bumble.appyx.core.lifecycle.subscribe +import com.bumble.appyx.core.node.Node +import com.squareup.anvil.annotations.ContributesBinding +import im.vector.app.features.analytics.plan.MobileScreen +import io.element.android.libraries.di.AppScope +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.ScreenTracker +import io.element.android.services.toolbox.api.systemclock.SystemClock +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultScreenTracker @Inject constructor( + private val analyticsService: AnalyticsService, + private val systemClock: SystemClock +) : ScreenTracker { + @Composable + override fun TrackScreen( + node: Node, + screen: MobileScreen.ScreenName, + ) { + LaunchedEffect(Unit) { + var startTime = 0L + node.lifecycle.subscribe( + onResume = { + startTime = systemClock.epochMillis() + }, + onPause = { + analyticsService.screen( + screen = MobileScreen( + durationMs = (systemClock.epochMillis() - startTime).toInt(), + screenName = screen + ) + ) + } + ) + } + } +} From 8da435b5148c5963a1d7db88c4652ee1b3281e42 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 16:16:06 +0100 Subject: [PATCH 051/124] Track NotificationTroubleshoot --- .../troubleshoot/TroubleshootTestSuite.kt | 13 +++++++++++++ .../TroubleshootNotificationsPresenterTests.kt | 2 ++ 2 files changed, 15 insertions(+) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt index 4996d278de..41fad1c3d5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt @@ -16,11 +16,13 @@ package io.element.android.features.preferences.impl.notifications.troubleshoot +import im.vector.app.features.analytics.plan.NotificationTroubleshoot import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.notifications.NotificationTroubleshootTest import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.core.notifications.TestFilterData import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -32,6 +34,7 @@ import javax.inject.Inject class TroubleshootTestSuite @Inject constructor( private val notificationTroubleshootTests: Set<@JvmSuppressWildcards NotificationTroubleshootTest>, private val getCurrentPushProvider: GetCurrentPushProvider, + private val analyticsService: AnalyticsService, ) { lateinit var tests: List @@ -77,6 +80,16 @@ class TroubleshootTestSuite @Inject constructor( private fun emitState() { val states = tests.map { it.state.value } + val mainState = states.computeMainState() + when (mainState) { + is AsyncAction.Success -> { + analyticsService.capture(NotificationTroubleshoot(hasError = false)) + } + is AsyncAction.Failure -> { + analyticsService.capture(NotificationTroubleshoot(hasError = true)) + } + else -> Unit + } _state.tryEmit( TroubleshootTestSuiteState( mainState = states.computeMainState(), diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt index e6e712b1b8..686aac2e56 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.notifications.NotificationTroubleshootTest import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test @@ -113,6 +114,7 @@ class TroubleshootNotificationsPresenterTests { return TroubleshootTestSuite( notificationTroubleshootTests = tests, getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider), + analyticsService = FakeAnalyticsService(), ) } From a3cb7ab26508684328f812fb0d6d23699fca6f02 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 16:19:05 +0100 Subject: [PATCH 052/124] use emit instead of tryEmit --- .../impl/notifications/troubleshoot/TroubleshootTestSuite.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt index 41fad1c3d5..361cf1601f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt @@ -78,7 +78,7 @@ class TroubleshootTestSuite @Inject constructor( } } - private fun emitState() { + private suspend fun emitState() { val states = tests.map { it.state.value } val mainState = states.computeMainState() when (mainState) { @@ -90,7 +90,7 @@ class TroubleshootTestSuite @Inject constructor( } else -> Unit } - _state.tryEmit( + _state.emit( TroubleshootTestSuiteState( mainState = states.computeMainState(), tests = states.toImmutableList() From 5e27c54deb68fee38e72298fa791c6351c5934c9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 18:16:44 +0100 Subject: [PATCH 053/124] FakeStringProvider: Ensure parameter are included in the result of getString() --- .../services/toolbox/test/strings/FakeStringProvider.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt index c9f9a84fc9..870b3acf39 100644 --- a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt @@ -26,10 +26,10 @@ class FakeStringProvider( } override fun getString(resId: Int, vararg formatArgs: Any?): String { - return defaultResult + return defaultResult + formatArgs.joinToString() } override fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any?): String { - return defaultResult + return defaultResult + " ($quantity) " + formatArgs.joinToString() } } From 33526db485414facd125145a20e80b56375dd12f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 18:25:31 +0100 Subject: [PATCH 054/124] Fix test in VersionFormatterTest (there was a swap in the test names by the way). --- .../impl/root/VersionFormatterTest.kt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt index ebab2960e9..e41895440c 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt @@ -23,25 +23,31 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class VersionFormatterTest { - @Test - fun `version formatter should return simplified version for other branch`() = runTest { - val sut = DefaultVersionFormatter( - stringProvider = FakeStringProvider(defaultResult = VERSION), - buildMeta = aBuildMeta(gitBranchName = "main") - ) - assertThat(sut.get()).isEqualTo(VERSION) - } - @Test fun `version formatter should return simplified version for main branch`() = runTest { val sut = DefaultVersionFormatter( stringProvider = FakeStringProvider(defaultResult = VERSION), buildMeta = aBuildMeta( + gitBranchName = "main", + versionName = "versionName", + versionCode = 123 + ) + ) + assertThat(sut.get()).isEqualTo("${VERSION}versionName, 123") + } + + @Test + fun `version formatter should return simplified version for other branch`() = runTest { + val sut = DefaultVersionFormatter( + stringProvider = FakeStringProvider(defaultResult = VERSION), + buildMeta = aBuildMeta( + versionName = "versionName", + versionCode = 123, gitBranchName = "branch", gitRevision = "1234567890", ) ) - assertThat(sut.get()).isEqualTo("$VERSION\nbranch (1234567890)") + assertThat(sut.get()).isEqualTo("${VERSION}versionName, 123\nbranch (1234567890)") } companion object { From 09b2cbaaf5f4c993e79adf5122d6541d661f6c6b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 18:16:47 +0100 Subject: [PATCH 055/124] Localize Troubleshoot notification feature. --- .../notifications/NotificationSettingsView.kt | 4 +-- .../TroubleshootNotificationsView.kt | 22 +++++++++------ .../impl/src/main/res/values/localazy.xml | 10 +++++++ ...ficationTroubleshootCheckPermissionTest.kt | 7 +++-- .../impl/src/main/res/values/localazy.xml | 5 ++++ ...tionTroubleshootCheckPermissionTestTest.kt | 10 +++++-- .../troubleshoot/CurrentPushProviderTest.kt | 11 +++++--- .../impl/troubleshoot/NotificationTest.kt | 17 ++++++----- .../impl/troubleshoot/PushLoopbackTest.kt | 17 ++++++----- .../impl/troubleshoot/PushProvidersTest.kt | 16 ++++++++--- .../impl/src/main/res/values/localazy.xml | 24 ++++++++++++++++ .../CurrentPushProviderTestTest.kt | 7 +++-- .../impl/troubleshoot/NotificationTestTest.kt | 4 ++- .../impl/troubleshoot/PushLoopbackTestTest.kt | 19 +++++++------ .../troubleshoot/PushProvidersTestTest.kt | 4 +++ .../pushproviders/firebase/build.gradle.kts | 3 ++ .../troubleshoot/FirebaseAvailabilityTest.kt | 11 +++++--- .../troubleshoot/FirebaseTokenTest.kt | 14 +++++++--- .../firebase/src/main/res/values/localazy.xml | 11 ++++++++ .../FirebaseAvailabilityTestTest.kt | 7 +++-- .../troubleshoot/FirebaseTokenTestTest.kt | 3 ++ .../unifiedpush/build.gradle.kts | 2 ++ .../troubleshoot/UnifiedPushTest.kt | 16 ++++++++--- .../src/main/res/values/localazy.xml | 10 +++++++ .../troubleshoot/UnifiedPushTestTest.kt | 3 ++ tools/localazy/config.json | 28 +++++++++++++++++-- 26 files changed, 221 insertions(+), 64 deletions(-) create mode 100644 libraries/permissions/impl/src/main/res/values/localazy.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values/localazy.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values/localazy.xml diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index 8f59ffcdba..5e3610b652 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -166,10 +166,10 @@ private fun NotificationSettingsContentView( onCheckedChange = onInviteForMeNotificationsChanged ) } - PreferenceCategory(title = "Troubleshoot") { + PreferenceCategory(title = stringResource(id = R.string.troubleshoot_notifications_entry_point_section)) { PreferenceText( modifier = Modifier, - title = "Troubleshoot notifications", + title = stringResource(id = R.string.troubleshoot_notifications_entry_point_title), onClick = onTroubleshootNotificationsClicked ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt index cadffa1ba0..451b4f778e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt @@ -21,11 +21,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.progressSemantics import androidx.compose.runtime.Composable 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 androidx.lifecycle.Lifecycle import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.preferences.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState.Status @@ -63,7 +65,7 @@ fun TroubleshootNotificationsView( PreferencePage( modifier = modifier, onBackPressed = onBackPressed, - title = "Troubleshoot notifications", + title = stringResource(id = R.string.troubleshoot_notifications_screen_title), ) { TroubleshootNotificationsContent(state) } @@ -120,7 +122,7 @@ private fun TroubleshootTestView( }, trailingContent = ListItemContent.Custom { Button( - text = "Attempt to fix", + text = stringResource(id = R.string.troubleshoot_notifications_screen_quick_fix_action), onClick = onQuickFixClicked ) } @@ -148,8 +150,7 @@ private fun TroubleshootNotificationsContent(state: TroubleshootNotificationsSta AsyncAction.Uninitialized -> { ListItem(headlineContent = { Text( - text = "Run the tests to detect any issue in your configuration " + - "that may make notifications not behave as expected." + text = stringResource(id = R.string.troubleshoot_notifications_screen_notice) ) }) RunTestButton(state = state) @@ -157,21 +158,21 @@ private fun TroubleshootNotificationsContent(state: TroubleshootNotificationsSta AsyncAction.Loading -> Unit is AsyncAction.Failure -> { ListItem(headlineContent = { - Text(text = "Some tests failed, please check the details.") + Text(text = stringResource(id = R.string.troubleshoot_notifications_screen_failure)) }) RunTestButton(state = state) } AsyncAction.Confirming -> { ListItem(headlineContent = { Text( - text = "Some tests require your attention. Please check the details." + text = stringResource(id = R.string.troubleshoot_notifications_screen_waiting) ) }) } is AsyncAction.Success -> { ListItem(headlineContent = { Text( - text = "All tests passed successfully." + text = stringResource(id = R.string.troubleshoot_notifications_screen_success) ) }) } @@ -183,7 +184,12 @@ private fun RunTestButton(state: TroubleshootNotificationsState) { ListItem( headlineContent = { Button( - text = if (state.testSuiteState.mainState is AsyncAction.Failure) "Run tests again" else "Run tests", + text = stringResource( + id = if (state.testSuiteState.mainState is AsyncAction.Failure) + R.string.troubleshoot_notifications_screen_action_again + else + R.string.troubleshoot_notifications_screen_action + ), onClick = { state.eventSink(TroubleshootNotificationsEvents.StartTests) }, diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index 492c75295a..b0e78e0d9e 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -49,4 +49,14 @@ If you proceed, some of your settings may change."
    "system settings" "System notifications turned off" "Notifications" + "Troubleshoot" + "Troubleshoot notifications" + "Run tests" + "Run tests again" + "Some tests failed. Please check the details." + "Run the tests to detect any issue in your configuration that may make notifications not behave as expected." + "Attempt to fix" + "All tests passed successfully." + "Troubleshoot notifications" + "Some tests require your attention. Please check the details." 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 f158ccbabd..903b5cd2c4 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 @@ -24,8 +24,10 @@ import io.element.android.libraries.core.notifications.NotificationTroubleshootT import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope 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 import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider +import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -35,12 +37,13 @@ class NotificationTroubleshootCheckPermissionTest @Inject constructor( private val permissionStateProvider: PermissionStateProvider, private val sdkVersionProvider: BuildVersionSdkIntProvider, private val permissionActions: PermissionActions, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { override val order: Int = 0 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Check permissions", - defaultDescription = "Ensure that the application can show notifications.", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_check_permission_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_check_permission_description), hasQuickFix = true, fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, ) diff --git a/libraries/permissions/impl/src/main/res/values/localazy.xml b/libraries/permissions/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..a8859205dc --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values/localazy.xml @@ -0,0 +1,5 @@ + + + "Ensure that the application can show notifications." + "Check permissions" + diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt index 80c07e2ce0..c17e61f5e7 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.core.notifications.NotificationTroubleshootT import io.element.android.libraries.permissions.impl.FakePermissionStateProvider import io.element.android.libraries.permissions.impl.action.FakePermissionActions import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider +import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test @@ -33,7 +34,8 @@ class NotificationTroubleshootCheckPermissionTestTest { val sut = NotificationTroubleshootCheckPermissionTest( permissionStateProvider = FakePermissionStateProvider(), sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU - 1), - permissionActions = FakePermissionActions() + permissionActions = FakePermissionActions(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -51,7 +53,8 @@ class NotificationTroubleshootCheckPermissionTestTest { val sut = NotificationTroubleshootCheckPermissionTest( permissionStateProvider = FakePermissionStateProvider(), sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU), - permissionActions = FakePermissionActions() + permissionActions = FakePermissionActions(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -77,7 +80,8 @@ class NotificationTroubleshootCheckPermissionTestTest { val sut = NotificationTroubleshootCheckPermissionTest( permissionStateProvider = permissionStateProvider, sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU), - permissionActions = actions + permissionActions = actions, + stringProvider = FakeStringProvider(), ) launch { sut.run(this) 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 32f99edd13..7a819b3d93 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 @@ -22,6 +22,8 @@ import io.element.android.libraries.core.notifications.NotificationTroubleshootT import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.push.impl.R +import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -29,11 +31,12 @@ import javax.inject.Inject @ContributesMultibinding(AppScope::class) class CurrentPushProviderTest @Inject constructor( private val getCurrentPushProvider: GetCurrentPushProvider, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { override val order = 110 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Current push provider", - defaultDescription = "Get the name of the current provider.", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_description), fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, ) override val state: StateFlow = delegate.state @@ -43,12 +46,12 @@ class CurrentPushProviderTest @Inject constructor( val provider = getCurrentPushProvider.getCurrentPushProvider() if (provider != null) { delegate.updateState( - description = "Current push provider: $provider", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_success, provider), status = NotificationTroubleshootTestState.Status.Success ) } else { delegate.updateState( - description = "No push providers selected", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_failure), status = NotificationTroubleshootTestState.Status.Failure(false) ) } 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 d2bf0b160d..d7e3641e22 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 @@ -21,8 +21,10 @@ import io.element.android.libraries.core.notifications.NotificationTroubleshootT import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationDisplayer import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator +import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first @@ -36,12 +38,13 @@ import kotlin.time.Duration.Companion.seconds class NotificationTest @Inject constructor( private val notificationCreator: NotificationCreator, private val notificationDisplayer: NotificationDisplayer, - private val notificationClickHandler: NotificationClickHandler + private val notificationClickHandler: NotificationClickHandler, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { override val order = 50 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Display notification", - defaultDescription = "Check that the application can display notification", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_description), fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, ) override val state: StateFlow = delegate.state @@ -53,12 +56,12 @@ class NotificationTest @Inject constructor( if (result) { coroutineScope.listenToNotificationClick() delegate.updateState( - description = "Please click on the notification to continue the test.", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_waiting), status = NotificationTroubleshootTestState.Status.WaitingForUser ) } else { delegate.updateState( - description = "Cannot display the notification.", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_permission_failure), status = NotificationTroubleshootTestState.Status.Failure(false) ) } @@ -76,12 +79,12 @@ class NotificationTest @Inject constructor( if (s == null) { notificationDisplayer.dismissDiagnosticNotification() delegate.updateState( - description = "The notification has not been clicked.", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_failure), status = NotificationTroubleshootTestState.Status.Failure(false) ) } else { delegate.updateState( - description = "The notification has been clicked!", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_success), status = NotificationTroubleshootTestState.Status.Success ) } 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 b4323b612f..2ecec61996 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 @@ -23,6 +23,8 @@ import io.element.android.libraries.core.notifications.NotificationTroubleshootT import io.element.android.libraries.di.AppScope import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.api.gateway.PushGatewayFailure +import io.element.android.libraries.push.impl.R +import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope @@ -39,11 +41,12 @@ class PushLoopbackTest @Inject constructor( private val pushService: PushService, private val diagnosticPushHandler: DiagnosticPushHandler, private val clock: SystemClock, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { override val order = 500 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Test Push loopback", - defaultDescription = "Ensure that the application is receiving push.", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_description), ) override val state: StateFlow = delegate.state @@ -59,7 +62,7 @@ class PushLoopbackTest @Inject constructor( pushService.testPush() } catch (pusherRejected: PushGatewayFailure.PusherRejected) { delegate.updateState( - description = "Error: pusher has rejected the request.", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_1), status = NotificationTroubleshootTestState.Status.Failure(false) ) job.cancel() @@ -67,7 +70,7 @@ class PushLoopbackTest @Inject constructor( } catch (e: Exception) { Timber.e(e, "Failed to test push") delegate.updateState( - description = "Error: ${e.message}.", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_2, e.message), status = NotificationTroubleshootTestState.Status.Failure(false) ) job.cancel() @@ -75,7 +78,7 @@ class PushLoopbackTest @Inject constructor( } if (!testPushResult) { delegate.updateState( - description = "Error, cannot test push.", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_3), status = NotificationTroubleshootTestState.Status.Failure(false) ) job.cancel() @@ -87,12 +90,12 @@ class PushLoopbackTest @Inject constructor( job.cancel() if (result == null) { delegate.updateState( - description = "Error, timeout waiting for push.", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_4), status = NotificationTroubleshootTestState.Status.Failure(false) ) } else { delegate.updateState( - description = "Push loopback took $result ms", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_success, result), status = NotificationTroubleshootTestState.Status.Success ) } 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 bbcee50a54..df6d220396 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 @@ -21,7 +21,9 @@ import io.element.android.libraries.core.notifications.NotificationTroubleshootT import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.impl.R import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -29,12 +31,13 @@ import javax.inject.Inject @ContributesMultibinding(AppScope::class) class PushProvidersTest @Inject constructor( pushProviders: Set<@JvmSuppressWildcards PushProvider>, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { private val sortedPushProvider = pushProviders.sortedBy { it.index } override val order = 100 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Detect push providers", - defaultDescription = "Ensure that the application has at least one push provider.", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_detect_push_provider_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_detect_push_provider_description), fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, ) override val state: StateFlow = delegate.state @@ -44,12 +47,17 @@ class PushProvidersTest @Inject constructor( val result = sortedPushProvider.isNotEmpty() if (result) { delegate.updateState( - description = "Found ${sortedPushProvider.size} push providers: ${sortedPushProvider.joinToString { it.name }}", + description = stringProvider.getQuantityString( + resId = R.plurals.troubleshoot_notifications_test_detect_push_provider_success, + quantity = sortedPushProvider.size, + sortedPushProvider.size, + sortedPushProvider.joinToString { it.name } + ), status = NotificationTroubleshootTestState.Status.Success ) } else { delegate.updateState( - description = "No push providers found", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_detect_push_provider_failure), status = NotificationTroubleshootTestState.Status.Failure(false) ) } diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index ed49f5edd8..c9d7627d07 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -50,4 +50,28 @@ "Background synchronization" "Google Services" "No valid Google Play Services found. Notifications may not work properly." + "Get the name of the current provider." + "No push providers selected." + "Current push provider: %1$s." + "Current push provider" + "Ensure that the application has at least one push provider." + "No push providers found." + + "Found %1$d push provider: %2$s" + "Found %1$d push providers: %2$s" + + "Detect push providers" + "Check that the application can display notification." + "The notification has not been clicked." + "Cannot display the notification." + "The notification has been clicked!" + "Display notification" + "Please click on the notification to continue the test." + "Ensure that the application is receiving push." + "Error: pusher has rejected the request." + "Error: %1$s." + "Error, cannot test push." + "Error, timeout waiting for push." + "Push loop back took %1$d ms." + "Test Push loop back" diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt index 8506d826b5..ea8de71222 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt @@ -20,6 +20,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test @@ -28,7 +29,8 @@ class CurrentPushProviderTestTest { @Test fun `test CurrentPushProviderTest with a push provider`() = runTest { val sut = CurrentPushProviderTest( - getCurrentPushProvider = FakeGetCurrentPushProvider("foo") + getCurrentPushProvider = FakeGetCurrentPushProvider("foo"), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -45,7 +47,8 @@ class CurrentPushProviderTestTest { @Test fun `test CurrentPushProviderTest without push provider`() = runTest { val sut = CurrentPushProviderTest( - getCurrentPushProvider = FakeGetCurrentPushProvider(null) + getCurrentPushProvider = FakeGetCurrentPushProvider(null), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt index 0a4ac2d066..a900d50b1f 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt @@ -21,6 +21,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationDisplayer +import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test @@ -82,7 +83,8 @@ class NotificationTestTest { return NotificationTest( notificationCreator = mockkNotificationCreator.instance, notificationDisplayer = mockkNotificationDisplayer.instance, - notificationClickHandler = notificationClickHandler + notificationClickHandler = notificationClickHandler, + stringProvider = FakeStringProvider(), ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt index 354ec60bb9..01fe2b9847 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.libraries.push.api.gateway.PushGatewayFailure import io.element.android.libraries.push.test.FakePushService +import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest @@ -35,7 +36,8 @@ class PushLoopbackTestTest { val sut = PushLoopbackTest( pushService = FakePushService(), diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock() + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -45,7 +47,6 @@ class PushLoopbackTestTest { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) val lastItem = awaitItem() assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) - assertThat(lastItem.description).contains("timeout") } } @@ -59,7 +60,8 @@ class PushLoopbackTestTest { } ), diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock() + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -69,7 +71,6 @@ class PushLoopbackTestTest { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) val lastItem = awaitItem() assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) - assertThat(lastItem.description).contains("rejected") } } @@ -81,7 +82,8 @@ class PushLoopbackTestTest { testPushBlock = { false } ), diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock() + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -91,7 +93,6 @@ class PushLoopbackTestTest { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) val lastItem = awaitItem() assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) - assertThat(lastItem.description).contains("cannot test push") } } @@ -105,7 +106,8 @@ class PushLoopbackTestTest { } ), diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock() + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -128,7 +130,8 @@ class PushLoopbackTestTest { true }), diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock() + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt index 644392c11b..cf7f750403 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt @@ -20,6 +20,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.test.FakePushProvider +import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test @@ -29,6 +30,7 @@ class PushProvidersTestTest { fun `test PushProvidersTest with empty list`() = runTest { val sut = PushProvidersTest( pushProviders = emptySet(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -48,6 +50,7 @@ class PushProvidersTestTest { FakePushProvider(name = "foo"), FakePushProvider(name = "bar"), ), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -57,6 +60,7 @@ class PushProvidersTestTest { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) val lastItem = awaitItem() assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + assertThat(lastItem.description).contains("2") assertThat(lastItem.description).contains("foo") assertThat(lastItem.description).contains("bar") } diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index 55dca139b0..de19420923 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -40,6 +40,8 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.uiStrings) + implementation(projects.services.toolbox.api) implementation(projects.libraries.pushstore.api) implementation(projects.libraries.pushproviders.api) @@ -57,4 +59,5 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) + testImplementation(projects.services.toolbox.test) } 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 acd907fb0a..64a5bad67f 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 @@ -24,6 +24,8 @@ import io.element.android.libraries.core.notifications.TestFilterData import io.element.android.libraries.di.AppScope import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable +import io.element.android.libraries.pushproviders.firebase.R +import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -31,11 +33,12 @@ import javax.inject.Inject @ContributesMultibinding(AppScope::class) class FirebaseAvailabilityTest @Inject constructor( private val isPlayServiceAvailable: IsPlayServiceAvailable, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { override val order = 300 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Check Firebase", - defaultDescription = "Ensure that Firebase is available.", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_description), visibleWhenIdle = false, fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY, ) @@ -50,12 +53,12 @@ class FirebaseAvailabilityTest @Inject constructor( val result = isPlayServiceAvailable.isAvailable() if (result) { delegate.updateState( - description = "Firebase is available", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_success), status = NotificationTroubleshootTestState.Status.Success ) } else { delegate.updateState( - description = "Firebase is not available", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_failure), status = NotificationTroubleshootTestState.Status.Failure(false) ) } 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 e501ce1e9d..b2164cda32 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 @@ -25,6 +25,8 @@ import io.element.android.libraries.di.AppScope import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.FirebaseStore import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter +import io.element.android.libraries.pushproviders.firebase.R +import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -33,11 +35,12 @@ import javax.inject.Inject class FirebaseTokenTest @Inject constructor( private val firebaseStore: FirebaseStore, private val firebaseTroubleshooter: FirebaseTroubleshooter, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { override val order = 310 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Check Firebase token", - defaultDescription = "Ensure that Firebase token is available.", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_description), visibleWhenIdle = false, fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY, ) @@ -52,12 +55,15 @@ class FirebaseTokenTest @Inject constructor( val token = firebaseStore.getFcmToken() if (token != null) { delegate.updateState( - description = "Firebase token: ${token.take(8)}*****", + description = stringProvider.getString( + R.string.troubleshoot_notifications_test_firebase_token_success, + "${token.take(8)}*****" + ), status = NotificationTroubleshootTestState.Status.Success ) } else { delegate.updateState( - description = "Firebase token is not known", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_failure), status = NotificationTroubleshootTestState.Status.Failure(true) ) } diff --git a/libraries/pushproviders/firebase/src/main/res/values/localazy.xml b/libraries/pushproviders/firebase/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..654ba04134 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values/localazy.xml @@ -0,0 +1,11 @@ + + + "Ensure that Firebase is available." + "Firebase is not available." + "Firebase is available." + "Check Firebase" + "Ensure that Firebase token is available." + "Firebase token is not known." + "Firebase token: %1$s." + "Check Firebase token" + diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt index eea622849b..0c742fa567 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt @@ -20,6 +20,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable +import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test @@ -32,7 +33,8 @@ class FirebaseAvailabilityTestTest { override fun isAvailable(): Boolean { return true } - } + }, + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -52,7 +54,8 @@ class FirebaseAvailabilityTestTest { override fun isAvailable(): Boolean { return false } - } + }, + stringProvider = FakeStringProvider(), ) launch { sut.run(this) diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt index 0463939234..b76e6861fc 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt @@ -21,6 +21,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore +import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test @@ -31,6 +32,7 @@ class FirebaseTokenTestTest { val sut = FirebaseTokenTest( firebaseStore = InMemoryFirebaseStore(FAKE_TOKEN), firebaseTroubleshooter = FakeFirebaseTroubleshooter(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -56,6 +58,7 @@ class FirebaseTokenTestTest { Result.success(Unit) } ), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index ecceed6026..190c63d350 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.uiStrings) implementation(projects.libraries.pushstore.api) implementation(projects.libraries.pushproviders.api) @@ -57,4 +58,5 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) + testImplementation(projects.services.toolbox.test) } 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 df79030c44..ec3b95e437 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 @@ -22,8 +22,10 @@ import io.element.android.libraries.core.notifications.NotificationTroubleshootT import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.core.notifications.TestFilterData import io.element.android.libraries.di.AppScope +import io.element.android.libraries.pushproviders.unifiedpush.R import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider +import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject @@ -32,11 +34,12 @@ import javax.inject.Inject class UnifiedPushTest @Inject constructor( private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider, private val openDistributorWebPageAction: OpenDistributorWebPageAction, + private val stringProvider: StringProvider, ) : NotificationTroubleshootTest { override val order = 400 private val delegate = NotificationTroubleshootTestDelegate( - defaultName = "Check UnifiedPush", - defaultDescription = "Ensure that UnifiedPush distributors are available.", + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_unified_push_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_unified_push_description), visibleWhenIdle = false, fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, ) @@ -51,12 +54,17 @@ class UnifiedPushTest @Inject constructor( val distributors = unifiedPushDistributorProvider.getDistributors() if (distributors.isNotEmpty()) { delegate.updateState( - description = "Distributors found: ${distributors.joinToString { it.name }}", + description = stringProvider.getQuantityString( + resId = R.plurals.troubleshoot_notifications_test_unified_push_success, + quantity = distributors.size, + distributors.size, + distributors.joinToString { it.name } + ), status = NotificationTroubleshootTestState.Status.Success ) } else { delegate.updateState( - description = "No push distributors found", + description = stringProvider.getString(R.string.troubleshoot_notifications_test_unified_push_failure), status = NotificationTroubleshootTestState.Status.Failure(true) ) } diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values/localazy.xml b/libraries/pushproviders/unifiedpush/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..0e16af1f3e --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values/localazy.xml @@ -0,0 +1,10 @@ + + + "Ensure that UnifiedPush distributors are available." + "No push distributors found." + + "%1$d distributor found: %2$s." + "%1$d distributors found: %2$s." + + "Check UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt index e7a8ccc680..29301515cd 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt @@ -20,6 +20,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Test @@ -34,6 +35,7 @@ class UnifiedPushTestTest { ) ), openDistributorWebPageAction = FakeOpenDistributorWebPageAction(), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) @@ -60,6 +62,7 @@ class UnifiedPushTestTest { ) } ), + stringProvider = FakeStringProvider(), ) launch { sut.run(this) diff --git a/tools/localazy/config.json b/tools/localazy/config.json index aaa1f6734b..603c04b55c 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -80,7 +80,29 @@ "name" : ":libraries:push:impl", "includeRegex" : [ "push_.*", - "notification_.*" + "notification_.*", + "troubleshoot_notifications_test_current_push_provider.*", + "troubleshoot_notifications_test_detect_push_provider.*", + "troubleshoot_notifications_test_display_notification_.*", + "troubleshoot_notifications_test_push_loop_back_.*" + ] + }, + { + "name" : ":libraries:permissions:impl", + "includeRegex" : [ + "troubleshoot_notifications_test_check_permission_.*" + ] + }, + { + "name" : ":libraries:pushproviders:firebase", + "includeRegex" : [ + "troubleshoot_notifications_test_firebase_.*" + ] + }, + { + "name" : ":libraries:pushproviders:unifiedpush", + "includeRegex" : [ + "troubleshoot_notifications_test_unified_push_.*" ] }, { @@ -182,7 +204,9 @@ "screen\\.advanced_settings\\..*", "screen_edit_profile_.*", "screen_notification_settings_.*", - "screen_blocked_users_.*" + "screen_blocked_users_.*", + "troubleshoot_notifications_entry_point_.*", + "troubleshoot_notifications_screen_.*" ] }, { From 91de03e4b97a7559d1e30fee1a8951e1582bb0a7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 21:30:07 +0100 Subject: [PATCH 056/124] Add test on NotificationSettingsView --- .../NotificationSettingsStateProvider.kt | 15 +- .../NotificationSettingsViewTest.kt | 207 ++++++++++++++++++ 2 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index 2dc0c02145..008f53d30b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -31,18 +31,23 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, + atRoomNotificationsEnabled: Boolean = true, + callNotificationsEnabled: Boolean = true, + inviteForMeNotificationsEnabled: Boolean = true, + appNotificationEnabled: Boolean = true, + eventSink: (NotificationSettingsEvents) -> Unit = {}, ) = NotificationSettingsState( matrixSettings = NotificationSettingsState.MatrixSettings.Valid( - atRoomNotificationsEnabled = true, - callNotificationsEnabled = true, - inviteForMeNotificationsEnabled = true, + atRoomNotificationsEnabled = atRoomNotificationsEnabled, + callNotificationsEnabled = callNotificationsEnabled, + inviteForMeNotificationsEnabled = inviteForMeNotificationsEnabled, defaultGroupNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES, ), appSettings = NotificationSettingsState.AppSettings( systemNotificationsEnabled = false, - appNotificationsEnabled = true, + appNotificationsEnabled = appNotificationEnabled, ), changeNotificationSettingAction = changeNotificationSettingAction, - eventSink = {} + eventSink = eventSink, ) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt new file mode 100644 index 0000000000..980a0aa962 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.preferences.impl.notifications + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.preferences.impl.R +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +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.pressBack +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 NotificationSettingsViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on back invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnce { + rule.setNotificationSettingsView( + state = aNotificationSettingsState( + eventSink = eventsRecorder + ), + onBackPressed = it + ) + rule.pressBack() + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on troubleshoot notification invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnce { + rule.setNotificationSettingsView( + state = aNotificationSettingsState( + eventSink = eventsRecorder + ), + onTroubleshootNotificationsClicked = it + ) + rule.clickOn(R.string.troubleshoot_notifications_entry_point_title) + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on group chats invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnceWithParam(false) { + rule.setNotificationSettingsView( + state = aNotificationSettingsState( + eventSink = eventsRecorder + ), + onOpenEditDefault = it + ) + rule.clickOn(R.string.screen_notification_settings_group_chats) + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on direct chats invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnceWithParam(true) { + rule.setNotificationSettingsView( + state = aNotificationSettingsState( + eventSink = eventsRecorder + ), + onOpenEditDefault = it + ) + rule.clickOn(R.string.screen_notification_settings_direct_chats) + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on disable notifications emits the expected events`() { + testNotificationToggle(true) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on enable notifications emits the expected events`() { + testNotificationToggle(false) + } + + private fun testNotificationToggle(initialState: Boolean) { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aNotificationSettingsState( + appNotificationEnabled = initialState, + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_notification_settings_enable_notifications) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.SetNotificationsEnabled(!initialState) + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on disable notify me on at room emits the expected events`() { + testAtRoomToggle(true) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on enable notify me on at room emits the expected events`() { + testAtRoomToggle(false) + } + + private fun testAtRoomToggle(initialState: Boolean) { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aNotificationSettingsState( + atRoomNotificationsEnabled = initialState, + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_notification_settings_room_mention_label) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.SetAtRoomNotificationsEnabled(!initialState) + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on disable notify me on invitation emits the expected events`() { + testInvitationToggle(true) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on enable notify me on invitation emits the expected events`() { + testInvitationToggle(false) + } + + private fun testInvitationToggle(initialState: Boolean) { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aNotificationSettingsState( + inviteForMeNotificationsEnabled = initialState, + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_notification_settings_invite_for_me_label) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(!initialState) + ) + ) + } +} + +private fun AndroidComposeTestRule.setNotificationSettingsView( + state: NotificationSettingsState, + onOpenEditDefault: (isOneToOne: Boolean) -> Unit = EnsureNeverCalledWithParam(), + onTroubleshootNotificationsClicked: () -> Unit = EnsureNeverCalled(), + onBackPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + NotificationSettingsView( + state = state, + onOpenEditDefault = onOpenEditDefault, + onTroubleshootNotificationsClicked = onTroubleshootNotificationsClicked, + onBackPressed = onBackPressed, + ) + } +} From 9f60a28b8c89d349078884b66cac25e3d8ea73ec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 21:52:15 +0100 Subject: [PATCH 057/124] Improve preview management for NotificationSettingsView --- .../NotificationSettingsStateProvider.kt | 25 ++++++++++++++++--- .../notifications/NotificationSettingsView.kt | 10 -------- .../NotificationSettingsViewTest.kt | 14 +++++------ 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index 008f53d30b..dc1e972aa6 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -23,13 +23,15 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode open class NotificationSettingsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aNotificationSettingsState(), - aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading), - aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), + aValidNotificationSettingsState(), + aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading), + aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), + aInvalidNotificationSettingsState(), + aInvalidNotificationSettingsState(fixFailed = true), ) } -fun aNotificationSettingsState( +fun aValidNotificationSettingsState( changeNotificationSettingAction: AsyncAction = AsyncAction.Uninitialized, atRoomNotificationsEnabled: Boolean = true, callNotificationsEnabled: Boolean = true, @@ -51,3 +53,18 @@ fun aNotificationSettingsState( changeNotificationSettingAction = changeNotificationSettingAction, eventSink = eventSink, ) + +fun aInvalidNotificationSettingsState( + fixFailed: Boolean = false, + eventSink: (NotificationSettingsEvents) -> Unit = {}, +) = NotificationSettingsState( + matrixSettings = NotificationSettingsState.MatrixSettings.Invalid( + fixFailed = fixFailed, + ), + appSettings = NotificationSettingsState.AppSettings( + systemNotificationsEnabled = false, + appNotificationsEnabled = true, + ), + changeNotificationSettingAction = AsyncAction.Uninitialized, + eventSink = eventSink, +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index 5e3610b652..d62f972d71 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -217,13 +217,3 @@ internal fun NotificationSettingsViewPreview(@PreviewParameter(NotificationSetti onTroubleshootNotificationsClicked = {}, ) } - -@PreviewsDayNight -@Composable -internal fun InvalidNotificationSettingsViewPreview() = ElementPreview { - InvalidNotificationSettingsView( - showError = false, - onContinueClicked = {}, - onDismissError = {}, - ) -} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt index 980a0aa962..754885fba8 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt @@ -44,7 +44,7 @@ class NotificationSettingsViewTest { val eventsRecorder = EventsRecorder() ensureCalledOnce { rule.setNotificationSettingsView( - state = aNotificationSettingsState( + state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onBackPressed = it @@ -60,7 +60,7 @@ class NotificationSettingsViewTest { val eventsRecorder = EventsRecorder() ensureCalledOnce { rule.setNotificationSettingsView( - state = aNotificationSettingsState( + state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onTroubleshootNotificationsClicked = it @@ -76,7 +76,7 @@ class NotificationSettingsViewTest { val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(false) { rule.setNotificationSettingsView( - state = aNotificationSettingsState( + state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onOpenEditDefault = it @@ -92,7 +92,7 @@ class NotificationSettingsViewTest { val eventsRecorder = EventsRecorder() ensureCalledOnceWithParam(true) { rule.setNotificationSettingsView( - state = aNotificationSettingsState( + state = aValidNotificationSettingsState( eventSink = eventsRecorder ), onOpenEditDefault = it @@ -117,7 +117,7 @@ class NotificationSettingsViewTest { private fun testNotificationToggle(initialState: Boolean) { val eventsRecorder = EventsRecorder() rule.setNotificationSettingsView( - state = aNotificationSettingsState( + state = aValidNotificationSettingsState( appNotificationEnabled = initialState, eventSink = eventsRecorder ), @@ -146,7 +146,7 @@ class NotificationSettingsViewTest { private fun testAtRoomToggle(initialState: Boolean) { val eventsRecorder = EventsRecorder() rule.setNotificationSettingsView( - state = aNotificationSettingsState( + state = aValidNotificationSettingsState( atRoomNotificationsEnabled = initialState, eventSink = eventsRecorder ), @@ -175,7 +175,7 @@ class NotificationSettingsViewTest { private fun testInvitationToggle(initialState: Boolean) { val eventsRecorder = EventsRecorder() rule.setNotificationSettingsView( - state = aNotificationSettingsState( + state = aValidNotificationSettingsState( inviteForMeNotificationsEnabled = initialState, eventSink = eventsRecorder ), From 7b16b10a64f77c1cbbb4fefee749570d637f588b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 21:56:36 +0100 Subject: [PATCH 058/124] More tests on NotificationSettingsView. --- .../NotificationSettingsViewTest.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt index 754885fba8..8397d5aef6 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt @@ -21,6 +21,9 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.preferences.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder @@ -188,6 +191,63 @@ class NotificationSettingsViewTest { ) ) } + + @Config(qualifiers = "h1024dp") + @Test + fun `with an error configuration, clicking on continue emits the expected events`() { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + changeNotificationSettingAction = AsyncAction.Failure(AN_EXCEPTION), + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.ClearNotificationChangeError + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `with invalid configuration, clicking on continue emits the expected events`() { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aInvalidNotificationSettingsState( + fixFailed = false, + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_continue) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.FixConfigurationMismatch + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `with invalid configuration and error, clicking on OK emits the expected events`() { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aInvalidNotificationSettingsState( + fixFailed = true, + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.ClearConfigurationMismatchError + ) + ) + } } private fun AndroidComposeTestRule.setNotificationSettingsView( From 9c89a3b0c7e5ec3d94aa0a4788a6ed25405b6682 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 22:02:22 +0100 Subject: [PATCH 059/124] Code quality --- .../troubleshoot/TroubleshootNotificationsView.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt index 451b4f778e..a908f59350 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt @@ -185,10 +185,11 @@ private fun RunTestButton(state: TroubleshootNotificationsState) { headlineContent = { Button( text = stringResource( - id = if (state.testSuiteState.mainState is AsyncAction.Failure) + id = if (state.testSuiteState.mainState is AsyncAction.Failure) { R.string.troubleshoot_notifications_screen_action_again - else + } else { R.string.troubleshoot_notifications_screen_action + } ), onClick = { state.eventSink(TroubleshootNotificationsEvents.StartTests) From f7f25e1ead7f620e950f415dd67f477d68832ea3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Mar 2024 22:45:49 +0100 Subject: [PATCH 060/124] towncrier --- changelog.d/2601.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/2601.feature diff --git a/changelog.d/2601.feature b/changelog.d/2601.feature new file mode 100644 index 0000000000..ecfc26061d --- /dev/null +++ b/changelog.d/2601.feature @@ -0,0 +1 @@ + Add a notification troubleshoot screen From 5a0e76677bab8783c4f9f8513ce597ea9acd2848 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 Mar 2024 09:30:07 +0100 Subject: [PATCH 061/124] Improve ScreenTracker. --- .../TroubleshootNotificationsNode.kt | 2 +- services/analytics/api/build.gradle.kts | 1 - .../services/analytics/api/ScreenTracker.kt | 2 -- services/analytics/impl/build.gradle.kts | 1 + .../analytics/impl/DefaultScreenTracker.kt | 35 ++++++++++--------- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt index 833f512c81..a662e94db8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt @@ -37,7 +37,7 @@ class TroubleshootNotificationsNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { @Composable override fun View(modifier: Modifier) { - screenTracker.TrackScreen(this, MobileScreen.ScreenName.NotificationTroubleshoot) + screenTracker.TrackScreen(MobileScreen.ScreenName.NotificationTroubleshoot) val state = presenter.present() TroubleshootNotificationsView( state = state, diff --git a/services/analytics/api/build.gradle.kts b/services/analytics/api/build.gradle.kts index 361ad09162..c78a050571 100644 --- a/services/analytics/api/build.gradle.kts +++ b/services/analytics/api/build.gradle.kts @@ -24,7 +24,6 @@ android { dependencies { api(projects.services.analyticsproviders.api) api(projects.services.toolbox.api) - api(libs.appyx.core) implementation(libs.coroutines.core) implementation(projects.libraries.matrix.api) implementation(projects.libraries.core) diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt index b2f1e3ab6d..c29a045f77 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt @@ -17,13 +17,11 @@ package io.element.android.services.analytics.api import androidx.compose.runtime.Composable -import com.bumble.appyx.core.node.Node import im.vector.app.features.analytics.plan.MobileScreen interface ScreenTracker { @Composable fun TrackScreen( - node: Node, screen: MobileScreen.ScreenName, ) } diff --git a/services/analytics/impl/build.gradle.kts b/services/analytics/impl/build.gradle.kts index 5dd72d77bd..623ab493c3 100644 --- a/services/analytics/impl/build.gradle.kts +++ b/services/analytics/impl/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) implementation(projects.libraries.sessionStorage.api) api(projects.services.analyticsproviders.api) diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt index 970fcd0c95..c53757384d 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt @@ -17,11 +17,14 @@ package io.element.android.services.analytics.impl import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import com.bumble.appyx.core.lifecycle.subscribe -import com.bumble.appyx.core.node.Node +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.lifecycle.Lifecycle import com.squareup.anvil.annotations.ContributesBinding import im.vector.app.features.analytics.plan.MobileScreen +import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.di.AppScope import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.ScreenTracker @@ -35,24 +38,22 @@ class DefaultScreenTracker @Inject constructor( ) : ScreenTracker { @Composable override fun TrackScreen( - node: Node, screen: MobileScreen.ScreenName, ) { - LaunchedEffect(Unit) { - var startTime = 0L - node.lifecycle.subscribe( - onResume = { + var startTime by remember { mutableLongStateOf(0L) } + OnLifecycleEvent { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> { startTime = systemClock.epochMillis() - }, - onPause = { - analyticsService.screen( - screen = MobileScreen( - durationMs = (systemClock.epochMillis() - startTime).toInt(), - screenName = screen - ) - ) } - ) + Lifecycle.Event.ON_PAUSE -> analyticsService.screen( + screen = MobileScreen( + durationMs = (systemClock.epochMillis() - startTime).toInt(), + screenName = screen + ) + ) + else -> Unit + } } } } From a89dde2bb46d9469a4b20f281ff461f85dd3f775 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 Mar 2024 09:30:22 +0100 Subject: [PATCH 062/124] Remove wrong comment --- .../troubleshoot/TroubleshootNotificationsView.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt index a908f59350..08d56e5bd0 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt @@ -42,9 +42,6 @@ import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.OnLifecycleEvent -/** - * A view that allows a user edit their global notification settings. - */ @Composable fun TroubleshootNotificationsView( state: TroubleshootNotificationsState, From 565e5ce87db162daff01573c90a5ce4a1f49d4fd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 Mar 2024 09:31:09 +0100 Subject: [PATCH 063/124] Improve openUrlInExternalApp API and move url to UnifiedPushConfig --- .../android/libraries/androidutils/system/SystemUtils.kt | 3 +-- .../libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt | 2 ++ .../unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt index 9f473a7089..737eab7ac7 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt @@ -156,10 +156,9 @@ fun Context.startSharePlainTextIntent( fun Context.openUrlInExternalApp( url: String, errorMessage: String = getString(R.string.error_no_compatible_app_found), - inNewTask: Boolean = false, ) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - if (inNewTask) { + if (this !is Activity) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } try { diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt index 07c57496db..b1d321f42f 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt @@ -23,6 +23,8 @@ object UnifiedPushConfig { */ const val DEFAULT_PUSH_GATEWAY_HTTP_URL: String = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify" + const val UNIFIED_PUSH_DISTRIBUTORS_URL = "https://unifiedpush.org/users/distributors/" + const val INDEX = 1 const val NAME = "UnifiedPush" } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt index daad9afda3..dda4292f12 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt @@ -21,6 +21,7 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig import javax.inject.Inject interface OpenDistributorWebPageAction { @@ -34,8 +35,7 @@ class DefaultOpenDistributorWebPageAction @Inject constructor( override fun execute() { // Open the distributor download page context.openUrlInExternalApp( - url = "https://unifiedpush.org/users/distributors/", - inNewTask = true + url = UnifiedPushConfig.UNIFIED_PUSH_DISTRIBUTORS_URL, ) } } From e18e5f1cc52212d8d3f5fbae2b8a5d0f07f33443 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 16:18:15 +0200 Subject: [PATCH 064/124] Run `./tools/localazy/downloadStrings.sh --all` --- .../src/main/res/values-be/translations.xml | 4 +- .../src/main/res/values-be/translations.xml | 4 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-sv/translations.xml | 4 ++ .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 10 ++++ .../src/main/res/values-cs/translations.xml | 10 ++++ .../src/main/res/values-de/translations.xml | 10 ++++ .../src/main/res/values-fr/translations.xml | 10 ++++ .../src/main/res/values-hu/translations.xml | 10 ++++ .../src/main/res/values-in/translations.xml | 10 ++++ .../src/main/res/values-ru/translations.xml | 10 ++++ .../src/main/res/values-sk/translations.xml | 10 ++++ .../src/main/res/values-be/translations.xml | 4 +- .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 1 + .../src/main/res/values-fr/translations.xml | 1 + .../impl/src/main/res/values/localazy.xml | 1 + .../src/main/res/values-be/translations.xml | 2 +- .../src/main/res/values-be/translations.xml | 5 ++ .../src/main/res/values-cs/translations.xml | 5 ++ .../src/main/res/values-de/translations.xml | 5 ++ .../src/main/res/values-fr/translations.xml | 5 ++ .../src/main/res/values-hu/translations.xml | 5 ++ .../src/main/res/values-ru/translations.xml | 5 ++ .../src/main/res/values-sk/translations.xml | 5 ++ .../impl/src/main/res/values/localazy.xml | 2 +- .../src/main/res/values-be/translations.xml | 25 +++++++++ .../src/main/res/values-bg/translations.xml | 1 + .../src/main/res/values-cs/translations.xml | 25 +++++++++ .../src/main/res/values-de/translations.xml | 24 +++++++++ .../src/main/res/values-fr/translations.xml | 24 +++++++++ .../src/main/res/values-hu/translations.xml | 24 +++++++++ .../src/main/res/values-in/translations.xml | 23 ++++++++ .../src/main/res/values-ru/translations.xml | 25 +++++++++ .../src/main/res/values-sk/translations.xml | 25 +++++++++ .../src/main/res/values-be/translations.xml | 11 ++++ .../src/main/res/values-cs/translations.xml | 11 ++++ .../src/main/res/values-de/translations.xml | 11 ++++ .../src/main/res/values-fr/translations.xml | 11 ++++ .../src/main/res/values-hu/translations.xml | 11 ++++ .../src/main/res/values-ru/translations.xml | 11 ++++ .../src/main/res/values-sk/translations.xml | 11 ++++ .../src/main/res/values-be/translations.xml | 11 ++++ .../src/main/res/values-cs/translations.xml | 11 ++++ .../src/main/res/values-de/translations.xml | 10 ++++ .../src/main/res/values-fr/translations.xml | 10 ++++ .../src/main/res/values-hu/translations.xml | 10 ++++ .../src/main/res/values-ru/translations.xml | 11 ++++ .../src/main/res/values-sk/translations.xml | 11 ++++ .../src/main/res/values-be/translations.xml | 53 ------------------- .../src/main/res/values-bg/translations.xml | 1 - .../src/main/res/values-cs/translations.xml | 53 ------------------- .../src/main/res/values-de/translations.xml | 51 ------------------ .../src/main/res/values-fr/translations.xml | 51 ------------------ .../src/main/res/values-hu/translations.xml | 51 ------------------ .../src/main/res/values-in/translations.xml | 49 ----------------- .../src/main/res/values-ru/translations.xml | 53 ------------------- .../src/main/res/values-sk/translations.xml | 53 ------------------- .../src/main/res/values-sv/translations.xml | 1 + .../src/main/res/values/localazy.xml | 51 ------------------ 62 files changed, 482 insertions(+), 478 deletions(-) create mode 100644 libraries/permissions/impl/src/main/res/values-be/translations.xml create mode 100644 libraries/permissions/impl/src/main/res/values-cs/translations.xml create mode 100644 libraries/permissions/impl/src/main/res/values-de/translations.xml create mode 100644 libraries/permissions/impl/src/main/res/values-fr/translations.xml create mode 100644 libraries/permissions/impl/src/main/res/values-hu/translations.xml create mode 100644 libraries/permissions/impl/src/main/res/values-ru/translations.xml create mode 100644 libraries/permissions/impl/src/main/res/values-sk/translations.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values-be/translations.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values-cs/translations.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values-de/translations.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values-fr/translations.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values-hu/translations.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values-ru/translations.xml create mode 100644 libraries/pushproviders/firebase/src/main/res/values-sk/translations.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values-be/translations.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values-cs/translations.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values-de/translations.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values-fr/translations.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values-hu/translations.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values-ru/translations.xml create mode 100644 libraries/pushproviders/unifiedpush/src/main/res/values-sk/translations.xml diff --git a/features/invitelist/impl/src/main/res/values-be/translations.xml b/features/invitelist/impl/src/main/res/values-be/translations.xml index 36c895dcb9..9a37d82357 100644 --- a/features/invitelist/impl/src/main/res/values-be/translations.xml +++ b/features/invitelist/impl/src/main/res/values-be/translations.xml @@ -1,8 +1,8 @@ - "Вы ўпэўненыя, што жадаеце адхіліць запрашэнне ў %1$s?" + "Вы ўпэўненыя, што хочаце адхіліць запрашэнне ў %1$s?" "Адхіліць запрашэнне" - "Вы ўпэўненыя, што жадаеце адмовіцца ад прыватных зносін з %1$s?" + "Вы ўпэўненыя, што хочаце адмовіцца ад прыватных зносін з %1$s?" "Адхіліць чат" "Няма запрашэнняў" "%1$s (%2$s) запрасіў вас" diff --git a/features/leaveroom/api/src/main/res/values-be/translations.xml b/features/leaveroom/api/src/main/res/values-be/translations.xml index 0e80bcd433..c8bb068fa7 100644 --- a/features/leaveroom/api/src/main/res/values-be/translations.xml +++ b/features/leaveroom/api/src/main/res/values-be/translations.xml @@ -1,7 +1,7 @@ "Вы ўпэўнены, што хочаце пакінуць гэту размову? Гэта размова не з\'яўляецца публічнай, і вы не зможаце далучыцца зноў без запрашэння." - "Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Вы тут адзіны карыстальнік. Калі вы выйдзеце, ніхто не зможа далучыцца ў будучыні, у тым ліку і вы." - "Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Гэты пакой не агульнадаступны, і вы не зможаце далучыцца да яго зноў без запрашэння." + "Вы ўпэўнены, што хочаце пакінуць гэты пакой? Вы тут адзіны карыстальнік. Калі вы выйдзеце, ніхто не зможа далучыцца ў будучыні, у тым ліку і вы." + "Вы ўпэўнены, што жхочаце пакінуць гэты пакой? Гэты пакой не агульнадаступны, і вы не зможаце далучыцца да яго зноў без запрашэння." "Вы ўпэўнены, што хочаце пакінуць пакой?" diff --git a/features/lockscreen/impl/src/main/res/values-be/translations.xml b/features/lockscreen/impl/src/main/res/values-be/translations.xml index f786884ed0..2c5bd554b0 100644 --- a/features/lockscreen/impl/src/main/res/values-be/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-be/translations.xml @@ -7,7 +7,7 @@ "Змяніць PIN-код" "Дазволіць біяметрычную разблакіроўку" "Выдаліць PIN-код" - "Вы ўпэўнены, што жадаеце выдаліць PIN-код?" + "Вы ўпэўнены, што хочаце выдаліць PIN-код?" "Выдаліць PIN-код?" "Дазволіць %1$s" "Я хацеў бы выкарыстоўваць PIN-код" diff --git a/features/lockscreen/impl/src/main/res/values-sv/translations.xml b/features/lockscreen/impl/src/main/res/values-sv/translations.xml index b89f26bae3..c0ffcd9cdf 100644 --- a/features/lockscreen/impl/src/main/res/values-sv/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-sv/translations.xml @@ -1,4 +1,8 @@ + "Byt PIN-kod" + "Tillåt biometrisk upplåsning" + "Ta bort PIN-kod" + "Ta bort PIN-koden?" "Loggar ut …" 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 ee875ab37a..f2ea6cfd09 100644 --- a/features/messages/impl/src/main/res/values-be/translations.xml +++ b/features/messages/impl/src/main/res/values-be/translations.xml @@ -9,7 +9,7 @@ "Падарожжы & Месцы" "Сімвалы" "Заблакіраваць карыстальніка" - "Адзначце, ці жадаеце вы схаваць усе бягучыя і будучыя паведамленні ад гэтага карыстальніка" + "Адзначце, ці хочаце вы схаваць усе бягучыя і будучыя паведамленні ад гэтага карыстальніка" "Гэтае паведамленне будзе перададзена адміністратару вашага хатняга сервера. Яны не змогуць прачытаць зашыфраваныя паведамленні." "Прычына, па якой вы паскардзіліся на гэты змест" "Камера" diff --git a/features/poll/impl/src/main/res/values-be/translations.xml b/features/poll/impl/src/main/res/values-be/translations.xml index 4526df8572..1d0238bca3 100644 --- a/features/poll/impl/src/main/res/values-be/translations.xml +++ b/features/poll/impl/src/main/res/values-be/translations.xml @@ -4,7 +4,7 @@ "Паказаць вынікі толькі пасля заканчэння апытання" "Схаваць галасы" "Варыянт %1$d" - "Вашы змены не былі захаваны. Вы ўпэўнены, што жадаеце вярнуцца?" + "Вашы змены не былі захаваны. Вы ўпэўнены, што хочаце вярнуцца?" "Пытанне або тэма" "Пра што апытанне?" "Стварэнне апытання" diff --git a/features/preferences/impl/src/main/res/values-be/translations.xml b/features/preferences/impl/src/main/res/values-be/translations.xml index 8f20b79a09..0a8e477f7f 100644 --- a/features/preferences/impl/src/main/res/values-be/translations.xml +++ b/features/preferences/impl/src/main/res/values-be/translations.xml @@ -49,4 +49,14 @@ "налады сістэмы" "Сістэмныя апавяшчэнні выключаны" "Апавяшчэнні" + "Выпраўленне непаладак" + "Выпраўленне непаладак з апавяшчэннямі" + "Запусціць тэсты" + "Запусціце тэсты яшчэ раз" + "Некаторыя тэсты не ўдаліся. Калі ласка, праглядзіце дэталі." + "Запусціце тэсты, каб выявіць праблемы ў вашай канфігурацыі, з-за якіх апавяшчэння могуць паводзіць сябе не так, як чакалася." + "Спроба выпраўлення" + "Усе тэсты паспяхова пройдзены." + "Выпраўленне непаладак з апавяшчэннямі" + "Некаторыя тэсты патрабуюць вашай увагі. Калі ласка, праглядзіце дэталі." diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml index ed209124ec..a2a3cc5955 100644 --- a/features/preferences/impl/src/main/res/values-cs/translations.xml +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -51,4 +51,14 @@ Pokud budete pokračovat, některá nastavení se mohou změnit."
    "systémová nastavení" "Systémová oznámení byla vypnuta" "Oznámení" + "Odstraňování problémů" + "Odstraňování problémů s upozorněními" + "Spustit testy" + "Spustit testy znovu" + "Některé testy selhaly. Zkontrolujte prosím podrobnosti." + "Spusťte testy, abyste zjistili jakýkoli problém ve vaší konfiguraci, který může způsobit, že se oznámení nebudou chovat podle očekávání." + "Pokus o opravu" + "Všechny testy proběhly úspěšně." + "Odstraňování problémů s upozorněními" + "Některé testy vyžadují vaši pozornost. Zkontrolujte prosím podrobnosti." diff --git a/features/preferences/impl/src/main/res/values-de/translations.xml b/features/preferences/impl/src/main/res/values-de/translations.xml index 6d47e4817d..04a27f9db6 100644 --- a/features/preferences/impl/src/main/res/values-de/translations.xml +++ b/features/preferences/impl/src/main/res/values-de/translations.xml @@ -49,4 +49,14 @@ Wenn du fortfährst, können sich einige deiner Einstellungen ändern."
    "Systemeinstellungen" "Systembenachrichtigungen deaktiviert" "Benachrichtigungen" + "Fehlerbehebung" + "Fehlerbehebung für Benachrichtigungen" + "Tests durchführen" + "Tests erneut durchführen" + "Einige Tests sind fehlgeschlagen. Bitte überprüfe die Details." + "Führe die Tests durch, um Probleme zu erkennen, die dazu führen können, dass sich die Benachrichtigungen nicht wie erwartet verhalten." + "Versuche das Problem zu beheben" + "Alle Tests wurden erfolgreich bestanden." + "Fehlerbehebung für Benachrichtigungen" + "Einige Tests erfordern deine Aufmerksamkeit. Bitte überprüfe die Details." diff --git a/features/preferences/impl/src/main/res/values-fr/translations.xml b/features/preferences/impl/src/main/res/values-fr/translations.xml index ddfd6eec5c..426c475d4c 100644 --- a/features/preferences/impl/src/main/res/values-fr/translations.xml +++ b/features/preferences/impl/src/main/res/values-fr/translations.xml @@ -49,4 +49,14 @@ Si vous continuez, il est possible que certains de vos paramètres soient modifi "paramètres du système" "Les notifications du système sont désactivées" "Notifications" + "Dépannage" + "Résoudre les problèmes liés aux notifications" + "Exécuter les tests" + "Relancer les tests" + "Certains tests ont échoué. Veuillez vérifier les détails." + "Exécuter les tests pour détecter tout problème dans votre configuration susceptible de provoquer un dysfonctionnement des notifications." + "Tenter de corriger" + "Tous les tests ont réussi." + "Dépanner les notifications" + "Certains tests nécessitent votre attention. Veuillez vérifier les détails." diff --git a/features/preferences/impl/src/main/res/values-hu/translations.xml b/features/preferences/impl/src/main/res/values-hu/translations.xml index 739722f07b..3574b49e6e 100644 --- a/features/preferences/impl/src/main/res/values-hu/translations.xml +++ b/features/preferences/impl/src/main/res/values-hu/translations.xml @@ -49,4 +49,14 @@ Ha folytatja, egyes beállítások megváltozhatnak."
    "rendszerbeállításokat" "A rendszerértesítések ki vannak kapcsolva" "Értesítések" + "Hibaelhárítás" + "Értesítések hibaelhárítása" + "Tesztek futtatása" + "Tesztek újbóli futtatása" + "Egyes tesztek sikertelenek voltak. Ellenőrizze a részleteket." + "A tesztek futtatása, hogy észlelje a konfigurációban felmerülő olyan problémákat, amelyek miatt az értesítések nem az elvárt módon viselkednek." + "Kísérlet a javításra" + "Minden teszt sikeresen lezajlott." + "Értesítések hibaelhárítása" + "Egyes tesztek a figyelmét igénylik. Ellenőrizze a részleteket." 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 f9fb4c0848..13ac802d4d 100644 --- a/features/preferences/impl/src/main/res/values-in/translations.xml +++ b/features/preferences/impl/src/main/res/values-in/translations.xml @@ -51,4 +51,14 @@ Jika Anda melanjutkan, beberapa pengaturan Anda dapat berubah."
    "pengaturan sistem" "Pemberitahuan sistem dimatikan" "Notifikasi" + "Pemecahan masalah" + "Pecahkan masalah notifikasi" + "Jalankan tes" + "Jalankan tes lagi" + "Beberapa tes gagal. Silakan periksa detailnya." + "Jalankan pengujian untuk mendeteksi masalah apa pun dalam konfigurasi Anda yang mungkin membuat notifikasi tidak berperilaku seperti yang diharapkan." + "Mencoba untuk memperbaiki" + "Semua tes berhasil dilalui." + "Pecahkan masalah notifikasi" + "Beberapa tes membutuhkan perhatian Anda. Silakan periksa detailnya." diff --git a/features/preferences/impl/src/main/res/values-ru/translations.xml b/features/preferences/impl/src/main/res/values-ru/translations.xml index 06b2cddad5..ca18672e97 100644 --- a/features/preferences/impl/src/main/res/values-ru/translations.xml +++ b/features/preferences/impl/src/main/res/values-ru/translations.xml @@ -49,4 +49,14 @@ "настройки системы" "Системные уведомления выключены" "Уведомления" + "Устранение неполадок" + "Уведомления об устранении неполадок" + "Выполнение тестов" + "Повторное выполнение тестов" + "Некоторые тесты провалились. Пожалуйста, проверьте детали." + "Выполните тесты, чтобы обнаружить любую проблему в конфигурации, из-за которой уведомления могут работать не так, как ожидалось." + "Попытка исправить" + "Все тесты прошли успешно." + "Уведомления об устранении неполадок" + "Некоторые тесты требуют вашего внимания. Пожалуйста, проверьте детали." diff --git a/features/preferences/impl/src/main/res/values-sk/translations.xml b/features/preferences/impl/src/main/res/values-sk/translations.xml index ddc71f8c67..12289b46f4 100644 --- a/features/preferences/impl/src/main/res/values-sk/translations.xml +++ b/features/preferences/impl/src/main/res/values-sk/translations.xml @@ -51,4 +51,14 @@ Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť.""nastavenia systému"
    "Systémové oznámenia sú vypnuté" "Oznámenia" + "Riešenie problémov" + "Oznámenia riešení problémov" + "Spustiť testy" + "Spustiť testy znova" + "Niektoré testy zlyhali. Skontrolujte prosím podrobnosti." + "Spustite testy, aby ste zistili akýkoľvek problém vo vašej konfigurácii, ktorý môže spôsobiť, že sa upozornenia nebudú správať podľa očakávania." + "Pokus o opravu" + "Všetky testy prebehli úspešne." + "Oznámenia riešení problémov" + "Niektoré testy si vyžadujú vašu pozornosť. Prosím skontrolujte podrobnosti." diff --git a/features/rageshake/api/src/main/res/values-be/translations.xml b/features/rageshake/api/src/main/res/values-be/translations.xml index a73c7eedd5..3d9eec29d2 100644 --- a/features/rageshake/api/src/main/res/values-be/translations.xml +++ b/features/rageshake/api/src/main/res/values-be/translations.xml @@ -1,7 +1,7 @@ - "Пры апошнім выкарыстанні %1$s адбыўся збой. Жадаеце падзяліцца справаздачай аб збоі?" - "Падобна, што вы трасеце тэлефон. Жадаеце адкрыць экран паведамлення пра памылку?" + "Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце падзяліцца справаздачай аб збоі?" + "Падобна, што вы трасеце тэлефон. Хочаце адкрыць экран паведамлення пра памылку?" "Rageshake" "Парог выяўлення" diff --git a/features/rageshake/impl/src/main/res/values-be/translations.xml b/features/rageshake/impl/src/main/res/values-be/translations.xml index 8bf98b9c85..f927ba3c5e 100644 --- a/features/rageshake/impl/src/main/res/values-be/translations.xml +++ b/features/rageshake/impl/src/main/res/values-be/translations.xml @@ -12,6 +12,6 @@ "Дазволіць журналы" "Адправіць здымак экрана" "Каб пераканацца, што ўсё працуе правільна, у паведамленне будуць уключаны часопісы. Каб адправіць паведамленне без часопісаў, адключыце гэтую наладу." - "Пры апошнім выкарыстанні %1$s адбыўся збой. Жадаеце падзяліцца справаздачай аб збоі?" + "Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце падзяліцца справаздачай аб збоі?" "Прагляд журналаў" diff --git a/features/roomdetails/impl/src/main/res/values-be/translations.xml b/features/roomdetails/impl/src/main/res/values-be/translations.xml index 68ef4d8903..be2ac558fd 100644 --- a/features/roomdetails/impl/src/main/res/values-be/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-be/translations.xml @@ -30,6 +30,7 @@ "Вы не зможаце адмяніць гэтае змяненне, бо паніжаеце сябе. Калі вы апошні адміністратар у пакоі, вярнуць права будзе немагчыма." "Панізіць сябе?" "%1$s (У чаканні)" + "(У чаканні)" "Рэдагаваць мадэратараў" "Адміністратары" "Мадэратары" diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml index 8479044057..005531b3db 100644 --- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml @@ -30,6 +30,7 @@ "Vous ne pourrez pas annuler ce changement car vous vous rétrogradez, si vous êtes le dernier utilisateur privilégié du salon il sera impossible de retrouver les privilèges." "Vous rétrograder ?" "%1$s (En attente)" + "(En attente)" "Modifier les modérateurs" "Administrateurs" "Modérateurs" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index b6bf76c440..04afc61ad6 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -30,6 +30,7 @@ "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges." "Demote yourself?" "%1$s (Pending)" + "(Pending)" "Edit Moderators" "Admins" "Moderators" diff --git a/features/securebackup/impl/src/main/res/values-be/translations.xml b/features/securebackup/impl/src/main/res/values-be/translations.xml index 38215dfd20..63a2a9aae8 100644 --- a/features/securebackup/impl/src/main/res/values-be/translations.xml +++ b/features/securebackup/impl/src/main/res/values-be/translations.xml @@ -15,7 +15,7 @@ "Адключэнне рэзервовага капіравання прывядзе да выдалення бягучай рэзервовай копіі ключа шыфравання і адключэння іншых функцый бяспекі. У гэтым выпадку вы:" "Няма зашыфраванай гісторыі паведамленняў на новых прыладах" "Калі вы выходзіце з сістэмы, то губляеце доступ да зашыфраваных паведамленняў %1$s усюды" - "Вы ўпэўнены, што жадаеце адключыць рэзервовае капіраванне?" + "Вы ўпэўнены, што хочаце адключыць рэзервовае капіраванне?" "Атрымайце новы ключ аднаўлення, калі вы страцілі існуючы. Пасля змены ключа аднаўлення ваш стары больш не будзе працаваць." "Стварыць новы ключ аднаўлення" "Пераканайцеся, што вы можаце захаваць ключ аднаўлення ў бяспечным месцы" diff --git a/libraries/permissions/impl/src/main/res/values-be/translations.xml b/libraries/permissions/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..44c9d8376a --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,5 @@ + + + "Пераканайцеся, што праграма можа паказваць апавяшчэнні." + "Праверце дазволы" + diff --git a/libraries/permissions/impl/src/main/res/values-cs/translations.xml b/libraries/permissions/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..038561d93b --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,5 @@ + + + "Ujistěte se, že aplikace může zobrazovat oznámení." + "Kontrola oprávnění" + diff --git a/libraries/permissions/impl/src/main/res/values-de/translations.xml b/libraries/permissions/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..a0ff0ff417 --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,5 @@ + + + "Stelle sicher, dass die Anwendung Benachrichtigungen anzeigen kann." + "Berechtigungen überprüfen" + diff --git a/libraries/permissions/impl/src/main/res/values-fr/translations.xml b/libraries/permissions/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..b2cd018579 --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,5 @@ + + + "Vérifie que l’application peut afficher des notifications." + "Vérifier les autorisations" + diff --git a/libraries/permissions/impl/src/main/res/values-hu/translations.xml b/libraries/permissions/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..4afe7f181b --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,5 @@ + + + "Ellenőrizze, hogy az alkalmazás képes-e értesítéseket megjeleníteni." + "Engedélyek ellenőrzése" + diff --git a/libraries/permissions/impl/src/main/res/values-ru/translations.xml b/libraries/permissions/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..d1acff7ebd --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,5 @@ + + + "Убедитесь, что приложение может показывать уведомления." + "Проверка разрешений" + diff --git a/libraries/permissions/impl/src/main/res/values-sk/translations.xml b/libraries/permissions/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..995754907e --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,5 @@ + + + "Uistite sa, že aplikácia dokáže zobrazovať upozornenia." + "Skontrolovať povolenia" + diff --git a/libraries/permissions/impl/src/main/res/values/localazy.xml b/libraries/permissions/impl/src/main/res/values/localazy.xml index a8859205dc..948b026970 100644 --- a/libraries/permissions/impl/src/main/res/values/localazy.xml +++ b/libraries/permissions/impl/src/main/res/values/localazy.xml @@ -1,5 +1,5 @@ - "Ensure that the application can show notifications." + "Check that the application can show notifications." "Check permissions" diff --git a/libraries/push/impl/src/main/res/values-be/translations.xml b/libraries/push/impl/src/main/res/values-be/translations.xml index f947eb3a00..686d10a281 100644 --- a/libraries/push/impl/src/main/res/values-be/translations.xml +++ b/libraries/push/impl/src/main/res/values-be/translations.xml @@ -56,4 +56,29 @@ "Фонавая сінхранізацыя" "Сэрвісы Google" "Службы Google Play не знойдзены. Апавяшчэнні могуць не працаваць належным чынам." + "Атрымаць назву бягучага пастаўшчыка." + "Пастаўшчыкі push-апавяшчэнняў не выбраны." + "Бягучы пастаўшчык push-апавяшчэнняў: %1$s." + "Бягучы пастаўшчык push-апавяшчэнняў" + "Пераканайцеся, што ў праграме ёсць хаця б адзін пастаўшчык push-апавяшчэнняў." + "Пастаўшчыкі push-апавяшчэнняў не знойдзены." + + "Знайшлі %1$d пастаўшчыка push-апавяшчэнняў: %2$s" + "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" + "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" + + "Выяўленне пастаўшчыкоў push-паслуг" + "Праверце, ці можа праграма паказваць апавяшчэнні." + "Апавяшчэнне не было націснута." + "Немагчыма паказаць апавяшчэнне." + "Апавяшчэнне было націснута!" + "Паказаць апавяшчэнне" + "Націсніце на апавяшчэнне, каб працягнуць тэст." + "Пераканайцеся, што праграма атрымлівае push-апавяшчэнні." + "Памылка: pusher адхіліў запыт." + "Памылка: %1$s." + "Памылка, немагчыма праверыць push-апавяшчэнне." + "Памылка, тайм-аўт у чаканні push-апавяшчэння." + "Зварот цыклу назад заняў %1$d мс." + "Тэст Націсніце кнопку вярнуцца" diff --git a/libraries/push/impl/src/main/res/values-bg/translations.xml b/libraries/push/impl/src/main/res/values-bg/translations.xml index 5a9f8d2a59..821ca2f5d8 100644 --- a/libraries/push/impl/src/main/res/values-bg/translations.xml +++ b/libraries/push/impl/src/main/res/values-bg/translations.xml @@ -36,4 +36,5 @@ "%d стая" "%d стаи" + "Грешка: %1$s" diff --git a/libraries/push/impl/src/main/res/values-cs/translations.xml b/libraries/push/impl/src/main/res/values-cs/translations.xml index db4e6e4c6a..fac2a69784 100644 --- a/libraries/push/impl/src/main/res/values-cs/translations.xml +++ b/libraries/push/impl/src/main/res/values-cs/translations.xml @@ -56,4 +56,29 @@ "Synchronizace na pozadí" "Služby Google" "Nebyly nalezeny žádné funkční služby Google Play. Oznámení nemusí fungovat správně." + "Získat název aktuálního poskytovatele." + "Nebyli vybráni žádní push poskytovatelé." + "Aktuální push poskytovatel: %1$s." + "Aktuální push poskytovatel" + "Ujistěte se, že aplikace má alespoň jednoho push poskytovatele." + "Nebyli nalezeni žádní push poskytovatelé." + + "Nalezen %1$d push poskytovatel: %2$s" + "Nalezeni %1$d push poskytovatelé: %2$s" + "Nalezeno %1$d push poskytovatelů: %2$s" + + "Zjistit push poskytovatele" + "Zkontrolujte, zda aplikace může zobrazit oznámení." + "Na oznámení nebylo kliknuto." + "Oznámení nelze zobrazit." + "Na oznámení bylo kliknuto!" + "Zobrazit oznámení" + "Kliknutím na oznámení pokračujte v testu." + "Ujistěte se, že aplikace přijímá push." + "Chyba: pusher odmítl požadavek." + "Chyba: %1$s." + "Chyba, nelze otestovat push." + "Chyba, časový limit čekání na push." + "Push zpětná smyčka trvala %1$d ms." + "Otestovat push zpětnou smyčku" diff --git a/libraries/push/impl/src/main/res/values-de/translations.xml b/libraries/push/impl/src/main/res/values-de/translations.xml index 5476ad2eea..2757e89dea 100644 --- a/libraries/push/impl/src/main/res/values-de/translations.xml +++ b/libraries/push/impl/src/main/res/values-de/translations.xml @@ -50,4 +50,28 @@ "Hintergrundsynchronisation" "Google-Dienste" "Keine gültigen Google Play-Dienste gefunden. Benachrichtigungen funktionieren möglicherweise nicht richtig." + "Ermittele den Namen des aktuellen Anbieters." + "Kein Push-Anbieter ausgewählt." + "Aktueller Push-Anbieter:%1$s." + "Aktueller Push-Anbieter" + "Stelle sicher, dass die Anwendung mindestens einen Push-Anbieter hat." + "Keine Push-Anbieter gefunden." + + "%1$d Push-Anbieter gefunden: %2$s" + "%1$d Push-Anbieter gefunden: %2$s" + + "Push-Anbieter erkennen" + "Prüfe, ob die Anwendung Benachrichtigungen anzeigen kann." + "Die Benachrichtigung wurde nicht angeklickt." + "Die Benachrichtigung kann nicht angezeigt werden." + "Die Benachrichtigung wurde angeklickt!" + "Benachrichtigung anzeigen" + "Bitte klicke auf die Benachrichtigung, um den Test fortzusetzen." + "Stelle sicher, dass die Anwendung Push-Nachrichten empfängt." + "Fehler: Der Pusher hat die Anfrage abgelehnt." + "Fehler:%1$s." + "Fehler: Push kann nicht getestet werden." + "Fehler: Timeout beim Warten auf Push." + "Push-Loop-Back Dauer: %1$d ms." + "Teste Push-Loop-Back" diff --git a/libraries/push/impl/src/main/res/values-fr/translations.xml b/libraries/push/impl/src/main/res/values-fr/translations.xml index eb6e4eabbc..8501b8ad76 100644 --- a/libraries/push/impl/src/main/res/values-fr/translations.xml +++ b/libraries/push/impl/src/main/res/values-fr/translations.xml @@ -50,4 +50,28 @@ "Synchronisation en arrière-plan" "Services Google" "Aucun service Google Play valide n’a été trouvé. Les notifications peuvent ne pas fonctionner correctement." + "Obtenir le nom du fournisseur de Push actuel." + "Aucun fournisseur de Push n’est sélectionné." + "Fournisseur de Push actuel : %1$s." + "Fournisseur de Push actuel" + "Vérifier que l’application possède au moins un fournisseur de Push." + "Aucun fournisseur de Push n’a été trouvé." + + "%1$d fournisseur de Push détecté : %2$s" + "%1$d fournisseurs de Push détectés : %2$s" + + "Détecter les fournisseurs de Push" + "Vérifier que l’application peut afficher des notifications." + "Vous n’avez pas cliqué sur la notification." + "Impossible d’afficher la notification." + "Vous avez cliqué sur la notification!" + "Affichage des notifications" + "Veuillez cliquer sur la notification pour continuer le test." + "Vérifier que l’application reçoit les Push." + "Erreur : le Pusher a rejeté la demande." + "Erreur :%1$s." + "Erreur, impossible de tester les Push." + "Erreur, le délai d’attente du Push est dépassé." + "La demande d’envoi de Push et sa réception ont pris %1$d ms." + "Tester la réception des Push" 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 de719fc0c6..7b0c66aa65 100644 --- a/libraries/push/impl/src/main/res/values-hu/translations.xml +++ b/libraries/push/impl/src/main/res/values-hu/translations.xml @@ -50,4 +50,28 @@ "Háttérszinkronizálás" "Google szolgáltatások" "A Google Play szolgáltatások nem találhatók. Előfordulhat, hogy az értesítések nem működnek megfelelően." + "A jelenlegi szolgáltató nevének lekérdezése." + "Nincs kiválasztva leküldéses értesítési szolgáltató." + "Jelenlegi leküldéses értesítési szolgáltató: %1$s." + "Jelenlegi leküldéses értesítési szolgáltató" + "Győződjön meg arról, hogy az alkalmazás legalább egy leküldéses értesítési szolgáltatóval rendelkezik." + "Nem található leküldéses értesítési szolgáltató." + + "%1$d leküldéses szolgáltató találva: %2$s" + "%1$d leküldéses szolgáltató találva: %2$s" + + "Leküldéses értesítési szolgáltatók észlelése" + "Ellenőrizze, hogy az alkalmazás képes-e megjeleníteni az értesítést." + "Az értesítésre nem kattintottak rá." + "Az értesítés nem jeleníthető meg." + "Az értesítésre rákattintottak!" + "Értesítés megjelenítése" + "A teszt folytatásához kattintson az értesítésre." + "Győződjön meg arról, hogy az alkalmazás megkapja-e a leküldéses értesítést." + "Hiba: a leküldő elutasította a kérést." + "Hiba: %1$s." + "Hiba, nem lehet tesztelni a leküldéses értesítést." + "Hiba, időtúllépés a leküldéses értesítésre való várakozás során." + "A leküldéses értesítés folyamata %1$d ezredmásodpercig tartott." + "Tesztelje a leküldéses értesítés folyamatát" diff --git a/libraries/push/impl/src/main/res/values-in/translations.xml b/libraries/push/impl/src/main/res/values-in/translations.xml index 1a81220eeb..b933330be1 100644 --- a/libraries/push/impl/src/main/res/values-in/translations.xml +++ b/libraries/push/impl/src/main/res/values-in/translations.xml @@ -44,4 +44,27 @@ "Sinkronisasi latar belakang" "Layanan Google" "Tidak ditemukan Layanan Google Play yang valid. Pemberitahuan mungkin tidak berfungsi dengan baik." + "Dapatkan nama penyedia saat ini." + "Tidak ada penyedia notifikasi dorongan yang dipilih." + "Penyedia notifikasi dorongan saat ini: %1$s." + "Penyedia notifikasi dorongan saat ini" + "Pastikan aplikasi memiliki setidaknya satu penyedia notifikasi dorongan." + "Tidak ada penyedia notifikasi dorongan yang ditemukan." + + "Ditemukan %1$d penyedia notifikasi dorongan: %2$s" + + "Deteksi penyedia notifikasi dorongan" + "Periksa apakah aplikasi dapat menampilkan notifikasi." + "Notifikasi belum diklik." + "Tidak dapat menampilkan notifikasi." + "Notifikasi telah diklik!" + "Tampilan notifikasi" + "Silakan klik pada notifikasi untuk melanjutkan tes." + "Pastikan aplikasi menerima notifikasi dorongan." + "Kesalahan: pendorong telah menolak permintaan." + "Kesalahan: %1$s." + "Terjadi kesalahan, tidak dapat menguji notifikasi dorongan." + "Terjadi kesalahan, melebihi batas waktu menunggu notifikasi dorongan." + "Ulangan notifikasi dorongan membutuhkan %1$d ms." + "Uji ulangan notifikasi dorongan lagi" 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 8881d488ba..65242651b2 100644 --- a/libraries/push/impl/src/main/res/values-ru/translations.xml +++ b/libraries/push/impl/src/main/res/values-ru/translations.xml @@ -56,4 +56,29 @@ "Фоновая синхронизация" "Сервисы Google" "Не найдены действующие службы Google Play. Уведомления могут работать некорректно." + "Получение имени текущего поставщика." + "Поставщики push-уведомлений не выбраны." + "Текущий поставщик push-уведомлений: %1$s." + "Текущий поставщик push-уведомлений" + "Убедитесь, что у приложения есть хотя бы один поставщик push-сообщений." + "Поставщики push-уведомлений не найдены." + + "Найден %1$d push-провайдер: %2$s" + "Найдено %1$d push-провайдеров: %2$s" + "Найдено %1$d push-провайдеров: %2$s" + + "Обнаружение поставщиков push-уведомлений" + "Убедитесь, что приложение может отображать уведомление." + "Уведомление не было нажато." + "Невозможно отобразить уведомление." + "Уведомление было нажато!" + "Отобразить уведомление" + "Нажмите на уведомление, чтобы продолжить тест." + "Убедитесь, что приложение получает push-сообщение." + "Ошибка: pusher отклонил запрос." + "Ошибка: %1$s." + "Ошибка, невозможно протестировать отправку." + "Ошибка, тайм-аут ожидания push-уведомления." + "Обратная отправка push-уведомления, заняла %1$d мс." + "Тест обратной отправки push-уведомления" 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 4517697945..95aed49bd3 100644 --- a/libraries/push/impl/src/main/res/values-sk/translations.xml +++ b/libraries/push/impl/src/main/res/values-sk/translations.xml @@ -56,4 +56,29 @@ "Synchronizácia na pozadí" "Služby Google" "Nenašli sa žiadne platné služby Google Play. Oznámenia nemusia fungovať správne." + "Získaťe názov aktuálneho poskytovateľa." + "Nie sú vybraní žiadni poskytovatelia push." + "Aktuálny poskytovateľ push: %1$s." + "Aktuálny poskytovateľ push" + "Uistite sa, že aplikácia má aspoň jedného poskytovateľa push." + "Nenašli sa žiadni poskytovatelia push." + + "Nájdený %1$d poskytovateľ služby push: %2$s" + "Nájdení %1$d poskytovatelia služby push: %2$s" + "Nájdených %1$d poskytovateľov služby push: %2$s" + + "Zistiť poskytovateľov push" + "Skontrolujte, či aplikácia dokáže zobraziť upozornenie." + "Na oznámenie nebolo kliknuté." + "Nie je možné zobraziť upozornenie." + "Na oznámenie bolo kliknuté!" + "Zobraziť upozornenie" + "Kliknite na upozornenie a pokračujte v teste." + "Uistite sa, že aplikácia prijíma push oznámenia." + "Chyba: pusher odmietol požiadavku." + "Chyba: %1$s." + "Chyba, nie je možné testovať push." + "Chyba, časový limit na push vypršal." + "Push loop back trvalo %1$d ms." + "Testovať Push loop back" diff --git a/libraries/pushproviders/firebase/src/main/res/values-be/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..cd9082e913 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-be/translations.xml @@ -0,0 +1,11 @@ + + + "Пераканайцеся, што Firebase даступны." + "Firebase недаступны." + "Firebase даступны." + "Праверыць Firebase" + "Пераканайцеся, што маркер Firebase даступны." + "Маркер Firebase невядомы." + "Маркер Firebase: %1$s." + "Праверце маркер Firebase" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-cs/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..e0b7eff47f --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-cs/translations.xml @@ -0,0 +1,11 @@ + + + "Ujistěte se, že je k dispozici Firebase." + "Firebase není k dispozici." + "Firebase je k dispozici." + "Zkontrolovat Firebase" + "Ujistěte se, že je k dispozici Firebase token." + "Firebase token není znám." + "Firebase token: %1$s." + "Zkontrolovat Firebase token" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-de/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..5df74b7284 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-de/translations.xml @@ -0,0 +1,11 @@ + + + "Stelle sicher, dass Firebase verfügbar ist." + "Firebase ist nicht verfügbar." + "Firebase ist verfügbar." + "Überprüfe Firebase" + "Stelle sicher, dass der Firebase Token verfügbar ist." + "Firebase Token ist nicht bekannt." + "Firebase Token: %1$s." + "Prüfe Firebase Token" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-fr/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..75256022a2 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-fr/translations.xml @@ -0,0 +1,11 @@ + + + "Vérification que Firebase est disponible." + "Firebase n’est pas disponible." + "Firebase est disponible." + "Vérification de Firebase" + "Vérifier que le jeton Firebase est disponible." + "Le jeton Firebase n’est pas connu." + "Jeton Firebase :%1$s." + "Vérifier le jeton Firebase" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-hu/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..10fa194159 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-hu/translations.xml @@ -0,0 +1,11 @@ + + + "Győződjön meg arról, hogy a Firebase elérhető-e." + "A Firebase nem érhető el." + "A Firebase elérhető." + "Ellenőrizze a Firebase-t" + "Győződjön meg arról, hogy a Firebase-token elérhető." + "A Firebase-token nem ismert." + "Firebase-token: %1$s." + "Ellenőrizze a Firebase-tokent" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-ru/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..4167dd0d36 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-ru/translations.xml @@ -0,0 +1,11 @@ + + + "Убедитесь, что Firebase доступен." + "Firebase недоступен." + "Firebase доступен." + "Проверить Firebase" + "Убедитесь, что токен Firebase доступен." + "Токен Firebase неизвестен." + "Токен Firebase: %1$s." + "Проверить токен Firebase" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-sk/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..312f751ce5 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-sk/translations.xml @@ -0,0 +1,11 @@ + + + "Uistite sa, že Firebase je k dispozícii." + "Firebase nie je k dispozícii." + "Firebase je k dispozícii." + "Skontrolovať Firebase" + "Uistite sa, že je k dispozícii token Firebase." + "Token Firebase nie je známy." + "Token Firebase: %1$s." + "Skontrolovať token Firebase" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-be/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..5fcab0a3f3 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-be/translations.xml @@ -0,0 +1,11 @@ + + + "Пераканайцеся, што размеркавальнікі UnifiedPush даступныя." + "Размеркавальнікі не знойдзены." + + "%1$d знойдзены размеркавальнік: %2$s." + "%1$d знойдзена размеркавальніка: %2$s." + "%1$d знойдзена размеркавальнікаў: %2$s." + + "Праверыць UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-cs/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..d4eeb3ef96 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-cs/translations.xml @@ -0,0 +1,11 @@ + + + "Ujistěte se, že jsou k dispozici distributoři UnifiedPush." + "Nebyli nalezeni žádní push distributoři." + + "Nalezen %1$d distributor: %2$s." + "Nalezeni %1$d distributoři: %2$s." + "Nalezeno %1$d distributorů: %2$s." + + "Zkontrolovat UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-de/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..92a632a39d --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-de/translations.xml @@ -0,0 +1,10 @@ + + + "Stelle sicher, dass UnifiedPush-Verteiler verfügbar sind." + "Keine Push-Verteiler gefunden." + + "%1$d Verteiler gefunden: %2$s." + "%1$d Verteiler gefunden: %2$s." + + "UnifiedPush prüfen" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-fr/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..fe7769da5e --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-fr/translations.xml @@ -0,0 +1,10 @@ + + + "Vérifier qu’au moins un distributeur UnifiedPush est disponible." + "Aucun distributeur UnifiedPush n’a été trouvé." + + "%1$d distributeur détecté :%2$s." + "%1$d distributeurs détectés :%2$s." + + "Vérifier UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-hu/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..7c92f521da --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-hu/translations.xml @@ -0,0 +1,10 @@ + + + "Győződjön meg arról, hogy a UnifiedPush forgalmazói elérhetők." + "Nem található forgalmazó a leküldéses értesítésekhez." + + "%1$d forgalmazó található: %2$s." + "%1$d forgalmazó található: %2$s." + + "Ellenőrizze a UnifiedPush szolgáltatást" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-ru/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..91a4dbc81b --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-ru/translations.xml @@ -0,0 +1,11 @@ + + + "Убедитесь, что дистрибьюторы UnifiedPush доступны." + "Поставщиков push-уведомлений не найдено." + + "%1$d провайдер найден: %2$s." + "%1$d провайдеров найдено: %2$s." + "%1$d провайдеров найдено: %2$s." + + "Проверка UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-sk/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..3dc876cb5b --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-sk/translations.xml @@ -0,0 +1,11 @@ + + + "Uistite sa, že sú dostupní distribútori UnifiedPush." + "Nenašli sa žiadni distribútori push." + + "%1$d nájdený distribútor: %2$s." + "%1$d nájdení distribútori: %2$s." + "%1$d nájdených distribútorov: %2$s." + + "Skontrolovať UnifiedPush" + 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 223ee9740a..ffbafa87f2 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -275,57 +275,4 @@ "Месцазнаходжанне" "Версія: %1$s (%2$s)" "be" - "Выпраўленне непаладак" - "Выпраўленне непаладак з апавяшчэннямі" - "Запусціць тэсты" - "Запусціце тэсты яшчэ раз" - "Некаторыя тэсты не ўдаліся. Калі ласка, праглядзіце дэталі." - "Запусціце тэсты, каб выявіць праблемы ў вашай канфігурацыі, з-за якіх апавяшчэння могуць паводзіць сябе не так, як чакалася." - "Спроба выпраўлення" - "Усе тэсты паспяхова пройдзены." - "Выпраўленне непаладак з апавяшчэннямі" - "Некаторыя тэсты патрабуюць вашай увагі. Калі ласка, праглядзіце дэталі." - "Пераканайцеся, што праграма можа паказваць апавяшчэнні." - "Праверце дазволы" - "Атрымаць назву бягучага пастаўшчыка." - "Пастаўшчыкі push-апавяшчэнняў не выбраны." - "Бягучы пастаўшчык push-апавяшчэнняў: %1$s." - "Бягучы пастаўшчык push-апавяшчэнняў" - "Пераканайцеся, што ў праграме ёсць хаця б адзін пастаўшчык push-апавяшчэнняў." - "Пастаўшчыкі push-апавяшчэнняў не знойдзены." - - "Знайшлі %1$d пастаўшчыка push-апавяшчэнняў: %2$s" - "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" - "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" - - "Выяўленне пастаўшчыкоў push-паслуг" - "Праверце, ці можа праграма паказваць апавяшчэнні." - "Апавяшчэнне не было націснута." - "Немагчыма паказаць апавяшчэнне." - "Апавяшчэнне было націснута!" - "Паказаць апавяшчэнне" - "Націсніце на апавяшчэнне, каб працягнуць тэст." - "Пераканайцеся, што Firebase даступны." - "Firebase недаступны." - "Firebase даступны." - "Праверыць Firebase" - "Пераканайцеся, што маркер Firebase даступны." - "Маркер Firebase невядомы." - "Маркер Firebase: %1$s." - "Праверце маркер Firebase" - "Пераканайцеся, што праграма атрымлівае push-апавяшчэнні." - "Памылка: pusher адхіліў запыт." - "Памылка: %1$s." - "Памылка, немагчыма праверыць push-апавяшчэнне." - "Памылка, тайм-аўт у чаканні push-апавяшчэння." - "Зварот цыклу назад заняў %1$d мс." - "Тэст Націсніце кнопку вярнуцца" - "Пераканайцеся, што размеркавальнікі UnifiedPush даступныя." - "Размеркавальнікі не знойдзены." - - "%1$d знойдзены размеркавальнік: %2$s." - "%1$d знойдзена размеркавальніка: %2$s." - "%1$d знойдзена размеркавальнікаў: %2$s." - - "Праверыць UnifiedPush" 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 0e6a31b71d..48c602706b 100644 --- a/libraries/ui-strings/src/main/res/values-bg/translations.xml +++ b/libraries/ui-strings/src/main/res/values-bg/translations.xml @@ -211,5 +211,4 @@ "Местоположение" "Версия: %1$s (%2$s)" "bg" - "Грешка: %1$s" 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 04968d8548..9dde757dc1 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -275,57 +275,4 @@ "Poloha" "Verze: %1$s (%2$s)" "en" - "Odstraňování problémů" - "Odstraňování problémů s upozorněními" - "Spustit testy" - "Spustit testy znovu" - "Některé testy selhaly. Zkontrolujte prosím podrobnosti." - "Spusťte testy, abyste zjistili jakýkoli problém ve vaší konfiguraci, který může způsobit, že se oznámení nebudou chovat podle očekávání." - "Pokus o opravu" - "Všechny testy proběhly úspěšně." - "Odstraňování problémů s upozorněními" - "Některé testy vyžadují vaši pozornost. Zkontrolujte prosím podrobnosti." - "Ujistěte se, že aplikace může zobrazovat oznámení." - "Kontrola oprávnění" - "Získat název aktuálního poskytovatele." - "Nebyli vybráni žádní push poskytovatelé." - "Aktuální push poskytovatel: %1$s." - "Aktuální push poskytovatel" - "Ujistěte se, že aplikace má alespoň jednoho push poskytovatele." - "Nebyli nalezeni žádní push poskytovatelé." - - "Nalezen %1$d push poskytovatel: %2$s" - "Nalezeni %1$d push poskytovatelé: %2$s" - "Nalezeno %1$d push poskytovatelů: %2$s" - - "Zjistit push poskytovatele" - "Zkontrolujte, zda aplikace může zobrazit oznámení." - "Na oznámení nebylo kliknuto." - "Oznámení nelze zobrazit." - "Na oznámení bylo kliknuto!" - "Zobrazit oznámení" - "Kliknutím na oznámení pokračujte v testu." - "Ujistěte se, že je k dispozici Firebase." - "Firebase není k dispozici." - "Firebase je k dispozici." - "Zkontrolovat Firebase" - "Ujistěte se, že je k dispozici Firebase token." - "Firebase token není znám." - "Firebase token: %1$s." - "Zkontrolovat Firebase token" - "Ujistěte se, že aplikace přijímá push." - "Chyba: pusher odmítl požadavek." - "Chyba: %1$s." - "Chyba, nelze otestovat push." - "Chyba, časový limit čekání na push." - "Push zpětná smyčka trvala %1$d ms." - "Otestovat push zpětnou smyčku" - "Ujistěte se, že jsou k dispozici distributoři UnifiedPush." - "Nebyli nalezeni žádní push distributoři." - - "Nalezen %1$d distributor: %2$s." - "Nalezeni %1$d distributoři: %2$s." - "Nalezeno %1$d distributorů: %2$s." - - "Zkontrolovat UnifiedPush" 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 19408138b7..8a3e035833 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -271,55 +271,4 @@ "Standort" "Version: %1$s (%2$s)" "en" - "Fehlerbehebung" - "Fehlerbehebung für Benachrichtigungen" - "Tests durchführen" - "Tests erneut durchführen" - "Einige Tests sind fehlgeschlagen. Bitte überprüfe die Details." - "Führe die Tests durch, um Probleme zu erkennen, die dazu führen können, dass sich die Benachrichtigungen nicht wie erwartet verhalten." - "Versuche das Problem zu beheben" - "Alle Tests wurden erfolgreich bestanden." - "Fehlerbehebung für Benachrichtigungen" - "Einige Tests erfordern deine Aufmerksamkeit. Bitte überprüfe die Details." - "Stelle sicher, dass die Anwendung Benachrichtigungen anzeigen kann." - "Berechtigungen überprüfen" - "Ermittele den Namen des aktuellen Anbieters." - "Kein Push-Anbieter ausgewählt." - "Aktueller Push-Anbieter:%1$s." - "Aktueller Push-Anbieter" - "Stelle sicher, dass die Anwendung mindestens einen Push-Anbieter hat." - "Keine Push-Anbieter gefunden." - - "%1$d Push-Anbieter gefunden: %2$s" - "%1$d Push-Anbieter gefunden: %2$s" - - "Push-Anbieter erkennen" - "Prüfe, ob die Anwendung Benachrichtigungen anzeigen kann." - "Die Benachrichtigung wurde nicht angeklickt." - "Die Benachrichtigung kann nicht angezeigt werden." - "Die Benachrichtigung wurde angeklickt!" - "Benachrichtigung anzeigen" - "Bitte klicke auf die Benachrichtigung, um den Test fortzusetzen." - "Stelle sicher, dass Firebase verfügbar ist." - "Firebase ist nicht verfügbar." - "Firebase ist verfügbar." - "Überprüfe Firebase" - "Stelle sicher, dass der Firebase Token verfügbar ist." - "Firebase Token ist nicht bekannt." - "Firebase Token: %1$s." - "Prüfe Firebase Token" - "Stelle sicher, dass die Anwendung Push-Nachrichten empfängt." - "Fehler: Der Pusher hat die Anfrage abgelehnt." - "Fehler:%1$s." - "Fehler: Push kann nicht getestet werden." - "Fehler: Timeout beim Warten auf Push." - "Push-Loop-Back Dauer: %1$d ms." - "Teste Push-Loop-Back" - "Stelle sicher, dass UnifiedPush-Verteiler verfügbar sind." - "Keine Push-Verteiler gefunden." - - "%1$d Verteiler gefunden: %2$s." - "%1$d Verteiler gefunden: %2$s." - - "UnifiedPush prüfen" 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 9cd0d2715d..e3b6e3d0d9 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -271,55 +271,4 @@ "Position" "Version : %1$s ( %2$s )" "Ang." - "Dépannage" - "Résoudre les problèmes liés aux notifications" - "Exécuter les tests" - "Relancer les tests" - "Certains tests ont échoué. Veuillez vérifier les détails." - "Exécuter les tests pour détecter tout problème dans votre configuration susceptible de provoquer un dysfonctionnement des notifications." - "Tenter de corriger" - "Tous les tests ont réussi." - "Dépanner les notifications" - "Certains tests nécessitent votre attention. Veuillez vérifier les détails." - "Vérifie que l’application peut afficher des notifications." - "Vérifier les autorisations" - "Obtenir le nom du fournisseur de Push actuel." - "Aucun fournisseur de Push n’est sélectionné." - "Fournisseur de Push actuel : %1$s." - "Fournisseur de Push actuel" - "Vérifier que l’application possède au moins un fournisseur de Push." - "Aucun fournisseur de Push n’a été trouvé." - - "%1$d fournisseur de Push détecté : %2$s" - "%1$d fournisseurs de Push détectés : %2$s" - - "Détecter les fournisseurs de Push" - "Vérifier que l’application peut afficher des notifications." - "Vous n’avez pas cliqué sur la notification." - "Impossible d’afficher la notification." - "Vous avez cliqué sur la notification!" - "Affichage des notifications" - "Veuillez cliquer sur la notification pour continuer le test." - "Vérification que Firebase est disponible." - "Firebase n’est pas disponible." - "Firebase est disponible." - "Vérification de Firebase" - "Vérifier que le jeton Firebase est disponible." - "Le jeton Firebase n’est pas connu." - "Jeton Firebase :%1$s." - "Vérifier le jeton Firebase" - "Vérifier que l’application reçoit les Push." - "Erreur : le Pusher a rejeté la demande." - "Erreur :%1$s." - "Erreur, impossible de tester les Push." - "Erreur, le délai d’attente du Push est dépassé." - "La demande d’envoi de Push et sa réception ont pris %1$d ms." - "Tester la réception des Push" - "Vérifier qu’au moins un distributeur UnifiedPush est disponible." - "Aucun distributeur UnifiedPush n’a été trouvé." - - "%1$d distributeur détecté :%2$s." - "%1$d distributeurs détectés :%2$s." - - "Vérifier UnifiedPush" 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 9c343ec200..43568cafb6 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -271,55 +271,4 @@ "Hely" "Verzió: %1$s (%2$s)" "hu" - "Hibaelhárítás" - "Értesítések hibaelhárítása" - "Tesztek futtatása" - "Tesztek újbóli futtatása" - "Egyes tesztek sikertelenek voltak. Ellenőrizze a részleteket." - "A tesztek futtatása, hogy észlelje a konfigurációban felmerülő olyan problémákat, amelyek miatt az értesítések nem az elvárt módon viselkednek." - "Kísérlet a javításra" - "Minden teszt sikeresen lezajlott." - "Értesítések hibaelhárítása" - "Egyes tesztek a figyelmét igénylik. Ellenőrizze a részleteket." - "Ellenőrizze, hogy az alkalmazás képes-e értesítéseket megjeleníteni." - "Engedélyek ellenőrzése" - "A jelenlegi szolgáltató nevének lekérdezése." - "Nincs kiválasztva leküldéses értesítési szolgáltató." - "Jelenlegi leküldéses értesítési szolgáltató: %1$s." - "Jelenlegi leküldéses értesítési szolgáltató" - "Győződjön meg arról, hogy az alkalmazás legalább egy leküldéses értesítési szolgáltatóval rendelkezik." - "Nem található leküldéses értesítési szolgáltató." - - "%1$d leküldéses szolgáltató találva: %2$s" - "%1$d leküldéses szolgáltató találva: %2$s" - - "Leküldéses értesítési szolgáltatók észlelése" - "Ellenőrizze, hogy az alkalmazás képes-e megjeleníteni az értesítést." - "Az értesítésre nem kattintottak rá." - "Az értesítés nem jeleníthető meg." - "Az értesítésre rákattintottak!" - "Értesítés megjelenítése" - "A teszt folytatásához kattintson az értesítésre." - "Győződjön meg arról, hogy a Firebase elérhető-e." - "A Firebase nem érhető el." - "A Firebase elérhető." - "Ellenőrizze a Firebase-t" - "Győződjön meg arról, hogy a Firebase-token elérhető." - "A Firebase-token nem ismert." - "Firebase-token: %1$s." - "Ellenőrizze a Firebase-tokent" - "Győződjön meg arról, hogy az alkalmazás megkapja-e a leküldéses értesítést." - "Hiba: a leküldő elutasította a kérést." - "Hiba: %1$s." - "Hiba, nem lehet tesztelni a leküldéses értesítést." - "Hiba, időtúllépés a leküldéses értesítésre való várakozás során." - "A leküldéses értesítés folyamata %1$d ezredmásodpercig tartott." - "Tesztelje a leküldéses értesítés folyamatát" - "Győződjön meg arról, hogy a UnifiedPush forgalmazói elérhetők." - "Nem található forgalmazó a leküldéses értesítésekhez." - - "%1$d forgalmazó található: %2$s." - "%1$d forgalmazó található: %2$s." - - "Ellenőrizze a UnifiedPush szolgáltatást" 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 a5a98b5d35..fc237cac27 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -267,53 +267,4 @@ "Lokasi" "Versi: %1$s (%2$s)" "id" - "Pemecahan masalah" - "Pecahkan masalah notifikasi" - "Jalankan tes" - "Jalankan tes lagi" - "Beberapa tes gagal. Silakan periksa detailnya." - "Jalankan pengujian untuk mendeteksi masalah apa pun dalam konfigurasi Anda yang mungkin membuat notifikasi tidak berperilaku seperti yang diharapkan." - "Mencoba untuk memperbaiki" - "Semua tes berhasil dilalui." - "Pecahkan masalah notifikasi" - "Beberapa tes membutuhkan perhatian Anda. Silakan periksa detailnya." - "Pastikan aplikasi dapat menampilkan notifikasi." - "Periksa izin" - "Dapatkan nama penyedia saat ini." - "Tidak ada penyedia notifikasi dorongan yang dipilih." - "Penyedia notifikasi dorongan saat ini: %1$s." - "Penyedia notifikasi dorongan saat ini" - "Pastikan aplikasi memiliki setidaknya satu penyedia notifikasi dorongan." - "Tidak ada penyedia notifikasi dorongan yang ditemukan." - - "Ditemukan %1$d penyedia notifikasi dorongan: %2$s" - - "Deteksi penyedia notifikasi dorongan" - "Periksa apakah aplikasi dapat menampilkan notifikasi." - "Notifikasi belum diklik." - "Tidak dapat menampilkan notifikasi." - "Notifikasi telah diklik!" - "Tampilan notifikasi" - "Silakan klik pada notifikasi untuk melanjutkan tes." - "Pastikan bahwa Firebase tersedia." - "Firebase tidak tersedia." - "Firebase tersedia." - "Periksa Firebase" - "Pastikan token Firebase tersedia." - "Token Firebase tidak diketahui." - "Token Firebase: %1$s." - "Periksa token Firebase" - "Pastikan aplikasi menerima notifikasi dorongan." - "Kesalahan: pendorong telah menolak permintaan." - "Kesalahan: %1$s." - "Terjadi kesalahan, tidak dapat menguji notifikasi dorongan." - "Terjadi kesalahan, melebihi batas waktu menunggu notifikasi dorongan." - "Ulangan notifikasi dorongan membutuhkan %1$d ms." - "Uji ulangan notifikasi dorongan lagi" - "Pastikan distributor UnifiedPush tersedia." - "Tidak ada distributor notifikasi dorongan yang ditemukan." - - "%1$d distributor ditemukan: %2$s." - - "Periksa UnifiedPush" 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 ca4731f34f..e7a564596d 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -275,57 +275,4 @@ "Местоположение" "Версия: %1$s (%2$s)" "ru" - "Устранение неполадок" - "Уведомления об устранении неполадок" - "Выполнение тестов" - "Повторное выполнение тестов" - "Некоторые тесты провалились. Пожалуйста, проверьте детали." - "Выполните тесты, чтобы обнаружить любую проблему в конфигурации, из-за которой уведомления могут работать не так, как ожидалось." - "Попытка исправить" - "Все тесты прошли успешно." - "Уведомления об устранении неполадок" - "Некоторые тесты требуют вашего внимания. Пожалуйста, проверьте детали." - "Убедитесь, что приложение может показывать уведомления." - "Проверка разрешений" - "Получение имени текущего поставщика." - "Поставщики push-уведомлений не выбраны." - "Текущий поставщик push-уведомлений: %1$s." - "Текущий поставщик push-уведомлений" - "Убедитесь, что у приложения есть хотя бы один поставщик push-сообщений." - "Поставщики push-уведомлений не найдены." - - "Найден %1$d push-провайдер: %2$s" - "Найдено %1$d push-провайдеров: %2$s" - "Найдено %1$d push-провайдеров: %2$s" - - "Обнаружение поставщиков push-уведомлений" - "Убедитесь, что приложение может отображать уведомление." - "Уведомление не было нажато." - "Невозможно отобразить уведомление." - "Уведомление было нажато!" - "Отобразить уведомление" - "Нажмите на уведомление, чтобы продолжить тест." - "Убедитесь, что Firebase доступен." - "Firebase недоступен." - "Firebase доступен." - "Проверить Firebase" - "Убедитесь, что токен Firebase доступен." - "Токен Firebase неизвестен." - "Токен Firebase: %1$s." - "Проверить токен Firebase" - "Убедитесь, что приложение получает push-сообщение." - "Ошибка: pusher отклонил запрос." - "Ошибка: %1$s." - "Ошибка, невозможно протестировать отправку." - "Ошибка, тайм-аут ожидания push-уведомления." - "Обратная отправка push-уведомления, заняла %1$d мс." - "Тест обратной отправки push-уведомления" - "Убедитесь, что дистрибьюторы UnifiedPush доступны." - "Поставщиков push-уведомлений не найдено." - - "%1$d провайдер найден: %2$s." - "%1$d провайдеров найдено: %2$s." - "%1$d провайдеров найдено: %2$s." - - "Проверка UnifiedPush" 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 61e7b50cc0..f49201bda1 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -274,57 +274,4 @@ "Poloha" "Verzia: %1$s (%2$s)" "sk" - "Riešenie problémov" - "Oznámenia riešení problémov" - "Spustiť testy" - "Spustiť testy znova" - "Niektoré testy zlyhali. Skontrolujte prosím podrobnosti." - "Spustite testy, aby ste zistili akýkoľvek problém vo vašej konfigurácii, ktorý môže spôsobiť, že sa upozornenia nebudú správať podľa očakávania." - "Pokus o opravu" - "Všetky testy prebehli úspešne." - "Oznámenia riešení problémov" - "Niektoré testy si vyžadujú vašu pozornosť. Prosím skontrolujte podrobnosti." - "Uistite sa, že aplikácia dokáže zobrazovať upozornenia." - "Skontrolovať povolenia" - "Získaťe názov aktuálneho poskytovateľa." - "Nie sú vybraní žiadni poskytovatelia push." - "Aktuálny poskytovateľ push: %1$s." - "Aktuálny poskytovateľ push" - "Uistite sa, že aplikácia má aspoň jedného poskytovateľa push." - "Nenašli sa žiadni poskytovatelia push." - - "Nájdený %1$d poskytovateľ služby push: %2$s" - "Nájdení %1$d poskytovatelia služby push: %2$s" - "Nájdených %1$d poskytovateľov služby push: %2$s" - - "Zistiť poskytovateľov push" - "Skontrolujte, či aplikácia dokáže zobraziť upozornenie." - "Na oznámenie nebolo kliknuté." - "Nie je možné zobraziť upozornenie." - "Na oznámenie bolo kliknuté!" - "Zobraziť upozornenie" - "Kliknite na upozornenie a pokračujte v teste." - "Uistite sa, že Firebase je k dispozícii." - "Firebase nie je k dispozícii." - "Firebase je k dispozícii." - "Skontrolovať Firebase" - "Uistite sa, že je k dispozícii token Firebase." - "Token Firebase nie je známy." - "Token Firebase: %1$s." - "Skontrolovať token Firebase" - "Uistite sa, že aplikácia prijíma push oznámenia." - "Chyba: pusher odmietol požiadavku." - "Chyba: %1$s." - "Chyba, nie je možné testovať push." - "Chyba, časový limit na push vypršal." - "Push loop back trvalo %1$d ms." - "Testovať Push loop back" - "Uistite sa, že sú dostupní distribútori UnifiedPush." - "Nenašli sa žiadni distribútori push." - - "%1$d nájdený distribútor: %2$s." - "%1$d nájdení distribútori: %2$s." - "%1$d nájdených distribútorov: %2$s." - - "Skontrolovať UnifiedPush" 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 7ed1b0daa8..b7029fa178 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -182,6 +182,7 @@ "%1$s kunde inte komma åt din plats. Vänligen försök igen senare." "%1$s är inte behörig att komma åt din plats. Du kan aktivera åtkomst i Inställningar." "%1$s är inte behörig att komma åt din plats. Aktivera åtkomst nedan." + "%1$s är inte behörig att komma åt din mikrofon. Aktivera åtkomst för att spela in ett röstmeddelande." "Vissa meddelanden har inte skickats" "Tyvärr, ett fel uppstod" "🔐️ Häng med mig på %1$s" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 1bc20364ce..2f9812fc79 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -272,55 +272,4 @@ "Version: %1$s (%2$s)" "en" "en" - "Troubleshoot" - "Troubleshoot notifications" - "Run tests" - "Run tests again" - "Some tests failed. Please check the details." - "Run the tests to detect any issue in your configuration that may make notifications not behave as expected." - "Attempt to fix" - "All tests passed successfully." - "Troubleshoot notifications" - "Some tests require your attention. Please check the details." - "Check that the application can show notifications." - "Check permissions" - "Get the name of the current provider." - "No push providers selected." - "Current push provider: %1$s." - "Current push provider" - "Ensure that the application has at least one push provider." - "No push providers found." - - "Found %1$d push provider: %2$s" - "Found %1$d push providers: %2$s" - - "Detect push providers" - "Check that the application can display notification." - "The notification has not been clicked." - "Cannot display the notification." - "The notification has been clicked!" - "Display notification" - "Please click on the notification to continue the test." - "Ensure that Firebase is available." - "Firebase is not available." - "Firebase is available." - "Check Firebase" - "Ensure that Firebase token is available." - "Firebase token is not known." - "Firebase token: %1$s." - "Check Firebase token" - "Ensure that the application is receiving push." - "Error: pusher has rejected the request." - "Error: %1$s." - "Error, cannot test push." - "Error, timeout waiting for push." - "Push loop back took %1$d ms." - "Test Push loop back" - "Ensure that UnifiedPush distributors are available." - "No push distributors found." - - "%1$d distributor found: %2$s." - "%1$d distributors found: %2$s." - - "Check UnifiedPush" From 52c039d6760ee359b075f4b21a87bad155080519 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 16:23:57 +0200 Subject: [PATCH 065/124] Rename `UserPushStoreFactory.create` to `UserPushStoreFactory.getOrCreate` for code clarity. --- .../impl/notifications/NotificationSettingsPresenter.kt | 2 +- .../libraries/push/impl/DefaultGetCurrentPushProvider.kt | 2 +- .../element/android/libraries/push/impl/DefaultPushService.kt | 2 +- .../io/element/android/libraries/push/impl/PushersManager.kt | 2 +- .../android/libraries/push/impl/push/DefaultPushHandler.kt | 2 +- .../pushproviders/firebase/FirebaseNewTokenHandler.kt | 2 +- .../pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt | 2 +- .../android/libraries/pushstore/api/UserPushStoreFactory.kt | 2 +- .../pushstore/impl/DefaultUserPushStoreFactoryTest.kt | 4 ++-- .../libraries/pushstore/impl/DefaultUserPushStoreFactory.kt | 4 ++-- .../pushstore/test/userpushstore/FakeUserPushStoreFactory.kt | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 7083b9f88b..9aa9cafb81 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -48,7 +48,7 @@ class NotificationSettingsPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): NotificationSettingsState { - val userPushStore = remember { userPushStoreFactory.create(matrixClient.sessionId) } + val userPushStore = remember { userPushStoreFactory.getOrCreate(matrixClient.sessionId) } val systemNotificationsEnabled: MutableState = remember { mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled()) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt index bdf36b7f31..977d41caca 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt @@ -35,7 +35,7 @@ class DefaultGetCurrentPushProvider @Inject constructor( .value .navigationState .currentSessionId() - ?.let { pushStoreFactory.create(it) } + ?.let { pushStoreFactory.getOrCreate(it) } ?.getPushProviderName() } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt index 5a4df57b47..cff18cfb3d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt @@ -49,7 +49,7 @@ class DefaultPushService @Inject constructor( * Get current push provider, compare with provided one, then unregister and register if different, and store change. */ override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) { - val userPushStore = userPushStoreFactory.create(matrixClient.sessionId) + val userPushStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId) val currentPushProviderName = userPushStore.getPushProviderName() if (currentPushProviderName != pushProvider.name) { // Unregister previous one if any diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index dbefc03126..4306072e48 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -63,7 +63,7 @@ class PushersManager @Inject constructor( * Register a pusher to the server if not done yet. */ override suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) { - val userDataStore = userPushStoreFactory.create(matrixClient.sessionId) + val userDataStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId) if (userDataStore.getCurrentRegisteredPushKey() == pushKey) { Timber.tag(loggerTag.value) .d("Unnecessary to register again the same pusher, but do it in case the pusher has been removed from the server") diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index 5de0620eca..2c9abb77b3 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -122,7 +122,7 @@ class DefaultPushHandler @Inject constructor( return } - val userPushStore = userPushStoreFactory.create(userId) + val userPushStore = userPushStoreFactory.getOrCreate(userId) if (!userPushStore.getNotificationEnabledForDevice().first()) { // TODO We need to check if this is an incoming call Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.") diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt index 313b9ab706..20d0de4ebf 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt @@ -44,7 +44,7 @@ class FirebaseNewTokenHandler @Inject constructor( sessionStore.getAllSessions().toUserList() .map { SessionId(it) } .forEach { userId -> - val userDataStore = userPushStoreFactory.create(userId) + val userDataStore = userPushStoreFactory.getOrCreate(userId) if (userDataStore.getPushProviderName() == FirebaseConfig.NAME) { matrixAuthenticationService.restoreSession(userId).getOrNull()?.use { client -> pusherSubscriber.registerPusher(client, firebaseToken, FirebaseConfig.PUSHER_HTTP_URL) diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt index 2ae8753a1e..4ee637a3ab 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt @@ -40,7 +40,7 @@ class UnifiedPushNewGatewayHandler @Inject constructor( val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Unit.also { Timber.w("Unable to retrieve session") } - val userDataStore = userPushStoreFactory.create(userId) + val userDataStore = userPushStoreFactory.getOrCreate(userId) if (userDataStore.getPushProviderName() == UnifiedPushConfig.NAME) { matrixAuthenticationService.restoreSession(userId).getOrNull()?.use { client -> pusherSubscriber.registerPusher(client, endpoint, pushGateway) diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt index 52e4596ca0..95097845ab 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt @@ -22,5 +22,5 @@ import io.element.android.libraries.matrix.api.core.SessionId * Store data related to push about a user. */ interface UserPushStoreFactory { - fun create(userId: SessionId): UserPushStore + fun getOrCreate(userId: SessionId): UserPushStore } diff --git a/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt index b7bbf46dc4..7f130459fa 100644 --- a/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt +++ b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt @@ -40,11 +40,11 @@ class DefaultUserPushStoreFactoryTest { val userPushStoreFactory = DefaultUserPushStoreFactory(context, NoOpSessionObserver()) var userPushStore1: UserPushStore? = null val thread1 = thread { - userPushStore1 = userPushStoreFactory.create(sessionId) + userPushStore1 = userPushStoreFactory.getOrCreate(sessionId) } var userPushStore2: UserPushStore? = null val thread2 = thread { - userPushStore2 = userPushStoreFactory.create(sessionId) + userPushStore2 = userPushStoreFactory.getOrCreate(sessionId) } thread1.join() thread2.join() diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt index 8c85dca80c..3c65c76a01 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt @@ -41,7 +41,7 @@ class DefaultUserPushStoreFactory @Inject constructor( // We can have only one class accessing a single data store, so keep a cache of them. private val cache = ConcurrentHashMap() - override fun create(userId: SessionId): UserPushStore { + override fun getOrCreate(userId: SessionId): UserPushStore { return cache.getOrPut(userId) { UserPushStoreDataStore( context = context, @@ -60,6 +60,6 @@ class DefaultUserPushStoreFactory @Inject constructor( override suspend fun onSessionDeleted(userId: String) { // Delete the store - create(SessionId(userId)).reset() + getOrCreate(SessionId(userId)).reset() } } diff --git a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt index a529e34bc1..2f4f524cc2 100644 --- a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt +++ b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.pushstore.api.UserPushStore import io.element.android.libraries.pushstore.api.UserPushStoreFactory class FakeUserPushStoreFactory : UserPushStoreFactory { - override fun create(userId: SessionId): UserPushStore { + override fun getOrCreate(userId: SessionId): UserPushStore { return FakeUserPushStore() } } From 4f320fd4f81ac42e58b91e6db8beec4e326c6ed9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 16:40:07 +0200 Subject: [PATCH 066/124] Code clarity: use withTimeout with combination of runCatching instead of withTimeoutOrNull --- .../impl/troubleshoot/NotificationTest.kt | 38 ++++++++++--------- .../impl/troubleshoot/PushLoopbackTest.kt | 36 ++++++++++-------- 2 files changed, 41 insertions(+), 33 deletions(-) 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 d7e3641e22..26b7161fca 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 @@ -29,7 +29,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.withTimeout import timber.log.Timber import javax.inject.Inject import kotlin.time.Duration.Companion.seconds @@ -72,22 +72,26 @@ class NotificationTest @Inject constructor( notificationClickHandler.state.first() Timber.d("Notification clicked!") } - val s = withTimeoutOrNull(30.seconds) { - job.join() - } - job.cancel() - if (s == null) { - notificationDisplayer.dismissDiagnosticNotification() - delegate.updateState( - description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_failure), - status = NotificationTroubleshootTestState.Status.Failure(false) - ) - } else { - delegate.updateState( - description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_success), - status = NotificationTroubleshootTestState.Status.Success - ) - } + runCatching { + withTimeout(30.seconds) { + job.join() + } + }.fold( + onSuccess = { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_success), + status = NotificationTroubleshootTestState.Status.Success + ) + }, + onFailure = { + job.cancel() + notificationDisplayer.dismissDiagnosticNotification() + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_failure), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + ) }.invokeOnCompletion { // Ensure that the notification is cancelled when the screen is left notificationDisplayer.dismissDiagnosticNotification() 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 2ecec61996..861554bef8 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 @@ -31,7 +31,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.withTimeout import timber.log.Timber import javax.inject.Inject import kotlin.time.Duration.Companion.seconds @@ -84,21 +84,25 @@ class PushLoopbackTest @Inject constructor( job.cancel() return } - val result = withTimeoutOrNull(10.seconds) { - completable.await() - } - job.cancel() - if (result == null) { - delegate.updateState( - description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_4), - status = NotificationTroubleshootTestState.Status.Failure(false) - ) - } else { - delegate.updateState( - description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_success, result), - status = NotificationTroubleshootTestState.Status.Success - ) - } + runCatching { + withTimeout(10.seconds) { + completable.await() + } + }.fold( + onSuccess = { duration -> + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_success, duration), + status = NotificationTroubleshootTestState.Status.Success + ) + }, + onFailure = { + job.cancel() + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_4), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + ) } override suspend fun reset() = delegate.reset() From cb435c523b5cf74cc149fbd740c559d5b45b1c7a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 18:03:39 +0200 Subject: [PATCH 067/124] Create dedicated module for notification troubleshoot. --- features/preferences/impl/build.gradle.kts | 1 + .../preferences/impl/PreferencesFlowNode.kt | 12 +++- .../src/main/res/values-be/translations.xml | 8 --- .../src/main/res/values-cs/translations.xml | 8 --- .../src/main/res/values-de/translations.xml | 8 --- .../src/main/res/values-fr/translations.xml | 8 --- .../src/main/res/values-hu/translations.xml | 8 --- .../src/main/res/values-in/translations.xml | 8 --- .../src/main/res/values-ru/translations.xml | 8 --- .../src/main/res/values-sk/translations.xml | 8 --- .../impl/src/main/res/values/localazy.xml | 8 --- libraries/permissions/impl/build.gradle.kts | 1 + ...ficationTroubleshootCheckPermissionTest.kt | 6 +- ...tionTroubleshootCheckPermissionTestTest.kt | 1 - libraries/push/impl/build.gradle.kts | 1 + .../troubleshoot/CurrentPushProviderTest.kt | 6 +- .../impl/troubleshoot/NotificationTest.kt | 6 +- .../impl/troubleshoot/PushLoopbackTest.kt | 6 +- .../impl/troubleshoot/PushProvidersTest.kt | 6 +- .../CurrentPushProviderTestTest.kt | 2 +- .../impl/troubleshoot/NotificationTestTest.kt | 2 +- .../impl/troubleshoot/PushLoopbackTestTest.kt | 2 +- .../troubleshoot/PushProvidersTestTest.kt | 2 +- .../pushproviders/firebase/build.gradle.kts | 1 + .../troubleshoot/FirebaseAvailabilityTest.kt | 8 +-- .../troubleshoot/FirebaseTokenTest.kt | 8 +-- .../FirebaseAvailabilityTestTest.kt | 2 +- .../troubleshoot/FirebaseTokenTestTest.kt | 2 +- .../unifiedpush/build.gradle.kts | 1 + .../troubleshoot/UnifiedPushTest.kt | 8 +-- .../troubleshoot/UnifiedPushTestTest.kt | 2 +- libraries/troubleshoot/api/build.gradle.kts | 28 +++++++++ .../api/NotificationTroubleShootEntryPoint.kt | 35 +++++++++++ .../api/test}/NotificationTroubleshootTest.kt | 2 +- .../NotificationTroubleshootTestDelegate.kt | 2 +- .../NotificationTroubleshootTestState.kt | 2 +- .../troubleshoot/api/test}/TestFilterData.kt | 2 +- libraries/troubleshoot/impl/build.gradle.kts | 58 +++++++++++++++++++ ...faultNotificationTroubleShootEntryPoint.kt | 44 ++++++++++++++ .../impl}/TroubleshootNotificationsEvents.kt | 2 +- .../impl}/TroubleshootNotificationsNode.kt | 12 +++- .../TroubleshootNotificationsPresenter.kt | 2 +- .../impl}/TroubleshootNotificationsState.kt | 2 +- .../TroubleshootNotificationsStateProvider.kt | 4 +- .../impl}/TroubleshootNotificationsView.kt | 7 +-- .../impl}/TroubleshootTestSuite.kt | 8 +-- .../impl}/TroubleshootTestSuiteState.kt | 4 +- .../src/main/res/values-be/translations.xml | 11 ++++ .../src/main/res/values-cs/translations.xml | 11 ++++ .../src/main/res/values-de/translations.xml | 11 ++++ .../src/main/res/values-fr/translations.xml | 11 ++++ .../src/main/res/values-hu/translations.xml | 11 ++++ .../src/main/res/values-ru/translations.xml | 11 ++++ .../src/main/res/values-sk/translations.xml | 11 ++++ .../impl/src/main/res/values/localazy.xml | 11 ++++ .../impl}/FakeNotificationTroubleshootTest.kt | 6 +- ...TroubleshootNotificationsPresenterTests.kt | 8 +-- .../TroubleshootNotificationsViewTest.kt | 14 ++++- .../kotlin/extension/DependencyHandleScope.kt | 1 + tools/localazy/config.json | 7 ++- 60 files changed, 355 insertions(+), 141 deletions(-) create mode 100644 libraries/troubleshoot/api/build.gradle.kts create mode 100644 libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt rename libraries/{core/src/main/kotlin/io/element/android/libraries/core/notifications => troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test}/NotificationTroubleshootTest.kt (94%) rename libraries/{core/src/main/kotlin/io/element/android/libraries/core/notifications => troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test}/NotificationTroubleshootTestDelegate.kt (97%) rename libraries/{core/src/main/kotlin/io/element/android/libraries/core/notifications => troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test}/NotificationTroubleshootTestState.kt (94%) rename libraries/{core/src/main/kotlin/io/element/android/libraries/core/notifications => troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test}/TestFilterData.kt (91%) create mode 100644 libraries/troubleshoot/impl/build.gradle.kts create mode 100644 libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsEvents.kt (91%) rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsNode.kt (82%) rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsPresenter.kt (96%) rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsState.kt (91%) rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsStateProvider.kt (96%) rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsView.kt (96%) rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootTestSuite.kt (93%) rename {features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootTestSuiteState.kt (83%) create mode 100644 libraries/troubleshoot/impl/src/main/res/values-be/translations.xml create mode 100644 libraries/troubleshoot/impl/src/main/res/values-cs/translations.xml create mode 100644 libraries/troubleshoot/impl/src/main/res/values-de/translations.xml create mode 100644 libraries/troubleshoot/impl/src/main/res/values-fr/translations.xml create mode 100644 libraries/troubleshoot/impl/src/main/res/values-hu/translations.xml create mode 100644 libraries/troubleshoot/impl/src/main/res/values-ru/translations.xml create mode 100644 libraries/troubleshoot/impl/src/main/res/values-sk/translations.xml create mode 100644 libraries/troubleshoot/impl/src/main/res/values/localazy.xml rename {features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl}/FakeNotificationTroubleshootTest.kt (91%) rename {features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsPresenterTests.kt (94%) rename {features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot => libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl}/TroubleshootNotificationsViewTest.kt (91%) diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index 415f0e36b4..edfb275f17 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { implementation(projects.libraries.pushstore.api) implementation(projects.libraries.indicator.api) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.troubleshoot.api) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.libraries.matrixui) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 5f57ad8b5e..1dc5f650fd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -24,6 +24,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -39,7 +40,6 @@ import io.element.android.features.preferences.impl.developer.DeveloperSettingsN import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode import io.element.android.features.preferences.impl.notifications.NotificationSettingsNode import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingNode -import io.element.android.features.preferences.impl.notifications.troubleshoot.TroubleshootNotificationsNode import io.element.android.features.preferences.impl.root.PreferencesRootNode import io.element.android.features.preferences.impl.user.editprofile.EditUserProfileNode import io.element.android.libraries.architecture.BackstackView @@ -48,6 +48,7 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint import kotlinx.parcelize.Parcelize @ContributesNode(SessionScope::class) @@ -55,6 +56,7 @@ class PreferencesFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val lockScreenEntryPoint: LockScreenEntryPoint, + private val notificationTroubleShootEntryPoint: NotificationTroubleShootEntryPoint, private val logoutEntryPoint: LogoutEntryPoint, ) : BaseFlowNode( backstack = BackStack( @@ -189,7 +191,13 @@ class PreferencesFlowNode @AssistedInject constructor( createNode(buildContext, listOf(notificationSettingsCallback)) } NavTarget.TroubleshootNotifications -> { - createNode(buildContext) + notificationTroubleShootEntryPoint.nodeBuilder(this, buildContext) + .callback(object : NotificationTroubleShootEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + }) + .build() } is NavTarget.EditDefaultNotificationSetting -> { val callback = object : EditDefaultNotificationSettingNode.Callback { diff --git a/features/preferences/impl/src/main/res/values-be/translations.xml b/features/preferences/impl/src/main/res/values-be/translations.xml index 0a8e477f7f..450a47ce61 100644 --- a/features/preferences/impl/src/main/res/values-be/translations.xml +++ b/features/preferences/impl/src/main/res/values-be/translations.xml @@ -51,12 +51,4 @@ "Апавяшчэнні" "Выпраўленне непаладак" "Выпраўленне непаладак з апавяшчэннямі" - "Запусціць тэсты" - "Запусціце тэсты яшчэ раз" - "Некаторыя тэсты не ўдаліся. Калі ласка, праглядзіце дэталі." - "Запусціце тэсты, каб выявіць праблемы ў вашай канфігурацыі, з-за якіх апавяшчэння могуць паводзіць сябе не так, як чакалася." - "Спроба выпраўлення" - "Усе тэсты паспяхова пройдзены." - "Выпраўленне непаладак з апавяшчэннямі" - "Некаторыя тэсты патрабуюць вашай увагі. Калі ласка, праглядзіце дэталі." diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml index a2a3cc5955..60bc4b2b3e 100644 --- a/features/preferences/impl/src/main/res/values-cs/translations.xml +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -53,12 +53,4 @@ Pokud budete pokračovat, některá nastavení se mohou změnit."
    "Oznámení" "Odstraňování problémů" "Odstraňování problémů s upozorněními" - "Spustit testy" - "Spustit testy znovu" - "Některé testy selhaly. Zkontrolujte prosím podrobnosti." - "Spusťte testy, abyste zjistili jakýkoli problém ve vaší konfiguraci, který může způsobit, že se oznámení nebudou chovat podle očekávání." - "Pokus o opravu" - "Všechny testy proběhly úspěšně." - "Odstraňování problémů s upozorněními" - "Některé testy vyžadují vaši pozornost. Zkontrolujte prosím podrobnosti." diff --git a/features/preferences/impl/src/main/res/values-de/translations.xml b/features/preferences/impl/src/main/res/values-de/translations.xml index 04a27f9db6..65b4923452 100644 --- a/features/preferences/impl/src/main/res/values-de/translations.xml +++ b/features/preferences/impl/src/main/res/values-de/translations.xml @@ -51,12 +51,4 @@ Wenn du fortfährst, können sich einige deiner Einstellungen ändern."
    "Benachrichtigungen" "Fehlerbehebung" "Fehlerbehebung für Benachrichtigungen" - "Tests durchführen" - "Tests erneut durchführen" - "Einige Tests sind fehlgeschlagen. Bitte überprüfe die Details." - "Führe die Tests durch, um Probleme zu erkennen, die dazu führen können, dass sich die Benachrichtigungen nicht wie erwartet verhalten." - "Versuche das Problem zu beheben" - "Alle Tests wurden erfolgreich bestanden." - "Fehlerbehebung für Benachrichtigungen" - "Einige Tests erfordern deine Aufmerksamkeit. Bitte überprüfe die Details." diff --git a/features/preferences/impl/src/main/res/values-fr/translations.xml b/features/preferences/impl/src/main/res/values-fr/translations.xml index 426c475d4c..9c415fea2d 100644 --- a/features/preferences/impl/src/main/res/values-fr/translations.xml +++ b/features/preferences/impl/src/main/res/values-fr/translations.xml @@ -51,12 +51,4 @@ Si vous continuez, il est possible que certains de vos paramètres soient modifi "Notifications" "Dépannage" "Résoudre les problèmes liés aux notifications" - "Exécuter les tests" - "Relancer les tests" - "Certains tests ont échoué. Veuillez vérifier les détails." - "Exécuter les tests pour détecter tout problème dans votre configuration susceptible de provoquer un dysfonctionnement des notifications." - "Tenter de corriger" - "Tous les tests ont réussi." - "Dépanner les notifications" - "Certains tests nécessitent votre attention. Veuillez vérifier les détails." diff --git a/features/preferences/impl/src/main/res/values-hu/translations.xml b/features/preferences/impl/src/main/res/values-hu/translations.xml index 3574b49e6e..841695edcf 100644 --- a/features/preferences/impl/src/main/res/values-hu/translations.xml +++ b/features/preferences/impl/src/main/res/values-hu/translations.xml @@ -51,12 +51,4 @@ Ha folytatja, egyes beállítások megváltozhatnak."
    "Értesítések" "Hibaelhárítás" "Értesítések hibaelhárítása" - "Tesztek futtatása" - "Tesztek újbóli futtatása" - "Egyes tesztek sikertelenek voltak. Ellenőrizze a részleteket." - "A tesztek futtatása, hogy észlelje a konfigurációban felmerülő olyan problémákat, amelyek miatt az értesítések nem az elvárt módon viselkednek." - "Kísérlet a javításra" - "Minden teszt sikeresen lezajlott." - "Értesítések hibaelhárítása" - "Egyes tesztek a figyelmét igénylik. Ellenőrizze a részleteket." 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 13ac802d4d..8bca603eeb 100644 --- a/features/preferences/impl/src/main/res/values-in/translations.xml +++ b/features/preferences/impl/src/main/res/values-in/translations.xml @@ -53,12 +53,4 @@ Jika Anda melanjutkan, beberapa pengaturan Anda dapat berubah."
    "Notifikasi" "Pemecahan masalah" "Pecahkan masalah notifikasi" - "Jalankan tes" - "Jalankan tes lagi" - "Beberapa tes gagal. Silakan periksa detailnya." - "Jalankan pengujian untuk mendeteksi masalah apa pun dalam konfigurasi Anda yang mungkin membuat notifikasi tidak berperilaku seperti yang diharapkan." - "Mencoba untuk memperbaiki" - "Semua tes berhasil dilalui." - "Pecahkan masalah notifikasi" - "Beberapa tes membutuhkan perhatian Anda. Silakan periksa detailnya." diff --git a/features/preferences/impl/src/main/res/values-ru/translations.xml b/features/preferences/impl/src/main/res/values-ru/translations.xml index ca18672e97..3b24cf8c80 100644 --- a/features/preferences/impl/src/main/res/values-ru/translations.xml +++ b/features/preferences/impl/src/main/res/values-ru/translations.xml @@ -51,12 +51,4 @@ "Уведомления" "Устранение неполадок" "Уведомления об устранении неполадок" - "Выполнение тестов" - "Повторное выполнение тестов" - "Некоторые тесты провалились. Пожалуйста, проверьте детали." - "Выполните тесты, чтобы обнаружить любую проблему в конфигурации, из-за которой уведомления могут работать не так, как ожидалось." - "Попытка исправить" - "Все тесты прошли успешно." - "Уведомления об устранении неполадок" - "Некоторые тесты требуют вашего внимания. Пожалуйста, проверьте детали." diff --git a/features/preferences/impl/src/main/res/values-sk/translations.xml b/features/preferences/impl/src/main/res/values-sk/translations.xml index 12289b46f4..f382d0ab6f 100644 --- a/features/preferences/impl/src/main/res/values-sk/translations.xml +++ b/features/preferences/impl/src/main/res/values-sk/translations.xml @@ -53,12 +53,4 @@ Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť.""Oznámenia"
    "Riešenie problémov" "Oznámenia riešení problémov" - "Spustiť testy" - "Spustiť testy znova" - "Niektoré testy zlyhali. Skontrolujte prosím podrobnosti." - "Spustite testy, aby ste zistili akýkoľvek problém vo vašej konfigurácii, ktorý môže spôsobiť, že sa upozornenia nebudú správať podľa očakávania." - "Pokus o opravu" - "Všetky testy prebehli úspešne." - "Oznámenia riešení problémov" - "Niektoré testy si vyžadujú vašu pozornosť. Prosím skontrolujte podrobnosti." diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index b0e78e0d9e..56a5c0ba03 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -51,12 +51,4 @@ If you proceed, some of your settings may change."
    "Notifications" "Troubleshoot" "Troubleshoot notifications" - "Run tests" - "Run tests again" - "Some tests failed. Please check the details." - "Run the tests to detect any issue in your configuration that may make notifications not behave as expected." - "Attempt to fix" - "All tests passed successfully." - "Troubleshoot notifications" - "Some tests require your attention. Please check the details." diff --git a/libraries/permissions/impl/build.gradle.kts b/libraries/permissions/impl/build.gradle.kts index 82904bc750..7b80ef0d4f 100644 --- a/libraries/permissions/impl/build.gradle.kts +++ b/libraries/permissions/impl/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) + implementation(projects.libraries.troubleshoot.api) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) implementation(projects.services.toolbox.api) 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 903b5cd2c4..7e916c37b5 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 @@ -19,13 +19,13 @@ package io.element.android.libraries.permissions.impl.troubleshoot import android.Manifest import android.os.Build import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope 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 +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt index c17e61f5e7..df0214b08d 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt @@ -19,7 +19,6 @@ package io.element.android.libraries.permissions.impl.troubleshoot import android.os.Build import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.permissions.impl.FakePermissionStateProvider import io.element.android.libraries.permissions.impl.action.FakePermissionActions import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index f16f7f23f0..ee528a4ae7 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.troubleshoot.api) api(projects.libraries.pushproviders.api) api(projects.libraries.pushstore.api) api(projects.libraries.push.api) 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 7a819b3d93..3e7fe1ae59 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 @@ -17,12 +17,12 @@ package io.element.android.libraries.push.impl.troubleshoot import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope import io.element.android.libraries.push.api.GetCurrentPushProvider import io.element.android.libraries.push.impl.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow 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 26b7161fca..8de8304c2d 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 @@ -17,13 +17,13 @@ package io.element.android.libraries.push.impl.troubleshoot import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationDisplayer import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow 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 861554bef8..42a6394fc9 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 @@ -17,13 +17,13 @@ package io.element.android.libraries.push.impl.troubleshoot import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.api.gateway.PushGatewayFailure import io.element.android.libraries.push.impl.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CompletableDeferred 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 df6d220396..190cf8fe98 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 @@ -17,12 +17,12 @@ package io.element.android.libraries.push.impl.troubleshoot import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.di.AppScope 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 +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt index ea8de71222..7f7beb9d9b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt @@ -18,8 +18,8 @@ package io.element.android.libraries.push.impl.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt index a900d50b1f..1351117527 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt @@ -18,9 +18,9 @@ package io.element.android.libraries.push.impl.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationDisplayer +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt index 01fe2b9847..2c1363af78 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt @@ -18,11 +18,11 @@ package io.element.android.libraries.push.impl.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_FAILURE_REASON import io.element.android.libraries.push.api.gateway.PushGatewayFailure import io.element.android.libraries.push.test.FakePushService +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import kotlinx.coroutines.launch diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt index cf7f750403..e58a490715 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt @@ -18,8 +18,8 @@ package io.element.android.libraries.push.impl.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.test.FakePushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index de19420923..6e36b92a09 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -41,6 +41,7 @@ dependencies { implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.troubleshoot.api) implementation(projects.services.toolbox.api) implementation(projects.libraries.pushstore.api) 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 64a5bad67f..6cca3d21af 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 @@ -17,14 +17,14 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState -import io.element.android.libraries.core.notifications.TestFilterData import io.element.android.libraries.di.AppScope import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable import io.element.android.libraries.pushproviders.firebase.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow 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 b2164cda32..a465ca3a7b 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 @@ -17,15 +17,15 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState -import io.element.android.libraries.core.notifications.TestFilterData import io.element.android.libraries.di.AppScope import io.element.android.libraries.pushproviders.firebase.FirebaseConfig import io.element.android.libraries.pushproviders.firebase.FirebaseStore import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter import io.element.android.libraries.pushproviders.firebase.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt index 0c742fa567..6f1a3da7cb 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt @@ -18,8 +18,8 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt index b76e6861fc..2d8de62ad9 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt @@ -18,9 +18,9 @@ package io.element.android.libraries.pushproviders.firebase.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index 190c63d350..d5dcc9727d 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -33,6 +33,7 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.matrix.api) implementation(projects.libraries.uiStrings) + api(projects.libraries.troubleshoot.api) implementation(projects.libraries.pushstore.api) implementation(projects.libraries.pushproviders.api) 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 ec3b95e437..77a2347429 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 @@ -17,14 +17,14 @@ package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot import com.squareup.anvil.annotations.ContributesMultibinding -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState -import io.element.android.libraries.core.notifications.TestFilterData import io.element.android.libraries.di.AppScope import io.element.android.libraries.pushproviders.unifiedpush.R import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData import io.element.android.services.toolbox.api.strings.StringProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt index 29301515cd..117e8b7457 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt @@ -18,8 +18,8 @@ package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest diff --git a/libraries/troubleshoot/api/build.gradle.kts b/libraries/troubleshoot/api/build.gradle.kts new file mode 100644 index 0000000000..5ac917fd0b --- /dev/null +++ b/libraries/troubleshoot/api/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.troubleshoot.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(libs.androidx.corektx) + implementation(libs.coroutines.core) +} diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt new file mode 100644 index 0000000000..6e4e1c39e3 --- /dev/null +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.troubleshoot.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint + +interface NotificationTroubleShootEntryPoint : FeatureEntryPoint { + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + interface Callback : Plugin { + fun onDone() + } +} diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt similarity index 94% rename from libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt rename to libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt index 7af0b85ffb..69073925fb 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTest.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.core.notifications +package io.element.android.libraries.troubleshoot.api.test import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt similarity index 97% rename from libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt rename to libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt index 88aaae7281..da36de8ee7 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestDelegate.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.core.notifications +package io.element.android.libraries.troubleshoot.api.test import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestState.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt similarity index 94% rename from libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestState.kt rename to libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt index 9657be4b3b..1429916bcc 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/NotificationTroubleshootTestState.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.core.notifications +package io.element.android.libraries.troubleshoot.api.test data class NotificationTroubleshootTestState( val name: String, diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/TestFilterData.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt similarity index 91% rename from libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/TestFilterData.kt rename to libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt index e22fefb7f1..77f633363d 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/notifications/TestFilterData.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.libraries.core.notifications +package io.element.android.libraries.troubleshoot.api.test data class TestFilterData( val currentPushProviderName: String?, diff --git a/libraries/troubleshoot/impl/build.gradle.kts b/libraries/troubleshoot/impl/build.gradle.kts new file mode 100644 index 0000000000..b5a0a9012d --- /dev/null +++ b/libraries/troubleshoot/impl/build.gradle.kts @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "io.element.android.libraries.troubleshoot.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + implementation(libs.dagger) + implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.di) + api(projects.libraries.troubleshoot.api) + api(projects.libraries.push.api) + implementation(projects.services.analytics.api) + + testImplementation(libs.test.junit) + testImplementation(libs.test.robolectric) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(libs.coroutines.test) + testImplementation(projects.services.analytics.test) + testImplementation(projects.tests.testutils) + testImplementation(projects.libraries.push.test) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt new file mode 100644 index 0000000000..81c0cdaf00 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.troubleshoot.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultNotificationTroubleShootEntryPoint @Inject constructor() : NotificationTroubleShootEntryPoint { + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NotificationTroubleShootEntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : NotificationTroubleShootEntryPoint.NodeBuilder { + override fun callback(callback: NotificationTroubleShootEntryPoint.Callback): NotificationTroubleShootEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsEvents.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt similarity index 91% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsEvents.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt index a6b361de13..f2398e3b78 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsEvents.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl sealed interface TroubleshootNotificationsEvents { data object StartTests : TroubleshootNotificationsEvents diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt similarity index 82% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt index a662e94db8..96404d6d8c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsNode.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt @@ -14,18 +14,20 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl import androidx.compose.runtime.Composable 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 com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint import io.element.android.services.analytics.api.ScreenTracker @ContributesNode(SessionScope::class) @@ -35,13 +37,19 @@ class TroubleshootNotificationsNode @AssistedInject constructor( private val presenter: TroubleshootNotificationsPresenter, private val screenTracker: ScreenTracker, ) : Node(buildContext, plugins = plugins) { + private fun onDone() { + plugins().forEach { + it.onDone() + } + } + @Composable override fun View(modifier: Modifier) { screenTracker.TrackScreen(MobileScreen.ScreenName.NotificationTroubleshoot) val state = presenter.present() TroubleshootNotificationsView( state = state, - onBackPressed = ::navigateUp, + onBackPressed = ::onDone, modifier = modifier, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt similarity index 96% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenter.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt index 1ccbf86ce9..a730199fac 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenter.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt similarity index 91% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsState.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt index 55032b2fe9..4ff7c2c04c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl data class TroubleshootNotificationsState( val testSuiteState: TroubleshootTestSuiteState, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsStateProvider.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt similarity index 96% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsStateProvider.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt index 316bd72ad7..f2d49a82a0 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsStateProvider.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import kotlinx.collections.immutable.toImmutableList open class TroubleshootNotificationsStateProvider : PreviewParameterProvider { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt similarity index 96% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt index 08d56e5bd0..8b2ba843be 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsView.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size @@ -27,10 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.preferences.impl.R import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState.Status import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.preview.ElementPreview @@ -41,6 +38,8 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.OnLifecycleEvent +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState.Status @Composable fun TroubleshootNotificationsView( diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt similarity index 93% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt index 361cf1601f..1c5fbbc3e3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuite.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl import im.vector.app.features.analytics.plan.NotificationTroubleshoot import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState -import io.element.android.libraries.core.notifications.TestFilterData import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuiteState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt similarity index 83% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuiteState.kt rename to libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt index e516b65109..32e71ff9a5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootTestSuiteState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import kotlinx.collections.immutable.ImmutableList data class TroubleshootTestSuiteState( diff --git a/libraries/troubleshoot/impl/src/main/res/values-be/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..905b9e89b6 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,11 @@ + + + "Запусціць тэсты" + "Запусціце тэсты яшчэ раз" + "Некаторыя тэсты не ўдаліся. Калі ласка, праглядзіце дэталі." + "Запусціце тэсты, каб выявіць праблемы ў вашай канфігурацыі, з-за якіх апавяшчэння могуць паводзіць сябе не так, як чакалася." + "Спроба выпраўлення" + "Усе тэсты паспяхова пройдзены." + "Выпраўленне непаладак з апавяшчэннямі" + "Некаторыя тэсты патрабуюць вашай увагі. Калі ласка, праглядзіце дэталі." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-cs/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..17e47cf56f --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,11 @@ + + + "Spustit testy" + "Spustit testy znovu" + "Některé testy selhaly. Zkontrolujte prosím podrobnosti." + "Spusťte testy, abyste zjistili jakýkoli problém ve vaší konfiguraci, který může způsobit, že se oznámení nebudou chovat podle očekávání." + "Pokus o opravu" + "Všechny testy proběhly úspěšně." + "Odstraňování problémů s upozorněními" + "Některé testy vyžadují vaši pozornost. Zkontrolujte prosím podrobnosti." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-de/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..98b8c579a9 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,11 @@ + + + "Tests durchführen" + "Tests erneut durchführen" + "Einige Tests sind fehlgeschlagen. Bitte überprüfe die Details." + "Führe die Tests durch, um Probleme zu erkennen, die dazu führen können, dass sich die Benachrichtigungen nicht wie erwartet verhalten." + "Versuche das Problem zu beheben" + "Alle Tests wurden erfolgreich bestanden." + "Fehlerbehebung für Benachrichtigungen" + "Einige Tests erfordern deine Aufmerksamkeit. Bitte überprüfe die Details." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-fr/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..497b3e3b56 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,11 @@ + + + "Exécuter les tests" + "Relancer les tests" + "Certains tests ont échoué. Veuillez vérifier les détails." + "Exécuter les tests pour détecter tout problème dans votre configuration susceptible de provoquer un dysfonctionnement des notifications." + "Tenter de corriger" + "Tous les tests ont réussi." + "Dépanner les notifications" + "Certains tests nécessitent votre attention. Veuillez vérifier les détails." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-hu/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..211b1f4a1d --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,11 @@ + + + "Tesztek futtatása" + "Tesztek újbóli futtatása" + "Egyes tesztek sikertelenek voltak. Ellenőrizze a részleteket." + "A tesztek futtatása, hogy észlelje a konfigurációban felmerülő olyan problémákat, amelyek miatt az értesítések nem az elvárt módon viselkednek." + "Kísérlet a javításra" + "Minden teszt sikeresen lezajlott." + "Értesítések hibaelhárítása" + "Egyes tesztek a figyelmét igénylik. Ellenőrizze a részleteket." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-ru/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..943652fc86 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,11 @@ + + + "Выполнение тестов" + "Повторное выполнение тестов" + "Некоторые тесты провалились. Пожалуйста, проверьте детали." + "Выполните тесты, чтобы обнаружить любую проблему в конфигурации, из-за которой уведомления могут работать не так, как ожидалось." + "Попытка исправить" + "Все тесты прошли успешно." + "Уведомления об устранении неполадок" + "Некоторые тесты требуют вашего внимания. Пожалуйста, проверьте детали." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-sk/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..3b7a08c2f0 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,11 @@ + + + "Spustiť testy" + "Spustiť testy znova" + "Niektoré testy zlyhali. Skontrolujte prosím podrobnosti." + "Spustite testy, aby ste zistili akýkoľvek problém vo vašej konfigurácii, ktorý môže spôsobiť, že sa upozornenia nebudú správať podľa očakávania." + "Pokus o opravu" + "Všetky testy prebehli úspešne." + "Oznámenia riešení problémov" + "Niektoré testy si vyžadujú vašu pozornosť. Prosím skontrolujte podrobnosti." + diff --git a/libraries/troubleshoot/impl/src/main/res/values/localazy.xml b/libraries/troubleshoot/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..eee100d711 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values/localazy.xml @@ -0,0 +1,11 @@ + + + "Run tests" + "Run tests again" + "Some tests failed. Please check the details." + "Run the tests to detect any issue in your configuration that may make notifications not behave as expected." + "Attempt to fix" + "All tests passed successfully." + "Troubleshoot notifications" + "Some tests require your attention. Please check the details." + diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt similarity index 91% rename from features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt rename to libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt index 361c59cae7..90b8a988bb 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/FakeNotificationTroubleshootTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl -import io.element.android.libraries.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTests.kt similarity index 94% rename from features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt rename to libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTests.kt index 686aac2e56..25082b63c4 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsPresenterTests.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTests.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 New Vector Ltd + * Copyright (c) 2024 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,16 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl 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.core.notifications.NotificationTroubleshootTest -import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt similarity index 91% rename from features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsViewTest.kt rename to libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt index af2b37d7c0..5acd0e5635 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/troubleshoot/TroubleshootNotificationsViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.preferences.impl.notifications.troubleshoot +package io.element.android.libraries.troubleshoot.impl import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule @@ -69,7 +69,11 @@ class TroubleshootNotificationsViewTest { val eventsRecorder = EventsRecorder() rule.setTroubleshootNotificationsView( aTroubleshootNotificationsState( - tests = listOf(aTroubleshootTestStateFailure(hasQuickFix = false)), + tests = listOf( + aTroubleshootTestStateFailure( + hasQuickFix = false + ) + ), eventSink = eventsRecorder ), ) @@ -88,7 +92,11 @@ class TroubleshootNotificationsViewTest { val eventsRecorder = EventsRecorder() rule.setTroubleshootNotificationsView( aTroubleshootNotificationsState( - tests = listOf(aTroubleshootTestStateFailure(hasQuickFix = true)), + tests = listOf( + aTroubleshootTestStateFailure( + hasQuickFix = true + ) + ), eventSink = eventsRecorder ), ) diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index cc254511bd..395f690d0e 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -114,6 +114,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:voicerecorder:impl")) implementation(project(":libraries:mediaplayer:impl")) implementation(project(":libraries:mediaviewer:impl")) + implementation(project(":libraries:troubleshoot:impl")) } fun DependencyHandlerScope.allServicesImpl() { diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 603c04b55c..d63ede5638 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -205,7 +205,12 @@ "screen_edit_profile_.*", "screen_notification_settings_.*", "screen_blocked_users_.*", - "troubleshoot_notifications_entry_point_.*", + "troubleshoot_notifications_entry_point_.*" + ] + }, + { + "name" : ":libraries:troubleshoot:impl", + "includeRegex" : [ "troubleshoot_notifications_screen_.*" ] }, From 30d77e5123a2467f3d7b1c50ef2ef8d1c3639dbd Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 2 Apr 2024 16:25:27 +0000 Subject: [PATCH 068/124] Update screenshots --- ...otificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png} | 0 ...ificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png} | 0 ...otificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png} | 0 ...otificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png} | 0 ...otificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png} | 0 ...otificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png} | 0 ...otificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png} | 0 ...ficationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png} | 0 ...ficationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png} | 0 ...ficationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png} | 0 ...ficationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png} | 0 ...ficationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png} | 0 ...dNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png | 3 --- ...otificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png | 3 --- ...otificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...otificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...otificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...otificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png | 3 +++ ...otificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png | 3 +++ ...ificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...ificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...ificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png | 3 +++ ...ificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png | 3 +++ ..._EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,en].png} | 0 ...ditUserProfileView-Night-10_12_null_0,NEXUS_5,1.0,en].png} | 0 ..._null_UserPreferences-Day-9_10_null_0,NEXUS_5,1.0,en].png} | 0 ..._null_UserPreferences-Day-9_10_null_1,NEXUS_5,1.0,en].png} | 0 ..._null_UserPreferences-Day-9_10_null_2,NEXUS_5,1.0,en].png} | 0 ...ull_UserPreferences-Night-9_11_null_0,NEXUS_5,1.0,en].png} | 0 ...ull_UserPreferences-Night-9_11_null_1,NEXUS_5,1.0,en].png} | 0 ...ull_UserPreferences-Night-9_11_null_2,NEXUS_5,1.0,en].png} | 0 32 files changed, 24 insertions(+), 18 deletions(-) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-8_10_null,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png} (100%) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-11_13_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_12_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_2,NEXUS_5,1.0,en].png} (100%) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-8_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-8_10_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png deleted file mode 100644 index e39ff5329b..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f99b8193089b4719c75acdee6339cc89307fb16255cf6e7deaf4486b03c42505 -size 42728 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 3fb79982e8..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ecc207ca338b66754c3d0e738f584c0712c8d46958e07bfd1ccd50b19df82006 -size 41413 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png index e1135c1e76..8399a4c808 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cae06a603dd5947aa035e0be90a7a649abdce67f5cdcb921c2fa259ead90ac0d -size 60521 +oid sha256:0a566ec9dea61ea21c42cdbdb011f7b74a530d591642c5616038a0cdd6c20a74 +size 63452 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png index cd66a12792..3abcdd8dfd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e860dc70a35f5fdecc69eec550e781f6974e66a5a12196572ea169b3422136be -size 54821 +oid sha256:c36ca0694f603d7a3f0ca1965f1b0fe2bb6738aece2a908317a8043b2cb82bc4 +size 57380 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png index 742e797349..844ed57393 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f84e508a1a5ec9a402d4adbe5cbe963c0a06c776f7627a9c8eac29b6209c84a -size 54772 +oid sha256:dfd063ea23382a25ec6576b74674d6c2f30a7fbeb9e99d4c4a753a7909ece947 +size 57353 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1da5935157 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5ae68e5b9dcf951dbc7a77e6606daaa86eceee6b8b54c4fc5072774f149dae2 +size 47346 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f9e918b587 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:585f8dcbeef19fbba8e3cddcba7c8ec2831a704ba3fbb3cc05369d42444d919c +size 47975 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png index a76c60413f..af460dc892 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22b0073e9e51a790231ab9feca750869e15b4c03e659f2b183dd6d5745be8918 -size 55839 +oid sha256:b9da4314613e463f2da12ac9e7d4c52446ca64133c13980b87a5c447873dfc5b +size 58568 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png index 2484839eab..12405f0ce7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02b620889589dcd10057991dad95dd84dfddf9cef135059d1bae45ba4790f5ee -size 50971 +oid sha256:6eed8d6163471878f5281c5fc922537a22216e666366a4d1c6c11535c09aeb11 +size 53402 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png index 0a1531e4d9..eba437daf5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:308f6f6d7da94c68212d263fa30f1612387d2e1dbf355314fb128e757f481209 -size 50382 +oid sha256:860cc7bbab5f72a951f7b83fad121040bd1fa1852bd44715a390b8e5db52eaeb +size 52801 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4ecedce7bd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4eacf4ac22f6c49b96f0891bf53fcc96bd333a6b42e66c7ac12aa4be03232667 +size 45141 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ac4c51e310 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6f6367b13643ae480f3d555ea3506bc4caeddeb51c0cd20de6c906b9886e5a5 +size 45015 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-11_13_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_12_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-11_13_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_12_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_2,NEXUS_5,1.0,en].png From 403240256ce7e936509a9fc390b66667eb69538f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 18:37:29 +0200 Subject: [PATCH 069/124] Add showkase processor --- libraries/troubleshoot/impl/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/troubleshoot/impl/build.gradle.kts b/libraries/troubleshoot/impl/build.gradle.kts index b5a0a9012d..4967a34cf3 100644 --- a/libraries/troubleshoot/impl/build.gradle.kts +++ b/libraries/troubleshoot/impl/build.gradle.kts @@ -16,6 +16,7 @@ plugins { id("io.element.android-compose-library") alias(libs.plugins.anvil) + alias(libs.plugins.ksp) alias(libs.plugins.kotlin.serialization) } @@ -43,6 +44,7 @@ dependencies { api(projects.libraries.troubleshoot.api) api(projects.libraries.push.api) implementation(projects.services.analytics.api) + ksp(libs.showkase.processor) testImplementation(libs.test.junit) testImplementation(libs.test.robolectric) From 2aa53f02c509e6c9fff974061bae548a2c96db54 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 2 Apr 2024 16:50:13 +0000 Subject: [PATCH 070/124] Update screenshots --- ...leshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 3 +++ ...leshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png | 3 +++ ...leshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png | 3 +++ ...leshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png | 3 +++ ...leshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png | 3 +++ ...leshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png | 3 +++ ...leshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png | 3 +++ ...leshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png | 3 +++ ...shootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png | 3 +++ 16 files changed, 48 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b232885ed5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7a8e9790e7d4b92e54a4161340c77f691685c28e9d40f272fb2c98808fec154 +size 32662 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..17ea7ac2f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5abc8664b6ef86b8f3357207635a3c038d78e3ff907ca56526242186c196cc22 +size 21152 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b53087f1c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:504c858c17ef7229ba2bd52f3be7a6769ea2031a97fc7a39461a523cba120e9e +size 21512 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9ee728e547 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07ad31cb2c341ecddf9963ff93bdd9818f6209c9331c89cac9122293f76097cd +size 32472 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..26cbd8a27f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05a287eaae96eb6c1d2aabf624d10a1ddf97f50cd9c680a65b0496123fe53b46 +size 26294 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..811a6f1f65 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2a11615004b3b883be7093086a42efda0efabcb03cd580ec813fc827be02937 +size 38878 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..03e98787ba --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:853b90c82ad1dbbd9c1fa75feaa2115445fbbd6f674fa41f486826bd1ef89b1c +size 27022 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a0f85489dd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb5f63b160d04544705cf778774d046dfc2915f49fe4adfaa473111c95c86be8 +size 25731 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b2eb2fc50d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01d2e5fdc0a9f1c5d534c965b0b6f30162dcb14d06e89af9eacc4c0084a13cdc +size 29052 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..99e171d097 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99bb674c5cfdbf86789426fdfb676f84d244b0452ba557915d803d78e4959224 +size 20188 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8c6c088e36 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac08cfe788fa4bd19f405862050a49da399c51b6921c977937a022a3f180f7b +size 20559 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f3877ac388 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a31489b3764a8eafe386fbcaf0b806d2acfeb7c92816230cba77e1f84704d645 +size 29818 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0e6149ea48 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af94bf79e1602817e800adb9a382681a9bf63b4435b9d5c7437a9a6a22d8c373 +size 24581 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..73e0749733 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ca71cd36ee1679c55eac28fc271f02a5dbbfc33d08202137d93a93e7285ac45 +size 35533 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..840c0da5d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d7937d6b674014e42d2b7c95104d51da0644f3557d91ea04e4ec9b626a2f4a0 +size 25044 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b878f43791 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75cec48fca9ca9d551b18fbfabd318c72c33eec16743984ae2be3409f22a8d47 +size 23902 From 5310456b6ced26786672b23db50380915af2c203 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 2 Apr 2024 19:50:41 +0200 Subject: [PATCH 071/124] Fix test compilation. --- .../NotificationTroubleshootCheckPermissionTestTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt index df0214b08d..b38d00ee22 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt @@ -21,6 +21,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.permissions.impl.FakePermissionStateProvider import io.element.android.libraries.permissions.impl.action.FakePermissionActions +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.launch From df30cf3cbd696ce1216c25b9c58e9405220868ba Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:52:12 +0000 Subject: [PATCH 072/124] Update dependency org.robolectric:robolectric to v4.12.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 599cb7ee2d..9df64a547a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -136,7 +136,7 @@ test_konsist = "com.lemonappdev:konsist:0.13.0" test_turbine = "app.cash.turbine:turbine:1.1.0" test_truth = "com.google.truth:truth:1.4.2" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.15" -test_robolectric = "org.robolectric:robolectric:4.12" +test_robolectric = "org.robolectric:robolectric:4.12.1" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } # Others From b7801de3e432ed7e9330994f074ff700c3de196d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 3 Apr 2024 14:39:51 +0200 Subject: [PATCH 073/124] Add issue template for a new Task. --- .../task-that-belongs-to-a-story-epic.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/task-that-belongs-to-a-story-epic.md diff --git a/.github/ISSUE_TEMPLATE/task-that-belongs-to-a-story-epic.md b/.github/ISSUE_TEMPLATE/task-that-belongs-to-a-story-epic.md new file mode 100644 index 0000000000..fe49564b9d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task-that-belongs-to-a-story-epic.md @@ -0,0 +1,11 @@ +--- +name: Task that belongs to a story/epic +about: A skeleton task where the details are all contained within a story or epic + on element-meta. +title: "[Task] " +labels: T-Task +assignees: '' + +--- + +Please see and discuss the details in the meta issue. From bf068f4f25d6a203b19d9ff509524e7278e141a8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 3 Apr 2024 15:36:39 +0200 Subject: [PATCH 074/124] Add action to copy permalink #2650 --- changelog.d/2650.feature | 1 + .../messages/impl/MessagesPresenter.kt | 16 ++++++++++++++ .../impl/actionlist/ActionListPresenter.kt | 4 ++++ .../actionlist/ActionListStateProvider.kt | 2 ++ .../actionlist/model/TimelineItemAction.kt | 1 + .../messages/impl/MessagesPresenterTest.kt | 21 +++++++++++++++++++ .../actionlist/ActionListPresenterTest.kt | 15 +++++++++++++ .../libraries/matrix/api/room/MatrixRoom.kt | 7 +++++++ .../matrix/impl/room/RustMatrixRoom.kt | 14 +++++++++++++ .../matrix/test/room/FakeMatrixRoom.kt | 5 +++++ 10 files changed, 86 insertions(+) create mode 100644 changelog.d/2650.feature diff --git a/changelog.d/2650.feature b/changelog.d/2650.feature new file mode 100644 index 0000000000..4287d42ac7 --- /dev/null +++ b/changelog.d/2650.feature @@ -0,0 +1 @@ +Add action to copy permalink 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 4c92585b0d..4edc943a73 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 @@ -89,6 +89,7 @@ import io.element.android.libraries.matrix.ui.room.canRedactOtherAsState import io.element.android.libraries.matrix.ui.room.canRedactOwnAsState import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.textcomposer.model.MessageComposerMode +import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -273,6 +274,7 @@ class MessagesPresenter @AssistedInject constructor( ) = launch { when (action) { TimelineItemAction.Copy -> handleCopyContents(targetEvent) + TimelineItemAction.CopyLink -> handleCopyLink(targetEvent) TimelineItemAction.Redact -> handleActionRedact(targetEvent) TimelineItemAction.Edit -> handleActionEdit(targetEvent, composerState, enableTextFormatting) TimelineItemAction.Reply, @@ -435,6 +437,20 @@ class MessagesPresenter @AssistedInject constructor( event.eventId?.let { timelineState.eventSink(TimelineEvents.PollEndClicked(it)) } } + private suspend fun handleCopyLink(event: TimelineItem.Event) { + event.eventId ?: return + room.getPermalinkFor(event.eventId).fold( + onSuccess = { permalink -> + clipboardHelper.copyPlainText(permalink) + snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_link_copied_to_clipboard)) + }, + onFailure = { + Timber.e(it, "Failed to get permalink for event ${event.eventId}") + snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_error)) + } + ) + } + private suspend fun handleCopyContents(event: TimelineItem.Event) { val content = when (event.content) { is TimelineItemTextBasedContent -> event.content.body 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 f836e2f8a0..5ebedeb06f 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 @@ -96,6 +96,7 @@ class ActionListPresenter @Inject constructor( is TimelineItemStateContent -> { buildList { add(TimelineItemAction.Copy) + add(TimelineItemAction.CopyLink) if (isDeveloperModeEnabled) { add(TimelineItemAction.ViewSource) } @@ -119,6 +120,7 @@ class ActionListPresenter @Inject constructor( if (timelineItem.content.canBeCopied()) { add(TimelineItemAction.Copy) } + add(TimelineItemAction.CopyLink) if (isDeveloperModeEnabled) { add(TimelineItemAction.ViewSource) } @@ -136,6 +138,7 @@ class ActionListPresenter @Inject constructor( add(TimelineItemAction.Reply) add(TimelineItemAction.Forward) } + add(TimelineItemAction.CopyLink) if (isDeveloperModeEnabled) { add(TimelineItemAction.ViewSource) } @@ -176,6 +179,7 @@ class ActionListPresenter @Inject constructor( if (timelineItem.content.canBeCopied()) { add(TimelineItemAction.Copy) } + add(TimelineItemAction.CopyLink) if (isDeveloperModeEnabled) { add(TimelineItemAction.ViewSource) } 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 10952058b5..be78037d76 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 @@ -135,6 +135,7 @@ fun aTimelineItemActionList(): ImmutableList { TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.Edit, TimelineItemAction.Redact, TimelineItemAction.ReportContent, @@ -146,6 +147,7 @@ fun aTimelineItemPollActionList(): ImmutableList { TimelineItemAction.EndPoll, TimelineItemAction.Reply, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, TimelineItemAction.Redact, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index f244f515f3..f61e6197c2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -31,6 +31,7 @@ sealed class TimelineItemAction( ) { data object Forward : TimelineItemAction(CommonStrings.action_forward, CompoundDrawables.ic_compound_forward) data object Copy : TimelineItemAction(CommonStrings.action_copy, CompoundDrawables.ic_compound_copy) + data object CopyLink : TimelineItemAction(CommonStrings.action_copy_link_to_message, CompoundDrawables.ic_compound_link) data object Redact : TimelineItemAction(CommonStrings.action_remove, CompoundDrawables.ic_compound_delete, destructive = true) data object Reply : TimelineItemAction(CommonStrings.action_reply, CompoundDrawables.ic_compound_reply) data object ReplyInThread : TimelineItemAction(CommonStrings.action_reply_in_thread, CompoundDrawables.ic_compound_reply) 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 6cceb6b5b5..bce97f4a6e 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 @@ -233,6 +233,27 @@ class MessagesPresenterTest { } } + @Test + fun `present - handle action copy link`() = runTest { + val clipboardHelper = FakeClipboardHelper() + val event = aMessageEvent() + val matrixRoom = FakeMatrixRoom( + permalinkResult = { Result.success("a link") }, + ) + val presenter = createMessagesPresenter( + clipboardHelper = clipboardHelper, + matrixRoom = matrixRoom, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitFirstItem() + initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event)) + assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) + assertThat(clipboardHelper.clipboardContents).isEqualTo("a link") + } + } + @Test fun `present - handle action reply`() = runTest { val presenter = createMessagesPresenter() 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 981e8fc8ac..c1062625f7 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 @@ -153,6 +153,7 @@ class ActionListPresenterTest { TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ) @@ -193,6 +194,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Forward, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ) @@ -232,6 +234,7 @@ class ActionListPresenterTest { TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, TimelineItemAction.Redact, @@ -272,6 +275,7 @@ class ActionListPresenterTest { TimelineItemAction.Reply, TimelineItemAction.Forward, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, TimelineItemAction.Redact, @@ -315,6 +319,7 @@ class ActionListPresenterTest { TimelineItemAction.Forward, TimelineItemAction.Edit, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, TimelineItemAction.Redact, ) @@ -357,6 +362,7 @@ class ActionListPresenterTest { TimelineItemAction.Forward, TimelineItemAction.Edit, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, ) ) @@ -396,6 +402,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, TimelineItemAction.Redact, ) @@ -435,6 +442,7 @@ class ActionListPresenterTest { displayEmojiReactions = false, actions = persistentListOf( TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.ViewSource, ) ) @@ -473,6 +481,7 @@ class ActionListPresenterTest { displayEmojiReactions = false, actions = persistentListOf( TimelineItemAction.Copy, + TimelineItemAction.CopyLink, ) ) ) @@ -513,6 +522,7 @@ class ActionListPresenterTest { TimelineItemAction.Forward, TimelineItemAction.Edit, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.Redact, ) ) @@ -595,6 +605,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Edit, TimelineItemAction.Copy, + TimelineItemAction.CopyLink, TimelineItemAction.Redact, ) ) @@ -632,6 +643,7 @@ class ActionListPresenterTest { TimelineItemAction.Reply, TimelineItemAction.Edit, TimelineItemAction.EndPoll, + TimelineItemAction.CopyLink, TimelineItemAction.Redact, ) ) @@ -668,6 +680,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.EndPoll, + TimelineItemAction.CopyLink, TimelineItemAction.Redact, ) ) @@ -703,6 +716,7 @@ class ActionListPresenterTest { displayEmojiReactions = true, actions = persistentListOf( TimelineItemAction.Reply, + TimelineItemAction.CopyLink, TimelineItemAction.Redact, ) ) @@ -738,6 +752,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, + TimelineItemAction.CopyLink, TimelineItemAction.Redact, ) ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 3e01155a86..aa1f3fa025 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -328,5 +328,12 @@ interface MatrixRoom : Closeable { */ fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result + /** + * Get the permalink for the provided [eventId]. + * @param eventId The event id to get the permalink for. + * @return The permalink, or a failure. + */ + suspend fun getPermalinkFor(eventId: EventId): Result + override fun close() = destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 7ab6e3640b..be61b06310 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.room +import io.element.android.appconfig.MatrixConfiguration import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.matrix.api.core.EventId @@ -711,6 +712,19 @@ class RustMatrixRoom( ) } + override suspend fun getPermalinkFor(eventId: EventId): Result { + // FIXME Use the SDK API once https://github.com/matrix-org/matrix-rust-sdk/issues/3259 has been done + // Now use a simple builder + return runCatching { + buildString { + append(MatrixConfiguration.MATRIX_TO_PERMALINK_BASE_URL) + append(roomId.value) + append("/") + append(eventId.value) + } + } + } + private fun sendAttachment(files: List, handle: () -> SendAttachmentJoinHandle): Result { return runCatching { MediaUploadHandlerImpl(files, handle()) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 32575e5924..f2395241b5 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -84,6 +84,7 @@ class FakeMatrixRoom( override val activeMemberCount: Long = 234L, val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), + private var permalinkResult: () -> Result = { Result.success("link") }, canRedactOwn: Boolean = false, canRedactOther: Boolean = false, ) : MatrixRoom { @@ -273,6 +274,10 @@ class FakeMatrixRoom( return cancelSendResult } + override suspend fun getPermalinkFor(eventId: EventId): Result { + return permalinkResult() + } + override suspend fun editMessage( originalEventId: EventId?, transactionId: TransactionId?, From ed9384bf591b137ea09b96d28c3984e9f9dee035 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Wed, 3 Apr 2024 13:47:24 +0000 Subject: [PATCH 075/124] Update screenshots --- ...tent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...ntent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png | 4 ++-- ...nt_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png | 4 ++-- ...ent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png | 4 ++-- 18 files changed, 36 insertions(+), 36 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png index a2fa0ff4c9..758bb38311 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:654d73d145ba000b0a58e1974206bd9c7970a5a384186336469504cd35b19890 -size 29036 +oid sha256:f5aa4c9aab5727c56ee6efcf8c136cca8276d54695d28a4f548af9d51f3df4b9 +size 34264 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png index 0cf654af98..8ac22522d0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32c2a61124bd68fc1921439f834744c1a40fa615de1270b8d97dd5c4365bbd25 -size 39317 +oid sha256:0d31d3549643b37a21f043f9ec9a33fa93e25f4004b077af713097855235effe +size 44276 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png index 942a986658..f5be007193 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17c00b93a8073bdc2741f0492623ffef80d37e3f3fc8744650233d489b6efc82 -size 39866 +oid sha256:dfe460e8bab0a27a31984ce8c85ed98aef2a994584f724375cd436d5c1c7c6ca +size 44758 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png index ca3fc4b9e2..8f629512ea 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bcd9a9db22a88210afa8ba3700abc1895d040c044ec11aa324d720651a3bc0b -size 40404 +oid sha256:e287869aa819ff569ab187f8995260410c47c32817caba61ba7639d6b0a2fe44 +size 45245 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png index 8a6fc8596a..e5b5b23b08 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00804549fdbada3e48531ffab2bd5e013cd592d11c56e65eb2385b07910bc6e3 -size 39998 +oid sha256:1f6602e1e19e1c5b545522ff7444b294cc926d02b1910f196461a9770f64af4a +size 44882 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png index 8f3fe73e50..9f81bd59e2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f71497d55a8cb048b3249221e8b24c883e3015533f4e543650b742542b5daac -size 40645 +oid sha256:4c321a39333a03b5eadf287e2a37e809da3620205d97caa1ae63f9ab99dc4f82 +size 45464 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png index c044d8db01..a9ba8ca80e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d4ad5e2fb1b518495f23b70fde606d791536adbea3ffac76c5fbb54e4c22138 -size 41343 +oid sha256:135f36df3ade2a4e210fc5560b8a00593e842153424da5efcd25b5c64036f59c +size 46240 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png index 870e1cd2c6..2d2bf39bf1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:675bbd412179ad4960c6cf78f973c67fd11809340e414f7ebf3eef5e41434e0e -size 41201 +oid sha256:ea154dca2cde0829bb70dbcf9e032e63d052c35194b159dd72db8c72a58863b0 +size 46082 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png index d8fffdef9a..97c322b6a9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33e82110711be0bdff074df90c93c3911e77bf53672df336c5fa6bf74f28d86f -size 28191 +oid sha256:f2124f3afde9e0fc4e42733d3624f69a74b898a035e47e4369acfad2834d0e02 +size 33378 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png index 683a9a4755..0ce605591d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7220aa1b48506d58f800d71c0aa1b3ea1650fa2153246d1572213e5ff0170b04 -size 27759 +oid sha256:61da843a4fd576317ce1dab5a95cba47f269c96096157f880da5c6f3f8a944c7 +size 32148 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png index 04e465574f..7e260976f9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36262a1ed8e2029519eb519e64dd973a5170269c04ad509464596b87bcbd9247 -size 37891 +oid sha256:0345399ddbe34e4fb13468571921fb2de8db1b7db01cb6a7439970b15046d37e +size 42361 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png index c36ec3d999..0cbbb24fd4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c75a4422846ab1e4d12c48916a5f306b506cd1c082f1709104560634106a8ba -size 38420 +oid sha256:63b40ee6d9b4fa6f049dedbfc628380069360e67129b8e53b94e04d25b5897bf +size 42898 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png index 08bc6aed16..02acc78564 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e28f6654d3fd5fb6e332a999fda8393bdff4988a87039e683cd94a251f4b9b07 -size 38926 +oid sha256:a5d9f66ab910df80652ef7658869ee05f570af134f1e05e8fc5b19d9a05a453e +size 43388 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png index e5917eb1ae..38a46d5412 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a2cd0f46f405978dda374ce865bc5f409280716830fcd2d29e290c11b36f118 -size 38538 +oid sha256:e37056a6b4d6416a77af752d04b040d1fc8dbd65bdaaae8d50c05e7a11309d05 +size 43013 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png index 490bfe9f87..0dd6058fd1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eac8803bd70cc677841f124eeb19e70f50a257cf89360410618db2c2f601c27a -size 39178 +oid sha256:995e4bfb172bbc09177d1427f63063f86348cedd5e497a5341eb0e8374e80cc9 +size 43634 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png index 6bd5bbeda8..139f2d887a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96c95a7ca59ed0d8bba59e735a2248d2cee3d0a8d4c045a0491fe1731c8c9574 -size 39848 +oid sha256:1d086a8c7b3f9e6dbbf30495ba1d209b8436a8ef5d31dc9a3605a0b2cd9ce72e +size 44276 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png index 3c897184fa..0058b2ffd3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a395ca5f5dbf4647ad1b5dea90f3c7a1cada2e934c4053f5ecc1e793e4efa879 -size 39718 +oid sha256:013dce6e34f67c62a514b958b502a511c9975551eb745b4e502c5794584e730a +size 44150 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png index 1afb6e9532..6505ba7ac7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Night-1_2_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4bf2d4ecc1c9e67cd833ca5cd61b4f77e5da9381e4425f332b91e7fbf8d9d0f4 -size 26781 +oid sha256:cda82238c49b88bf0da5f462bcb05dee7fe2e659345c03997456be635ff4bae2 +size 31287 From 41287c5f595a26de0d509575ee5e714f671330ae Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 3 Apr 2024 16:53:17 +0200 Subject: [PATCH 076/124] Move session verification to FTUE flow, make it mandatory (#2594) * Move session verification to the FTUE * Allow session verification flow to be restarted * Use `EncryptionService` to display session verification faster * Remove session verification item from settings * Remove session verification banner from room list * Remove 'verification needed' variant from the `TimelineEncryptedHistoryBanner` * Improve verification flow UI and UX * Remove 'verification successful' snackbar message * Only register push provider after the session has been verified * Hide room list while the session hasn't been verified * Prevent deep links from changing the navigation if the session isn't verified * Update screenshots * Renamed `FtueState` to `FtueService`, created an actual `FtueState`. --------- Co-authored-by: ElementBot --- .maestro/tests/account/login.yaml | 3 +- .maestro/tests/account/verifySession.yaml | 2 - .../assertSessionVerificationDisplayed.yaml | 5 + .../android/appnav/LoggedInEventProcessor.kt | 13 -- .../android/appnav/LoggedInFlowNode.kt | 84 ++++---- .../io/element/android/appnav/RootFlowNode.kt | 2 +- .../appnav/loggedin/LoggedInPresenter.kt | 22 +- .../appnav/loggedin/LoggedInPresenterTest.kt | 2 + changelog.d/2580.feature | 1 + .../state/{FtueState.kt => FtueService.kt} | 21 +- features/ftue/impl/build.gradle.kts | 2 + .../features/ftue/impl/FtueFlowNode.kt | 19 +- .../FtueSessionVerificationFlowNode.kt | 98 +++++++++ ...aultFtueState.kt => DefaultFtueService.kt} | 37 +++- ...ateTests.kt => DefaultFtueServiceTests.kt} | 54 ++++- .../impl/timeline/TimelinePresenter.kt | 20 -- .../messages/impl/timeline/TimelineState.kt | 2 - .../impl/timeline/TimelineStateProvider.kt | 5 - .../messages/impl/timeline/TimelineView.kt | 1 - .../TimelineItemGroupedEventsRow.kt | 8 - .../timeline/components/TimelineItemRow.kt | 4 - .../components/TimelineItemVirtualRow.kt | 4 +- .../TimelineEncryptedHistoryBannerView.kt | 22 +- .../timeline/session/SessionStateProvider.kt | 36 ---- .../messages/impl/MessagesPresenterTest.kt | 4 - .../impl/timeline/TimelinePresenterTest.kt | 6 - .../preferences/api/PreferencesEntryPoint.kt | 1 - .../preferences/impl/PreferencesFlowNode.kt | 4 - .../impl/root/PreferencesRootNode.kt | 6 - .../impl/root/PreferencesRootPresenter.kt | 5 +- .../impl/root/PreferencesRootState.kt | 1 - .../impl/root/PreferencesRootStateProvider.kt | 1 - .../impl/root/PreferencesRootView.kt | 11 - .../impl/tasks/ClearCacheUseCase.kt | 6 +- .../impl/root/PreferencesRootPresenterTest.kt | 1 - .../roomlist/api/RoomListEntryPoint.kt | 1 - .../features/roomlist/impl/RoomListNode.kt | 5 - .../roomlist/impl/RoomListPresenter.kt | 9 - .../features/roomlist/impl/RoomListState.kt | 1 - .../roomlist/impl/RoomListStateProvider.kt | 1 - .../features/roomlist/impl/RoomListView.kt | 5 - .../components/RequestVerificationHeader.kt | 49 ----- .../impl/components/RoomListContentView.kt | 21 +- .../roomlist/impl/RoomListPresenterTests.kt | 48 ++--- .../roomlist/impl/RoomListViewTest.kt | 31 --- .../api/SecureBackupEntryPoint.kt | 6 + .../impl/DefaultSecureBackupEntryPoint.kt | 5 + .../securebackup/impl/SecureBackupFlowNode.kt | 15 +- .../enter/SecureBackupEnterRecoveryKeyNode.kt | 28 +-- .../impl/VerifySelfSessionNode.kt | 2 +- .../impl/VerifySelfSessionPresenter.kt | 6 +- .../impl/VerifySelfSessionStateMachine.kt | 70 ++++-- .../impl/VerifySelfSessionView.kt | 203 ++++++++++-------- .../impl/VerifySelfSessionViewEvents.kt | 4 +- .../res/drawable/ic_verification_devices.xml | 9 - .../res/drawable/ic_verification_emoji.xml | 9 - .../res/drawable/ic_verification_waiting.xml | 9 - .../res/drawable/ic_verification_warning.xml | 9 - .../impl/src/main/res/values/localazy.xml | 6 + .../impl/VerifySelfSessionPresenterTests.kt | 43 +++- .../impl/VerifySelfSessionViewTest.kt | 120 ++++++++--- .../molecules/IconTitleSubtitleMolecule.kt | 12 ++ .../designsystem/components/PageTitle.kt | 4 +- .../SessionVerificationService.kt | 3 + .../libraries/matrix/impl/RustMatrixClient.kt | 31 +-- .../RustSessionVerificationService.kt | 135 ++++++++---- .../FakeSessionVerificationService.kt | 6 +- .../src/main/res/values/localazy.xml | 2 + .../android/samples/minimal/RoomListScreen.kt | 1 - ...erView-Day-57_57_null,NEXUS_5,1.0,en].png} | 0 ...rView-Day-57_57_null_0,NEXUS_5,1.0,en].png | 3 - ...rView-Day-57_57_null_2,NEXUS_5,1.0,en].png | 3 - ...View-Night-57_58_null,NEXUS_5,1.0,en].png} | 0 ...iew-Night-57_58_null_0,NEXUS_5,1.0,en].png | 3 - ...iew-Night-57_58_null_2,NEXUS_5,1.0,en].png | 3 - ...otViewDark--1_1_null_0,NEXUS_5,1.0,en].png | 4 +- ...otViewDark--1_1_null_1,NEXUS_5,1.0,en].png | 4 +- ...tViewLight--0_0_null_0,NEXUS_5,1.0,en].png | 4 +- ...tViewLight--0_0_null_1,NEXUS_5,1.0,en].png | 4 +- ...ndicator-Day-7_8_null,NEXUS_5,1.0,en].png} | 0 ...icator-Night-7_9_null,NEXUS_5,1.0,en].png} | 0 ...stTopBar-Day-6_7_null,NEXUS_5,1.0,en].png} | 0 ...TopBar-Night-6_8_null,NEXUS_5,1.0,en].png} | 0 ...ionHeader-Day-5_6_null,NEXUS_5,1.0,en].png | 3 - ...nHeader-Night-5_7_null,NEXUS_5,1.0,en].png | 3 - ...ntView-Day-5_6_null_0,NEXUS_5,1.0,en].png} | 0 ...ntView-Day-5_6_null_1,NEXUS_5,1.0,en].png} | 0 ...ntView-Day-5_6_null_2,NEXUS_5,1.0,en].png} | 0 ...ntView-Day-5_6_null_3,NEXUS_5,1.0,en].png} | 0 ...ntView-Day-5_6_null_4,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_1,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_3,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_4,NEXUS_5,1.0,en].png} | 0 ...olderRow-Day-8_9_null,NEXUS_5,1.0,en].png} | 0 ...erRow-Night-8_10_null,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_0,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_1,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_10,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_11,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_12,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_13,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_14,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_15,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_16,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_17,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_18,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_19,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_2,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_20,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_21,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_22,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_23,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_24,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_25,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_26,NEXUS_5,1.0,en].png} | 0 ...yRow-Day-9_10_null_27,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_3,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_4,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_5,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_6,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_7,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_8,NEXUS_5,1.0,en].png} | 0 ...ryRow-Day-9_10_null_9,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_0,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_1,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_10,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_11,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_12,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_13,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_14,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_15,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_16,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_17,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_18,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_19,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_2,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_20,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_21,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_22,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_23,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_24,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_25,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_26,NEXUS_5,1.0,en].png} | 0 ...ow-Night-9_11_null_27,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_3,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_4,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_5,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_6,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_7,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_8,NEXUS_5,1.0,en].png} | 0 ...Row-Night-9_11_null_9,NEXUS_5,1.0,en].png} | 0 ...View-Day-10_11_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Day-10_11_null_1,NEXUS_5,1.0,en].png} | 0 ...ew-Night-10_12_null_0,NEXUS_5,1.0,en].png} | 0 ...ew-Night-10_12_null_1,NEXUS_5,1.0,en].png} | 0 ...onView-Day-11_12_null,NEXUS_5,1.0,en].png} | 0 ...View-Night-11_13_null,NEXUS_5,1.0,en].png} | 0 ...tent-Day-12_13_null_0,NEXUS_5,1.0,en].png} | 0 ...tent-Day-12_13_null_1,NEXUS_5,1.0,en].png} | 0 ...tent-Day-12_13_null_2,NEXUS_5,1.0,en].png} | 0 ...nt-Night-12_14_null_0,NEXUS_5,1.0,en].png} | 0 ...nt-Night-12_14_null_1,NEXUS_5,1.0,en].png} | 0 ...nt-Night-12_14_null_2,NEXUS_5,1.0,en].png} | 0 ...stView-Day-3_4_null_10,NEXUS_5,1.0,en].png | 4 +- ...stView-Day-3_4_null_11,NEXUS_5,1.0,en].png | 4 +- ...stView-Day-3_4_null_12,NEXUS_5,1.0,en].png | 4 +- ...stView-Day-3_4_null_13,NEXUS_5,1.0,en].png | 3 - ...istView-Day-3_4_null_7,NEXUS_5,1.0,en].png | 4 +- ...istView-Day-3_4_null_8,NEXUS_5,1.0,en].png | 4 +- ...istView-Day-3_4_null_9,NEXUS_5,1.0,en].png | 4 +- ...View-Night-3_5_null_10,NEXUS_5,1.0,en].png | 4 +- ...View-Night-3_5_null_11,NEXUS_5,1.0,en].png | 4 +- ...View-Night-3_5_null_12,NEXUS_5,1.0,en].png | 4 +- ...View-Night-3_5_null_13,NEXUS_5,1.0,en].png | 3 - ...tView-Night-3_5_null_7,NEXUS_5,1.0,en].png | 4 +- ...tView-Night-3_5_null_8,NEXUS_5,1.0,en].png | 4 +- ...tView-Night-3_5_null_9,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_1,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_2,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_3,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_4,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_5,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_6,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_7,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_1,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_2,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_3,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_4,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_5,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_6,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_7,NEXUS_5,1.0,en].png | 4 +- ...WithResIcon-Day_0_null,NEXUS_5,1.0,en].png | 3 + ...thResIcon-Night_1_null,NEXUS_5,1.0,en].png | 3 + tools/localazy/config.json | 3 +- 198 files changed, 822 insertions(+), 761 deletions(-) create mode 100644 .maestro/tests/assertions/assertSessionVerificationDisplayed.yaml create mode 100644 changelog.d/2580.feature rename features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/{FtueState.kt => FtueService.kt} (53%) create mode 100644 features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt rename features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/{DefaultFtueState.kt => DefaultFtueService.kt} (74%) rename features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/{DefaultFtueStateTests.kt => DefaultFtueServiceTests.kt} (70%) delete mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/session/SessionStateProvider.kt delete mode 100644 features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RequestVerificationHeader.kt delete mode 100644 features/verifysession/impl/src/main/res/drawable/ic_verification_devices.xml delete mode 100644 features/verifysession/impl/src/main/res/drawable/ic_verification_emoji.xml delete mode 100644 features/verifysession/impl/src/main/res/drawable/ic_verification_waiting.xml delete mode 100644 features/verifysession/impl/src/main/res/drawable/ic_verification_warning.xml rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null,NEXUS_5,1.0,en].png} (100%) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null,NEXUS_5,1.0,en].png} (100%) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_2,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-8_9_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-8_10_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-7_9_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-7_8_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-7_9_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-6_8_null,NEXUS_5,1.0,en].png} (100%) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-5_7_null,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-9_10_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-8_9_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-9_11_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-8_10_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_10,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_10,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_11,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_11,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_12,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_12,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_13,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_13,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_14,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_14,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_15,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_15,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_16,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_16,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_17,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_17,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_18,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_18,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_19,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_19,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_20,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_20,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_21,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_21,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_22,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_22,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_23,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_23,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_24,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_24,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_25,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_25,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_26,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_26,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_27,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_27,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_7,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_8,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_8,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_9,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_9,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_10,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_10,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_11,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_11,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_12,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_12,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_13,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_13,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_14,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_14,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_15,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_15,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_16,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_16,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_17,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_17,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_18,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_18,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_19,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_19,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_20,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_20,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_21,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_21,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_22,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_22,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_23,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_23,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_24,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_24,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_25,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_25,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_26,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_26,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_27,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_27,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_7,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_8,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_8,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_9,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_9,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-12_13_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-12_14_null,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-11_13_null,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_2,NEXUS_5,1.0,en].png} (100%) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Day_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Night_1_null,NEXUS_5,1.0,en].png diff --git a/.maestro/tests/account/login.yaml b/.maestro/tests/account/login.yaml index dcb5cd3223..69833a985e 100644 --- a/.maestro/tests/account/login.yaml +++ b/.maestro/tests/account/login.yaml @@ -23,7 +23,8 @@ appId: ${MAESTRO_APP_ID} - inputText: ${MAESTRO_PASSWORD} - pressKey: Enter - tapOn: "Continue" +- runFlow: ../assertions/assertSessionVerificationDisplayed.yaml +- runFlow: ./verifySession.yaml - runFlow: ../assertions/assertAnalyticsDisplayed.yaml - tapOn: "Not now" - runFlow: ../assertions/assertHomeDisplayed.yaml -- runFlow: ./verifySession.yaml diff --git a/.maestro/tests/account/verifySession.yaml b/.maestro/tests/account/verifySession.yaml index eeb0489e3e..5449f4f7da 100644 --- a/.maestro/tests/account/verifySession.yaml +++ b/.maestro/tests/account/verifySession.yaml @@ -1,6 +1,5 @@ appId: ${MAESTRO_APP_ID} --- -- tapOn: "Continue" - takeScreenshot: build/maestro/150-Verify - tapOn: "Enter recovery key" - tapOn: @@ -8,4 +7,3 @@ appId: ${MAESTRO_APP_ID} - inputText: ${MAESTRO_RECOVERY_KEY} - hideKeyboard - tapOn: "Confirm" -- runFlow: ../assertions/assertHomeDisplayed.yaml diff --git a/.maestro/tests/assertions/assertSessionVerificationDisplayed.yaml b/.maestro/tests/assertions/assertSessionVerificationDisplayed.yaml new file mode 100644 index 0000000000..85785ac968 --- /dev/null +++ b/.maestro/tests/assertions/assertSessionVerificationDisplayed.yaml @@ -0,0 +1,5 @@ +appId: ${MAESTRO_APP_ID} +--- +- extendedWaitUntil: + visible: "Confirm that it's you" + timeout: 10000 diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt index b59c1cebdf..22300b7714 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt @@ -19,8 +19,6 @@ package io.element.android.appnav import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.room.RoomMembershipObserver -import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -34,16 +32,12 @@ import javax.inject.Inject class LoggedInEventProcessor @Inject constructor( private val snackbarDispatcher: SnackbarDispatcher, roomMembershipObserver: RoomMembershipObserver, - sessionVerificationService: SessionVerificationService, ) { private var observingJob: Job? = null private val displayLeftRoomMessage = roomMembershipObserver.updates .map { !it.isUserInRoom } - private val displayVerificationSuccessfulMessage = sessionVerificationService.verificationFlowState - .map { it == VerificationFlowState.Finished } - fun observeEvents(coroutineScope: CoroutineScope) { observingJob = coroutineScope.launch { displayLeftRoomMessage @@ -52,13 +46,6 @@ class LoggedInEventProcessor @Inject constructor( displayMessage(CommonStrings.common_current_user_left_room) } .launchIn(this) - - displayVerificationSuccessfulMessage - .filter { it } - .onEach { - displayMessage(CommonStrings.common_verification_complete) - } - .launchIn(this) } } 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 a9dd485285..2027f2868a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -45,6 +45,7 @@ import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.room.RoomLoadedFlowNode import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.ftue.api.FtueEntryPoint +import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.invitelist.api.InviteListEntryPoint import io.element.android.features.lockscreen.api.LockScreenEntryPoint @@ -56,13 +57,13 @@ import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint import io.element.android.features.roomlist.api.RoomListEntryPoint import io.element.android.features.securebackup.api.SecureBackupEntryPoint -import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.waitForChildAttached import io.element.android.libraries.deeplink.DeeplinkData import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE @@ -75,6 +76,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize @@ -88,14 +91,13 @@ class LoggedInFlowNode @AssistedInject constructor( private val preferencesEntryPoint: PreferencesEntryPoint, private val createRoomEntryPoint: CreateRoomEntryPoint, private val appNavigationStateService: AppNavigationStateService, - private val verifySessionEntryPoint: VerifySessionEntryPoint, private val secureBackupEntryPoint: SecureBackupEntryPoint, private val inviteListEntryPoint: InviteListEntryPoint, private val ftueEntryPoint: FtueEntryPoint, private val coroutineScope: CoroutineScope, private val networkMonitor: NetworkMonitor, private val notificationDrawerManager: NotificationDrawerManager, - private val ftueState: FtueState, + private val ftueService: FtueService, private val lockScreenEntryPoint: LockScreenEntryPoint, private val lockScreenStateService: LockScreenService, private val roomDirectoryEntryPoint: RoomDirectoryEntryPoint, @@ -103,7 +105,7 @@ class LoggedInFlowNode @AssistedInject constructor( snackbarDispatcher: SnackbarDispatcher, ) : BaseFlowNode( backstack = BackStack( - initialElement = NavTarget.RoomList, + initialElement = NavTarget.Placeholder, savedStateMap = buildContext.savedStateMap, ), permanentNavModel = PermanentNavModel( @@ -121,7 +123,6 @@ class LoggedInFlowNode @AssistedInject constructor( private val loggedInFlowProcessor = LoggedInEventProcessor( snackbarDispatcher, matrixClient.roomMembershipObserver(), - matrixClient.sessionVerificationService(), ) override fun onBuilt() { @@ -133,9 +134,15 @@ class LoggedInFlowNode @AssistedInject constructor( appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE) loggedInFlowProcessor.observeEvents(coroutineScope) - if (ftueState.shouldDisplayFlow.value) { - backstack.push(NavTarget.Ftue) - } + ftueService.state + .onEach { ftueState -> + when (ftueState) { + is FtueState.Unknown -> Unit // Nothing to do + is FtueState.Incomplete -> backstack.safeRoot(NavTarget.Ftue) + is FtueState.Complete -> backstack.safeRoot(NavTarget.RoomList) + } + } + .launchIn(lifecycleScope) }, onStop = { coroutineScope.launch { @@ -191,6 +198,9 @@ class LoggedInFlowNode @AssistedInject constructor( } sealed interface NavTarget : Parcelable { + @Parcelize + data object Placeholder : NavTarget + @Parcelize data object LoggedInPermanent : NavTarget @@ -214,9 +224,6 @@ class LoggedInFlowNode @AssistedInject constructor( @Parcelize data object CreateRoom : NavTarget - @Parcelize - data object VerifySession : NavTarget - @Parcelize data class SecureBackup( val initialElement: SecureBackupEntryPoint.InitialTarget = SecureBackupEntryPoint.InitialTarget.Root @@ -234,6 +241,7 @@ class LoggedInFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { + NavTarget.Placeholder -> createNode(buildContext) NavTarget.LoggedInPermanent -> { createNode(buildContext) } @@ -256,10 +264,6 @@ class LoggedInFlowNode @AssistedInject constructor( backstack.push(NavTarget.CreateRoom) } - override fun onSessionVerificationClicked() { - backstack.push(NavTarget.VerifySession) - } - override fun onSessionConfirmRecoveryKeyClicked() { backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey)) } @@ -308,10 +312,6 @@ class LoggedInFlowNode @AssistedInject constructor( plugins().forEach { it.onOpenBugReport() } } - override fun onVerifyClicked() { - backstack.push(NavTarget.VerifySession) - } - override fun onSecureBackupClicked() { backstack.push(NavTarget.SecureBackup()) } @@ -338,25 +338,6 @@ class LoggedInFlowNode @AssistedInject constructor( .callback(callback) .build() } - NavTarget.VerifySession -> { - val callback = object : VerifySessionEntryPoint.Callback { - override fun onEnterRecoveryKey() { - backstack.replace( - NavTarget.SecureBackup( - initialElement = SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey - ) - ) - } - - override fun onDone() { - backstack.pop() - } - } - verifySessionEntryPoint - .nodeBuilder(this, buildContext) - .callback(callback) - .build() - } is NavTarget.SecureBackup -> { secureBackupEntryPoint.nodeBuilder(this, buildContext) .params(SecureBackupEntryPoint.Params(initialElement = navTarget.initialElement)) @@ -381,7 +362,7 @@ class LoggedInFlowNode @AssistedInject constructor( ftueEntryPoint.nodeBuilder(this, buildContext) .callback(object : FtueEntryPoint.Callback { override fun onFtueFlowFinished() { - backstack.pop() + lifecycleScope.launch { attachRoomList() } } }) .build() @@ -398,20 +379,23 @@ class LoggedInFlowNode @AssistedInject constructor( } } - suspend fun attachRoot(): Node { - return attachChild { + suspend fun attachRoomList() { + if (!canShowRoomList()) return + attachChild { backstack.singleTop(NavTarget.RoomList) } } - suspend fun attachRoom(roomId: RoomId): RoomFlowNode { - return attachChild { + suspend fun attachRoom(roomId: RoomId) { + if (!canShowRoomList()) return + attachChild { backstack.singleTop(NavTarget.RoomList) backstack.push(NavTarget.Room(roomId)) } } internal suspend fun attachInviteList(deeplinkData: DeeplinkData.InviteList) = withContext(lifecycleScope.coroutineContext) { + if (!canShowRoomList()) return@withContext notificationDrawerManager.clearMembershipNotificationForSession(deeplinkData.sessionId) backstack.singleTop(NavTarget.RoomList) backstack.push(NavTarget.InviteList) @@ -420,13 +404,17 @@ class LoggedInFlowNode @AssistedInject constructor( } } + private fun canShowRoomList(): Boolean { + return ftueService.state.value is FtueState.Complete + } + @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { val lockScreenState by lockScreenStateService.lockState.collectAsState() + val isFtueDisplayed by ftueService.state.collectAsState() BackstackView() - val isFtueDisplayed by ftueState.shouldDisplayFlow.collectAsState() - if (!isFtueDisplayed) { + if (isFtueDisplayed is FtueState.Complete) { PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) } if (lockScreenState == LockScreenLockState.Locked) { @@ -434,4 +422,10 @@ class LoggedInFlowNode @AssistedInject constructor( } } } + + @ContributesNode(AppScope::class) + class PlaceholderNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + ) : Node(buildContext, plugins = plugins) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index e4ecdd8864..d310a02b99 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -288,7 +288,7 @@ class RootFlowNode @AssistedInject constructor( .attachSession() .apply { when (deeplinkData) { - is DeeplinkData.Root -> attachRoot() + is DeeplinkData.Root -> attachRoomList() is DeeplinkData.Room -> attachRoom(deeplinkData.roomId) is DeeplinkData.InviteList -> attachInviteList(deeplinkData) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index a9c4a6ecf8..7cb1d634b8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -27,22 +27,32 @@ import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.api.verification.SessionVerificationService +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.push.api.PushService +import kotlinx.coroutines.flow.map import javax.inject.Inject class LoggedInPresenter @Inject constructor( private val matrixClient: MatrixClient, private val networkMonitor: NetworkMonitor, private val pushService: PushService, + private val sessionVerificationService: SessionVerificationService, ) : Presenter { @Composable override fun present(): LoggedInState { - LaunchedEffect(Unit) { - // Ensure pusher is registered - // TODO Manually select push provider for now - val pushProvider = pushService.getAvailablePushProviders().firstOrNull() ?: return@LaunchedEffect - val distributor = pushProvider.getDistributors().firstOrNull() ?: return@LaunchedEffect - pushService.registerWith(matrixClient, pushProvider, distributor) + val isVerified by remember { + sessionVerificationService.sessionVerifiedStatus.map { it == SessionVerifiedStatus.Verified } + }.collectAsState(initial = false) + + LaunchedEffect(isVerified) { + if (isVerified) { + // Ensure pusher is registered + // TODO Manually select push provider for now + val pushProvider = pushService.getAvailablePushProviders().firstOrNull() ?: return@LaunchedEffect + val distributor = pushProvider.getDistributors().firstOrNull() ?: return@LaunchedEffect + pushService.registerWith(matrixClient, pushProvider, distributor) + } } val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState() diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index 0da579aa76..df17053512 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -25,6 +25,7 @@ import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.push.test.FakePushService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate @@ -71,6 +72,7 @@ class LoggedInPresenterTest { matrixClient = FakeMatrixClient(roomListService = roomListService), networkMonitor = FakeNetworkMonitor(networkStatus), pushService = FakePushService(), + sessionVerificationService = FakeSessionVerificationService(), ) } } diff --git a/changelog.d/2580.feature b/changelog.d/2580.feature new file mode 100644 index 0000000000..8d3ede2ccc --- /dev/null +++ b/changelog.d/2580.feature @@ -0,0 +1 @@ +Move session verification to the after login flow and make it mandatory. diff --git a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueState.kt b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueService.kt similarity index 53% rename from features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueState.kt rename to features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueService.kt index cd172669cc..77dd258b22 100644 --- a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueState.kt +++ b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueService.kt @@ -18,8 +18,25 @@ package io.element.android.features.ftue.api.state import kotlinx.coroutines.flow.StateFlow -interface FtueState { - val shouldDisplayFlow: StateFlow +/** + * Service to manage the First Time User Experience state (aka Onboarding). + */ +interface FtueService { + /** The current state of the FTUE. */ + val state: StateFlow + /** Reset the FTUE state. */ suspend fun reset() } + +/** The state of the FTUE. */ +sealed interface FtueState { + /** The FTUE state is unknown, nothing to do for now. */ + data object Unknown : FtueState + + /** The FTUE state is incomplete. The FTUE flow should be displayed. */ + data object Incomplete : FtueState + + /** The FTUE state is complete. The FTUE flow should not be displayed anymore. */ + data object Complete : FtueState +} diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index 1719aecbe1..06251cfe5d 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { implementation(projects.libraries.uiStrings) implementation(projects.libraries.testtags) implementation(projects.features.analytics.api) + implementation(projects.features.securebackup.api) + implementation(projects.features.verifysession.api) implementation(projects.services.analytics.api) implementation(projects.features.lockscreen.api) implementation(projects.libraries.permissions.api) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 652b6dddd4..cb77100d94 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -34,7 +34,8 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.features.ftue.impl.notifications.NotificationsOptInNode -import io.element.android.features.ftue.impl.state.DefaultFtueState +import io.element.android.features.ftue.impl.sessionverification.FtueSessionVerificationFlowNode +import io.element.android.features.ftue.impl.state.DefaultFtueService import io.element.android.features.ftue.impl.state.FtueStep import io.element.android.features.lockscreen.api.LockScreenEntryPoint import io.element.android.libraries.architecture.BackstackView @@ -55,7 +56,7 @@ import kotlinx.parcelize.Parcelize class FtueFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val ftueState: DefaultFtueState, + private val ftueState: DefaultFtueService, private val analyticsEntryPoint: AnalyticsEntryPoint, private val analyticsService: AnalyticsService, private val lockScreenEntryPoint: LockScreenEntryPoint, @@ -72,6 +73,9 @@ class FtueFlowNode @AssistedInject constructor( @Parcelize data object Placeholder : NavTarget + @Parcelize + data object SessionVerification : NavTarget + @Parcelize data object NotificationsOptIn : NavTarget @@ -106,6 +110,14 @@ class FtueFlowNode @AssistedInject constructor( NavTarget.Placeholder -> { createNode(buildContext) } + NavTarget.SessionVerification -> { + val callback = object : FtueSessionVerificationFlowNode.Callback { + override fun onDone() { + lifecycleScope.launch { moveToNextStep() } + } + } + createNode(buildContext, listOf(callback)) + } NavTarget.NotificationsOptIn -> { val callback = object : NotificationsOptInNode.Callback { override fun onNotificationsOptInFinished() { @@ -133,6 +145,9 @@ class FtueFlowNode @AssistedInject constructor( private fun moveToNextStep() { when (ftueState.getNextStep()) { + FtueStep.SessionVerification -> { + backstack.newRoot(NavTarget.SessionVerification) + } FtueStep.NotificationsOptIn -> { backstack.newRoot(NavTarget.NotificationsOptIn) } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt new file mode 100644 index 0000000000..53e7b32995 --- /dev/null +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.ftue.impl.sessionverification + +import android.os.Parcelable +import androidx.compose.runtime.Composable +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 com.bumble.appyx.core.plugin.plugins +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.push +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.securebackup.api.SecureBackupEntryPoint +import io.element.android.features.verifysession.api.VerifySessionEntryPoint +import io.element.android.libraries.architecture.BackstackView +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.di.SessionScope +import kotlinx.parcelize.Parcelize + +@ContributesNode(SessionScope::class) +class FtueSessionVerificationFlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val verifySessionEntryPoint: VerifySessionEntryPoint, + private val secureBackupEntryPoint: SecureBackupEntryPoint, +) : BaseFlowNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + + @Parcelize + data object EnterRecoveryKey : NavTarget + } + + interface Callback : Plugin { + fun onDone() + } + + private val callback = plugins().first() + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + is NavTarget.Root -> { + verifySessionEntryPoint.nodeBuilder(this, buildContext) + .callback(object : VerifySessionEntryPoint.Callback { + override fun onEnterRecoveryKey() { + backstack.push(NavTarget.EnterRecoveryKey) + } + + override fun onDone() { + callback.onDone() + } + }) + .build() + } + is NavTarget.EnterRecoveryKey -> { + secureBackupEntryPoint.nodeBuilder(this, buildContext) + .params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey)) + .callback(object : SecureBackupEntryPoint.Callback { + override fun onDone() { + callback.onDone() + } + }) + .build() + } + } + } + + @Composable + override fun View(modifier: Modifier) { + BackstackView() + } +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt similarity index 74% rename from features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt rename to features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt index f1a1c84545..384d518452 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt @@ -20,9 +20,12 @@ import android.Manifest import android.os.Build import androidx.annotation.VisibleForTesting import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.verification.SessionVerificationService +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.permissions.api.PermissionStateProvider import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider @@ -35,14 +38,15 @@ import kotlinx.coroutines.runBlocking import javax.inject.Inject @ContributesBinding(SessionScope::class) -class DefaultFtueState @Inject constructor( +class DefaultFtueService @Inject constructor( private val sdkVersionProvider: BuildVersionSdkIntProvider, coroutineScope: CoroutineScope, private val analyticsService: AnalyticsService, private val permissionStateProvider: PermissionStateProvider, private val lockScreenService: LockScreenService, -) : FtueState { - override val shouldDisplayFlow = MutableStateFlow(isAnyStepIncomplete()) + private val sessionVerificationService: SessionVerificationService, +) : FtueService { + override val state = MutableStateFlow(FtueState.Unknown) override suspend fun reset() { analyticsService.reset() @@ -52,6 +56,10 @@ class DefaultFtueState @Inject constructor( } init { + sessionVerificationService.sessionVerifiedStatus + .onEach { updateState() } + .launchIn(coroutineScope) + analyticsService.didAskUserConsent() .onEach { updateState() } .launchIn(coroutineScope) @@ -59,7 +67,12 @@ class DefaultFtueState @Inject constructor( fun getNextStep(currentStep: FtueStep? = null): FtueStep? = when (currentStep) { - null -> if (shouldAskNotificationPermissions()) { + null -> if (isSessionNotVerified()) { + FtueStep.SessionVerification + } else { + getNextStep(FtueStep.SessionVerification) + } + FtueStep.SessionVerification -> if (shouldAskNotificationPermissions()) { FtueStep.NotificationsOptIn } else { getNextStep(FtueStep.NotificationsOptIn) @@ -79,12 +92,21 @@ class DefaultFtueState @Inject constructor( private fun isAnyStepIncomplete(): Boolean { return listOf( + { isSessionNotVerified() }, { shouldAskNotificationPermissions() }, { needsAnalyticsOptIn() }, { shouldDisplayLockscreenSetup() }, ).any { it() } } + private fun isSessionVerificationServiceReady(): Boolean { + return sessionVerificationService.sessionVerifiedStatus.value != SessionVerifiedStatus.Unknown + } + + private fun isSessionNotVerified(): Boolean { + return sessionVerificationService.sessionVerifiedStatus.value == SessionVerifiedStatus.NotVerified + } + private fun needsAnalyticsOptIn(): Boolean { // We need this function to not be suspend, so we need to load the value through runBlocking return runBlocking { analyticsService.didAskUserConsent().first().not() } @@ -109,11 +131,16 @@ class DefaultFtueState @Inject constructor( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun updateState() { - shouldDisplayFlow.value = isAnyStepIncomplete() + state.value = when { + !isSessionVerificationServiceReady() -> FtueState.Unknown + isAnyStepIncomplete() -> FtueState.Incomplete + else -> FtueState.Complete + } } } sealed interface FtueStep { + data object SessionVerification : FtueStep data object NotificationsOptIn : FtueStep data object AnalyticsOptIn : FtueStep data object LockscreenSetup : FtueStep diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt similarity index 70% rename from features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt rename to features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt index 1f0b817850..f3f21836b9 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueStateTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt @@ -17,11 +17,15 @@ package io.element.android.features.ftue.impl import android.os.Build +import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.ftue.impl.state.DefaultFtueState +import io.element.android.features.ftue.api.state.FtueState +import io.element.android.features.ftue.impl.state.DefaultFtueService import io.element.android.features.ftue.impl.state.FtueStep import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.features.lockscreen.test.FakeLockScreenService +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus +import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.permissions.impl.FakePermissionStateProvider import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService @@ -32,38 +36,51 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.test.runTest import org.junit.Test -class DefaultFtueStateTests { +class DefaultFtueServiceTests { @Test - fun `given any check being false, should display flow is true`() = runTest { + fun `given any check being false and session verification state being loaded, FtueState is Incomplete`() = runTest { + val sessionVerificationService = FakeSessionVerificationService().apply { + givenVerifiedStatus(SessionVerifiedStatus.Unknown) + } val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) - val state = createState(coroutineScope) + val state = createState(coroutineScope, sessionVerificationService) - assertThat(state.shouldDisplayFlow.value).isTrue() + state.state.test { + // Verification state is unknown, we don't display the flow yet + assertThat(awaitItem()).isEqualTo(FtueState.Unknown) + + // Verification state is known, we should display the flow if any check is false + sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.NotVerified) + assertThat(awaitItem()).isEqualTo(FtueState.Incomplete) + } // Cleanup coroutineScope.cancel() } @Test - fun `given all checks being true, should display flow is false`() = runTest { + fun `given all checks being true, FtueState is Complete`() = runTest { val analyticsService = FakeAnalyticsService() + val sessionVerificationService = FakeSessionVerificationService() val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true) val lockScreenService = FakeLockScreenService() val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) val state = createState( coroutineScope = coroutineScope, + sessionVerificationService = sessionVerificationService, analyticsService = analyticsService, permissionStateProvider = permissionStateProvider, lockScreenService = lockScreenService, ) + sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.Verified) analyticsService.setDidAskUserConsent() permissionStateProvider.setPermissionGranted() lockScreenService.setIsPinSetup(true) state.updateState() - assertThat(state.shouldDisplayFlow.value).isFalse() + assertThat(state.state.value).isEqualTo(FtueState.Complete) // Cleanup coroutineScope.cancel() @@ -71,6 +88,9 @@ class DefaultFtueStateTests { @Test fun `traverse flow`() = runTest { + val sessionVerificationService = FakeSessionVerificationService().apply { + givenVerifiedStatus(SessionVerifiedStatus.NotVerified) + } val analyticsService = FakeAnalyticsService() val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false) val lockScreenService = FakeLockScreenService() @@ -78,12 +98,17 @@ class DefaultFtueStateTests { val state = createState( coroutineScope = coroutineScope, + sessionVerificationService = sessionVerificationService, analyticsService = analyticsService, permissionStateProvider = permissionStateProvider, lockScreenService = lockScreenService, ) val steps = mutableListOf() + // Session verification + steps.add(state.getNextStep(steps.lastOrNull())) + sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.Verified) + // Notifications opt in steps.add(state.getNextStep(steps.lastOrNull())) permissionStateProvider.setPermissionGranted() @@ -100,6 +125,7 @@ class DefaultFtueStateTests { steps.add(state.getNextStep(steps.lastOrNull())) assertThat(steps).containsExactly( + FtueStep.SessionVerification, FtueStep.NotificationsOptIn, FtueStep.LockscreenSetup, FtueStep.AnalyticsOptIn, @@ -114,17 +140,20 @@ class DefaultFtueStateTests { @Test fun `if a check for a step is true, start from the next one`() = runTest { val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) + val sessionVerificationService = FakeSessionVerificationService() val analyticsService = FakeAnalyticsService() val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false) val lockScreenService = FakeLockScreenService() val state = createState( coroutineScope = coroutineScope, + sessionVerificationService = sessionVerificationService, analyticsService = analyticsService, permissionStateProvider = permissionStateProvider, lockScreenService = lockScreenService, ) - // Skip first 2 steps + // Skip first 3 steps + sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.Verified) permissionStateProvider.setPermissionGranted() lockScreenService.setIsPinSetup(true) @@ -140,16 +169,19 @@ class DefaultFtueStateTests { @Test fun `if version is older than 13 we don't display the notification opt in screen`() = runTest { val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob()) + val sessionVerificationService = FakeSessionVerificationService() val analyticsService = FakeAnalyticsService() val lockScreenService = FakeLockScreenService() val state = createState( sdkIntVersion = Build.VERSION_CODES.M, + sessionVerificationService = sessionVerificationService, coroutineScope = coroutineScope, analyticsService = analyticsService, lockScreenService = lockScreenService, ) + sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.Verified) lockScreenService.setIsPinSetup(true) assertThat(state.getNextStep()).isEqualTo(FtueStep.AnalyticsOptIn) @@ -163,14 +195,16 @@ class DefaultFtueStateTests { private fun createState( coroutineScope: CoroutineScope, + sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), analyticsService: AnalyticsService = FakeAnalyticsService(), permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false), lockScreenService: LockScreenService = FakeLockScreenService(), // First version where notification permission is required sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU, - ) = DefaultFtueState( - sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion), + ) = DefaultFtueService( coroutineScope = coroutineScope, + sessionVerificationService = sessionVerificationService, + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion), analyticsService = analyticsService, permissionStateProvider = permissionStateProvider, lockScreenService = lockScreenService, 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 67915bf9d4..3cd525c614 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 @@ -33,7 +33,6 @@ import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory 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.timeline.session.SessionState import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.features.poll.api.actions.SendPollResponseAction @@ -41,15 +40,11 @@ import io.element.android.features.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.encryption.BackupState -import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MessageEventType 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.item.event.TimelineItemEventOrigin -import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope @@ -68,8 +63,6 @@ class TimelinePresenter @AssistedInject constructor( private val dispatchers: CoroutineDispatchers, private val appScope: CoroutineScope, @Assisted private val navigator: MessagesNavigator, - private val verificationService: SessionVerificationService, - private val encryptionService: EncryptionService, private val redactedVoiceMessageManager: RedactedVoiceMessageManager, private val sendPollResponseAction: SendPollResponseAction, private val endPollAction: EndPollAction, @@ -101,21 +94,9 @@ class TimelinePresenter @AssistedInject constructor( val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val newItemState = remember { mutableStateOf(NewEventState.None) } - val sessionVerifiedStatus by verificationService.sessionVerifiedStatus.collectAsState() - val keyBackupState by encryptionService.backupStateStateFlow.collectAsState() - val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true) val renderReadReceipts by sessionPreferencesStore.isRenderReadReceiptsEnabled().collectAsState(initial = true) - val sessionState by remember { - derivedStateOf { - SessionState( - isSessionVerified = sessionVerifiedStatus == SessionVerifiedStatus.Verified, - isKeyBackupEnabled = keyBackupState == BackupState.ENABLED - ) - } - } - fun handleEvents(event: TimelineEvents) { when (event) { TimelineEvents.LoadMore -> localScope.paginateBackwards() @@ -184,7 +165,6 @@ class TimelinePresenter @AssistedInject constructor( timelineItems = timelineItems, renderReadReceipts = renderReadReceipts, newEventState = newItemState.value, - sessionState = sessionState, eventSink = { handleEvents(it) } ) } 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 ab9d93d678..4e2f9b8d42 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 @@ -19,7 +19,6 @@ package io.element.android.features.messages.impl.timeline import androidx.compose.runtime.Immutable 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.timeline.session.SessionState import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import kotlinx.collections.immutable.ImmutableList @@ -32,7 +31,6 @@ data class TimelineState( val highlightedEventId: EventId?, val paginationState: MatrixTimeline.PaginationState, val newEventState: NewEventState, - val sessionState: SessionState, val eventSink: (TimelineEvents) -> Unit ) 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 aa35b620b9..ae7f62ebd7 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 @@ -29,7 +29,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel -import io.element.android.features.messages.impl.timeline.session.aSessionState 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.EventId @@ -58,10 +57,6 @@ fun aTimelineState( renderReadReceipts = renderReadReceipts, highlightedEventId = null, newEventState = NewEventState.None, - sessionState = aSessionState( - isSessionVerified = true, - isKeyBackupEnabled = true, - ), eventSink = eventSink, ) 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 5869238753..56a2676f43 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 @@ -148,7 +148,6 @@ fun TimelineView( onMoreReactionsClick = onMoreReactionsClicked, onReadReceiptClick = onReadReceiptClick, onTimestampClicked = onTimestampClicked, - sessionState = state.sessionState, eventSink = state.eventSink, onSwipeToReply = onSwipeToReply, ) 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 bc05882f1b..a5d42d5dbf 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 @@ -32,8 +32,6 @@ import io.element.android.features.messages.impl.timeline.components.group.Group import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView import io.element.android.features.messages.impl.timeline.model.TimelineItem -import io.element.android.features.messages.impl.timeline.session.SessionState -import io.element.android.features.messages.impl.timeline.session.aSessionState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.EventId @@ -46,7 +44,6 @@ fun TimelineItemGroupedEventsRow( renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, highlightedItem: String?, - sessionState: SessionState, onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, @@ -74,7 +71,6 @@ fun TimelineItemGroupedEventsRow( highlightedItem = highlightedItem, renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, - sessionState = sessionState, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, @@ -99,7 +95,6 @@ private fun TimelineItemGroupedEventsRowContent( highlightedItem: String?, renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, - sessionState: SessionState, onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, @@ -133,7 +128,6 @@ private fun TimelineItemGroupedEventsRowContent( renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, - sessionState = sessionState, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, @@ -174,7 +168,6 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi highlightedItem = null, renderReadReceipts = true, isLastOutgoingMessage = false, - sessionState = aSessionState(), onClick = {}, onLongClick = {}, inReplyToClick = {}, @@ -200,7 +193,6 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi highlightedItem = null, renderReadReceipts = true, isLastOutgoingMessage = false, - sessionState = aSessionState(), onClick = {}, onLongClick = {}, inReplyToClick = {}, 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 5146c87df6..89b223dafe 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 @@ -23,7 +23,6 @@ import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent -import io.element.android.features.messages.impl.timeline.session.SessionState import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId @@ -34,7 +33,6 @@ internal fun TimelineItemRow( renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, highlightedItem: String?, - sessionState: SessionState, onUserDataClick: (UserId) -> Unit, onLinkClicked: (String) -> Unit, onClick: (TimelineItem.Event) -> Unit, @@ -53,7 +51,6 @@ internal fun TimelineItemRow( is TimelineItem.Virtual -> { TimelineItemVirtualRow( virtual = timelineItem, - sessionState = sessionState, modifier = modifier, ) } @@ -100,7 +97,6 @@ internal fun TimelineItemRow( renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, highlightedItem = highlightedItem, - sessionState = sessionState, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, 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 5ef3c2ef2c..306c11aaba 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 @@ -25,17 +25,15 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemDaySeparatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemEncryptedHistoryBannerVirtualModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel -import io.element.android.features.messages.impl.timeline.session.SessionState @Composable fun TimelineItemVirtualRow( virtual: TimelineItem.Virtual, - sessionState: SessionState, modifier: Modifier = Modifier ) { when (virtual.model) { is TimelineItemDaySeparatorModel -> TimelineItemDaySeparatorView(virtual.model, modifier) TimelineItemReadMarkerModel -> TimelineItemReadMarkerView() - is TimelineItemEncryptedHistoryBannerVirtualModel -> TimelineEncryptedHistoryBannerView(sessionState, modifier) + is TimelineItemEncryptedHistoryBannerVirtualModel -> TimelineEncryptedHistoryBannerView(modifier) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt index c555352864..db0dd1ae9a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt @@ -16,7 +16,6 @@ package io.element.android.features.messages.impl.timeline.components.virtual -import androidx.annotation.StringRes import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement @@ -29,20 +28,16 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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.compound.tokens.generated.CompoundIcons import io.element.android.features.messages.impl.R -import io.element.android.features.messages.impl.timeline.session.SessionState -import io.element.android.features.messages.impl.timeline.session.SessionStateProvider 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 @Composable fun TimelineEncryptedHistoryBannerView( - sessionState: SessionState, modifier: Modifier = Modifier, ) { Row( @@ -61,26 +56,15 @@ fun TimelineEncryptedHistoryBannerView( tint = ElementTheme.colors.iconInfoPrimary ) Text( - text = stringResource(sessionState.toStringResId()), + text = stringResource(R.string.screen_room_encrypted_history_banner), style = ElementTheme.typography.fontBodyMdMedium, color = ElementTheme.colors.textInfoPrimary ) } } -@StringRes -private fun SessionState.toStringResId(): Int { - return when { - isSessionVerified.not() -> R.string.screen_room_encrypted_history_banner_unverified - isKeyBackupEnabled.not() -> R.string.screen_room_encrypted_history_banner - else -> R.string.screen_room_encrypted_history_banner // TODO strings need to be updated - } -} - @PreviewsDayNight @Composable -internal fun EncryptedHistoryBannerViewPreview( - @PreviewParameter(SessionStateProvider::class) sessionState: SessionState, -) = ElementPreview { - TimelineEncryptedHistoryBannerView(sessionState = sessionState) +internal fun EncryptedHistoryBannerViewPreview() = ElementPreview { + TimelineEncryptedHistoryBannerView() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/session/SessionStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/session/SessionStateProvider.kt deleted file mode 100644 index 2dd27d5456..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/session/SessionStateProvider.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.messages.impl.timeline.session - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider - -open class SessionStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aSessionState(isSessionVerified = false, isKeyBackupEnabled = false), - aSessionState(isSessionVerified = true, isKeyBackupEnabled = false), - aSessionState(isSessionVerified = true, isKeyBackupEnabled = true), - ) -} - -internal fun aSessionState( - isSessionVerified: Boolean = false, - isKeyBackupEnabled: Boolean = false, -) = SessionState( - isSessionVerified = isSessionVerified, - isKeyBackupEnabled = isKeyBackupEnabled, -) 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 6cceb6b5b5..237c7f357a 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 @@ -76,13 +76,11 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.core.aBuildMeta -import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom 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.verification.FakeSessionVerificationService import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.mediaupload.api.MediaSender @@ -745,8 +743,6 @@ class MessagesPresenterTest { dispatchers = coroutineDispatchers, appScope = this, navigator = navigator, - encryptionService = FakeEncryptionService(), - verificationService = FakeSessionVerificationService(), redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), endPollAction = endPollAction, sendPollResponseAction = FakeSendPollResponseAction(), 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 5ee6ca8aa0..2485e57172 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 @@ -26,7 +26,6 @@ import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory 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.timeline.session.SessionState import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.features.messages.impl.voicemessages.timeline.aRedactedMatrixTimeline @@ -47,13 +46,11 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID -import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem -import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem @@ -89,7 +86,6 @@ class TimelinePresenterTest { assertThat(initialState.timelineItems).isEmpty() val loadedNoTimelineState = awaitItem() assertThat(loadedNoTimelineState.timelineItems).isEmpty() - assertThat(loadedNoTimelineState.sessionState).isEqualTo(SessionState(isSessionVerified = false, isKeyBackupEnabled = false)) } } @@ -512,8 +508,6 @@ class TimelinePresenterTest { dispatchers = testCoroutineDispatchers(), appScope = this, navigator = messagesNavigator, - encryptionService = FakeEncryptionService(), - verificationService = FakeSessionVerificationService(), redactedVoiceMessageManager = redactedVoiceMessageManager, endPollAction = endPollAction, sendPollResponseAction = sendPollResponseAction, diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt index 0577412604..e488d911ed 100644 --- a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt @@ -45,7 +45,6 @@ interface PreferencesEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onOpenBugReport() - fun onVerifyClicked() fun onSecureBackupClicked() fun onOpenRoomNotificationSettings(roomId: RoomId) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index 1dc5f650fd..b93e02dd39 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -115,10 +115,6 @@ class PreferencesFlowNode @AssistedInject constructor( plugins().forEach { it.onOpenBugReport() } } - override fun onVerifyClicked() { - plugins().forEach { it.onVerifyClicked() } - } - override fun onSecureBackupClicked() { plugins().forEach { it.onSecureBackupClicked() } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index 3bd762794d..11f3fc3dd3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -44,7 +44,6 @@ class PreferencesRootNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun onOpenBugReport() - fun onVerifyClicked() fun onSecureBackupClicked() fun onOpenAnalytics() fun onOpenAbout() @@ -61,10 +60,6 @@ class PreferencesRootNode @AssistedInject constructor( plugins().forEach { it.onOpenBugReport() } } - private fun onVerifyClicked() { - plugins().forEach { it.onVerifyClicked() } - } - private fun onSecureBackupClicked() { plugins().forEach { it.onSecureBackupClicked() } } @@ -138,7 +133,6 @@ class PreferencesRootNode @AssistedInject constructor( onOpenRageShake = this::onOpenBugReport, onOpenAnalytics = this::onOpenAnalytics, onOpenAbout = this::onOpenAbout, - onVerifyClicked = this::onVerifyClicked, onSecureBackupClicked = this::onSecureBackupClicked, onOpenDeveloperSettings = this::onOpenDeveloperSettings, onOpenAdvancedSettings = this::onOpenAdvancedSettings, 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 f80ee11aba..20c28427a8 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 @@ -74,7 +74,7 @@ class PreferencesRootPresenter @Inject constructor( } // We should display the 'complete verification' option if the current session can be verified - val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false) + val canVerifyUserSession by sessionVerificationService.canVerifySessionFlow.collectAsState(false) val showSecureBackupIndicator by indicatorService.showSettingChatBackupIndicator() @@ -102,8 +102,7 @@ class PreferencesRootPresenter @Inject constructor( myUser = matrixUser.value, version = versionFormatter.get(), deviceId = matrixClient.deviceId, - showCompleteVerification = showCompleteVerification, - showSecureBackup = !showCompleteVerification, + showSecureBackup = !canVerifyUserSession, showSecureBackupBadge = showSecureBackupIndicator, accountManagementUrl = accountManagementUrl.value, devicesManagementUrl = devicesManagementUrl.value, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index eabdc80da0..336690638d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -24,7 +24,6 @@ data class PreferencesRootState( val myUser: MatrixUser, val version: String, val deviceId: String?, - val showCompleteVerification: Boolean, val showSecureBackup: Boolean, val showSecureBackupBadge: Boolean, val accountManagementUrl: String?, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index b688a493b6..3373cb6ba0 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -27,7 +27,6 @@ fun aPreferencesRootState( myUser = myUser, version = "Version 1.1 (1)", deviceId = "ILAKNDNASDLK", - showCompleteVerification = true, showSecureBackup = true, showSecureBackupBadge = true, accountManagementUrl = "aUrl", diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 293889e868..c6283158f2 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -51,7 +51,6 @@ import io.element.android.libraries.ui.strings.CommonStrings fun PreferencesRootView( state: PreferencesRootState, onBackPressed: () -> Unit, - onVerifyClicked: () -> Unit, onSecureBackupClicked: () -> Unit, onManageAccountClicked: (url: String) -> Unit, onOpenAnalytics: () -> Unit, @@ -81,13 +80,6 @@ fun PreferencesRootView( }, user = state.myUser, ) - if (state.showCompleteVerification) { - ListItem( - headlineContent = { Text(text = stringResource(CommonStrings.common_verify_device)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.CheckCircle())), - onClick = onVerifyClicked - ) - } if (state.showSecureBackup) { ListItem( headlineContent = { Text(stringResource(id = CommonStrings.common_chat_backup)) }, @@ -95,8 +87,6 @@ fun PreferencesRootView( trailingContent = ListItemContent.Badge.takeIf { state.showSecureBackupBadge }, onClick = onSecureBackupClicked, ) - } - if (state.showCompleteVerification || state.showSecureBackup) { HorizontalDivider() } if (state.accountManagementUrl != null) { @@ -232,7 +222,6 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenDeveloperSettings = {}, onOpenAdvancedSettings = {}, onOpenAbout = {}, - onVerifyClicked = {}, onSecureBackupClicked = {}, onManageAccountClicked = {}, onOpenNotificationSettings = {}, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt index 2d590b90c1..9227eef364 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt @@ -22,7 +22,7 @@ import android.content.Context import coil.Coil import coil.annotation.ExperimentalCoilApi import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.features.ftue.api.state.FtueState +import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.preferences.impl.DefaultCacheService import io.element.android.features.roomlist.api.migration.MigrationScreenStore import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -45,7 +45,7 @@ class DefaultClearCacheUseCase @Inject constructor( private val coroutineDispatchers: CoroutineDispatchers, private val defaultCacheIndexProvider: DefaultCacheService, private val okHttpClient: Provider, - private val ftueState: FtueState, + private val ftueService: FtueService, private val migrationScreenStore: MigrationScreenStore, ) : ClearCacheUseCase { override suspend fun invoke() = withContext(coroutineDispatchers.io) { @@ -61,7 +61,7 @@ class DefaultClearCacheUseCase @Inject constructor( // Clear app cache context.cacheDir.deleteRecursively() // Clear some settings - ftueState.reset() + ftueService.reset() // Clear migration screen store migrationScreenStore.reset() // Ensure the app is restarted diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 3288e94eb4..8bc6e92328 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -92,7 +92,6 @@ class PreferencesRootPresenterTest { ) ) assertThat(initialState.version).isEqualTo("A Version") - assertThat(loadedState.showCompleteVerification).isTrue() assertThat(loadedState.showSecureBackup).isFalse() assertThat(loadedState.showSecureBackupBadge).isTrue() assertThat(loadedState.accountManagementUrl).isNull() diff --git a/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt b/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt index c3b2ff36a2..b5d1c1299f 100644 --- a/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt +++ b/features/roomlist/api/src/main/kotlin/io/element/android/features/roomlist/api/RoomListEntryPoint.kt @@ -33,7 +33,6 @@ interface RoomListEntryPoint : FeatureEntryPoint { fun onRoomClicked(roomId: RoomId) fun onCreateRoomClicked() fun onSettingsClicked() - fun onSessionVerificationClicked() fun onSessionConfirmRecoveryKeyClicked() fun onInvitesClicked() fun onRoomSettingsClicked(roomId: RoomId) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt index a9740bcfcb..e9af66d331 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListNode.kt @@ -64,10 +64,6 @@ class RoomListNode @AssistedInject constructor( plugins().forEach { it.onCreateRoomClicked() } } - private fun onSessionVerificationClicked() { - plugins().forEach { it.onSessionVerificationClicked() } - } - private fun onSessionConfirmRecoveryKeyClicked() { plugins().forEach { it.onSessionConfirmRecoveryKeyClicked() } } @@ -104,7 +100,6 @@ class RoomListNode @AssistedInject constructor( onRoomClicked = this::onRoomClicked, onSettingsClicked = this::onOpenSettings, onCreateRoomClicked = this::onCreateRoomClicked, - onVerifyClicked = this::onSessionVerificationClicked, onConfirmRecoveryKeyClicked = this::onSessionConfirmRecoveryKeyClicked, onInvitesClicked = this::onInvitesClicked, onRoomSettingsClicked = this::onRoomSettingsClicked, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 900e640981..0b5d07dc69 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -58,7 +58,6 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.timeline.ReceiptType -import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.toPersistentList @@ -92,7 +91,6 @@ class RoomListPresenter @Inject constructor( private val analyticsService: AnalyticsService, ) : Presenter { private val encryptionService: EncryptionService = client.encryptionService() - private val sessionVerificationService: SessionVerificationService = client.sessionVerificationService() private val syncService: SyncService = client.syncService() @Composable @@ -159,19 +157,12 @@ class RoomListPresenter @Inject constructor( securityBannerDismissed: Boolean, ): State { val currentSecurityBannerDismissed by rememberUpdatedState(securityBannerDismissed) - val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false) - val isLastDevice by encryptionService.isLastDevice.collectAsState() val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() val syncState by syncService.syncState.collectAsState() return remember { derivedStateOf { when { currentSecurityBannerDismissed -> SecurityBannerState.None - canVerifySession -> if (isLastDevice) { - SecurityBannerState.RecoveryKeyConfirmation - } else { - SecurityBannerState.SessionVerification - } recoveryState == RecoveryState.INCOMPLETE && syncState == SyncState.Running -> SecurityBannerState.RecoveryKeyConfirmation else -> SecurityBannerState.None diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index 250f1c8e87..62f59b4eaa 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -63,7 +63,6 @@ enum class InvitesState { enum class SecurityBannerState { None, - SessionVerification, RecoveryKeyConfirmation, } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index 00f6833f02..c80430f9fe 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt @@ -45,7 +45,6 @@ open class RoomListStateProvider : PreviewParameterProvider { aRoomListState(contentState = aRoomsContentState(invitesState = InvitesState.NewInvites)), aRoomListState(contextMenu = aContextMenuShown(roomName = "A nice room name")), aRoomListState(contextMenu = aContextMenuShown(isFavorite = true)), - aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SessionVerification)), aRoomListState(contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation)), aRoomListState(contentState = anEmptyContentState()), aRoomListState(contentState = aSkeletonContentState()), diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index e0a3fc5201..881d06411e 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -53,7 +53,6 @@ fun RoomListView( state: RoomListState, onRoomClicked: (RoomId) -> Unit, onSettingsClicked: () -> Unit, - onVerifyClicked: () -> Unit, onConfirmRecoveryKeyClicked: () -> Unit, onCreateRoomClicked: () -> Unit, onInvitesClicked: () -> Unit, @@ -86,7 +85,6 @@ fun RoomListView( RoomListScaffold( modifier = Modifier.padding(top = topPadding), state = state, - onVerifyClicked = onVerifyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, onRoomClicked = onRoomClicked, onRoomLongClicked = { onRoomLongClicked(it) }, @@ -115,7 +113,6 @@ fun RoomListView( @Composable private fun RoomListScaffold( state: RoomListState, - onVerifyClicked: () -> Unit, onConfirmRecoveryKeyClicked: () -> Unit, onRoomClicked: (RoomId) -> Unit, onRoomLongClicked: (RoomListRoomSummary) -> Unit, @@ -154,7 +151,6 @@ private fun RoomListScaffold( contentState = state.contentState, filtersState = state.filtersState, eventSink = state.eventSink, - onVerifyClicked = onVerifyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, onRoomClicked = ::onRoomClicked, onRoomLongClicked = onRoomLongClicked, @@ -193,7 +189,6 @@ internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class) state = state, onRoomClicked = {}, onSettingsClicked = {}, - onVerifyClicked = {}, onConfirmRecoveryKeyClicked = {}, onCreateRoomClicked = {}, onInvitesClicked = {}, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RequestVerificationHeader.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RequestVerificationHeader.kt deleted file mode 100644 index d3ad6db206..0000000000 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RequestVerificationHeader.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.roomlist.impl.components - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import io.element.android.features.roomlist.impl.R -import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight - -@Composable -internal fun RequestVerificationHeader( - onVerifyClicked: () -> Unit, - onDismissClicked: () -> Unit, - modifier: Modifier = Modifier, -) { - DialogLikeBannerMolecule( - modifier = modifier, - title = stringResource(R.string.session_verification_banner_title), - content = stringResource(R.string.session_verification_banner_message), - onSubmitClicked = onVerifyClicked, - onDismissClicked = onDismissClicked, - ) -} - -@PreviewsDayNight -@Composable -internal fun RequestVerificationHeaderPreview() = ElementPreview { - RequestVerificationHeader( - onVerifyClicked = {}, - onDismissClicked = {}, - ) -} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt index 1bb5af4e7a..552ff008a0 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListContentView.kt @@ -73,7 +73,6 @@ fun RoomListContentView( contentState: RoomListContentState, filtersState: RoomListFiltersState, eventSink: (RoomListEvents) -> Unit, - onVerifyClicked: () -> Unit, onConfirmRecoveryKeyClicked: () -> Unit, onRoomClicked: (RoomListRoomSummary) -> Unit, onRoomLongClicked: (RoomListRoomSummary) -> Unit, @@ -103,7 +102,6 @@ fun RoomListContentView( state = contentState, filtersState = filtersState, eventSink = eventSink, - onVerifyClicked = onVerifyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, onRoomClicked = onRoomClicked, onRoomLongClicked = onRoomLongClicked, @@ -161,7 +159,6 @@ private fun RoomsView( state: RoomListContentState.Rooms, filtersState: RoomListFiltersState, eventSink: (RoomListEvents) -> Unit, - onVerifyClicked: () -> Unit, onConfirmRecoveryKeyClicked: () -> Unit, onRoomClicked: (RoomListRoomSummary) -> Unit, onRoomLongClicked: (RoomListRoomSummary) -> Unit, @@ -177,7 +174,6 @@ private fun RoomsView( RoomsViewList( state = state, eventSink = eventSink, - onVerifyClicked = onVerifyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, onRoomClicked = onRoomClicked, onRoomLongClicked = onRoomLongClicked, @@ -191,7 +187,6 @@ private fun RoomsView( private fun RoomsViewList( state: RoomListContentState.Rooms, eventSink: (RoomListEvents) -> Unit, - onVerifyClicked: () -> Unit, onConfirmRecoveryKeyClicked: () -> Unit, onRoomClicked: (RoomListRoomSummary) -> Unit, onRoomLongClicked: (RoomListRoomSummary) -> Unit, @@ -222,14 +217,6 @@ private fun RoomsViewList( contentPadding = PaddingValues(bottom = 80.dp) ) { when (state.securityBannerState) { - SecurityBannerState.SessionVerification -> { - item { - RequestVerificationHeader( - onVerifyClicked = onVerifyClicked, - onDismissClicked = { eventSink(RoomListEvents.DismissRequestVerificationPrompt) } - ) - } - } SecurityBannerState.RecoveryKeyConfirmation -> { item { ConfirmRecoveryKeyBanner( @@ -316,10 +303,10 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) } ), eventSink = {}, - onVerifyClicked = { }, - onConfirmRecoveryKeyClicked = { }, + onConfirmRecoveryKeyClicked = {}, onRoomClicked = {}, onRoomLongClicked = {}, - onCreateRoomClicked = { }, - onInvitesClicked = { }) + onCreateRoomClicked = {}, + onInvitesClicked = {} + ) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 6b5877a010..0f2c288a63 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -239,52 +239,28 @@ class RoomListPresenterTests { } } - @Test - fun `present - handle RecoveryKeyConfirmation last session`() = runTest { - val scope = CoroutineScope(context = coroutineContext + SupervisorJob()) - val roomListService = FakeRoomListService().apply { - postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) - } - val presenter = createRoomListPresenter( - coroutineScope = scope, - client = FakeMatrixClient( - encryptionService = FakeEncryptionService().apply { - emitIsLastDevice(true) - }, - roomListService = roomListService - ), - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val eventSink = consumeItemsUntilPredicate { - it.contentState is RoomListContentState.Rooms - }.last().eventSink - // For the last session, the state is not SessionVerification, but RecoveryKeyConfirmation - assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation) - eventSink(RoomListEvents.DismissRequestVerificationPrompt) - assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None) - scope.cancel() - } - } - @Test fun `present - handle DismissRequestVerificationPrompt`() = runTest { val scope = CoroutineScope(context = coroutineContext + SupervisorJob()) val roomListService = FakeRoomListService().apply { postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) } + val encryptionService = FakeEncryptionService().apply { + emitRecoveryState(RecoveryState.INCOMPLETE) + } + val syncService = FakeSyncService(initialState = SyncState.Running) val presenter = createRoomListPresenter( - client = FakeMatrixClient(roomListService = roomListService), + client = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService, syncService = syncService), coroutineScope = scope, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val eventSink = consumeItemsUntilPredicate { + val eventWithContentAsRooms = consumeItemsUntilPredicate { it.contentState is RoomListContentState.Rooms - }.last().eventSink - assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SessionVerification) + }.last() + val eventSink = eventWithContentAsRooms.eventSink + assertThat(eventWithContentAsRooms.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation) eventSink(RoomListEvents.DismissRequestVerificationPrompt) assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None) scope.cancel() @@ -342,10 +318,10 @@ class RoomListPresenterTests { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - consumeItemsUntilPredicate { + val firstItem = consumeItemsUntilPredicate { it.contentState is RoomListContentState.Rooms - } - assertThat(awaitItem().contentAsRooms().invitesState).isEqualTo(InvitesState.NoInvites) + }.last() + assertThat(firstItem.contentAsRooms().invitesState).isEqualTo(InvitesState.NoInvites) inviteStateFlow.value = InvitesState.SeenInvites assertThat(awaitItem().contentAsRooms().invitesState).isEqualTo(InvitesState.SeenInvites) diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt index c860b6fc42..2dbec36395 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt @@ -43,35 +43,6 @@ import org.junit.runner.RunWith class RoomListViewTest { @get:Rule val rule = createAndroidComposeRule() - @Test - fun `clicking on close verification banner emits the expected Event`() { - val eventsRecorder = EventsRecorder() - rule.setRoomListView( - state = aRoomListState( - contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SessionVerification), - eventSink = eventsRecorder, - ) - ) - val close = rule.activity.getString(CommonStrings.action_close) - rule.onNodeWithContentDescription(close).performClick() - eventsRecorder.assertSingle(RoomListEvents.DismissRequestVerificationPrompt) - } - - @Test - fun `clicking on continue verification banner invokes the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) - ensureCalledOnce { callback -> - rule.setRoomListView( - state = aRoomListState( - contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SessionVerification), - eventSink = eventsRecorder, - ), - onVerifyClicked = callback, - ) - rule.clickOn(CommonStrings.action_continue) - } - } - @Test fun `clicking on close recovery key banner emits the expected Event`() { val eventsRecorder = EventsRecorder() @@ -185,7 +156,6 @@ private fun AndroidComposeTestRule.setRoomL state: RoomListState, onRoomClicked: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onSettingsClicked: () -> Unit = EnsureNeverCalled(), - onVerifyClicked: () -> Unit = EnsureNeverCalled(), onConfirmRecoveryKeyClicked: () -> Unit = EnsureNeverCalled(), onCreateRoomClicked: () -> Unit = EnsureNeverCalled(), onInvitesClicked: () -> Unit = EnsureNeverCalled(), @@ -198,7 +168,6 @@ private fun AndroidComposeTestRule.setRoomL state = state, onRoomClicked = onRoomClicked, onSettingsClicked = onSettingsClicked, - onVerifyClicked = onVerifyClicked, onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, onCreateRoomClicked = onCreateRoomClicked, onInvitesClicked = onInvitesClicked, diff --git a/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt b/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt index 1fee6418a9..8a00063fef 100644 --- a/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt +++ b/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt @@ -19,6 +19,7 @@ package io.element.android.features.securebackup.api import android.os.Parcelable import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.NodeInputs import kotlinx.parcelize.Parcelize @@ -36,8 +37,13 @@ interface SecureBackupEntryPoint : FeatureEntryPoint { fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + interface Callback : Plugin { + fun onDone() + } + interface NodeBuilder { fun params(params: Params): NodeBuilder + fun callback(callback: Callback): NodeBuilder fun build(): Node } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt index e3d5fde961..d238560463 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt @@ -36,6 +36,11 @@ class DefaultSecureBackupEntryPoint @Inject constructor() : SecureBackupEntryPoi return this } + override fun callback(callback: SecureBackupEntryPoint.Callback): SecureBackupEntryPoint.NodeBuilder { + plugins += callback + return this + } + override fun build(): Node { return parentNode.createNode(buildContext, plugins) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt index 172c6672e5..b698d2719f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt @@ -22,7 +22,9 @@ 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 com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -74,6 +76,8 @@ class SecureBackupFlowNode @AssistedInject constructor( data object EnterRecoveryKey : NavTarget } + private val callback = plugins().firstOrNull() + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { @@ -119,7 +123,16 @@ class SecureBackupFlowNode @AssistedInject constructor( createNode(buildContext) } NavTarget.EnterRecoveryKey -> { - createNode(buildContext) + val callback = object : SecureBackupEnterRecoveryKeyNode.Callback { + override fun onEnterRecoveryKeySuccess() { + if (callback != null) { + callback.onDone() + } else { + backstack.pop() + } + } + } + createNode(buildContext, plugins = listOf(callback)) } } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt index 8c6ae282fa..597ab4c14e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt @@ -17,48 +17,36 @@ package io.element.android.features.securebackup.impl.enter import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope 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 com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.securebackup.impl.R -import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher -import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.di.SessionScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch @ContributesNode(SessionScope::class) class SecureBackupEnterRecoveryKeyNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val presenter: SecureBackupEnterRecoveryKeyPresenter, - private val snackbarDispatcher: SnackbarDispatcher, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onEnterRecoveryKeySuccess() + } + + private val callback = plugins().first() + @Composable override fun View(modifier: Modifier) { - val coroutineScope = rememberCoroutineScope() val state = presenter.present() SecureBackupEnterRecoveryKeyView( state = state, modifier = modifier, - onDone = { - coroutineScope.postSuccessSnackbar() - navigateUp() - }, + onDone = callback::onEnterRecoveryKeySuccess, onBackClicked = ::navigateUp, ) } - - private fun CoroutineScope.postSuccessSnackbar() = launch { - snackbarDispatcher.post( - SnackbarMessage( - messageResId = R.string.screen_recovery_key_confirm_success - ) - ) - } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt index cc97faa6e3..d2e8ad39da 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt @@ -53,7 +53,7 @@ class VerifySelfSessionNode @AssistedInject constructor( state = state, modifier = modifier, onEnterRecoveryKey = ::onEnterRecoveryKey, - goBack = ::onDone, + onFinished = ::onDone, ) } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt index 2a7740d8d0..a060469199 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt @@ -68,10 +68,10 @@ class VerifySelfSessionPresenter @Inject constructor( when (event) { VerifySelfSessionViewEvents.RequestVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.RequestVerification) VerifySelfSessionViewEvents.StartSasVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.StartSasVerification) - VerifySelfSessionViewEvents.Restart -> stateAndDispatch.dispatchAction(StateMachineEvent.Restart) VerifySelfSessionViewEvents.ConfirmVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.AcceptChallenge) VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge) - VerifySelfSessionViewEvents.CancelAndClose -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel) + VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel) + VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset) } } return VerifySelfSessionState( @@ -118,7 +118,7 @@ class VerifySelfSessionPresenter @Inject constructor( private fun CoroutineScope.observeVerificationService() { sessionVerificationService.verificationFlowState.onEach { verificationAttemptState -> when (verificationAttemptState) { - VerificationFlowState.Initial -> stateMachine.dispatch(VerifySelfSessionStateMachine.Event.Restart) + VerificationFlowState.Initial -> stateMachine.dispatch(VerifySelfSessionStateMachine.Event.Reset) VerificationFlowState.AcceptedVerificationRequest -> { stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidAcceptVerificationRequest) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt index 2a1c63370e..29a484521a 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateMachine.kt @@ -20,24 +20,35 @@ package io.element.android.features.verifysession.impl import com.freeletics.flowredux.dsl.FlowReduxStateMachine +import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.SessionVerificationService import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.timeout import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds import com.freeletics.flowredux.dsl.State as MachineState +@OptIn(FlowPreview::class) class VerifySelfSessionStateMachine @Inject constructor( private val sessionVerificationService: SessionVerificationService, + private val encryptionService: EncryptionService, ) : FlowReduxStateMachine( initialState = State.Initial ) { init { spec { inState { - on { _: Event.RequestVerification, state: MachineState -> + on { _: Event.RequestVerification, state -> state.override { State.RequestingVerification } } - on { _: Event.StartSasVerification, state: MachineState -> + on { _: Event.StartSasVerification, state -> state.override { State.StartingSasVerification } } } @@ -45,12 +56,9 @@ class VerifySelfSessionStateMachine @Inject constructor( onEnterEffect { sessionVerificationService.requestVerification() } - on { _: Event.DidAcceptVerificationRequest, state: MachineState -> + on { _: Event.DidAcceptVerificationRequest, state -> state.override { State.VerificationRequestAccepted } } - on { _: Event.DidFail, state: MachineState -> - state.override { State.Initial } - } } inState { onEnterEffect { @@ -58,25 +66,28 @@ class VerifySelfSessionStateMachine @Inject constructor( } } inState { - on { _: Event.StartSasVerification, state: MachineState -> + on { _: Event.StartSasVerification, state -> state.override { State.StartingSasVerification } } } inState { - on { _: Event.Restart, state: MachineState -> + on { _: Event.RequestVerification, state -> state.override { State.RequestingVerification } } + on { _: Event.Reset, state -> + state.override { State.Initial } + } } inState { - on { event: Event.DidReceiveChallenge, state: MachineState -> + on { event: Event.DidReceiveChallenge, state -> state.override { State.Verifying.ChallengeReceived(event.data) } } } inState { - on { _: Event.AcceptChallenge, state: MachineState -> + on { _: Event.AcceptChallenge, state -> state.override { State.Verifying.Replying(state.snapshot.data, accept = true) } } - on { _: Event.DeclineChallenge, state: MachineState -> + on { _: Event.DeclineChallenge, state -> state.override { State.Verifying.Replying(state.snapshot.data, accept = false) } } } @@ -88,11 +99,21 @@ class VerifySelfSessionStateMachine @Inject constructor( sessionVerificationService.declineVerification() } } - on { _: Event.DidAcceptChallenge, state: MachineState -> + on { _: Event.DidAcceptChallenge, state -> + // If a key backup exists, wait until it's restored or a timeout happens + val hasBackup = encryptionService.doesBackupExistOnServer().getOrNull().orFalse() + if (hasBackup) { + tryOrNull { + encryptionService.recoveryStateStateFlow.filter { it == RecoveryState.ENABLED } + .timeout(10.seconds) + .first() + } + } state.override { State.Completed } } } inState { + // TODO The 'Canceling' -> 'Canceled' transitions doesn't seem to work anymore, check if something changed in the Rust SDK onEnterEffect { sessionVerificationService.cancelVerification() } @@ -102,21 +123,24 @@ class VerifySelfSessionStateMachine @Inject constructor( state.override { State.SasVerificationStarted } } on { _: Event.Cancel, state: MachineState -> - if (state.snapshot in sequenceOf( - State.Initial, - State.Completed, - State.Canceled - )) { - state.noChange() - } else { - state.override { State.Canceling } + when (state.snapshot) { + State.Initial, State.Completed, State.Canceled -> state.noChange() + // For some reason `cancelVerification` is not calling its delegate `didCancel` method so we don't pass from + // `Canceling` state to `Canceled` automatically anymore + else -> { + sessionVerificationService.cancelVerification() + state.override { State.Canceled } + } } } on { _: Event.DidCancel, state: MachineState -> state.override { State.Canceled } } on { _: Event.DidFail, state: MachineState -> - state.override { State.Canceled } + when (state.snapshot) { + is State.RequestingVerification -> state.override { State.Initial } + else -> state.override { State.Canceled } + } } } } @@ -190,7 +214,7 @@ class VerifySelfSessionStateMachine @Inject constructor( /** Request failed. */ data object DidFail : Event - /** Restart the verification flow. */ - data object Restart : Event + /** Reset the verification flow to the initial state. */ + data object Reset : Event } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt index 69fdfc5dfc..7a1ad12b09 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt @@ -30,9 +30,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -42,15 +39,16 @@ import androidx.compose.ui.text.style.TextOverflow 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.verifysession.impl.emoji.toEmojiResource import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule -import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.PageTitle 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.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.matrix.api.verification.SessionVerificationData @@ -62,36 +60,37 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver fun VerifySelfSessionView( state: VerifySelfSessionState, onEnterRecoveryKey: () -> Unit, - goBack: () -> Unit, + onFinished: () -> Unit, modifier: Modifier = Modifier, ) { - fun goBackAndCancelIfNeeded() { - state.eventSink(VerifySelfSessionViewEvents.CancelAndClose) - goBack() - } - if (state.verificationFlowStep is FlowStep.Completed) { - goBack() + fun resetFlow() { + state.eventSink(VerifySelfSessionViewEvents.Reset) } BackHandler { - goBackAndCancelIfNeeded() + when (state.verificationFlowStep) { + is FlowStep.Canceled -> resetFlow() + is FlowStep.AwaitingOtherDeviceResponse, FlowStep.Ready -> state.eventSink(VerifySelfSessionViewEvents.Cancel) + is FlowStep.Verifying -> { + if (!state.verificationFlowStep.state.isLoading()) { + state.eventSink(VerifySelfSessionViewEvents.DeclineVerification) + } + } + else -> Unit + } } val verificationFlowStep = state.verificationFlowStep - val buttonsVisible by remember(verificationFlowStep) { - derivedStateOf { verificationFlowStep != FlowStep.AwaitingOtherDeviceResponse && verificationFlowStep != FlowStep.Completed } - } HeaderFooterPage( modifier = modifier, header = { HeaderContent(verificationFlowStep = verificationFlowStep) }, footer = { - if (buttonsVisible) { - BottomMenu( - screenState = state, - goBack = ::goBackAndCancelIfNeeded, - onEnterRecoveryKey = onEnterRecoveryKey - ) - } + BottomMenu( + screenState = state, + goBack = ::resetFlow, + onEnterRecoveryKey = onEnterRecoveryKey, + onFinished = onFinished, + ) } ) { Content(flowState = verificationFlowStep) @@ -100,40 +99,38 @@ fun VerifySelfSessionView( @Composable private fun HeaderContent(verificationFlowStep: FlowStep) { - val iconResourceId = when (verificationFlowStep) { - is FlowStep.Initial -> R.drawable.ic_verification_devices - FlowStep.Canceled -> R.drawable.ic_verification_warning - FlowStep.AwaitingOtherDeviceResponse -> R.drawable.ic_verification_waiting - FlowStep.Ready, is FlowStep.Verifying, FlowStep.Completed -> R.drawable.ic_verification_emoji + val iconStyle = when (verificationFlowStep) { + is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> BigIcon.Style.Default(CompoundIcons.LockSolid()) + FlowStep.Canceled -> BigIcon.Style.AlertSolid + FlowStep.Ready, is FlowStep.Verifying -> BigIcon.Style.Default(CompoundIcons.Reaction()) + FlowStep.Completed -> BigIcon.Style.SuccessSolid } val titleTextId = when (verificationFlowStep) { - is FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_title + is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_identity_confirmation_title FlowStep.Canceled -> CommonStrings.common_verification_cancelled - FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_title - FlowStep.Ready, - FlowStep.Completed -> R.string.screen_session_verification_compare_emojis_title + FlowStep.Ready -> R.string.screen_session_verification_compare_emojis_title + FlowStep.Completed -> R.string.screen_identity_confirmed_title is FlowStep.Verifying -> when (verificationFlowStep.data) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_title is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_title } } val subtitleTextId = when (verificationFlowStep) { - is FlowStep.Initial -> R.string.screen_session_verification_open_existing_session_subtitle + is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_identity_confirmation_subtitle FlowStep.Canceled -> R.string.screen_session_verification_cancelled_subtitle - FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_session_verification_waiting_to_accept_subtitle FlowStep.Ready -> R.string.screen_session_verification_ready_subtitle - FlowStep.Completed -> R.string.screen_session_verification_compare_emojis_subtitle + FlowStep.Completed -> R.string.screen_identity_confirmation_subtitle is FlowStep.Verifying -> when (verificationFlowStep.data) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_subtitle is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_subtitle } } - IconTitleSubtitleMolecule( + PageTitle( modifier = Modifier.padding(top = 60.dp), - iconResourceId = iconResourceId, + iconStyle = iconStyle, title = stringResource(id = titleTextId), - subTitle = stringResource(id = subtitleTextId) + subtitle = stringResource(id = subtitleTextId) ) } @@ -141,20 +138,12 @@ private fun HeaderContent(verificationFlowStep: FlowStep) { private fun Content(flowState: FlowStep) { Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) { when (flowState) { - is FlowStep.Initial, FlowStep.Ready, FlowStep.Canceled, FlowStep.Completed -> Unit - FlowStep.AwaitingOtherDeviceResponse -> ContentWaiting() + is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse, FlowStep.Ready, FlowStep.Canceled, FlowStep.Completed -> Unit is FlowStep.Verifying -> ContentVerifying(flowState) } } } -@Composable -private fun ContentWaiting() { - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { - CircularProgressIndicator() - } -} - @Composable private fun ContentVerifying(verificationFlowStep: FlowStep.Verifying) { when (verificationFlowStep.data) { @@ -212,76 +201,102 @@ private fun BottomMenu( screenState: VerifySelfSessionState, onEnterRecoveryKey: () -> Unit, goBack: () -> Unit, + onFinished: () -> Unit, ) { val verificationViewState = screenState.verificationFlowStep val eventSink = screenState.eventSink val isVerifying = (verificationViewState as? FlowStep.Verifying)?.state is AsyncData.Loading - val positiveButtonTitle = when (verificationViewState) { - is FlowStep.Initial -> R.string.screen_session_verification_positive_button_initial - FlowStep.Canceled -> R.string.screen_session_verification_positive_button_canceled + + when (verificationViewState) { + is FlowStep.Initial -> { + BottomMenu( + positiveButtonTitle = stringResource(R.string.screen_identity_use_another_device), + onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.RequestVerification) }, + negativeButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key), + onNegativeButtonClicked = onEnterRecoveryKey, + ) + } + is FlowStep.Canceled -> { + BottomMenu( + positiveButtonTitle = stringResource(R.string.screen_session_verification_positive_button_canceled), + onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.RequestVerification) }, + negativeButtonTitle = stringResource(CommonStrings.action_cancel), + onNegativeButtonClicked = goBack, + ) + } + is FlowStep.Ready -> { + BottomMenu( + positiveButtonTitle = stringResource(CommonStrings.action_start), + onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.StartSasVerification) }, + negativeButtonTitle = stringResource(CommonStrings.action_cancel), + onNegativeButtonClicked = goBack, + ) + } + is FlowStep.AwaitingOtherDeviceResponse -> { + BottomMenu( + positiveButtonTitle = stringResource(R.string.screen_identity_waiting_on_other_device), + onPositiveButtonClicked = {}, + isLoading = true, + ) + } is FlowStep.Verifying -> { - if (isVerifying) { - R.string.screen_session_verification_positive_button_verifying_ongoing + val positiveButtonTitle = if (isVerifying) { + stringResource(R.string.screen_session_verification_positive_button_verifying_ongoing) } else { - R.string.screen_session_verification_they_match + stringResource(R.string.screen_session_verification_they_match) } + BottomMenu( + positiveButtonTitle = positiveButtonTitle, + onPositiveButtonClicked = { + if (!isVerifying) { + eventSink(VerifySelfSessionViewEvents.ConfirmVerification) + } + }, + negativeButtonTitle = stringResource(R.string.screen_session_verification_they_dont_match), + onNegativeButtonClicked = { eventSink(VerifySelfSessionViewEvents.DeclineVerification) }, + isLoading = isVerifying, + ) } - FlowStep.Ready -> CommonStrings.action_start - else -> null - } - val negativeButtonTitle = when (verificationViewState) { - is FlowStep.Initial -> CommonStrings.action_cancel - FlowStep.Canceled -> CommonStrings.action_cancel - is FlowStep.Verifying -> R.string.screen_session_verification_they_dont_match - else -> null - } - val negativeButtonEnabled = !isVerifying - - val positiveButtonEvent = when (verificationViewState) { - is FlowStep.Initial -> VerifySelfSessionViewEvents.RequestVerification - FlowStep.Ready -> VerifySelfSessionViewEvents.StartSasVerification - is FlowStep.Verifying -> if (!isVerifying) VerifySelfSessionViewEvents.ConfirmVerification else null - FlowStep.Canceled -> VerifySelfSessionViewEvents.Restart - else -> null - } - - val negativeButtonCallback: () -> Unit = when (verificationViewState) { - is FlowStep.Verifying -> { - { eventSink(VerifySelfSessionViewEvents.DeclineVerification) } + is FlowStep.Completed -> { + BottomMenu( + positiveButtonTitle = stringResource(CommonStrings.action_continue), + onPositiveButtonClicked = onFinished, + ) } - else -> goBack } +} +@Composable +private fun BottomMenu( + positiveButtonTitle: String?, + onPositiveButtonClicked: () -> Unit, + modifier: Modifier = Modifier, + negativeButtonTitle: String? = null, + negativeButtonEnabled: Boolean = negativeButtonTitle != null, + onNegativeButtonClicked: () -> Unit = {}, + isLoading: Boolean = false, +) { ButtonColumnMolecule( - modifier = Modifier.padding(bottom = 20.dp) + modifier = modifier.padding(bottom = 16.dp) ) { if (positiveButtonTitle != null) { Button( - text = stringResource(positiveButtonTitle), - showProgress = isVerifying, + text = positiveButtonTitle, + showProgress = isLoading, modifier = Modifier.fillMaxWidth(), - onClick = { positiveButtonEvent?.let { eventSink(it) } } + onClick = onPositiveButtonClicked, ) } if (negativeButtonTitle != null) { TextButton( - text = stringResource(negativeButtonTitle), + text = negativeButtonTitle, modifier = Modifier.fillMaxWidth(), - onClick = negativeButtonCallback, + onClick = onNegativeButtonClicked, enabled = negativeButtonEnabled, ) - } - if (verificationViewState is FlowStep.Initial && verificationViewState.canEnterRecoveryKey) { - Text( - text = stringResource(id = CommonStrings.common_or), - color = ElementTheme.colors.textSecondary, - ) - TextButton( - text = stringResource(R.string.screen_session_verification_enter_recovery_key), - modifier = Modifier.fillMaxWidth(), - onClick = onEnterRecoveryKey, - ) + } else { + Spacer(modifier = Modifier.height(48.dp)) } } } @@ -292,6 +307,6 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta VerifySelfSessionView( state = state, onEnterRecoveryKey = {}, - goBack = {}, + onFinished = {}, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt index 8f9c69085c..2b86ca6f18 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt @@ -19,8 +19,8 @@ package io.element.android.features.verifysession.impl sealed interface VerifySelfSessionViewEvents { data object RequestVerification : VerifySelfSessionViewEvents data object StartSasVerification : VerifySelfSessionViewEvents - data object Restart : VerifySelfSessionViewEvents data object ConfirmVerification : VerifySelfSessionViewEvents data object DeclineVerification : VerifySelfSessionViewEvents - data object CancelAndClose : VerifySelfSessionViewEvents + data object Cancel : VerifySelfSessionViewEvents + data object Reset : VerifySelfSessionViewEvents } diff --git a/features/verifysession/impl/src/main/res/drawable/ic_verification_devices.xml b/features/verifysession/impl/src/main/res/drawable/ic_verification_devices.xml deleted file mode 100644 index 8ae6dd30fa..0000000000 --- a/features/verifysession/impl/src/main/res/drawable/ic_verification_devices.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/features/verifysession/impl/src/main/res/drawable/ic_verification_emoji.xml b/features/verifysession/impl/src/main/res/drawable/ic_verification_emoji.xml deleted file mode 100644 index 82583a4011..0000000000 --- a/features/verifysession/impl/src/main/res/drawable/ic_verification_emoji.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/features/verifysession/impl/src/main/res/drawable/ic_verification_waiting.xml b/features/verifysession/impl/src/main/res/drawable/ic_verification_waiting.xml deleted file mode 100644 index 5b9f2e3cfc..0000000000 --- a/features/verifysession/impl/src/main/res/drawable/ic_verification_waiting.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/features/verifysession/impl/src/main/res/drawable/ic_verification_warning.xml b/features/verifysession/impl/src/main/res/drawable/ic_verification_warning.xml deleted file mode 100644 index 882ac62cd7..0000000000 --- a/features/verifysession/impl/src/main/res/drawable/ic_verification_warning.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/features/verifysession/impl/src/main/res/values/localazy.xml b/features/verifysession/impl/src/main/res/values/localazy.xml index b46954f42b..9fea2d98df 100644 --- a/features/verifysession/impl/src/main/res/values/localazy.xml +++ b/features/verifysession/impl/src/main/res/values/localazy.xml @@ -1,5 +1,11 @@ + "Verify this device to set up secure messaging." + "Confirm that it\'s you" + "Now you can read or send messages securely, and anyone you chat with can also trust this device." + "Device verified" + "Use another device" + "Waiting on other device…" "Something doesn’t seem right. Either the request timed out or the request was denied." "Confirm that the emojis below match those shown on your other session." "Compare emojis" diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index ad128b450d..025e782e8f 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt @@ -106,13 +106,13 @@ class VerifySelfSessionPresenterTests { val initialState = awaitItem() assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) val eventSink = initialState.eventSink - eventSink(VerifySelfSessionViewEvents.CancelAndClose) + eventSink(VerifySelfSessionViewEvents.Cancel) expectNoEvents() } } @Test - fun `present - A fail in the flow cancels it`() = runTest { + fun `present - A failure when verifying cancels it`() = runTest { val service = FakeSessionVerificationService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { @@ -128,6 +128,21 @@ class VerifySelfSessionPresenterTests { } } + @Test + fun `present - A fail when requesting verification resets the state to the initial one`() = runTest { + val service = FakeSessionVerificationService() + val presenter = createVerifySelfSessionPresenter(service) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + service.shouldFail = true + awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification) + service.shouldFail = false + assertThat(awaitItem().verificationFlowStep).isInstanceOf(VerificationStep.AwaitingOtherDeviceResponse::class.java) + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + } + } + @Test fun `present - Canceling the flow once it's verifying cancels it`() = runTest { val service = FakeSessionVerificationService() @@ -136,8 +151,7 @@ class VerifySelfSessionPresenterTests { presenter.present() }.test { val state = requestVerificationAndAwaitVerifyingState(service) - state.eventSink(VerifySelfSessionViewEvents.CancelAndClose) - assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse) + state.eventSink(VerifySelfSessionViewEvents.Cancel) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled) } } @@ -165,13 +179,30 @@ class VerifySelfSessionPresenterTests { val state = requestVerificationAndAwaitVerifyingState(service) service.givenVerificationFlowState(VerificationFlowState.Canceled) assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled) - state.eventSink(VerifySelfSessionViewEvents.Restart) + state.eventSink(VerifySelfSessionViewEvents.RequestVerification) // Went back to requesting verification assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse) cancelAndIgnoreRemainingEvents() } } + @Test + fun `present - Go back after cancelation returns to initial state`() = runTest { + val service = FakeSessionVerificationService() + val presenter = createVerifySelfSessionPresenter(service) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = requestVerificationAndAwaitVerifyingState(service) + service.givenVerificationFlowState(VerificationFlowState.Canceled) + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled) + state.eventSink(VerifySelfSessionViewEvents.Reset) + // Went back to initial state + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + cancelAndIgnoreRemainingEvents() + } + } + @Test fun `present - When verification is approved, the flow completes if there is no error`() = runTest { val emojis = listOf( @@ -247,7 +278,7 @@ class VerifySelfSessionPresenterTests { return VerifySelfSessionPresenter( sessionVerificationService = service, encryptionService = encryptionService, - stateMachine = VerifySelfSessionStateMachine(service), + stateMachine = VerifySelfSessionStateMachine(service, encryptionService), ) } } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt index 4dfad8c9c9..1ca5bca32f 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt @@ -36,45 +36,98 @@ class VerifySelfSessionViewTest { @get:Rule val rule = createAndroidComposeRule() @Test - fun `clicking on cancel calls the expected callback and emits the expected Event`() { + fun `back key pressed - when canceled resets the flow`() { val eventsRecorder = EventsRecorder() - ensureCalledOnce { callback -> - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true), - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - goBack = callback, - ) - } - rule.clickOn(CommonStrings.action_cancel) + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled, + eventSink = eventsRecorder + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), + ) } - eventsRecorder.assertSingle(VerifySelfSessionViewEvents.CancelAndClose) + rule.pressBackKey() + eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Reset) } @Test - fun `clicking on back key calls the expected callback and emits the expected Event`() { + fun `back key pressed - when awaiting response cancels the verification`() { val eventsRecorder = EventsRecorder() - ensureCalledOnce { callback -> - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true), - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - goBack = callback, - ) - } - rule.pressBackKey() + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse, + eventSink = eventsRecorder + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), + ) } - eventsRecorder.assertSingle(VerifySelfSessionViewEvents.CancelAndClose) + rule.pressBackKey() + eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel) } @Test - fun `when flow is completed, the expected callback is invoked`() { + fun `back key pressed - when ready to verify cancels the verification`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready, + eventSink = eventsRecorder + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), + ) + } + rule.pressBackKey() + eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel) + } + + @Test + fun `back key pressed - when verifying and not loading declines the verification`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( + data = aEmojisSessionVerificationData(), + state = AsyncData.Uninitialized, + ), + eventSink = eventsRecorder + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), + ) + } + rule.pressBackKey() + eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification) + } + + @Test + fun `back key pressed - when verifying and loading does nothing`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( + data = aEmojisSessionVerificationData(), + state = AsyncData.Loading(), + ), + eventSink = eventsRecorder + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), + ) + } + rule.pressBackKey() + eventsRecorder.assertEmpty() + } + + @Test + fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> rule.setContent { @@ -84,9 +137,10 @@ class VerifySelfSessionViewTest { eventSink = eventsRecorder ), onEnterRecoveryKey = EnsureNeverCalled(), - goBack = callback, + onFinished = callback, ) } + rule.clickOn(CommonStrings.action_continue) } } @@ -102,7 +156,7 @@ class VerifySelfSessionViewTest { eventSink = eventsRecorder ), onEnterRecoveryKey = callback, - goBack = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), ) } rule.clickOn(R.string.screen_session_verification_enter_recovery_key) @@ -122,7 +176,7 @@ class VerifySelfSessionViewTest { eventSink = eventsRecorder ), onEnterRecoveryKey = EnsureNeverCalled(), - goBack = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), ) } rule.clickOn(R.string.screen_session_verification_they_match) @@ -142,7 +196,7 @@ class VerifySelfSessionViewTest { eventSink = eventsRecorder ), onEnterRecoveryKey = EnsureNeverCalled(), - goBack = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), ) } rule.clickOn(R.string.screen_session_verification_they_dont_match) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index 1dc7230973..b90cf80aaa 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -32,6 +32,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text @@ -95,3 +96,14 @@ internal fun IconTitleSubtitleMoleculePreview() = ElementPreview { subTitle = "Subtitle", ) } + +@PreviewsDayNight +@Composable +internal fun IconTitleSubtitleMoleculeWithResIconPreview() = ElementPreview { + IconTitleSubtitleMolecule( + iconResourceId = CompoundDrawables.ic_compound_admin, + iconTint = Color.Black, + title = "Title", + subTitle = "Subtitle", + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PageTitle.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PageTitle.kt index 9ff8ef38da..de1e25f5f3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PageTitle.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PageTitle.kt @@ -52,9 +52,7 @@ fun PageTitle( callToAction: @Composable (() -> Unit)? = null, ) { Column( - modifier = modifier - .fillMaxWidth() - .padding(bottom = 40.dp), + modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { BigIcon(style = iconStyle) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index 00fd9dc561..8d22fc174e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -85,6 +85,9 @@ sealed interface SessionVerifiedStatus { /** Verified session status. */ data object Verified : SessionVerifiedStatus + + /** Returns whether the session is [Verified]. */ + fun isVerified(): Boolean = this is Verified } /** States produced by the [SessionVerificationService]. */ 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 0212a5293a..546acc6e84 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 @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryServic import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.awaitLoaded import io.element.android.libraries.matrix.api.sync.SyncService +import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -84,8 +85,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.buffer import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -130,11 +130,6 @@ class RustMatrixClient( private val innerRoomListService = syncService.roomListService() private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val rustSyncService = RustSyncService(syncService, sessionCoroutineScope) - private val verificationService = RustSessionVerificationService( - client = client, - syncService = rustSyncService, - sessionCoroutineScope = sessionCoroutineScope, - ).apply { start() } private val pushersService = RustPushersService( client = client, dispatchers = dispatchers, @@ -230,6 +225,12 @@ class RustMatrixClient( ), ) + private val verificationService = RustSessionVerificationService( + client = client, + isSyncServiceReady = rustSyncService.syncState.map { it == SyncState.Running }, + sessionCoroutineScope = sessionCoroutineScope, + ) + private val eventFilters = TimelineConfig.excludedEvents .takeIf { it.isNotEmpty() } ?.let { listStateEventType -> @@ -274,11 +275,6 @@ class RustMatrixClient( .stateIn(sessionCoroutineScope, started = SharingStarted.Eagerly, initialValue = persistentListOf()) init { - roomListService.state.onEach { state -> - if (state == RoomListService.State.Running) { - setupVerificationControllerIfNeeded() - } - }.launchIn(sessionCoroutineScope) sessionCoroutineScope.launch { // Force a refresh of the profile getUserProfile() @@ -490,6 +486,7 @@ class RustMatrixClient( ignoreSdkError: Boolean, ): String? { var result: String? = null + syncService.stop() withContext(sessionDispatcher) { if (doRequest) { try { @@ -525,16 +522,6 @@ class RustMatrixClient( } } - private fun setupVerificationControllerIfNeeded() { - if (verificationService.verificationController == null) { - try { - verificationService.verificationController = client.getSessionVerificationController() - } catch (e: Throwable) { - Timber.e(e, "Could not start verification service. Will try again on the next sliding sync update.") - } - } - } - override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver private suspend fun File.getCacheSize( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index 157e751d21..bb0821570b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -16,92 +16,135 @@ package io.element.android.libraries.matrix.impl.verification -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState -import io.element.android.libraries.matrix.impl.sync.RustSyncService import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.Encryption import org.matrix.rustcomponents.sdk.RecoveryState import org.matrix.rustcomponents.sdk.RecoveryStateListener import org.matrix.rustcomponents.sdk.SessionVerificationController import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate -import org.matrix.rustcomponents.sdk.SessionVerificationControllerInterface import org.matrix.rustcomponents.sdk.TaskHandle +import org.matrix.rustcomponents.sdk.VerificationState +import org.matrix.rustcomponents.sdk.VerificationStateListener import org.matrix.rustcomponents.sdk.use +import timber.log.Timber import org.matrix.rustcomponents.sdk.SessionVerificationData as RustSessionVerificationData class RustSessionVerificationService( client: Client, - private val syncService: RustSyncService, - private val sessionCoroutineScope: CoroutineScope, + isSyncServiceReady: Flow, + sessionCoroutineScope: CoroutineScope, ) : SessionVerificationService, SessionVerificationControllerDelegate { + private var verificationStateListenerTaskHandle: TaskHandle? = null private var recoveryStateListenerTaskHandle: TaskHandle? = null private val encryptionService: Encryption = client.encryption() - var verificationController: SessionVerificationControllerInterface? = null - set(value) { - field = value - _isReady.value = value != null - // If status was 'Unknown', move it to either 'Verified' or 'NotVerified' - if (value != null) { - value.setDelegate(this) - sessionCoroutineScope.launch { updateVerificationStatus(value.isVerified()) } - } - } + private lateinit var verificationController: SessionVerificationController private val _verificationFlowState = MutableStateFlow(VerificationFlowState.Initial) override val verificationFlowState = _verificationFlowState.asStateFlow() - private val _isReady = MutableStateFlow(false) - override val isReady = _isReady.asStateFlow() - private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus.asStateFlow() - override val canVerifySessionFlow = combine(sessionVerifiedStatus, syncService.syncState) { verificationStatus, syncState -> - syncState == SyncState.Running && verificationStatus == SessionVerifiedStatus.NotVerified + override val isReady = MutableStateFlow(false) + + override val canVerifySessionFlow = combine(sessionVerifiedStatus, isReady) { verificationStatus, isReady -> + isReady && verificationStatus == SessionVerifiedStatus.NotVerified } - fun start() { - recoveryStateListenerTaskHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { - override fun onUpdate(status: RecoveryState) { - sessionCoroutineScope.launch { - updateVerificationStatus(verificationController?.isVerified().orFalse()) + init { + isSyncServiceReady + .onEach { syncServiceReady -> + if (syncServiceReady) { + isReady.value = true + runCatching { + // If the controller was failed to initialize before, we try to get it again + if (!this::verificationController.isInitialized) { + verificationController = client.getSessionVerificationController() + } + } + .onFailure { + isReady.value = false + Timber.e(it, "Failed to get verification controller. Trying again in next sync.") + } + } else { + isReady.value = false } } - }) + .launchIn(sessionCoroutineScope) + + isReady.onEach { isReady -> + if (isReady) { + Timber.d("Starting verification service") + // Setup delegate + verificationController.setDelegate(this) + + // Immediate status update + updateVerificationStatus(encryptionService.verificationState()) + + // Listen for changes in verification status and update accordingly + verificationStateListenerTaskHandle?.cancelAndDestroy() + verificationStateListenerTaskHandle = encryptionService.verificationStateListener(object : VerificationStateListener { + override fun onUpdate(status: VerificationState) { + Timber.d("New verification state: $status") + updateVerificationStatus(status) + } + }) + + // In case we enter the recovery key instead we check changes in the recovery state, since the listener above won't be triggered + recoveryStateListenerTaskHandle?.cancelAndDestroy() + recoveryStateListenerTaskHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { + override fun onUpdate(status: RecoveryState) { + Timber.d("New recovery state: $status") + // We could check the `RecoveryState`, but it's easier to just use the verification state directly + updateVerificationStatus(encryptionService.verificationState()) + } + }) + } else { + Timber.d("Stopping verification service") + if (this::verificationController.isInitialized) { + verificationController.setDelegate(null) + } + } + } + .launchIn(sessionCoroutineScope) } override suspend fun requestVerification() = tryOrFail { - verificationController?.requestVerification() + verificationController.requestVerification() } - override suspend fun cancelVerification() = tryOrFail { verificationController?.cancelVerification() } + override suspend fun cancelVerification() = tryOrFail { verificationController.cancelVerification() } - override suspend fun approveVerification() = tryOrFail { verificationController?.approveVerification() } + override suspend fun approveVerification() = tryOrFail { verificationController.approveVerification() } - override suspend fun declineVerification() = tryOrFail { verificationController?.declineVerification() } + override suspend fun declineVerification() = tryOrFail { verificationController.declineVerification() } override suspend fun startVerification() = tryOrFail { - verificationController?.startSasVerification() + verificationController.startSasVerification() } private suspend fun tryOrFail(block: suspend () -> Unit) { runCatching { block() - }.onFailure { didFail() } + }.onFailure { + Timber.e(it, "Failed to verify session") + didFail() + } } // region Delegate implementation @@ -116,13 +159,14 @@ class RustSessionVerificationService( } override fun didFail() { + Timber.e("Session verification failed with an unknown error") _verificationFlowState.value = VerificationFlowState.Failed } override fun didFinish() { _verificationFlowState.value = VerificationFlowState.Finished // Ideally this should be `verificationController?.isVerified().orFalse()` but for some reason it always returns false - updateVerificationStatus(isVerified = true) + updateVerificationStatus(VerificationState.VERIFIED) } override fun didReceiveVerificationData(data: RustSessionVerificationData) { @@ -139,25 +183,26 @@ class RustSessionVerificationService( override suspend fun reset() { if (isReady.value) { // Cancel any pending verification attempt - tryOrNull { verificationController?.cancelVerification() } + tryOrNull { verificationController.cancelVerification() } } _verificationFlowState.value = VerificationFlowState.Initial } fun destroy() { + Timber.d("Destroying RustSessionVerificationService") recoveryStateListenerTaskHandle?.cancelAndDestroy() - verificationController?.setDelegate(null) - (verificationController as? SessionVerificationController)?.destroy() - verificationController = null + if (this::verificationController.isInitialized) { + verificationController.setDelegate(null) + (verificationController as? SessionVerificationController)?.destroy() + } } - private fun updateVerificationStatus(isVerified: Boolean) { - val newValue = when { - !isReady.value -> SessionVerifiedStatus.Unknown - !isVerified -> SessionVerifiedStatus.NotVerified - else -> SessionVerifiedStatus.Verified + private fun updateVerificationStatus(verificationState: VerificationState) { + _sessionVerifiedStatus.value = when (verificationState) { + VerificationState.UNKNOWN -> SessionVerifiedStatus.Unknown + VerificationState.VERIFIED -> SessionVerifiedStatus.Verified + VerificationState.UNVERIFIED -> SessionVerifiedStatus.NotVerified } - _sessionVerifiedStatus.value = newValue } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 4a7aa1c304..64c7c8ab97 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -38,7 +38,11 @@ class FakeSessionVerificationService : SessionVerificationService { override val isReady: StateFlow = _isReady override suspend fun requestVerification() { - _verificationFlowState.value = VerificationFlowState.AcceptedVerificationRequest + if (!shouldFail) { + _verificationFlowState.value = VerificationFlowState.AcceptedVerificationRequest + } else { + _verificationFlowState.value = VerificationFlowState.Failed + } } override suspend fun cancelVerification() { diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 2f9812fc79..97ddec4b3b 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -254,6 +254,8 @@ "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." + "Failed loading" + "Room directory" "Failed processing media to upload, please try again." "Could not retrieve user details" "Block" diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index 7bf3261371..264a754e73 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -152,7 +152,6 @@ class RoomListScreen( state = state, onRoomClicked = ::onRoomClicked, onSettingsClicked = {}, - onVerifyClicked = {}, onConfirmRecoveryKeyClicked = {}, onCreateRoomClicked = {}, onInvitesClicked = {}, diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index a8bf2184ef..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:455964d5b4d1652b5b9eeb8b7236f14c2c9490123399e8c65f1b9e920d20071d -size 21053 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index e6c83e61de..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84d78e6672aaafd2c7974630e7d9391ee7e649c9a9de470627d1ed6e1241a1f4 -size 13366 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 098d9fe800..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c90e7168839f33c8ea4ef79308cc2b1c7a1ad4ec07dbeedba14dd080a3a841d8 -size 20169 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index 302905149e..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0628f8cd42815e725efc8f7bfe0bc790c2369f8d4cabfd31aabd494a469f7379 -size 12867 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png index d4df69f743..c54c15e9ca 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be4d708775d03680be450dfd5eebfeda24f1841eedced3e7de2ba036669ac479 -size 39086 +oid sha256:75fb611e02345fe1cf7947d1e160e309cf8520f0256ebd25b43bab8c3102f3f8 +size 37138 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png index 9d950f5916..90e78c16a5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53660f4354d2a750ab539370151c9ee4ac49f9d06712f7646fdd487bf133753a -size 38766 +oid sha256:a7be4df5a4e391d2253f283acf0fb9362b965fa887235221b794915d74891899 +size 36783 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png index d8b913deea..4f2683cf78 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ae1828957e00adcbdbb772a0ebdc1fb6604afbe644cc5d8662da9fcd3973129 -size 41162 +oid sha256:67503a61f560ef79d7e981057094dccf46eda167ee84427c1aceb9f83edf0146 +size 39080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png index 7f24f64da5..12e2374878 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58e567815b4dc36abf8422b17f9902d8581dbec1a1ed216df2f120c6198b6fe5 -size 41124 +oid sha256:5cb3be70de8e3878bdd90db9945aed60fb2b38d8c66ea610806226c21b1f5a5a +size 39052 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-8_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-8_9_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-8_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-7_9_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-8_10_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-7_9_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-7_8_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-6_8_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-7_9_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-6_8_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 1ff9a4e140..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f5fd867f51badedc8e9ded0900eb904ac11af348243573dcf34b8aaa3c1548b7 -size 26926 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-5_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-5_7_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 604e02b79d..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-5_7_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:318838247c7d0cee133fd5c98bbbee6f2c5be09a95b6c2fd293c041e21480ab8 -size 26080 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-9_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-8_9_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-9_10_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-8_9_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-9_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-8_10_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-9_11_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-8_10_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_22,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_22,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_22,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_22,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_23,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_23,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_23,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_23,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_24,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_24,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_24,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_24,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_25,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_25,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_25,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_25,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_26,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_26,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_26,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_26,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_27,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_27,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_27,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_27,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_10,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_10,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_10,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_11,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_11,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_11,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_12,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_12,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_12,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_13,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_13,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_13,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_14,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_14,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_14,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_14,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_15,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_15,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_15,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_15,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_16,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_16,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_16,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_16,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_17,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_17,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_17,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_17,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_18,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_18,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_18,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_18,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_19,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_19,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_19,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_19,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_20,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_20,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_20,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_20,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_21,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_21,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_21,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_21,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_22,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_22,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_22,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_22,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_23,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_23,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_23,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_23,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_24,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_24,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_24,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_24,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_25,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_25,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_25,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_25,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_26,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_26,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_26,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_26,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_27,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_27,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_27,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_27,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_9,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_9,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_9,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-12_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-12_13_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-12_14_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-11_13_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-12_14_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-11_13_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png index 761cb8d3a8..1ff067e54b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8de26fbd49153871bdd18defc2297b2ab314f541fe598894a9c81fe633488db7 -size 51359 +oid sha256:0197c643bbbbc46dbb58ac8911b61003bb09fceb15f1997cba64bcb00aee90fe +size 162642 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png index 1ff067e54b..3057c59ef7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0197c643bbbbc46dbb58ac8911b61003bb09fceb15f1997cba64bcb00aee90fe -size 162642 +oid sha256:af42c9891a6670cdde07cf054e139ce1f83e877bb68f60463fcad6dd28d8e049 +size 6867 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png index 3057c59ef7..19b6010d17 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af42c9891a6670cdde07cf054e139ce1f83e877bb68f60463fcad6dd28d8e049 -size 6867 +oid sha256:46e28da384551af27bf9c52a45c17b8441f8875494b3d3fd901ad66f5d7ff89b +size 74803 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png deleted file mode 100644 index 19b6010d17..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:46e28da384551af27bf9c52a45c17b8441f8875494b3d3fd901ad66f5d7ff89b -size 74803 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png index a5a84223bb..872429b353 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ecdd0cf6f38c2be14eaad9152d7f0eac02682d12c10703abc605bcc73adbb92 -size 86084 +oid sha256:3f314f241b95aeaccd576c8235cfd907abaf5d796b5dc6f4e2acac8a4b9c7c2d +size 88678 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,en].png index 872429b353..6efd101dac 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f314f241b95aeaccd576c8235cfd907abaf5d796b5dc6f4e2acac8a4b9c7c2d -size 88678 +oid sha256:a0037f0401b53088f2a75af67f8e4df374866fbce80c052535af1d97354c9b22 +size 56605 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png index 6efd101dac..761cb8d3a8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0037f0401b53088f2a75af67f8e4df374866fbce80c052535af1d97354c9b22 -size 56605 +oid sha256:8de26fbd49153871bdd18defc2297b2ab314f541fe598894a9c81fe633488db7 +size 51359 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png index 168ee31a4d..043a1223a4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35f35f058cff4435c278ba8cb9f60d68edabcc5da3ab58823abf99a082346978 -size 53292 +oid sha256:4b167704c2d05c857715aa11c3d7fb3abdf3b59ad7d6d2c479936d934a67f32d +size 186756 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png index 043a1223a4..08cfca5c0b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b167704c2d05c857715aa11c3d7fb3abdf3b59ad7d6d2c479936d934a67f32d -size 186756 +oid sha256:6e95dac6e75d3f615ccf4d8e98f1780d8aeace1cddf0b33bba8b485860d3216b +size 6688 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png index 08cfca5c0b..52eccc7865 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e95dac6e75d3f615ccf4d8e98f1780d8aeace1cddf0b33bba8b485860d3216b -size 6688 +oid sha256:58ce650742396cd4addf9f545b2488bc2666e848b361a501d3ea2508c92c2a8a +size 76908 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png deleted file mode 100644 index 52eccc7865..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58ce650742396cd4addf9f545b2488bc2666e848b361a501d3ea2508c92c2a8a -size 76908 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png index 21f5c4f252..2728c83aa1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:308e785ea0c43cafd2daea869ecad96cb2e4baeffd747148f1c2f853604603c8 -size 88246 +oid sha256:59c4e7f70dbef1f7c95edc705729dc37f7be3e96f48264e5b63c51f92c13aa22 +size 90762 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_8,NEXUS_5,1.0,en].png index 2728c83aa1..9b8391501a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59c4e7f70dbef1f7c95edc705729dc37f7be3e96f48264e5b63c51f92c13aa22 -size 90762 +oid sha256:528d43fa1989f1368c33f4f447347caa2fefb92c3a0a1e48846974ad96f262de +size 58510 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png index 9b8391501a..168ee31a4d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:528d43fa1989f1368c33f4f447347caa2fefb92c3a0a1e48846974ad96f262de -size 58510 +oid sha256:35f35f058cff4435c278ba8cb9f60d68edabcc5da3ab58823abf99a082346978 +size 53292 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png index 25be545a89..87165509bc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e5c225258a6b257afcfc935971df619199a8985ad87203528d27a166a6fda2a -size 28334 +oid sha256:2ef711f98b345d4913e930a4ef2794d1d4ae2f19e29f842548664713dbc2f82c +size 27670 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png index abbb0fefb9..cdd4902766 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c13a37dfd42c7e3872cb7854b5f4b5b03e24e40f1751a8a3c430376cab8a0669 -size 27203 +oid sha256:97b2c76523b5475a45d7023960356feee4748ef3be6fa6a33b0f727b327dc667 +size 25541 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png index f239a76249..dddcf52c2f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4545536311f20615a2809a067af9eabf1a9398f39a9df819dd3c0a4c3de64bd -size 51321 +oid sha256:5f6269ea5f1be9794e768671ba27d44932ce8d7a2d2d599ad2a9837e6346d7ba +size 50761 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png index 4e7f01ae48..ff7200bbe7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26759996939952786a94b9d860e02c40154c0903a49afb8e7be05a20787f0fa4 -size 52615 +oid sha256:e1ba54ee6e97535ca0a350f687ea64e826c1c590e67fddf95ff5e6f85c9ca0ea +size 52342 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png index 22ffbb29ae..eeb7a029bb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ce368edd4536137fb4545fb21892c51af43d7325f21b6e34e5ac1a72b818b34 -size 31575 +oid sha256:eb6e3df51c248b69357ec7dd3dd8c5ba8bb8c579c8f08e5279039c78d50c541e +size 31024 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png index 7dec5258da..771889d3c2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4bff39ac46298be6b968326b64c632aa73b13a51f1efff512d5c6894c414a1b -size 20951 +oid sha256:aa8ed00e0184ac4636fbf26b42dd10893e1bc9aa64b6107922d7ad97e7304d80 +size 21927 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png index 7908dfeabd..e37ce30fa7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0843965afef3d142709f377ab408866cacfb016f652107ee84dc2fd39770ff17 -size 35490 +oid sha256:ae000f0f2e0f81cf75588d800f3b623f875db1dee7d5459abebfb1cbb67d5da1 +size 34937 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png index 609952770e..87165509bc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cbaaf6ee91626a9e8db83ce42be14e1a02a8c841230bf7829fa74aff8cbdfab -size 32513 +oid sha256:2ef711f98b345d4913e930a4ef2794d1d4ae2f19e29f842548664713dbc2f82c +size 27670 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png index 0795fad021..e2a4d4b4b3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ef7b72a829b4add1f21b5f7fc06d6a260227b9c065c08ad770d70656b28f815 -size 26848 +oid sha256:f8cb1d19005b68d1f9f09240f58937bdf30efd00bf87758693791bda6627191f +size 25745 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png index 54d46147c0..74ea9fb0bc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:383436d7acb3474d2d217bce7e566f9ca2957324fca95eb2ffb93fa1b2038082 -size 26117 +oid sha256:1141421ad8fc3ebc2e321a45604dd1d6dc4494a9ae7b860e7853e4a0cd610d5f +size 23697 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png index 128acd813b..a3bd57d7a0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fdcab5fa8620cb0ba0cc0cb822fe4a4807198dbbcd7de7dd7a230d2fe3030e0 -size 49141 +oid sha256:7a1b73e341ce8c0932f1253b9ee159a219e1b64abef54f14b286ac551b2542e4 +size 48479 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png index c8329b273e..1ccd2010c9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06b8e6bce1573d8e39a1bf5823945733152fde066a394a3e90f2bf8dc7929ea3 -size 50526 +oid sha256:f06386b912ef02aa34cd1806588c065e76ab1373892cd62ded705537dd593a81 +size 50027 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png index 2db49fef25..62977b86c3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10c16c9a29f898a6a33f8bfa10df24d2da9adb93eeb764e2b744c6efe531890e -size 30007 +oid sha256:118b4d64a5977842c3d65d27bcf545cef8b5bf62f8417ab66934553fd3985b67 +size 29534 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png index 29ac2f3227..e00771d913 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4786a280d694ead76992dfa75f09bd2e20c292376509f86462a359d19be3e50 -size 19984 +oid sha256:35409dad83ef81e0aaa74a36b350e028d07a5d90cdcc75c29f9156fbdeb2e3c1 +size 20771 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png index b2be2a2397..507ff9cd8e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59cabf858188557db3f2660f71cafc9b041977c656693dc5bc009da71a5c7648 -size 33457 +oid sha256:5da97854c7dd24f5f890ff1bcce8e8061892f2a66b919caf254c85ecf1831c38 +size 32717 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png index cb997ffa7d..e2a4d4b4b3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efcf672eba104371f27b43c9204d207f11f9ce42098da0e4dce3da4aa6639153 -size 30517 +oid sha256:f8cb1d19005b68d1f9f09240f58937bdf30efd00bf87758693791bda6627191f +size 25745 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Day_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..461a325d97 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Day_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2050a2e304b492d54d2cc20ee65f02f7ae0d380a33e207b41e2bb8a8cae4dac8 +size 10117 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Night_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ae59789889 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Night_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfb15c08455c3b7c323ed81362d56beddf84a387ff06d30336dbfab75b4df8a9 +size 9732 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index d63ede5638..566ea071e0 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -48,7 +48,8 @@ { "name" : ":features:verifysession:impl", "includeRegex" : [ - "screen_session_verification_.*" + "screen_session_verification_.*", + "screen_identity_.*" ] }, { From 1d336cb2974b930e30d4203a2dec4465fb9917a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:16:18 +0000 Subject: [PATCH 077/124] Update dependency androidx.compose:compose-bom to v2024.04.00 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca34d4ec2b..12c5e9ebe4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ activity = "1.8.2" media3 = "1.3.0" # Compose -compose_bom = "2024.03.00" +compose_bom = "2024.04.00" composecompiler = "1.5.11" # Coroutines From 476a54c5cdfd24ca73095dc9622457d60c77e2da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:34:23 +0200 Subject: [PATCH 078/124] Update wysiwyg to v2.36.0 (#2656) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 12c5e9ebe4..b05833f4f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,7 +38,7 @@ serialization_json = "1.6.3" showkase = "1.0.2" appyx = "1.4.0" sqldelight = "2.0.1" -wysiwyg = "2.35.0" +wysiwyg = "2.36.0" telephoto = "0.9.0" # DI From 2f7e30a88f057f9628af96122d2fd9d92bfbf556 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 14:47:47 +0200 Subject: [PATCH 079/124] Remove old ExtraPadding workaround. --- .../timeline/components/event/ExtraPadding.kt | 98 ------------------- 1 file changed, 98 deletions(-) delete mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt deleted file mode 100644 index c9706f06ac..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/ExtraPadding.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.messages.impl.timeline.components.event - -import androidx.compose.material3.LocalTextStyle -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextMeasurer -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.rememberTextMeasurer -import androidx.compose.ui.unit.Density -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.model.TimelineItem -import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState -import io.element.android.libraries.ui.strings.CommonStrings -import kotlin.math.ceil - -// Allow to not overlap the timestamp with the text, in the message bubble. -// Compute the size of the worst case. -data class ExtraPadding(val extraWidth: Dp) - -val noExtraPadding = ExtraPadding(0.dp) - -/** - * See [io.element.android.features.messages.impl.timeline.components.TimelineEventTimestampView] for the related View. - * And https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=1819%253A99506 for the design. - */ -@Composable -fun TimelineItem.Event.toExtraPadding(): ExtraPadding { - val formattedTime = sentTime - val hasMessageSendingFailed = localSendState is LocalEventSendState.SendingFailed - val isMessageEdited = (content as? TimelineItemTextBasedContent)?.isEdited.orFalse() - - val textMeasurer = rememberTextMeasurer(cacheSize = 128) - val density = LocalDensity.current - - var strLen = 2.dp // Extra space char - if (isMessageEdited) { - val editedText = stringResource(id = CommonStrings.common_edited_suffix) - val extraLen = remember(editedText, density) { textMeasurer.getExtraPadding(editedText, density) } + 10.dp // Text + spacing - strLen += extraLen - } - strLen += remember(formattedTime, density) { textMeasurer.getExtraPadding(formattedTime, density) } - if (hasMessageSendingFailed) { - strLen += 19.dp // Image + spacing - // I do not know why, but adding extra widths avoid overlapping when the - // message is edited and in error. - if (isMessageEdited) { - strLen += 2.dp - } - } - return ExtraPadding(strLen) -} - -private fun TextMeasurer.getExtraPadding(text: String, density: Density): Dp { - val timestampTextStyle = ElementTheme.typography.fontBodyXsRegular - val textWidth = measure(text = text, style = timestampTextStyle).size.width - return (textWidth / density.density).dp -} - -/** - * Get a string to add to the content of the message to avoid overlapping the timestamp. - */ -@Composable -fun ExtraPadding.getStr(textStyle: TextStyle = LocalTextStyle.current): String { - if (extraWidth == 0.dp) return "" - val density = LocalDensity.current - val textMeasurer = rememberTextMeasurer(128) - val charWidth = remember(textStyle) { textMeasurer.measure(text = "\u00A0", style = textStyle).size.width } - val widthPx = remember(density, extraWidth) { with(density) { extraWidth.toPx() } } - // A space and some unbreakable spaces, always rounding the result to the next value if not a integer - return " " + "\u00A0".repeat(ceil(widthPx / charWidth).toInt()) -} - -@Composable -fun ExtraPadding.getDpSize(): Dp { - return extraWidth -} From b4582564ab4882cb02ae6b3e09e74d2872db740c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 14:49:42 +0200 Subject: [PATCH 080/124] Remove unused circularReveal Modifier. --- .../designsystem/modifiers/CircularReveal.kt | 107 ------------------ 1 file changed, 107 deletions(-) delete mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt deleted file mode 100644 index 3f9ea0040e..0000000000 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.designsystem.modifiers - -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.updateTransition -import androidx.compose.runtime.State -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.drawscope.clipPath -import androidx.compose.ui.platform.debugInspectorInfo -import kotlin.math.sqrt - -// Note: these modifiers come from https://gist.github.com/darvld/eb3844474baf2f3fc6d3ab44a4b4b5f8 - -/** - * A modifier that clips the composable content using an animated circle. The circle will - * expand/shrink with an animation whenever [visible] changes. - * - * For more fine-grained control over the transition, see this method's overload, which allows passing - * a [State] object to control the progress of the reveal animation. - * - * By default, the circle is centered in the content, but custom positions may be specified using - * [revealFrom]. Specified offsets should be between 0 (left/top) and 1 (right/bottom).*/ -fun Modifier.circularReveal( - visible: Boolean, - showScrim: Boolean = false, - revealFrom: Offset = Offset(0.5f, 0.5f), -): Modifier = composed( - factory = { - val factor = updateTransition(visible, label = "Visibility") - .animateFloat(label = "revealFactor") { if (it) 1f else 0f } - - circularReveal(factor, showScrim, revealFrom) - }, - inspectorInfo = debugInspectorInfo { - name = "circularReveal" - properties["visible"] = visible - properties["revealFrom"] = revealFrom - } -) - -/** - * A modifier that clips the composable content using a circular shape. The radius of the circle - * will be determined by the [transitionProgress]. - * - * The values of the progress should be between 0 and 1. - * - * By default, the circle is centered in the content, but custom positions may be specified using - * [revealFrom]. Specified offsets should be between 0 (left/top) and 1 (right/bottom). - * */ -fun Modifier.circularReveal( - transitionProgress: State, - showScrim: Boolean = false, - revealFrom: Offset = Offset(0.5f, 0.5f) -): Modifier { - return drawWithCache { - val path = Path() - val center = revealFrom.mapTo(size) - val radius = calculateRadius(revealFrom, size) - val scrimColor = if (showScrim) { - Color.Gray - } else { - Color.Transparent - } - - path.addOval(Rect(center, radius * transitionProgress.value)) - - onDrawWithContent { - if (showScrim) { - drawRect(scrimColor, alpha = transitionProgress.value * 0.75f) - } - clipPath(path) { this@onDrawWithContent.drawContent() } - } - } -} - -private fun Offset.mapTo(size: Size): Offset { - return Offset(x * size.width, y * size.height) -} - -private fun calculateRadius(normalizedOrigin: Offset, size: Size) = with(normalizedOrigin) { - val x = (if (x > 0.5f) x else 1 - x) * size.width - val y = (if (y > 0.5f) y else 1 - y) * size.height - - sqrt(x * x + y * y) -} From 9d30adfed809a29d86c84746be896125dfbee856 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 14:51:23 +0200 Subject: [PATCH 081/124] Remove unused NotificationPermissionManager --- .../NotificationPermissionManager.kt | 67 ------------------- 1 file changed, 67 deletions(-) delete mode 100644 libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt deleted file mode 100644 index 7496b3a16b..0000000000 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/permission/NotificationPermissionManager.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.push.impl.permission - -import android.Manifest -import android.app.Activity -import android.content.Context -import android.content.pm.PackageManager -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.core.content.ContextCompat -import io.element.android.libraries.di.ApplicationContext -import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider -import javax.inject.Inject - -// TODO EAx move -class NotificationPermissionManager @Inject constructor( - private val sdkIntProvider: BuildVersionSdkIntProvider, - @ApplicationContext private val context: Context, -) { - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - fun isPermissionGranted(): Boolean { - return ContextCompat.checkSelfPermission( - context, - Manifest.permission.POST_NOTIFICATIONS - ) == PackageManager.PERMISSION_GRANTED - } - - /* - fun eventuallyRequestPermission( - activity: Activity, - requestPermissionLauncher: ActivityResultLauncher>, - showRationale: Boolean = true, - ignorePreference: Boolean = false, - ) { - if (!sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) return - // if (!vectorPreferences.areNotificationEnabledForDevice() && !ignorePreference) return - checkPermissions( - listOf(Manifest.permission.POST_NOTIFICATIONS), - activity, - activityResultLauncher = requestPermissionLauncher, - if (showRationale) R.string.permissions_rationale_msg_notification else 0 - ) - } - */ - - fun eventuallyRevokePermission( - activity: Activity, - ) { - if (!sdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) return - activity.revokeSelfPermissionOnKill(Manifest.permission.POST_NOTIFICATIONS) - } -} From 3bf2bf7bcaa92fe54d943ec5b3e41752c4566fd0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 14:56:26 +0200 Subject: [PATCH 082/124] Improve preview of RetrySendMenuMenu --- .../retrysendmenu/RetrySendMessageMenu.kt | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt index 1eec2547cd..9382d6103a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt @@ -17,7 +17,6 @@ package io.element.android.features.messages.impl.timeline.components.retrysendmenu import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height @@ -148,16 +147,10 @@ private fun ColumnScope.RetrySendMenuContents( ) } -@Suppress("UNUSED_PARAMETER") -@OptIn(ExperimentalMaterial3Api::class) @PreviewsDayNight @Composable internal fun RetrySendMessageMenuPreview(@PreviewParameter(RetrySendMenuStateProvider::class) state: RetrySendMenuState) = ElementPreview { - // TODO restore RetrySendMessageMenuBottomSheet once the issue with bottom sheet not being previewable is fixed - Column { - RetrySendMenuContents( - onRetry = {}, - onRemoveFailed = {}, - ) - } + RetrySendMessageMenu( + state = state, + ) } From 94eb1dac4f579f7daa24ee0e9d5b11089c19db64 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 15:08:25 +0200 Subject: [PATCH 083/124] Some renaming for clarity, especially "RemoveFailed" was not clear, developer may think the that removing has failed. --- .../retrysendmenu/RetrySendMenuEvents.kt | 4 ++-- .../retrysendmenu/RetrySendMenuPresenter.kt | 4 ++-- .../retrysendmenu/RetrySendMessageMenu.kt | 19 +++++++++++-------- .../RetrySendMenuPresenterTests.kt | 12 ++++++------ 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt index 77be3815e5..77854773c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuEvents.kt @@ -20,7 +20,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem sealed interface RetrySendMenuEvents { data class EventSelected(val event: TimelineItem.Event) : RetrySendMenuEvents - data object RetrySend : RetrySendMenuEvents - data object RemoveFailed : RetrySendMenuEvents + data object Retry : RetrySendMenuEvents + data object Remove : RetrySendMenuEvents data object Dismiss : RetrySendMenuEvents } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt index bc1415829f..8745df9e53 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenter.kt @@ -41,7 +41,7 @@ class RetrySendMenuPresenter @Inject constructor( is RetrySendMenuEvents.EventSelected -> { selectedEvent = event.event } - RetrySendMenuEvents.RetrySend -> { + RetrySendMenuEvents.Retry -> { coroutineScope.launch { selectedEvent?.transactionId?.let { transactionId -> room.retrySendMessage(transactionId) @@ -49,7 +49,7 @@ class RetrySendMenuPresenter @Inject constructor( selectedEvent = null } } - RetrySendMenuEvents.RemoveFailed -> { + RetrySendMenuEvents.Remove -> { coroutineScope.launch { selectedEvent?.transactionId?.let { transactionId -> room.cancelSend(transactionId) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt index 9382d6103a..ce01c18dfa 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt @@ -53,18 +53,18 @@ internal fun RetrySendMessageMenu( } fun onRetry() { - state.eventSink(RetrySendMenuEvents.RetrySend) + state.eventSink(RetrySendMenuEvents.Retry) } - fun onRemoveFailed() { - state.eventSink(RetrySendMenuEvents.RemoveFailed) + fun onRemove() { + state.eventSink(RetrySendMenuEvents.Remove) } RetrySendMessageMenuBottomSheet( modifier = modifier, isVisible = isVisible, onRetry = ::onRetry, - onRemoveFailed = ::onRemoveFailed, + onRemove = ::onRemove, onDismiss = ::onDismiss ) } @@ -74,7 +74,7 @@ internal fun RetrySendMessageMenu( private fun RetrySendMessageMenuBottomSheet( isVisible: Boolean, onRetry: () -> Unit, - onRemoveFailed: () -> Unit, + onRemove: () -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { @@ -94,7 +94,10 @@ private fun RetrySendMessageMenuBottomSheet( } } ) { - RetrySendMenuContents(onRetry = onRetry, onRemoveFailed = onRemoveFailed) + RetrySendMenuContents( + onRetry = onRetry, + onRemove = onRemove, + ) // FIXME remove after https://issuetracker.google.com/issues/275849044 Spacer(modifier = Modifier.height(32.dp)) } @@ -105,7 +108,7 @@ private fun RetrySendMessageMenuBottomSheet( @Composable private fun ColumnScope.RetrySendMenuContents( onRetry: () -> Unit, - onRemoveFailed: () -> Unit, + onRemove: () -> Unit, sheetState: SheetState = rememberModalBottomSheetState(), ) { val coroutineScope = rememberCoroutineScope() @@ -141,7 +144,7 @@ private fun ColumnScope.RetrySendMenuContents( modifier = Modifier.clickable { coroutineScope.launch { sheetState.hide() - onRemoveFailed() + onRemove() } } ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt index afed2d8b8f..edf6509631 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt @@ -73,7 +73,7 @@ class RetrySendMenuPresenterTests { initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.RetrySend) + initialState.eventSink(RetrySendMenuEvents.Retry) assertThat(room.retrySendMessageCount).isEqualTo(1) assertThat(awaitItem().selectedEvent).isNull() } @@ -89,7 +89,7 @@ class RetrySendMenuPresenterTests { initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.RetrySend) + initialState.eventSink(RetrySendMenuEvents.Retry) assertThat(room.retrySendMessageCount).isEqualTo(0) assertThat(awaitItem().selectedEvent).isNull() } @@ -106,7 +106,7 @@ class RetrySendMenuPresenterTests { initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.RetrySend) + initialState.eventSink(RetrySendMenuEvents.Retry) assertThat(room.retrySendMessageCount).isEqualTo(1) assertThat(awaitItem().selectedEvent).isNull() } @@ -122,7 +122,7 @@ class RetrySendMenuPresenterTests { initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.RemoveFailed) + initialState.eventSink(RetrySendMenuEvents.Remove) assertThat(room.cancelSendCount).isEqualTo(1) assertThat(awaitItem().selectedEvent).isNull() } @@ -138,7 +138,7 @@ class RetrySendMenuPresenterTests { initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.RemoveFailed) + initialState.eventSink(RetrySendMenuEvents.Remove) assertThat(room.cancelSendCount).isEqualTo(0) assertThat(awaitItem().selectedEvent).isNull() } @@ -155,7 +155,7 @@ class RetrySendMenuPresenterTests { initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.RemoveFailed) + initialState.eventSink(RetrySendMenuEvents.Remove) assertThat(room.cancelSendCount).isEqualTo(1) assertThat(awaitItem().selectedEvent).isNull() } From 39af0b8ecd7e9a96b62e9b1930d6081be0046dc7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 15:12:24 +0200 Subject: [PATCH 084/124] Add more assertions --- .../retrysendmenu/RetrySendMenuPresenterTests.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt index edf6509631..21bc5b53ac 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMenuPresenterTests.kt @@ -43,7 +43,6 @@ class RetrySendMenuPresenterTests { val initialState = awaitItem() val selectedEvent = aTimelineItemEvent() initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) - assertThat(awaitItem().selectedEvent).isSameInstanceAs(selectedEvent) } } @@ -57,8 +56,9 @@ class RetrySendMenuPresenterTests { val selectedEvent = aTimelineItemEvent() initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.Dismiss) + assertThat(room.cancelSendCount).isEqualTo(0) + assertThat(room.retrySendMessageCount).isEqualTo(0) assertThat(awaitItem().selectedEvent).isNull() } } @@ -72,8 +72,8 @@ class RetrySendMenuPresenterTests { val selectedEvent = aTimelineItemEvent(transactionId = A_TRANSACTION_ID) initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.Retry) + assertThat(room.cancelSendCount).isEqualTo(0) assertThat(room.retrySendMessageCount).isEqualTo(1) assertThat(awaitItem().selectedEvent).isNull() } @@ -88,8 +88,8 @@ class RetrySendMenuPresenterTests { val selectedEvent = aTimelineItemEvent(transactionId = null) initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.Retry) + assertThat(room.cancelSendCount).isEqualTo(0) assertThat(room.retrySendMessageCount).isEqualTo(0) assertThat(awaitItem().selectedEvent).isNull() } @@ -105,8 +105,8 @@ class RetrySendMenuPresenterTests { val selectedEvent = aTimelineItemEvent(transactionId = A_TRANSACTION_ID) initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.Retry) + assertThat(room.cancelSendCount).isEqualTo(0) assertThat(room.retrySendMessageCount).isEqualTo(1) assertThat(awaitItem().selectedEvent).isNull() } @@ -121,9 +121,9 @@ class RetrySendMenuPresenterTests { val selectedEvent = aTimelineItemEvent(transactionId = A_TRANSACTION_ID) initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.Remove) assertThat(room.cancelSendCount).isEqualTo(1) + assertThat(room.retrySendMessageCount).isEqualTo(0) assertThat(awaitItem().selectedEvent).isNull() } } @@ -137,9 +137,9 @@ class RetrySendMenuPresenterTests { val selectedEvent = aTimelineItemEvent(transactionId = null) initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.Remove) assertThat(room.cancelSendCount).isEqualTo(0) + assertThat(room.retrySendMessageCount).isEqualTo(0) assertThat(awaitItem().selectedEvent).isNull() } } @@ -154,9 +154,9 @@ class RetrySendMenuPresenterTests { val selectedEvent = aTimelineItemEvent(transactionId = A_TRANSACTION_ID) initialState.eventSink(RetrySendMenuEvents.EventSelected(selectedEvent)) skipItems(1) - initialState.eventSink(RetrySendMenuEvents.Remove) assertThat(room.cancelSendCount).isEqualTo(1) + assertThat(room.retrySendMessageCount).isEqualTo(0) assertThat(awaitItem().selectedEvent).isNull() } } From d1965005b08d8e21e6dc58718f763bf1f8a2aa75 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 15:29:16 +0200 Subject: [PATCH 085/124] Add unit test on RetrySendMessageMenu --- .../retrysendmenu/RetrySendMessageMenuTest.kt | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenuTest.kt diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenuTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenuTest.kt new file mode 100644 index 0000000000..41d6d5610f --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenuTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.components.retrysendmenu + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.messages.impl.R +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.pressBackKey +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 RetrySendMessageMenuTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `dismiss the bottom sheet emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setRetrySendMessageMenu( + aRetrySendMenuState( + event = aTimelineItemEvent(), + eventSink = eventsRecorder + ), + ) + rule.pressBackKey() + // Cannot test this for now. + // eventsRecorder.assertSingle(RetrySendMenuEvents.Dismiss) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `retry to send the event emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setRetrySendMessageMenu( + aRetrySendMenuState( + event = aTimelineItemEvent(), + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_room_retry_send_menu_send_again_action) + eventsRecorder.assertSingle(RetrySendMenuEvents.Retry) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `remove the event emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setRetrySendMessageMenu( + aRetrySendMenuState( + event = aTimelineItemEvent(), + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_remove) + eventsRecorder.assertSingle(RetrySendMenuEvents.Remove) + } +} + +private fun AndroidComposeTestRule.setRetrySendMessageMenu( + state: RetrySendMenuState, +) { + setContent { + RetrySendMessageMenu( + state = state, + ) + } +} From 20ebb5ed04f841c642d1da7fcd849226d8bb244b Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 4 Apr 2024 13:42:04 +0000 Subject: [PATCH 086/124] Update screenshots --- ..._RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,en].png | 4 ++-- ..._RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,en].png | 4 ++-- ...etrySendMessageMenu-Night-56_57_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...etrySendMessageMenu-Night-56_57_null_1,NEXUS_5,1.0,en].png | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,en].png index 273762d670..665c8811ac 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6293aceaed02886f3eb1d344c70f93772377d0ae09e69e6a382b33f577a26cd8 -size 14765 +oid sha256:bb0d3bfcfd75cbd75fd9270ff1dc27090e5dbac79ca8db8a46d91a4c12bc966b +size 4457 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,en].png index 273762d670..e247986272 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6293aceaed02886f3eb1d344c70f93772377d0ae09e69e6a382b33f577a26cd8 -size 14765 +oid sha256:703fe69f26ac8b3b7d199def97c4198bcf1e462b68fdc39924a865a54f298e47 +size 16524 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_0,NEXUS_5,1.0,en].png index 1276837323..fae8a6fca3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bc02697864bb7289bccfdfc137a7768b763c378e2ca057103058ae0d8c94f98 -size 13711 +oid sha256:8c89ac73df77c2bccb0c2aa80cee1420f78e7d07f0eda89a90bffef55e8cf753 +size 4464 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_1,NEXUS_5,1.0,en].png index 1276837323..b0f29f0321 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bc02697864bb7289bccfdfc137a7768b763c378e2ca057103058ae0d8c94f98 -size 13711 +oid sha256:7d1750522119e356ab0ee5697b16eddca04fe04f16c29b1d4f750756c5a59965 +size 14281 From 3ed8b0d055285dfd7d35e487b05a4a5decb03016 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 17:48:04 +0200 Subject: [PATCH 087/124] Avoid using fixture method in production code. --- .../changeroles/ChangeRolesEvent.kt | 4 ++-- .../changeroles/ChangeRolesPresenter.kt | 11 +++------ .../changeroles/ChangeRolesView.kt | 8 +++---- .../changeroles/ChangeRolesPresenterTests.kt | 23 +++++++++---------- .../libraries/matrix/api/room/RoomMember.kt | 8 +++++++ 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesEvent.kt index afb12a2e50..4176f5f9ce 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesEvent.kt @@ -16,12 +16,12 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles -import io.element.android.libraries.matrix.api.room.RoomMember +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 roomMember: RoomMember) : ChangeRolesEvent + data class UserSelectionToggled(val matrixUser: MatrixUser) : ChangeRolesEvent data object Save : ChangeRolesEvent data object Exit : ChangeRolesEvent data object CancelExit : ChangeRolesEvent diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt index dc1c2ef70e..aec05f1af1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt @@ -42,6 +42,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange +import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.ImmutableList @@ -129,11 +130,11 @@ class ChangeRolesPresenter @AssistedInject constructor( } is ChangeRolesEvent.UserSelectionToggled -> { val newList = selectedUsers.value.toMutableList() - val index = newList.indexOfFirst { it.userId == event.roomMember.userId } + val index = newList.indexOfFirst { it.userId == event.matrixUser.userId } if (index >= 0) { newList.removeAt(index) } else { - newList.add(event.roomMember.toMatrixUser()) + newList.add(event.matrixUser) } selectedUsers.value = newList.toImmutableList() } @@ -183,12 +184,6 @@ class ChangeRolesPresenter @AssistedInject constructor( return sortedWith(PowerLevelRoomMemberComparator()).toImmutableList() } - private fun RoomMember.toMatrixUser() = MatrixUser( - userId = userId, - displayName = displayName, - avatarUrl = avatarUrl, - ) - private fun CoroutineScope.save( usersWithRole: ImmutableList, selectedUsers: MutableState>, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt index 9510ca4272..3caa036a8f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt @@ -46,7 +46,6 @@ 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.roomdetails.impl.R -import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.async.AsyncActionView @@ -68,6 +67,7 @@ import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.components.MatrixUserRow import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList @@ -143,7 +143,7 @@ fun ChangeRolesView( searchResults = members, selectedUsers = state.selectedUsers, canRemoveMember = state.canChangeMemberRole, - onSelectionToggled = { state.eventSink(ChangeRolesEvent.UserSelectionToggled(it)) }, + onSelectionToggled = { state.eventSink(ChangeRolesEvent.UserSelectionToggled(it.toMatrixUser())) }, selectedUsersList = {}, ) } @@ -159,13 +159,13 @@ fun ChangeRolesView( searchResults = (state.searchResults as? SearchBarResultState.Results)?.results ?: persistentListOf(), selectedUsers = state.selectedUsers, canRemoveMember = state.canChangeMemberRole, - onSelectionToggled = { state.eventSink(ChangeRolesEvent.UserSelectionToggled(it)) }, + onSelectionToggled = { state.eventSink(ChangeRolesEvent.UserSelectionToggled(it.toMatrixUser())) }, selectedUsersList = { users -> SelectedUsersRowList( contentPadding = PaddingValues(start = 16.dp, end = 16.dp, bottom = 16.dp), selectedUsers = users, onUserRemoved = { - state.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(it.userId))) + state.eventSink(ChangeRolesEvent.UserSelectionToggled(it)) }, canDeselect = { state.canChangeMemberRole(it.userId) }, ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt index ff5e185bbd..1dbce1fc53 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt @@ -21,7 +21,6 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration -import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.aRoomMemberList import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.ChangeRolesEvent import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.ChangeRolesPresenter @@ -30,6 +29,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.user.MatrixUser 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.room.FakeMatrixRoom @@ -154,10 +154,10 @@ class ChangeRolesPresenterTests { val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) assertThat(awaitItem().selectedUsers).hasSize(2) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) assertThat(awaitItem().selectedUsers).hasSize(1) } } @@ -177,13 +177,13 @@ class ChangeRolesPresenterTests { assertThat(initialState.hasPendingChanges).isFalse() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) with(awaitItem()) { assertThat(selectedUsers).hasSize(2) assertThat(hasPendingChanges).isTrue() } - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) with(awaitItem()) { assertThat(selectedUsers).hasSize(1) assertThat(hasPendingChanges).isFalse() @@ -226,7 +226,7 @@ class ChangeRolesPresenterTests { assertThat(initialState.hasPendingChanges).isFalse() assertThat(initialState.exitState).isEqualTo(AsyncAction.Uninitialized) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Exit) val confirmingState = awaitItem() @@ -252,7 +252,7 @@ class ChangeRolesPresenterTests { assertThat(initialState.hasPendingChanges).isFalse() assertThat(initialState.exitState).isEqualTo(AsyncAction.Uninitialized) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) val updatedState = awaitItem() assertThat(updatedState.hasPendingChanges).isTrue() skipItems(1) @@ -279,8 +279,7 @@ class ChangeRolesPresenterTests { val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) - + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Save) val confirmingState = awaitItem() assertThat(confirmingState.savingState).isEqualTo(AsyncAction.Confirming) @@ -304,7 +303,7 @@ class ChangeRolesPresenterTests { val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Save) val confirmingState = awaitItem() @@ -334,7 +333,7 @@ class ChangeRolesPresenterTests { val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Save) assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(Unit)) @@ -357,7 +356,7 @@ class ChangeRolesPresenterTests { val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(aRoomMember(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Save) val failedState = awaitItem() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index f6609a2bce..0e4247310c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.api.room import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser data class RoomMember( val userId: UserId, @@ -78,3 +79,10 @@ enum class RoomMembershipState { fun RoomMember.getBestName(): String { return displayName?.takeIf { it.isNotEmpty() } ?: userId.value } + +fun RoomMember.toMatrixUser() = MatrixUser( + userId = userId, + displayName = displayName, + avatarUrl = avatarUrl, +) + From 5f071daf64637a2ef529f34fa3780ee53f4556c6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 18:34:10 +0200 Subject: [PATCH 088/124] Fix non-scrollable screens. --- .../permissions/ChangeRoomPermissionsView.kt | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt index 1d57545a0b..c9e09c7c68 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt @@ -17,8 +17,9 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -80,29 +81,35 @@ fun ChangeRoomPermissionsView( ) } ) { padding -> - Column(modifier = Modifier.padding(padding)) { + LazyColumn( + modifier = Modifier + .padding(padding) + .fillMaxSize() + ) { for ((index, permissionItem) in state.items.withIndex()) { - ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0) - SelectRoleItem( - permissionsItem = permissionItem, - role = RoomMember.Role.ADMIN, - currentPermissions = state.currentPermissions - ) { item, role -> - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) - } - SelectRoleItem( - permissionsItem = permissionItem, - role = RoomMember.Role.MODERATOR, - currentPermissions = state.currentPermissions - ) { item, role -> - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) - } - SelectRoleItem( - permissionsItem = permissionItem, - role = RoomMember.Role.USER, - currentPermissions = state.currentPermissions - ) { item, role -> - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) + item { + ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0) + SelectRoleItem( + permissionsItem = permissionItem, + role = RoomMember.Role.ADMIN, + currentPermissions = state.currentPermissions + ) { item, role -> + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) + } + SelectRoleItem( + permissionsItem = permissionItem, + role = RoomMember.Role.MODERATOR, + currentPermissions = state.currentPermissions + ) { item, role -> + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) + } + SelectRoleItem( + permissionsItem = permissionItem, + role = RoomMember.Role.USER, + currentPermissions = state.currentPermissions + ) { item, role -> + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) + } } } } From 04eb59bd0712097e48bd9f92ac062e7a9b6e3297 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 18:45:56 +0200 Subject: [PATCH 089/124] Ensure SearchBar are displayed with max width. --- .../impl/rolesandpermissions/changeroles/ChangeRolesView.kt | 4 +++- .../android/libraries/roomselect/impl/RoomSelectView.kt | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt index 3caa036a8f..a013a5a775 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt @@ -129,7 +129,9 @@ fun ChangeRolesView( ) { val lazyListState = rememberLazyListState() SearchBar( - modifier = Modifier.padding(bottom = 16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), placeHolderTitle = stringResource(CommonStrings.common_search_for_someone), query = state.query.orEmpty(), onQueryChange = { state.eventSink(ChangeRolesEvent.QueryChanged(it)) }, 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 9851a8c7c2..b2c703fdb1 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 @@ -127,6 +127,7 @@ fun RoomSelectView( .consumeWindowInsets(paddingValues) ) { SearchBar( + modifier = Modifier.fillMaxWidth(), placeHolderTitle = stringResource(CommonStrings.action_search), query = state.query, onQueryChange = { state.eventSink(RoomSelectEvents.UpdateQuery(it)) }, From 97491c3f3fadd00161138e733d7137bfb24ef50e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 4 Apr 2024 19:17:36 +0200 Subject: [PATCH 090/124] Member role: only count and display joined members. --- .../RolesAndPermissionsPresenter.kt | 20 ++++++++++++------- .../libraries/matrix/api/room/MatrixRoom.kt | 5 ++--- .../matrix/api/room/MatrixRoomMembersState.kt | 4 ++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt index 659dc6c255..ae50f5058f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt @@ -29,9 +29,11 @@ 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.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.joinedRoomMembers import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope @@ -47,14 +49,20 @@ class RolesAndPermissionsPresenter @Inject constructor( override fun present(): RolesAndPermissionsState { val coroutineScope = rememberCoroutineScope() val roomInfo by room.roomInfoFlow.collectAsState(initial = null) + val roomMembers by room.membersStateFlow.collectAsState() + val joinedRoomMemberIds by remember { + derivedStateOf { + roomMembers.joinedRoomMembers().map { it.userId } + } + } val moderatorCount by remember { derivedStateOf { - roomInfo.userCountWithRole(RoomMember.Role.MODERATOR) + roomInfo.userCountWithRole(joinedRoomMemberIds, RoomMember.Role.MODERATOR) } } val adminCount by remember { derivedStateOf { - roomInfo.userCountWithRole(RoomMember.Role.ADMIN) + roomInfo.userCountWithRole(joinedRoomMemberIds, RoomMember.Role.ADMIN) } } val changeOwnRoleAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } @@ -108,11 +116,9 @@ class RolesAndPermissionsPresenter @Inject constructor( } } - private fun MatrixRoomInfo?.userCountWithRole(role: RoomMember.Role): Int { - return if (this != null) { - userPowerLevels.count { (_, level) -> RoomMember.Role.forPowerLevel(level) == role } - } else { - 0 + private fun MatrixRoomInfo?.userCountWithRole(joinedRoomMemberIds: List, role: RoomMember.Role): Int { + return this?.userPowerLevels.orEmpty().count { (userId, level) -> + RoomMember.Role.forPowerLevel(level) == role && userId in joinedRoomMemberIds } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index aa1f3fa025..c4ed620a28 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -186,13 +186,12 @@ interface MatrixRoom : Closeable { fun usersWithRole(role: RoomMember.Role): Flow> { return roomInfoFlow .map { it.userPowerLevels.filter { (_, powerLevel) -> RoomMember.Role.forPowerLevel(powerLevel) == role } } - .distinctUntilChanged() .combine(membersStateFlow) { powerLevels, membersState -> - membersState.roomMembers() - .orEmpty() + membersState.joinedRoomMembers() .filter { powerLevels.containsKey(it.userId) } .toPersistentList() } + .distinctUntilChanged() } suspend fun updateAvatar(mimeType: String, data: ByteArray): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt index 759b0f46cb..13f19fe0e0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt @@ -35,3 +35,7 @@ fun MatrixRoomMembersState.roomMembers(): List? { else -> null } } + +fun MatrixRoomMembersState.joinedRoomMembers(): List { + return roomMembers().orEmpty().filter { it.membership == RoomMembershipState.JOIN } +} From 6f20746227b324c7f2f15be78fa31d2222254a99 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 00:19:54 +0000 Subject: [PATCH 091/124] Update dependencyAnalysis to v1.31.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b05833f4f7..ff6051f50f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ test_core = "1.5.0" #other coil = "2.6.0" datetime = "0.5.0" -dependencyAnalysis = "1.30.0" +dependencyAnalysis = "1.31.0" serialization_json = "1.6.3" showkase = "1.0.2" appyx = "1.4.0" From 2cb056711dba25e0a03193a7a107702d4d060994 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 09:23:36 +0200 Subject: [PATCH 092/124] SearchBar handle the back press when it is active, there is no need to handle the event here. --- .../rolesandpermissions/changeroles/ChangeRolesView.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt index a013a5a775..f24ee3b0f1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt @@ -83,12 +83,8 @@ fun ChangeRolesView( modifier: Modifier = Modifier, ) { val updatedOnBackPressed by rememberUpdatedState(newValue = onBackPressed) - BackHandler { - if (state.isSearchActive) { - state.eventSink(ChangeRolesEvent.ToggleSearchActive) - } else { - state.eventSink(ChangeRolesEvent.Exit) - } + BackHandler(enabled = !state.isSearchActive) { + state.eventSink(ChangeRolesEvent.Exit) } Box(modifier = modifier) { From 0a14ed67affa9030444c55acfd92eb45fb990efb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 09:46:30 +0200 Subject: [PATCH 093/124] Add UI test on ChangeRolesView --- .../changeroles/ChangeRolesStateProvider.kt | 3 +- .../changeroles/ChangeRolesViewTest.kt | 298 ++++++++++++++++++ 2 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt index 69e8d72be6..8cd5db01d6 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt @@ -62,6 +62,7 @@ internal fun aChangeRolesState( exitState: AsyncAction = AsyncAction.Uninitialized, savingState: AsyncAction = AsyncAction.Uninitialized, canRemoveMember: (UserId) -> Boolean = { true }, + eventSink: (ChangeRolesEvent) -> Unit = {}, ) = ChangeRolesState( role = role, query = query, @@ -72,7 +73,7 @@ internal fun aChangeRolesState( exitState = exitState, savingState = savingState, canChangeMemberRole = canRemoveMember, - eventSink = {}, + eventSink = eventSink, ) internal fun aChangeRolesStateWithSelectedUsers() = aChangeRolesState( diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt new file mode 100644 index 0000000000..c5aa04a0db --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.rolesandpermissions.changeroles + +import androidx.activity.ComponentActivity +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.ChangeRolesEvent +import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.ChangeRolesState +import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.ChangeRolesView +import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.aChangeRolesState +import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.aChangeRolesStateWithSelectedUsers +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.toMatrixUser +import io.element.android.libraries.matrix.ui.components.aMatrixUserList +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.pressBack +import kotlinx.collections.immutable.toImmutableList +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ChangeRolesViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `click on back icon search not active emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + eventSink = eventsRecorder, + ), + ) + rule.pressBack() + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.Exit, + ) + ) + } + + @Test + fun `click on back icon search active emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + isSearchActive = true, + eventSink = eventsRecorder, + ), + ) + rule.pressBack() + // This event should be there, maybe a problem with the SearchBar + // It's working fine in the app, so let's ignore it for now + // eventsRecorder.assertSingle(ChangeRolesEvent.ToggleSearchActive) + } + + @Test + fun `click on search bar emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.common_search_for_someone) + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + // This event should be there, maybe a problem with the SearchBar + // It's working fine in the app, so let's ignore it for now + // ChangeRolesEvent.ToggleSearchActive, + ) + ) + } + + @Test + fun `click on save button emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + hasPendingChanges = true, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_save) + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.Save, + ) + ) + } + + @Test + fun `testing exit confirmation dialog ok emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + exitState = AsyncAction.Confirming, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.Exit, + ) + ) + } + + @Test + fun `testing exit confirmation dialog cancel emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + exitState = AsyncAction.Confirming, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.CancelExit + ) + ) + } + + @Test + fun `testing saving dialog failure OK emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + savingState = AsyncAction.Failure(Exception("boom")), + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.ClearError, + ) + ) + } + + @Test + fun `testing saving confirmation dialog for admin OK emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + role = RoomMember.Role.ADMIN, + savingState = AsyncAction.Confirming, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.Save, + ) + ) + } + + @Test + fun `testing saving confirmation dialog for admin cancel emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesView( + state = aChangeRolesState( + role = RoomMember.Role.ADMIN, + savingState = AsyncAction.Confirming, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.ClearError, + ) + ) + } + + @Test + fun `testing removing user from selected list emits the expected event`() { + val eventsRecorder = EventsRecorder() + val selectedUsers = aMatrixUserList().take(2) + val userToDeselect = selectedUsers[1] + assertThat(userToDeselect.displayName).isEqualTo("Bob") + rule.setChangeRolesView( + state = aChangeRolesStateWithSelectedUsers().copy( + selectedUsers = selectedUsers.toImmutableList(), + eventSink = eventsRecorder, + ), + ) + // Unselect the user from the row list + val contentDescription = rule.activity.getString(CommonStrings.action_remove) + rule.onNodeWithContentDescription(contentDescription).performClick() + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.UserSelectionToggled(userToDeselect), + ) + ) + } + + @Test + fun `testing adding user to the selected list emits the expected event`() { + val eventsRecorder = EventsRecorder() + val selectedUsers = aMatrixUserList().take(2) + val state = aChangeRolesStateWithSelectedUsers().copy( + selectedUsers = selectedUsers.toImmutableList(), + eventSink = eventsRecorder, + ) + val userToSelect = (state.searchResults as SearchBarResultState.Results).results[2].toMatrixUser() + assertThat(userToSelect.displayName).isEqualTo("Carol") + rule.setChangeRolesView( + state = state, + ) + // Select the user from the rom list + rule.onNodeWithText("Carol").performClick() + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.UserSelectionToggled(userToSelect), + ) + ) + } + + @Test + fun `testing removing user to the selected list emits the expected event`() { + val eventsRecorder = EventsRecorder() + val selectedUsers = aMatrixUserList().take(2) + val state = aChangeRolesStateWithSelectedUsers().copy( + selectedUsers = selectedUsers.toImmutableList(), + eventSink = eventsRecorder, + ) + val userToSelect = (state.searchResults as SearchBarResultState.Results).results[1].toMatrixUser() + assertThat(userToSelect.displayName).isEqualTo("Bob") + rule.setChangeRolesView( + state = state, + ) + // Select the user from the rom list + rule.onAllNodesWithText("Bob")[1].performClick() + eventsRecorder.assertList( + listOf( + ChangeRolesEvent.QueryChanged(""), + ChangeRolesEvent.UserSelectionToggled(userToSelect), + ) + ) + } +} + +private fun AndroidComposeTestRule.setChangeRolesView( + state: ChangeRolesState, + onBackPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + CompositionLocalProvider(LocalInspectionMode provides true) { + ChangeRolesView( + state = state, + onBackPressed = onBackPressed, + ) + } + } +} From 3fdc281bd37f1dec607b0e0fb2eb5eaa6aee950e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 10:27:49 +0200 Subject: [PATCH 094/124] No need to use CompositionLocalProvider here. --- .../changeroles/ChangeRolesViewTest.kt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt index c5aa04a0db..037223327e 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt @@ -17,8 +17,6 @@ package io.element.android.features.roomdetails.rolesandpermissions.changeroles import androidx.activity.ComponentActivity -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onAllNodesWithText @@ -288,11 +286,9 @@ private fun AndroidComposeTestRule.setChang onBackPressed: () -> Unit = EnsureNeverCalled(), ) { setContent { - CompositionLocalProvider(LocalInspectionMode provides true) { - ChangeRolesView( - state = state, - onBackPressed = onBackPressed, - ) - } + ChangeRolesView( + state = state, + onBackPressed = onBackPressed, + ) } } From 3a6959585b0982c69220255db5434b3d5784f6d7 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 5 Apr 2024 08:37:52 +0000 Subject: [PATCH 095/124] Update screenshots --- ...geRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...geRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...geRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...geRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...geRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...geRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png | 4 ++-- ...RoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...RoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png | 4 ++-- ...RoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...RoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...RoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...RoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png index c1fa064495..25e2ed257b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cce394023e7c5bac40bcf234f9073196b1eb424893760f29d609eb329f62817f -size 42587 +oid sha256:491c4c2465dd79a98e0062a7dbae9dffb1dca66ac991cacae44bc31fbd415b1d +size 42557 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png index 8428ee7714..0d7ebc9df2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d6818023d37493825ba7b929470152847c397b5f19a5bd8ee862fc900dee690 -size 40755 +oid sha256:373b21c8f450fcdde73431e55fad3b8f7f255ca3f5d954e9555b65ca9d891e4a +size 40720 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png index 52135a6c75..ee3dc1981a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86aa47d55841e5041bf56534852dd716b4546cc3657a30133b88c0c87998f16c -size 42464 +oid sha256:8b9b89615f55b7f33c4d16c2affaf0ad768c2dc940dc512c22a3376c7a70266c +size 42434 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png index d7bdf55419..3c662c7fd9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:237125629b6449d6898391235b625248b71629b4d96415b80fb7ef5e773e86b2 -size 40484 +oid sha256:a54f44acb3270d7822cbd3e1a4ff92dbc15e3efb01d84b4b86157820e68b8304 +size 40492 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png index 6c7a7c2b33..bcbd74311f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:753eecb930bf0659671f2f1ecb2a64ae226aac96c5365ed549c635fd90c8c729 -size 40951 +oid sha256:592b7775769f8a51bd3a2a3a4d81fe5e2cc95de9d781b446a74cfb39bd53aa0b +size 40954 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png index bca446759d..1e8a0d2813 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0b37566f62e30fcb4f16364a9ac2c008a98a5429c3fa5724d7ef6455e79cf34 -size 47995 +oid sha256:eee33a3534c66fb6a6ecebfdfe3518a7446880905da5bb0aae11ff260f7470eb +size 47999 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png index 35d6c4ad87..101d42b4bc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dff4c4a80156b2977da007d6258d1cd6cefc93bc709ec33bff0e1b484604cf0a -size 38506 +oid sha256:be6fc53e78d78c117a1e166fdd3e618eb3e2a44ef3effb74f9aa0a2116a0fec3 +size 38484 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png index 97de47b574..d8a11cb617 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af5a843cc9cd5a51d986ba7dae7d2c6a088f862f46bac961667a1fb1d62b8a20 -size 36966 +oid sha256:56c2f01102a7e2902981a4a71efaf838ffe0fd1efd0c4a1f42afbdac013c6f3c +size 36940 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png index e7363aa84b..a031323cc9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:059e598dc973e8c01dba4ef49accf47ea2bd74377dfbfb441c3c213fc77a1cee -size 38158 +oid sha256:540804fa0abf2754e55616fefc03991d692a464bb4679b110a0a7bfc99754338 +size 38135 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png index 7f5fe9dbc4..d5c54c295e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c34415877b279b2018d02667cc98b4857f4ae5bfa68d169f3226a0c99ed4620b -size 36699 +oid sha256:9759cca46c3158225f780439f7da527b5a76c75a201b10f6ba50ccc2022a9f1b +size 36664 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png index 17ca2569d6..e4539c272a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc02c16667b78ca5a9985da6816f17bbd0f7bb2307569aacaf3391ccd8431bd5 -size 36567 +oid sha256:2f4eb202f7cffe9eb5dd51ee32abccc5e97877c6d35df82b6d8632fea611370f +size 36530 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png index 9d7aa5c1be..41b61c015c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4faece23a4780f5465abe42587f56933ecded55bf3ae966a9e6b634e7df1e049 -size 42845 +oid sha256:2d332b41b0a566848e81376e335706e822a25cbd3c40233cedef3f21eba7dbf0 +size 42811 From f2afc9ccda46a9135d7c478d7f006d322f9fc770 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 10:38:20 +0200 Subject: [PATCH 096/124] Remove blank line --- .../io/element/android/libraries/matrix/api/room/RoomMember.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index 0e4247310c..4415e51327 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -85,4 +85,3 @@ fun RoomMember.toMatrixUser() = MatrixUser( displayName = displayName, avatarUrl = avatarUrl, ) - From fd1ff97c534966018838fded3f65378553409835 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 12:01:29 +0200 Subject: [PATCH 097/124] Add comment for clarity --- .../impl/rolesandpermissions/RolesAndPermissionsPresenter.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt index ae50f5058f..3f7224602d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt @@ -50,6 +50,8 @@ class RolesAndPermissionsPresenter @Inject constructor( val coroutineScope = rememberCoroutineScope() val roomInfo by room.roomInfoFlow.collectAsState(initial = null) val roomMembers by room.membersStateFlow.collectAsState() + // Get the list of joined room members, in order to filter members present in the power + // level state Event, but not member of the room anymore. val joinedRoomMemberIds by remember { derivedStateOf { roomMembers.joinedRoomMembers().map { it.userId } From 34f3208fc077e36d12839efab78608153e52dbec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 12:07:11 +0200 Subject: [PATCH 098/124] Fix documentation --- .../matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt index f2577fcb6b..ef4e6f747e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomPowerLevels.kt @@ -42,7 +42,7 @@ suspend fun MatrixRoom.canInvite(): Result = canUserInvite(sessionId) suspend fun MatrixRoom.canKick(): Result = canUserKick(sessionId) /** - * Shortcut for calling [MatrixRoom.canBanUser] with our own user. + * Shortcut for calling [MatrixRoom.canUserBan] with our own user. */ suspend fun MatrixRoom.canBan(): Result = canUserBan(sessionId) From c0caa907aabe2c2605d50ae638a8085aba74cf83 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 12:13:40 +0200 Subject: [PATCH 099/124] Move usersWithRole() function to a dedicated file and make it a documented extension of MatrixRoom. --- .../changeroles/ChangeRolesPresenter.kt | 1 + .../libraries/matrix/api/room/MatrixRoom.kt | 16 ------- .../powerlevels/MatrixRoomMembersWithRole.kt | 42 +++++++++++++++++++ 3 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomMembersWithRole.kt diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt index aec05f1af1..403b3f3e5d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt @@ -42,6 +42,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange +import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.services.analytics.api.AnalyticsService diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index c4ed620a28..fef657571c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -35,13 +35,8 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import java.io.Closeable import java.io.File @@ -183,17 +178,6 @@ interface MatrixRoom : Closeable { suspend fun canUserJoinCall(userId: UserId): Result = canUserSendState(userId, StateEventType.CALL_MEMBER) - fun usersWithRole(role: RoomMember.Role): Flow> { - return roomInfoFlow - .map { it.userPowerLevels.filter { (_, powerLevel) -> RoomMember.Role.forPowerLevel(powerLevel) == role } } - .combine(membersStateFlow) { powerLevels, membersState -> - membersState.joinedRoomMembers() - .filter { powerLevels.containsKey(it.userId) } - .toPersistentList() - } - .distinctUntilChanged() - } - suspend fun updateAvatar(mimeType: String, data: ByteArray): Result suspend fun removeAvatar(): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomMembersWithRole.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomMembersWithRole.kt new file mode 100644 index 0000000000..2c17718ccf --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomMembersWithRole.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.room.powerlevels + +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.joinedRoomMembers +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +/** + * Return a flow of the list of room members who are still in the room (with membership == RoomMembershipState.JOIN) + * and who have the given role. + */ +fun MatrixRoom.usersWithRole(role: RoomMember.Role): Flow> { + return roomInfoFlow + .map { it.userPowerLevels.filter { (_, powerLevel) -> RoomMember.Role.forPowerLevel(powerLevel) == role } } + .combine(membersStateFlow) { powerLevels, membersState -> + membersState.joinedRoomMembers() + .filter { powerLevels.containsKey(it.userId) } + .toPersistentList() + } + .distinctUntilChanged() +} From 704889c64faf2a34045480e3271253eb88f3a489 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Apr 2024 11:39:14 +0000 Subject: [PATCH 100/124] Update dependency org.matrix.rustcomponents:sdk-android to v0.2.13 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b05833f4f7..d773fa88aa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -154,7 +154,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:1.4.2" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.12" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.13" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From f704cca67821688196a474c93618cb19e6801bd9 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Sun, 7 Apr 2024 18:39:05 +0200 Subject: [PATCH 101/124] Fall back to initials avatar when image avatar doesn't work Initially I had it implemented such that it would only fallback on error, but actually it's nice to have also while loading if it takes a while to load the avatar. --- changelog.d/2667.bugfix | 1 + .../designsystem/components/avatar/Avatar.kt | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 changelog.d/2667.bugfix diff --git a/changelog.d/2667.bugfix b/changelog.d/2667.bugfix new file mode 100644 index 0000000000..f72b79503f --- /dev/null +++ b/changelog.d/2667.bugfix @@ -0,0 +1 @@ +Fall back to name-based generated avatars when image avatars don't load. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index b1e7bc6be0..5a63eaa7ec 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -32,12 +33,13 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter +import coil.compose.SubcomposeAsyncImage +import coil.compose.SubcomposeAsyncImageContent import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup -import io.element.android.libraries.designsystem.preview.debugPlaceholderAvatar import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.Text import timber.log.Timber @@ -71,16 +73,23 @@ private fun ImageAvatar( modifier: Modifier = Modifier, contentDescription: String? = null, ) { - AsyncImage( + SubcomposeAsyncImage( model = avatarData, - onError = { - Timber.e(it.result.throwable, "Error loading avatar $it\n${it.result}") - }, contentDescription = contentDescription, contentScale = ContentScale.Crop, - placeholder = debugPlaceholderAvatar(), modifier = modifier - ) + ) { + when (val state = painter.state) { + is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent() + is AsyncImagePainter.State.Error -> { + SideEffect { + Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}") + } + InitialsAvatar(avatarData = avatarData) + } + else -> InitialsAvatar(avatarData = avatarData) + } + } } @Composable From e6bd14a1a93a3a764e81c24c892b1c5621a41b9c Mon Sep 17 00:00:00 2001 From: bmarty <3940906+bmarty@users.noreply.github.com> Date: Mon, 8 Apr 2024 00:20:53 +0000 Subject: [PATCH 102/124] Sync Strings from Localazy --- .../src/main/res/values-sv/translations.xml | 1 + .../src/main/res/values-be/translations.xml | 2 + .../src/main/res/values-cs/translations.xml | 3 + .../src/main/res/values-de/translations.xml | 3 + .../src/main/res/values-hu/translations.xml | 3 + .../src/main/res/values-in/translations.xml | 3 + .../src/main/res/values-it/translations.xml | 2 +- .../src/main/res/values-ru/translations.xml | 3 + .../src/main/res/values-sk/translations.xml | 3 + .../impl/src/main/res/values/localazy.xml | 2 + .../src/main/res/values-it/translations.xml | 5 + .../src/main/res/values-be/translations.xml | 2 + .../src/main/res/values-cs/translations.xml | 2 + .../src/main/res/values-hu/translations.xml | 2 + .../src/main/res/values-in/translations.xml | 2 + .../src/main/res/values-it/translations.xml | 1 + .../src/main/res/values-ru/translations.xml | 7 +- .../src/main/res/values-sk/translations.xml | 2 + .../impl/src/main/res/values/localazy.xml | 2 + .../src/main/res/values-be/translations.xml | 8 +- .../src/main/res/values-cs/translations.xml | 6 +- .../src/main/res/values-de/translations.xml | 11 +- .../src/main/res/values-hu/translations.xml | 8 +- .../src/main/res/values-in/translations.xml | 8 +- .../src/main/res/values-ru/translations.xml | 67 +++++++-- .../src/main/res/values-sk/translations.xml | 8 +- .../impl/src/main/res/values/localazy.xml | 8 +- .../src/main/res/values-be/translations.xml | 7 + .../src/main/res/values-cs/translations.xml | 7 + .../src/main/res/values-de/translations.xml | 7 + .../src/main/res/values-fr/translations.xml | 6 + .../src/main/res/values-hu/translations.xml | 7 + .../src/main/res/values-in/translations.xml | 7 + .../src/main/res/values-it/translations.xml | 6 + .../src/main/res/values-ru/translations.xml | 15 +- .../src/main/res/values-sk/translations.xml | 7 + .../src/main/res/values-uk/translations.xml | 4 + .../main/res/values-zh-rTW/translations.xml | 3 + .../impl/src/main/res/values/localazy.xml | 1 + .../src/main/res/values-be/translations.xml | 16 ++- .../src/main/res/values-cs/translations.xml | 16 ++- .../src/main/res/values-de/translations.xml | 29 +++- .../src/main/res/values-fr/translations.xml | 6 - .../src/main/res/values-hu/translations.xml | 14 +- .../src/main/res/values-in/translations.xml | 16 ++- .../src/main/res/values-ru/translations.xml | 22 ++- .../src/main/res/values-sk/translations.xml | 14 +- .../src/main/res/values-sv/translations.xml | 3 + .../src/main/res/values-uk/translations.xml | 4 - .../main/res/values-zh-rTW/translations.xml | 3 - .../src/main/res/values/localazy.xml | 16 +-- ...ontent-Day-1_1_null_10,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_2,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_3,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_4,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_5,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_6,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_7,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_8,NEXUS_5,1.0,de].png | 4 +- ...Content-Day-1_1_null_9,NEXUS_5,1.0,de].png | 4 +- ...eMenu-Day-56_56_null_0,NEXUS_5,1.0,de].png | 3 - ...eMenu-Day-56_56_null_1,NEXUS_5,1.0,de].png | 4 +- ...erView-Day-57_57_null,NEXUS_5,1.0,de].png} | 0 ...rView-Day-57_57_null_0,NEXUS_5,1.0,de].png | 3 - ...rView-Day-57_57_null_2,NEXUS_5,1.0,de].png | 3 - ...ngOption-Day-7_8_null,NEXUS_5,1.0,de].png} | 0 ...ngView-Day-8_9_null_0,NEXUS_5,1.0,de].png} | 0 ...ngView-Day-8_9_null_1,NEXUS_5,1.0,de].png} | 0 ...ngView-Day-8_9_null_2,NEXUS_5,1.0,de].png} | 0 ...ngView-Day-8_9_null_3,NEXUS_5,1.0,de].png} | 0 ...ngView-Day-8_9_null_4,NEXUS_5,1.0,de].png} | 0 ...tingsView-Day-7_8_null,NEXUS_5,1.0,de].png | 3 - ...ngsView-Day-6_7_null_0,NEXUS_5,1.0,de].png | 4 +- ...ngsView-Day-6_7_null_1,NEXUS_5,1.0,de].png | 4 +- ...ngsView-Day-6_7_null_2,NEXUS_5,1.0,de].png | 4 +- ...ngsView-Day-6_7_null_3,NEXUS_5,1.0,de].png | 3 + ...ngsView-Day-6_7_null_4,NEXUS_5,1.0,de].png | 3 + ...otViewDark--1_1_null_0,NEXUS_5,1.0,de].png | 4 +- ...otViewDark--1_1_null_1,NEXUS_5,1.0,de].png | 4 +- ...tViewLight--0_0_null_0,NEXUS_5,1.0,de].png | 4 +- ...tViewLight--0_0_null_1,NEXUS_5,1.0,de].png | 4 +- ...View-Day-10_11_null_0,NEXUS_5,1.0,de].png} | 0 ...sView-Day-10_11_null_0,NEXUS_5,1.0,de].png | 4 +- ...sView-Day-10_11_null_2,NEXUS_5,1.0,de].png | 4 +- ...sView-Day-10_11_null_3,NEXUS_5,1.0,de].png | 4 +- ...sView-Day-10_11_null_4,NEXUS_5,1.0,de].png | 4 +- ...sView-Day-10_11_null_5,NEXUS_5,1.0,de].png | 4 +- ...sView-Day-10_11_null_6,NEXUS_5,1.0,de].png | 4 +- ...ndicator-Day-7_8_null,NEXUS_5,1.0,de].png} | 0 ...stTopBar-Day-6_7_null,NEXUS_5,1.0,de].png} | 0 ...ionHeader-Day-5_6_null,NEXUS_5,1.0,de].png | 3 - ...ntView-Day-5_6_null_1,NEXUS_5,1.0,de].png} | 0 ...ntView-Day-5_6_null_3,NEXUS_5,1.0,de].png} | 0 ...ntView-Day-5_6_null_4,NEXUS_5,1.0,de].png} | 0 ...View-Day-10_11_null_0,NEXUS_5,1.0,de].png} | 0 ...View-Day-10_11_null_1,NEXUS_5,1.0,de].png} | 0 ...onView-Day-11_12_null,NEXUS_5,1.0,de].png} | 0 ...tent-Day-12_13_null_1,NEXUS_5,1.0,de].png} | 0 ...tView-Day-3_4_null_10,NEXUS_5,1.0,de].png} | 0 ...tView-Day-3_4_null_12,NEXUS_5,1.0,de].png} | 0 ...istView-Day-3_4_null_7,NEXUS_5,1.0,de].png | 4 +- ...istView-Day-3_4_null_8,NEXUS_5,1.0,de].png | 4 +- ...istView-Day-3_4_null_9,NEXUS_5,1.0,de].png | 3 - ...KeyView-Day-2_3_null_0,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-2_3_null_1,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-2_3_null_2,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-2_3_null_3,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_0,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_1,NEXUS_5,1.0,de].png | 4 +- ...eyView-Day-6_7_null_10,NEXUS_5,1.0,de].png | 4 +- ...eyView-Day-6_7_null_11,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_2,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_3,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_4,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_5,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_6,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_7,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_8,NEXUS_5,1.0,de].png | 4 +- ...KeyView-Day-6_7_null_9,NEXUS_5,1.0,de].png | 4 +- ...wChange-Day-5_6_null_0,NEXUS_5,1.0,de].png | 4 +- ...wChange-Day-5_6_null_1,NEXUS_5,1.0,de].png | 4 +- ...wChange-Day-5_6_null_2,NEXUS_5,1.0,de].png | 4 +- ...wChange-Day-5_6_null_3,NEXUS_5,1.0,de].png | 4 +- ...tupView-Day-4_5_null_0,NEXUS_5,1.0,de].png | 4 +- ...tupView-Day-4_5_null_1,NEXUS_5,1.0,de].png | 4 +- ...tupView-Day-4_5_null_2,NEXUS_5,1.0,de].png | 4 +- ...tupView-Day-4_5_null_3,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_0,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_1,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_2,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_3,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_4,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_5,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_6,NEXUS_5,1.0,de].png | 4 +- ...ionView-Day-0_1_null_7,NEXUS_5,1.0,de].png | 4 +- ...oBlurShadow-Day_0_null,NEXUS_5,1.0,de].png | 3 - ...onsView-Day-0_1_null_0,NEXUS_5,1.0,de].png | 3 + ...onsView-Day-0_1_null_1,NEXUS_5,1.0,de].png | 3 + ...onsView-Day-0_1_null_2,NEXUS_5,1.0,de].png | 3 + ...onsView-Day-0_1_null_3,NEXUS_5,1.0,de].png | 3 + ...onsView-Day-0_1_null_4,NEXUS_5,1.0,de].png | 3 + ...onsView-Day-0_1_null_5,NEXUS_5,1.0,de].png | 3 + ...onsView-Day-0_1_null_6,NEXUS_5,1.0,de].png | 3 + ...onsView-Day-0_1_null_7,NEXUS_5,1.0,de].png | 3 + screenshots/html/data.js | 134 +++++++++--------- ...KeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-2_4_null_0,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-2_4_null_1,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-2_4_null_2,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-2_4_null_3,NEXUS_5,1.0,en].png | 4 +- ...eyView-Day-6_7_null_10,NEXUS_5,1.0,en].png | 4 +- ...eyView-Day-6_7_null_11,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png | 4 +- ...View-Night-6_8_null_10,NEXUS_5,1.0,en].png | 4 +- ...View-Night-6_8_null_11,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-6_8_null_8,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-6_8_null_9,NEXUS_5,1.0,en].png | 4 +- 161 files changed, 552 insertions(+), 335 deletions(-) create mode 100644 features/roomdirectory/impl/src/main/res/values-it/translations.xml delete mode 100644 screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,de].png rename screenshots/de/{ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_1,NEXUS_5,1.0,de].png => ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null,NEXUS_5,1.0,de].png} (100%) delete mode 100644 screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,de].png delete mode 100644 screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,de].png rename screenshots/de/{ui_T_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,de].png => ui_T_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,de].png => ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,de].png => ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,de].png => ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,de].png => ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,de].png => ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,de].png} (100%) delete mode 100644 screenshots/de/ui_T_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,de].png rename screenshots/de/{ui_T_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,de].png => ui_T_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-8_9_null,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-7_8_null,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,de].png} (100%) delete mode 100644 screenshots/de/ui_T_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,de].png rename screenshots/de/{ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_1,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_1,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_3,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_3,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_4,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_4,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_0,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_1,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-12_13_null,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,de].png} (100%) rename screenshots/de/{ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,de].png => ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,de].png} (100%) delete mode 100644 screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,de].png delete mode 100644 screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,de].png create mode 100644 screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,de].png diff --git a/features/lockscreen/impl/src/main/res/values-sv/translations.xml b/features/lockscreen/impl/src/main/res/values-sv/translations.xml index c0ffcd9cdf..f8c420da9d 100644 --- a/features/lockscreen/impl/src/main/res/values-sv/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-sv/translations.xml @@ -3,6 +3,7 @@ "Byt PIN-kod" "Tillåt biometrisk upplåsning" "Ta bort PIN-kod" + "Är du säker på att du vill ta bort PIN-koden?" "Ta bort PIN-koden?" "Loggar ut …" diff --git a/features/roomdetails/impl/src/main/res/values-be/translations.xml b/features/roomdetails/impl/src/main/res/values-be/translations.xml index be2ac558fd..7980d6d41b 100644 --- a/features/roomdetails/impl/src/main/res/values-be/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-be/translations.xml @@ -31,6 +31,7 @@ "Панізіць сябе?" "%1$s (У чаканні)" "(У чаканні)" + "Адміністратары аўтаматычна маюць права мадэратара" "Рэдагаваць мадэратараў" "Адміністратары" "Мадэратары" @@ -58,6 +59,7 @@ "Назва пакоя" "Бяспека" "Падзяліцца пакоем" + "Інфармацыя аб пакоі" "Тэма" "Ідзе абнаўленне пакоя…" "Заблакіраваць" diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index 93e7fcf389..f44cdd210a 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -30,6 +30,8 @@ "Tuto změnu nebudete moci vrátit zpět, protože sami degradujete, pokud jste posledním privilegovaným uživatelem v místnosti, nebude možné znovu získat oprávnění." "Degradovat se?" "%1$s (čekající)" + "(Čeká na vyřízení)" + "Správci mají automaticky oprávnění moderátora" "Upravit moderátory" "Správci" "Moderátoři" @@ -57,6 +59,7 @@ "Název místnosti" "Zabezpečení" "Sdílet místnost" + "Informace o místnosti" "Téma" "Aktualizace místnosti…" "Vykázat" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index 980f149bad..025e07a8bd 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -30,6 +30,8 @@ "Du stufst dich selbst herab. Diese Änderung kann nicht rückgängig gemacht werden. Wenn du der letzte Benutzer mit dieser Rolle bist, ist es nicht möglich, diese Rolle wiederzuerlangen." "Möchtest Du Dich selbst herabstufen?" "%1$s (Ausstehend)" + "(Ausstehend)" + "Administratoren haben automatisch Moderatorenrechte" "Moderatoren bearbeiten" "Administratoren" "Moderatoren" @@ -57,6 +59,7 @@ "Raumname" "Sicherheit" "Raum teilen" + "Raum Informationen" "Thema" "Raum wird aktualisiert…" "Sperren" 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 66c05c38a7..6cbbe35d90 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -30,6 +30,8 @@ "Ezt a változtatást nem fogja tudni visszavonni, mivel lefokozza magát, ha Ön az utolsó jogosultságokkal rendelkező felhasználó a szobában, akkor lehetetlen lesz visszaszerezni a jogosultságokat." "Lefokozza magát?" "%1$s (függőben)" + "(Függőben)" + "Az adminisztrátorok automatikusan moderátori jogosultságokkal rendelkeznek" "Moderátorok szerkesztése" "Rendszergazdák" "Moderátorok" @@ -57,6 +59,7 @@ "Szoba neve" "Biztonság" "Szoba megosztása" + "Szobainformációk" "Téma" "Szoba frissítése…" "Kitiltás" 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 ddfd4859ea..c9aedeefd1 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -30,6 +30,8 @@ "Anda tidak akan dapat mengurungkan perubahan ini karena Anda sedang menurunkan Anda sendiri, jika Anda merupakan pengguna dengan hak khusus dalam ruangan maka tidak akan memungkinkan untuk mendapatkan hak tersebut lagi." "Turunkan Anda sendiri?" "%1$s (Tertunda)" + "(Tertunda)" + "Admin secara otomatis memiliki hak moderator" "Sunting Moderator" "Admin" "Moderator" @@ -57,6 +59,7 @@ "Nama ruangan" "Keamanan" "Bagikan ruangan" + "Info ruangan" "Topik" "Memperbarui ruangan…" "Cekal" diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml index 77b46cdeda..8326d592e8 100644 --- a/features/roomdetails/impl/src/main/res/values-it/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -11,7 +11,7 @@ "Sondaggi" "Solo amministratori" "Escludi membri" - "Eliminare messaggi" + "Rimuovi messaggi" "Tutti" "Invitare persone" "Moderazione dei membri" 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 200b2ef3c7..9e6b963efb 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -30,6 +30,8 @@ "Вы не сможете отменить это изменение, так как понижаете себя статус. Если вы являетесь последним привилегированным пользователем в комнате, восстановить привилегии будет невозможно." "Понизить свой уровень?" "%1$s (Ожидание)" + "(В ожидании)" + "Администраторы автоматически получают права модератора" "Редактировать роль модераторов" "Администраторы" "Модераторы" @@ -57,6 +59,7 @@ "Название комнаты" "Безопасность" "Поделиться комнатой" + "Информация о комнате" "Тема" "Обновление комнаты…" "Заблокировать" 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 c85b04017c..7feb1d4e41 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -30,6 +30,8 @@ "Túto zmenu nebudete môcť vrátiť späť, pretože znižujete svoju úroveň. Ak ste posledným privilegovaným používateľom v miestnosti, nebude možné získať znova oprávnenia." "Znížiť svoju úroveň?" "%1$s (Čaká sa)" + "(Čaká sa)" + "Správcovia majú automaticky oprávnenia moderátora" "Upraviť moderátorov" "Správcovia" "Moderátori" @@ -57,6 +59,7 @@ "Názov miestnosti" "Bezpečnosť" "Zdieľať miestnosť" + "Informácie o miestnosti" "Téma" "Aktualizácia miestnosti…" "Zakázať" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 04afc61ad6..8067676f93 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -31,6 +31,7 @@ "Demote yourself?" "%1$s (Pending)" "(Pending)" + "Admins automatically have moderator privileges" "Edit Moderators" "Admins" "Moderators" @@ -58,6 +59,7 @@ "Room name" "Security" "Share room" + "Room info" "Topic" "Updating room…" "Ban" diff --git a/features/roomdirectory/impl/src/main/res/values-it/translations.xml b/features/roomdirectory/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..04a9602d78 --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,5 @@ + + + "Caricamento fallito" + "Elenco delle stanze" + diff --git a/features/roomlist/impl/src/main/res/values-be/translations.xml b/features/roomlist/impl/src/main/res/values-be/translations.xml index 39cbd31bc6..8da1365931 100644 --- a/features/roomlist/impl/src/main/res/values-be/translations.xml +++ b/features/roomlist/impl/src/main/res/values-be/translations.xml @@ -11,6 +11,8 @@ "Дадаць чат у абранае можна ў наладах чата. На дадзены момант вы можаце прыбраць фільтры, каб убачыць іншыя вашыя чаты." "У вас пакуль няма абраных чатаў" + "Запрашэнні" + "У вас няма непрынятых запрашэнняў." "Нізкі прыярытэт" "Вы можаце прыбраць фільтры, каб убачыць іншыя вашыя чаты." "У вас няма чатаў для гэтай катэгорыі" diff --git a/features/roomlist/impl/src/main/res/values-cs/translations.xml b/features/roomlist/impl/src/main/res/values-cs/translations.xml index 417e64863b..3a91eb04dc 100644 --- a/features/roomlist/impl/src/main/res/values-cs/translations.xml +++ b/features/roomlist/impl/src/main/res/values-cs/translations.xml @@ -11,6 +11,8 @@ "V nastavení chatu můžete přidat chat k oblíbeným. Prozatím můžete zrušit výběr filtrů, abyste viděli své další chaty" "Zatím nemáte oblíbené chaty" + "Pozvánky" + "Nemáte žádné nevyřízené pozvánky." "Nízká priorita" "Můžete zrušit výběr filtrů, abyste viděli své další chaty" "Nemáte chaty pro tento výběr" diff --git a/features/roomlist/impl/src/main/res/values-hu/translations.xml b/features/roomlist/impl/src/main/res/values-hu/translations.xml index 559a481246..e25c493b13 100644 --- a/features/roomlist/impl/src/main/res/values-hu/translations.xml +++ b/features/roomlist/impl/src/main/res/values-hu/translations.xml @@ -11,6 +11,8 @@ "A csevegési beállításokban csevegéseket adhat hozzá a kedvencekhez. Egyelőre törölheti a szűrőket a többi csevegés megtekintéséhez." "Még nincsenek kedvenc csevegései" + "Meghívások" + "Nincsenek függőben lévő meghívásai." "Alacsony prioritás" "Kikapcsolhatja a szűrőket a többi csevegés megtekintéséhez" "Ehhez a kiválasztáshoz nem tartoznak csevegések" diff --git a/features/roomlist/impl/src/main/res/values-in/translations.xml b/features/roomlist/impl/src/main/res/values-in/translations.xml index 74c90c1319..8f194e1e34 100644 --- a/features/roomlist/impl/src/main/res/values-in/translations.xml +++ b/features/roomlist/impl/src/main/res/values-in/translations.xml @@ -11,6 +11,8 @@ "Anda dapat menambahkan percakapan ke favorit Anda dalam pengaturan percakapan. Untuk sementara, Anda dapat membatalkan pilihan saringan untuk melihat percakapan Anda yang lain" "Anda belum memiliki percakapan favorit" + "Undangan" + "Anda tidak memiliki undangan yang tertunda." "Prioritas Rendah" "Anda dapat membatalkan pilihan saringan untuk melihat percakapan Anda yang lain" "Anda tidak memiliki percakapan untuk pemilihan ini" diff --git a/features/roomlist/impl/src/main/res/values-it/translations.xml b/features/roomlist/impl/src/main/res/values-it/translations.xml index cd1261f3b3..05e41594a2 100644 --- a/features/roomlist/impl/src/main/res/values-it/translations.xml +++ b/features/roomlist/impl/src/main/res/values-it/translations.xml @@ -24,6 +24,7 @@ Non hai messaggi non letti!" "Tutte le conversazioni" "Segna come letto" "Segna come non letto" + "Sfoglia tutte le stanze" "Sembra che tu stia usando un nuovo dispositivo. Verificati con un altro dispositivo per accedere ai tuoi messaggi cifrati." "Verifica che sei tu" diff --git a/features/roomlist/impl/src/main/res/values-ru/translations.xml b/features/roomlist/impl/src/main/res/values-ru/translations.xml index d18fc1fd03..68cb98436d 100644 --- a/features/roomlist/impl/src/main/res/values-ru/translations.xml +++ b/features/roomlist/impl/src/main/res/values-ru/translations.xml @@ -1,7 +1,10 @@ "В настоящее время резервная копия вашего чата не синхронизирована. Требуется подтвердить вашим ключом восстановления, чтобы сохранить доступ к резервной копии чата." - "Подтвердите ключ восстановления" + + "Введите " + "ключ восстановления" + "Это одноразовый процесс, спасибо, что подождали." "Настройка учетной записи." "Создайте новую беседу или комнату" @@ -11,6 +14,8 @@ "Добавить чат в избранное можно в настройках чата. На данный момент вы можете убрать фильтры, чтобы увидеть другие ваши чаты." "У вас пока нет избранных чатов" + "Приглашает" + "У вас нет отложенных приглашений." "Низкий приоритет" "Вы можете убрать фильтры, чтобы увидеть другие ваши чаты." "У вас нет чатов для этой подборки" diff --git a/features/roomlist/impl/src/main/res/values-sk/translations.xml b/features/roomlist/impl/src/main/res/values-sk/translations.xml index 08320d1c30..101df1792d 100644 --- a/features/roomlist/impl/src/main/res/values-sk/translations.xml +++ b/features/roomlist/impl/src/main/res/values-sk/translations.xml @@ -11,6 +11,8 @@ "Môžete pridať konverzáciu medzi obľúbené v nastaveniach konverzácie. Zatiaľ môžete zrušiť výber filtrov, aby ste videli ostatné konverzácie" "Zatiaľ nemáte obľúbené konverzácie" + "Pozvánky" + "Nemáte žiadne čakajúce pozvánky." "Nízka priorita" "Môžete zrušiť výber filtrov, aby ste videli svoje ostatné konverzácie" "Nemáte konverzácie pre tento výber" diff --git a/features/roomlist/impl/src/main/res/values/localazy.xml b/features/roomlist/impl/src/main/res/values/localazy.xml index 11466fdc56..deeead48c2 100644 --- a/features/roomlist/impl/src/main/res/values/localazy.xml +++ b/features/roomlist/impl/src/main/res/values/localazy.xml @@ -11,6 +11,8 @@ "You can add a chat to your favourites in the chat settings. For now, you can deselect filters in order to see your other chats" "You don’t have favourite chats yet" + "Invites" + "You don\'t have any pending invites." "Low Priority" "You can deselect filters in order to see your other chats" "You don’t have chats for this selection" diff --git a/features/securebackup/impl/src/main/res/values-be/translations.xml b/features/securebackup/impl/src/main/res/values-be/translations.xml index 63a2a9aae8..2a3913043c 100644 --- a/features/securebackup/impl/src/main/res/values-be/translations.xml +++ b/features/securebackup/impl/src/main/res/values-be/translations.xml @@ -21,13 +21,15 @@ "Пераканайцеся, што вы можаце захаваць ключ аднаўлення ў бяспечным месцы" "Ключ аднаўлення зменены" "Змяніць ключ аднаўлення?" - "Увядзіце ключ аднаўлення, каб пацвердзіць доступ да рэзервовай копіі чата." + "Стварыць новы ключ аднаўлення" + "Пераканайцеся, што ніхто не бачыць гэты экран!" "Паўтарыце спробу, каб пацвердзіць доступ да рэзервовай копіі чата." "Няправільны ключ аднаўлення" - "Увядзіце код з 48 сімвалаў." + "Калі ў вас ёсць ключ аднаўлення або парольная фраза/ключ, гэта таксама будзе працаваць." + "Ключ аднаўлення або код доступу" "Увесці…" "Ключ аднаўлення пацверджаны" - "Пацвердзіце ключ аднаўлення" + "Увядзіце ключ аднаўлення або код доступу" "Ключ аднаўлення скапіраваны" "Стварэнне…" "Захаваць ключ аднаўлення" diff --git a/features/securebackup/impl/src/main/res/values-cs/translations.xml b/features/securebackup/impl/src/main/res/values-cs/translations.xml index a34d77aeee..cf0302d684 100644 --- a/features/securebackup/impl/src/main/res/values-cs/translations.xml +++ b/features/securebackup/impl/src/main/res/values-cs/translations.xml @@ -21,10 +21,12 @@ "Ujistěte se, že můžete klíč pro obnovení uložit někde v bezpečí" "Klíč pro obnovení byl změněn" "Změnit klíč pro obnovení?" - "Zadejte klíč pro obnovení a potvrďte přístup k záloze chatu." + "Vytvořit nový klíč pro obnovení" + "Ujistěte se, že tuto obrazovku nikdo nevidí!" "Zkuste prosím znovu potvrdit přístup k záloze chatu." "Nesprávný klíč pro obnovení" - "Zadejte kód o délce 48 znaků." + "Pokud máte frázi pro obnovení nebo tajnou přístupovou frázi/klíč, bude to fungovat také." + "Klíč pro obnovení nebo přístupový kód" "Zadejte…" "Klíč pro obnovení potvrzen" "Potvrďte klíč pro obnovení" diff --git a/features/securebackup/impl/src/main/res/values-de/translations.xml b/features/securebackup/impl/src/main/res/values-de/translations.xml index 5ec8f99e8c..afb1c764d1 100644 --- a/features/securebackup/impl/src/main/res/values-de/translations.xml +++ b/features/securebackup/impl/src/main/res/values-de/translations.xml @@ -21,13 +21,18 @@ "Stelle sicher, dass du deinen Wiederherstellungsschlüssel an einem sicheren Ort aufbewahren kannst" "Wiederherstellungsschlüssel geändert" "Wiederherstellungsschlüssel ändern?" - "Gib deinen Wiederherstellungsschlüssel ein, um den Zugriff auf dein Chat-Backup zu bestätigen." + + "Neuen " + "Wiederherstellungsschlüssel" + " erstellen" + + "Sorge dafür, dass niemand diesen Bildschirm sehen kann!" "Bitte versuche es noch einmal, um den Zugriff auf dein Chat-Backup zu bestätigen." "Falscher Wiederherstellungsschlüssel" - "Gib den 48-stelligen Code ein." + "Dies funktioniert auch mit einer Wiederherstellungspassphrase oder einer geheime Passphrase/einem geheimen Schlüssel." "Eingeben…" "Wiederherstellungsschlüssel bestätigt" - "Wiederherstellungsschlüssel bestätigen." + "Wiederherstellungsschlüssel oder Passcode bestätigen" "Wiederherstellungsschlüssel kopiert" "Generieren…" "Wiederherstellungsschlüssel speichern" diff --git a/features/securebackup/impl/src/main/res/values-hu/translations.xml b/features/securebackup/impl/src/main/res/values-hu/translations.xml index 0eff260322..3588eddde0 100644 --- a/features/securebackup/impl/src/main/res/values-hu/translations.xml +++ b/features/securebackup/impl/src/main/res/values-hu/translations.xml @@ -21,13 +21,15 @@ "Gondoskodjon arról, hogy biztonságos helyen tárolja a helyreállítási kulcsát" "Helyreállítási kulcs lecserélve" "Módosítja a helyreállítási kulcsot?" - "Adja meg a helyreállítási kulcsát, hogy megerősítse a csevegések biztonsági mentéséhez való hozzáférését." + "Új helyreállítási kulcs létrehozása" + "Győződjön meg arról, hogy senki sem látja ezt a képernyőt!" "Próbálja meg újra megerősíteni a csevegés biztonsági mentéséhez való hozzáférését." "Helytelen helyreállítási kulcs" - "Adja meg a 48 karakteres kódot." + "Ha van helyreállítási kulcsa vagy titkos jelmondata/kulcsa, akkor ez is fog működni." + "Helyreállítási kulcs vagy jelkód" "Megadás…" "Helyreállítási kulcs megerősítve" - "Erősítse meg a helyreállítási kulcsát" + "Adja meg a helyreállítási kulcsát vagy a jelkódját" "Helyreállítási kulcs másolva" "Előállítás…" "Helyreállítási kulcs mentése" diff --git a/features/securebackup/impl/src/main/res/values-in/translations.xml b/features/securebackup/impl/src/main/res/values-in/translations.xml index c7712832d2..e053f513e4 100644 --- a/features/securebackup/impl/src/main/res/values-in/translations.xml +++ b/features/securebackup/impl/src/main/res/values-in/translations.xml @@ -21,13 +21,15 @@ "Pastikan Anda dapat menyimpan kunci pemulihan Anda di tempat yang aman" "Kunci pemulihan diganti" "Ubah kunci pemulihan?" - "Masukkan kunci pemulihan Anda untuk mengonfirmasi akses ke cadangan percakapan Anda." + "Buat kunci pemulihan baru" + "Pastikan tidak ada yang bisa melihat layar ini!" "Silakan coba lagi untuk mengonfirmasi akses ke cadangan percakapan Anda." "Kunci pemulihan salah" - "Masukkan kode 48 karakter." + "Jika Anda memiliki frasa sandi pemulihan atau frasa/kunci sandi rahasia, ini juga dapat digunakan." + "Kunci pemulihan atau kode sandi" "Masukkan…" "Kunci pemulihan dikonfirmasi" - "Konfirmasi kunci pemulihan Anda" + "Konfirmasi kunci pemulihan atau kode sandi Anda" "Kunci pemulihan disalin" "Membuat…" "Simpan kunci pemulihan" diff --git a/features/securebackup/impl/src/main/res/values-ru/translations.xml b/features/securebackup/impl/src/main/res/values-ru/translations.xml index fd2bb28208..8df45698ff 100644 --- a/features/securebackup/impl/src/main/res/values-ru/translations.xml +++ b/features/securebackup/impl/src/main/res/values-ru/translations.xml @@ -4,8 +4,14 @@ "Включить резервное копирование" "Резервное копирование гарантирует, что вы не потеряете историю сообщений. %1$s." "Резервное копирование" - "Изменить ключ восстановления" - "Подтвердить ключ восстановления" + + "Изменить " + "ключ восстановления" + + + "Ввести " + "ключ восстановления" + "Резервная копия чата в настоящее время не синхронизирована." "Настроить восстановление" "Получите доступ к зашифрованным сообщениям, если вы потеряете все свои устройства или выйдете из системы %1$s отовсюду." @@ -17,27 +23,62 @@ "Потерять доступ к зашифрованным сообщениям, если вы вышли из %1$s любой точки мира" "Вы действительно хотите отключить резервное копирование?" "Получите новый ключ восстановления, если вы потеряли существующий. После смены ключа восстановления старый ключ больше не будет работать." - "Создать новый ключ восстановления" + + "Создать новый " + "ключ восстановления" + "Убедитесь, что вы можете хранить ключ восстановления в безопасном месте" - "Ключ восстановления изменен" + + "Ключ восстановления" + " изменен" + "Изменить ключ восстановления?" - "Введите ключ восстановления, чтобы подтвердить доступ к резервной копии чата." + + "Создать новый " + "ключ восстановления" + + "Убедитесь, что никто не видит этот экран!" "Пожалуйста, попробуйте еще раз, чтобы подтвердить доступ к резервной копии чата." - "Неверный ключ восстановления" - "Введите 48 значный код." + + "Неверный " + "ключ восстановления" + + "Если у вас есть пароль для восстановления или секретная пароль/ключ, это тоже сработает." + + "Ключ восстановления" + " или пароль" + "Вход…" - "Ключ восстановления подтвержден" - "Подтвердите ключ восстановления" - "Ключ восстановления скопирован" + + "Ключ восстановления" + " подтвержден" + + + "Подтвердите " + "ключ восстановления" + + + "Ключ восстановления" + " скопирован" + "Генерация…" - "Сохранить ключ восстановления" + + "Сохранить " + "ключ восстановления" + "Запишите ключ восстановления в безопасном месте или сохраните его в менеджере паролей." "Нажмите, чтобы скопировать ключ восстановления" - "Сохраните ключ восстановления" + + "Сохраните " + "ключ восстановления" + "После этого шага вы не сможете получить доступ к новому ключу восстановления." "Вы сохранили ключ восстановления?" "Резервная копия чата защищена ключом восстановления. Если после настройки вам понадобится новый ключ восстановления, вы можете создать его заново, выбрав «Изменить ключ восстановления»." - "Сгенерируйте свой ключ восстановления" + + "Создайте " + "ключ восстановления" + "Убедитесь, что вы можете хранить ключ восстановления в безопасном месте" "Настройка восстановления выполнена успешно" "Настроить восстановление" diff --git a/features/securebackup/impl/src/main/res/values-sk/translations.xml b/features/securebackup/impl/src/main/res/values-sk/translations.xml index ffc7e78c87..a2347cf6cd 100644 --- a/features/securebackup/impl/src/main/res/values-sk/translations.xml +++ b/features/securebackup/impl/src/main/res/values-sk/translations.xml @@ -21,13 +21,15 @@ "Uistite sa, že kľúč na obnovenie môžete uložiť niekde v bezpečí" "Kľúč na obnovenie bol zmenený" "Zmeniť kľúč na obnovenie?" - "Zadajte kľúč na obnovenie a potvrďte prístup k zálohe konverzácie." + "Vytvoriť nový kľúč na obnovenie" + "Uistite sa, že túto obrazovku nikto nevidí!" "Skúste prosím znova potvrdiť prístup k vašej zálohe konverzácie." "Nesprávny kľúč na obnovenie" - "Zadajte 48-znakový kód." + "Ak máte frázu na obnovenie alebo tajné heslo/kľúč, bude to tiež fungovať." + "Kľúč na obnovenie alebo prístupový kód" "Zadať…" "Kľúč na obnovu potvrdený" - "Potvrďte kľúč na obnovenie" + "Zadajte kľúč na obnovenie alebo prístupový kód" "Skopírovaný kľúč na obnovenie" "Generovanie…" "Uložiť kľúč na obnovenie" diff --git a/features/securebackup/impl/src/main/res/values/localazy.xml b/features/securebackup/impl/src/main/res/values/localazy.xml index f05725a075..5b87567f7d 100644 --- a/features/securebackup/impl/src/main/res/values/localazy.xml +++ b/features/securebackup/impl/src/main/res/values/localazy.xml @@ -21,13 +21,15 @@ "Make sure you can store your recovery key somewhere safe" "Recovery key changed" "Change recovery key?" - "Enter your recovery key to confirm access to your chat backup." + "Create new recovery key" + "Make sure nobody can see this screen!" "Please try again to confirm access to your chat backup." "Incorrect recovery key" - "Enter the 48 character code." + "If you have a recovery passphrase or secret passphrase/key, this will work too." + "Recovery key or passcode" "Enter…" "Recovery key confirmed" - "Enter your recovery key" + "Enter your recovery key or passcode" "Copied recovery key" "Generating…" "Save recovery key" diff --git a/features/verifysession/impl/src/main/res/values-be/translations.xml b/features/verifysession/impl/src/main/res/values-be/translations.xml index 57786fec8d..8b2c5e920d 100644 --- a/features/verifysession/impl/src/main/res/values-be/translations.xml +++ b/features/verifysession/impl/src/main/res/values-be/translations.xml @@ -1,5 +1,12 @@ + "Стварыць новы ключ аднаўлення" + "Пацвердзіце гэтую прыладу, каб наладзіць бяспечны абмен паведамленнямі." + "Пацвердзіце, што гэта вы" + "Цяпер вы можаце бяспечна чытаць і адпраўляць паведамленні, і ўсе, з кім вы маеце зносіны ў чаце, таксама могуць давяраць гэтай прыладзе." + "Прылада праверана" + "Выкарыстоўвайце іншую прыладу" + "Чаканне на іншай прыладзе…" "Здаецца, нешта не так. Альбо час чакання запыту скончыўся, альбо запыт быў адхілены." "Пераканайцеся, што прыведзеныя ніжэй эмодзі супадаюць з эмодзі, паказанымі ў вашым іншым сеансе." "Параўнайце эмодзі" diff --git a/features/verifysession/impl/src/main/res/values-cs/translations.xml b/features/verifysession/impl/src/main/res/values-cs/translations.xml index 3dd49ce9f2..fadcba869c 100644 --- a/features/verifysession/impl/src/main/res/values-cs/translations.xml +++ b/features/verifysession/impl/src/main/res/values-cs/translations.xml @@ -1,5 +1,12 @@ + "Vytvoření nového klíče pro obnovení" + "Ověřte toto zařízení a nastavte zabezpečené zasílání zpráv." + "Potvrďte, že jste to vy" + "Nyní můžete bezpečně číst nebo odesílat zprávy, a kdokoli, s kým chatujete, může tomuto zařízení důvěřovat." + "Zařízení ověřeno" + "Použít jiné zařízení" + "Čekání na jiném zařízení…" "Něco není v pořádku. Buď vypršel časový limit požadavku, nebo byl požadavek zamítnut." "Zkontrolujte, zda se níže uvedené emotikony shodují s emotikony zobrazenými na jiné relaci." "Porovnání emotikonů" diff --git a/features/verifysession/impl/src/main/res/values-de/translations.xml b/features/verifysession/impl/src/main/res/values-de/translations.xml index dd308b215d..baad607403 100644 --- a/features/verifysession/impl/src/main/res/values-de/translations.xml +++ b/features/verifysession/impl/src/main/res/values-de/translations.xml @@ -1,5 +1,12 @@ + "Erstelle einen neuen Wiederherstellungsschlüssel" + "Verifiziere dieses Gerät, um sicheres Messaging einzurichten." + "Bestätige, dass du es bist" + "Du kannst nun verschlüsselte Nachrichten lesen oder versenden." + "Gerät verifiziert" + "Ein anderes Gerät verwenden" + "Bitte warten bis das andere Gerät bereit ist." "Etwas scheint nicht zu stimmen. Entweder ist das Zeitlimit für die Anfrage abgelaufen oder die Anfrage wurde abgelehnt." "Vergewissere dich dass die folgenden Emojis mit denen in deiner anderen Session übereinstimmen." "Emojis vergleichen" diff --git a/features/verifysession/impl/src/main/res/values-fr/translations.xml b/features/verifysession/impl/src/main/res/values-fr/translations.xml index 7b57eba9a6..a37a51f8c5 100644 --- a/features/verifysession/impl/src/main/res/values-fr/translations.xml +++ b/features/verifysession/impl/src/main/res/values-fr/translations.xml @@ -1,5 +1,11 @@ + "Vérifier cette session pour configurer votre messagerie sécurisée." + "Confirmez votre identité" + "Vous pouvez désormais lire ou envoyer des messages en toute sécurité, et toute personne avec qui vous discutez peut également faire confiance à cette session." + "Session vérifiée" + "Utiliser une autre session" + "En attente d’une autre session…" "Quelque chose ne va pas. Soit la demande a expiré, soit elle a été refusée." "Confirmez que les emojis ci-dessous correspondent à ceux affichés sur votre autre session." "Comparez les émojis" diff --git a/features/verifysession/impl/src/main/res/values-hu/translations.xml b/features/verifysession/impl/src/main/res/values-hu/translations.xml index aac80254ac..c31adb9736 100644 --- a/features/verifysession/impl/src/main/res/values-hu/translations.xml +++ b/features/verifysession/impl/src/main/res/values-hu/translations.xml @@ -1,5 +1,12 @@ + "Új helyreállítási kulcs létrehozása" + "A biztonságos üzenetkezelés beállításához ellenőrizze ezt az eszközt." + "Erősítse meg, hogy Ön az" + "Mostantól biztonságosan olvashat vagy küldhet üzeneteket, és bármelyik csevegőpartnere megbízhat ebben az eszközben." + "Eszköz ellenőrizve" + "Másik eszköz használata" + "Várakozás a másik eszközre…" "Valami hibásnak tűnik. A kérés vagy időtúllépésre futott, vagy elutasították." "Erősítse meg, hogy a lenti emodzsik egyeznek-e a másik munkamenetben megjelenítettekkel." "Emodzsik összehasonlítása" diff --git a/features/verifysession/impl/src/main/res/values-in/translations.xml b/features/verifysession/impl/src/main/res/values-in/translations.xml index f498f7a52b..3a3bec959e 100644 --- a/features/verifysession/impl/src/main/res/values-in/translations.xml +++ b/features/verifysession/impl/src/main/res/values-in/translations.xml @@ -1,5 +1,12 @@ + "Buat kunci pemulihan baru" + "Verifikasi perangkat ini untuk menyiapkan perpesanan aman." + "Konfirmasi bahwa ini Anda" + "Sekarang Anda dapat membaca atau mengirim pesan dengan aman, dan siapa pun yang mengobrol dengan Anda juga dapat mempercayai perangkat ini." + "Perangkat terverifikasi" + "Gunakan perangkat lain" + "Menunggu di perangkat lain…" "Sepertinya ada yang tidak beres. Entah permintaan sudah habis masa berlakunya atau permintaan ditolak." "Konfirmasikan bahwa emoji di bawah ini sesuai dengan yang ditampilkan pada sesi Anda yang lain." "Bandingkan emoji" diff --git a/features/verifysession/impl/src/main/res/values-it/translations.xml b/features/verifysession/impl/src/main/res/values-it/translations.xml index 6fb227308e..a3b300793d 100644 --- a/features/verifysession/impl/src/main/res/values-it/translations.xml +++ b/features/verifysession/impl/src/main/res/values-it/translations.xml @@ -1,5 +1,11 @@ + "Verifica questo dispositivo per segnare i tuoi messaggi come sicuri." + "Conferma la tua identità" + "Ora puoi leggere o inviare messaggi in tutta sicurezza e anche chi chatta con te può fidarsi di questo dispositivo." + "Dispositivo verificato" + "Usa un altro dispositivo" + "In attesa sull\'altro dispositivo…" "C\'è qualcosa che non va. La richiesta è scaduta o è stata rifiutata." "Verifica che gli emoji sottostanti corrispondano a quelli mostrati nell\'altra sessione." "Confronta le emoji" diff --git a/features/verifysession/impl/src/main/res/values-ru/translations.xml b/features/verifysession/impl/src/main/res/values-ru/translations.xml index 69311e39e7..c89077d172 100644 --- a/features/verifysession/impl/src/main/res/values-ru/translations.xml +++ b/features/verifysession/impl/src/main/res/values-ru/translations.xml @@ -1,12 +1,25 @@ + + "Создайте новый " + "ключ восстановления" + + "Подтвердите это устройство, чтобы настроить безопасный обмен сообщениями." + "Подтвердите, что это вы" + "Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству." + "Устройство проверено" + "Используйте другое устройство" + "Ожидание на другом устройстве…" "Похоже, что-то не так. Время ожидания запроса либо истекло, либо запрос был отклонен." "Убедитесь, что приведенные ниже емоджи совпадают с емоджи показанными во время другого сеанса." "Сравните емодзи" "Убедитесь, что приведенные ниже числа совпадают с цифрами, показанными в другом сеансе." "Сравните числа" "Ваш новый сеанс подтвержден. У него есть доступ к вашим зашифрованным сообщениям, и другие пользователи увидят его как доверенное." - "Введите ключ восстановления" + + "Введите " + "ключ восстановления" + "Чтобы получить доступ к зашифрованной истории сообщений, докажите, что это вы." "Открыть существующий сеанс" "Повторить проверку" diff --git a/features/verifysession/impl/src/main/res/values-sk/translations.xml b/features/verifysession/impl/src/main/res/values-sk/translations.xml index 6452b85fcb..b3089b7b30 100644 --- a/features/verifysession/impl/src/main/res/values-sk/translations.xml +++ b/features/verifysession/impl/src/main/res/values-sk/translations.xml @@ -1,5 +1,12 @@ + "Vytvoriť nový kľúč na obnovenie" + "Ak chcete nastaviť zabezpečené správy, overte toto zariadenie." + "Potvrďte, že ste to vy" + "Teraz môžete bezpečne čítať alebo odosielať správy a tomuto zariadeniu môže dôverovať aj ktokoľvek, s kým konverzujete." + "Zariadenie overené" + "Použiť iné zariadenie" + "Čaká sa na druhom zariadení…" "Zdá sa, že niečo nie je v poriadku. Časový limit žiadosti vypršal alebo bola žiadosť zamietnutá." "Skontrolujte, či sa emotikony uvedené nižšie zhodujú s emotikonmi zobrazenými vo vašej druhej relácii." "Porovnajte emotikony" diff --git a/features/verifysession/impl/src/main/res/values-uk/translations.xml b/features/verifysession/impl/src/main/res/values-uk/translations.xml index beed211c15..9be10a2bb0 100644 --- a/features/verifysession/impl/src/main/res/values-uk/translations.xml +++ b/features/verifysession/impl/src/main/res/values-uk/translations.xml @@ -1,5 +1,9 @@ + "Перевірте цей пристрій, щоб налаштувати безпечний обмін повідомленнями." + "Підтвердіть, що це ви" + "Тепер ви можете безпечно читати або надсилати повідомлення, і кожен, з ким ви спілкуєтесь, також може довіряти цьому пристрою." + "Пристрій перевірено" "Щось не так. Або час очікування запиту минув, або в запиті було відмовлено." "Переконайтеся, що емодзі нижче збігаються з тими, що відображаються під час іншого сеансу." "Порівняти емодзі" diff --git a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml index ed85823769..d73ea8e2c4 100644 --- a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,5 +1,8 @@ + "裝置已認證" + "使用另一個裝置" + "正在等待其他裝置……" "似乎出了一點問題。有可能是因為等候逾時,或是請求被拒絕。" "確認顯示在其他工作階段上的表情符號是否和下方的相同。" "比對表情符號" diff --git a/features/verifysession/impl/src/main/res/values/localazy.xml b/features/verifysession/impl/src/main/res/values/localazy.xml index 9fea2d98df..41cd2f8dbc 100644 --- a/features/verifysession/impl/src/main/res/values/localazy.xml +++ b/features/verifysession/impl/src/main/res/values/localazy.xml @@ -1,5 +1,6 @@ + "Create a new recovery key" "Verify this device to set up secure messaging." "Confirm that it\'s you" "Now you can read or send messages securely, and anyone you chat with can also trust this device." 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 ffbafa87f2..58721077e5 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -202,7 +202,7 @@ "Сервер не падтрымліваецца" "URL-адрас сервера" "Налады" - "Абагуленыя геаданыя" + "Абагуліць месцазнаходжанне" "Выхад" "Пачатак чата…" "Стыкер" @@ -249,12 +249,13 @@ "Гэй, пагавары са мной у %1$s: %2$s" "%1$s Android" "Паведаміць аб памылцы з дапамогай Rageshake" - "Пацвердзіце гэтую прыладу, каб наладзіць бяспечны абмен паведамленнямі." - "Пацвердзіце, што гэта вы" - "Цяпер вы можаце бяспечна чытаць і адпраўляць паведамленні, і ўсе, з кім вы маеце зносіны ў чаце, таксама могуць давяраць гэтай прыладзе." - "Прылада праверана" - "Выкарыстоўвайце іншую прыладу" - "Чаканне на іншай прыладзе…" + "Адкрыйце Element на настольнай прыладзе" + "Увайдзіце ў свой уліковы запіс яшчэ раз" + "Калі будзе прапанавана пацвердзіць вашу прыладу, выберыце %1$s" + "“Скінуць усе”" + "Выконвайце інструкцыі, каб стварыць новы ключ аднаўлення" + "Захавайце новы ключ аднаўлення ў ме́неджэры пароляў або ў зашыфраванай нататке" + "Скіньце шыфраванне для вашага ўліковага запісу з дапамогай іншай прылады" "Не ўдалося выбраць носьбіт, паўтарыце спробу." "Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз." "Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз." @@ -263,6 +264,7 @@ "Заблакіраваць" "Заблакіраваныя карыстальнікі не змогуць адпраўляць вам паведамленні, і ўсе іх паведамленні будуць схаваны. Вы можаце разблакіраваць іх у любы час." "Заблакіраваць карыстальніка" + "Профіль" "Разблакіраваць" "Вы зноў зможаце ўбачыць усе паведамленні." "Разблакіраваць карыстальніка" 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 9dde757dc1..9f5942f5f8 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -179,7 +179,7 @@ "Zásady ochrany osobních údajů" "Reakce" "Reakce" - "Klíč pro obnovu" + "Klíč pro obnovení" "Obnovování…" "Odpověď na %1$s" "Nahlásit chybu" @@ -249,12 +249,13 @@ "Ahoj, ozvi se mi na %1$s: %2$s" "%1$s Android" "Zatřeste zařízením pro nahlášení chyby" - "Ověřte toto zařízení a nastavte zabezpečené zasílání zpráv." - "Potvrďte, že jste to vy" - "Nyní můžete bezpečně číst nebo odesílat zprávy, a kdokoli, s kým chatujete, může tomuto zařízení důvěřovat." - "Zařízení ověřeno" - "Použít jiné zařízení" - "Čekání na jiném zařízení…" + "Otevřít Element na stolním počítači" + "Znovu se přihlaste ke svému účtu" + "Když budete vyzváni k ověření vašeho zařízení, vyberte %1$s" + "\"Resetovat vše\"" + "Postupujte podle pokynů k vytvoření nového obnovovacího klíče" + "Uložte nový klíč pro obnovení do správce hesel nebo do zašifrované poznámky" + "Obnovte šifrování účtu pomocí jiného zařízení" "Výběr média se nezdařil, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." @@ -263,6 +264,7 @@ "Zablokovat" "Blokovaní uživatelé vám nebudou moci posílat zprávy a všechny jejich zprávy budou skryty. Můžete je kdykoli odblokovat." "Zablokovat uživatele" + "Profil" "Odblokovat" "Znovu uvidíte všechny zprávy od nich." "Odblokovat uživatele" 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 8a3e035833..719dcab093 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -175,7 +175,7 @@ "Datenschutz­erklärung" "Reaktion" "Reaktionen" - "Wiederherstellungsschlüssel" + "Wiederherstellungsschlüssel oder Passcode" "Wird erneuert…" "%1$s antworten" "Einen Fehler melden" @@ -245,12 +245,26 @@ "Hey, sprich mit mir auf %1$s: %2$s" "%1$s Android" "Schüttel heftig zum Melden von Fehlern" - "Verifiziere dieses Gerät, um sicheres Messaging einzurichten." - "Bestätige, dass du es bist" - "Du kannst nun verschlüsselte Nachrichten lesen oder versenden." - "Gerät verifiziert" - "Ein anderes Gerät verwenden" - "Bitte warten bis das andere Gerät bereit ist." + + "Öffne " + "Element" + " auf einem " + "Desktop-Gerät" + + "Melde dich erneut bei deinem Konto an" + "Wenn du aufgefordert wirst dein Gerät zu verifizieren, wähle \"%1$s\"." + "Alles zurücksetzen" + "Folge den Anweisungen, um einen neuen Wiederherstellungsschlüssel zu erstellen" + + "Speichere deinen neuen " + "Wiederherstellungsschlüssel" + " in einem Passwortmanager oder einer verschlüsselten Notiz" + + + "Erstelle einen neuen " + "Wiederherstellungsschlüssel" + " mit einem anderen Gerät" + "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Das Hochladen der Medien ist fehlgeschlagen. Bitte versuche es erneut." @@ -259,6 +273,7 @@ "Blockieren" "Blockierte Benutzer können Dir keine Nachrichten senden und alle ihre alten Nachrichten werden ausgeblendet. Die Blockierung kann jederzeit aufgehoben werden." "Benutzer blockieren" + "Profil" "Blockierung aufheben" "Der Nutzer kann dir wieder Nachrichten senden & alle Nachrichten des Nutzers werden wieder angezeigt." "Blockierung aufheben" 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 e3b6e3d0d9..1b9befbc45 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -245,12 +245,6 @@ "Salut, parle-moi sur %1$s : %2$s" "%1$s Android" "Rageshake pour signaler un problème" - "Vérifier cette session pour configurer votre messagerie sécurisée." - "Confirmez votre identité" - "Vous pouvez désormais lire ou envoyer des messages en toute sécurité, et toute personne avec qui vous discutez peut également faire confiance à cette session." - "Session vérifiée" - "Utiliser une autre session" - "En attente d’une autre session…" "Échec de la sélection du média, veuillez réessayer." "Échec du traitement des médias à télécharger, veuillez réessayer." "Échec du téléchargement du média, veuillez réessayer." 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 43568cafb6..6d492b0889 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -245,12 +245,13 @@ "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" "Az eszköz rázása a hibajelentéshez" - "A biztonságos üzenetkezelés beállításához ellenőrizze ezt az eszközt." - "Erősítse meg, hogy Ön az" - "Mostantól biztonságosan olvashat vagy küldhet üzeneteket, és bármelyik csevegőpartnere megbízhat ebben az eszközben." - "Eszköz ellenőrizve" - "Másik eszköz használata" - "Várakozás a másik eszközre…" + "Nyissa meg az Elementet egy asztali eszközön" + "Jelentkezzen be újra a fiókjába" + "Amikor az eszköz ellenőrzését kéri, válassza ezt a lehetőséget: %1$s" + "„Minden visszaállítása”" + "Kövesse az utasításokat egy új helyreállítási kulcs létrehozásához" + "Mentse az új helyreállítási kulcsot egy jelszókezelőbe vagy egy titkosított jegyzetbe." + "A fiók titkosításának visszaállítása egy másik eszköz használatával" "Nem sikerült kiválasztani a médiát, próbálja újra." "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." "Nem sikerült a média feltöltése, próbálja újra." @@ -259,6 +260,7 @@ "Letiltás" "A letiltott felhasználók nem fognak tudni üzeneteket küldeni, és az összes üzenetük rejtve lesz. Bármikor feloldhatja a letiltásukat." "Felhasználó letiltása" + "Profil" "Letiltás feloldása" "Újra láthatja az összes üzenetét." "Felhasználó kitiltásának feloldása" 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 fc237cac27..a6a4338f79 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -194,7 +194,7 @@ "Server tidak didukung" "URL Server" "Pengaturan" - "Membagikan lokasi" + "Lokasi terbagi" "Mengeluarkan dari akun" "Memulai obrolan…" "Stiker" @@ -241,12 +241,13 @@ "Hai, bicaralah dengan saya di %1$s: %2$s" "%1$s Android" "Rageshake untuk melaporkan kutu" - "Verifikasi perangkat ini untuk menyiapkan perpesanan aman." - "Konfirmasi bahwa ini Anda" - "Sekarang Anda dapat membaca atau mengirim pesan dengan aman, dan siapa pun yang mengobrol dengan Anda juga dapat mempercayai perangkat ini." - "Perangkat terverifikasi" - "Gunakan perangkat lain" - "Menunggu di perangkat lain…" + "Buka Element di perangkat desktop" + "Masuk ke akun Anda lagi" + "Saat diminta untuk memverifikasi perangkat Anda, pilih %1$s" + "“Atur ulang semua”" + "Ikuti petunjuk untuk membuat kunci pemulihan baru" + "Simpan kunci pemulihan baru Anda dalam pengelola kata sandi atau catatan terenkripsi" + "Atur ulang enkripsi untuk akun Anda menggunakan perangkat lain" "Gagal memilih media, silakan coba lagi." "Gagal memproses media untuk diunggah, silakan coba lagi." "Gagal mengunggah media, silakan coba lagi." @@ -255,6 +256,7 @@ "Blokir" "Pengguna yang diblokir tidak akan dapat mengirim Anda pesan dan semua pesan mereka akan disembunyikan. Anda dapat membuka blokirnya kapan saja." "Blokir pengguna" + "Profil" "Buka blokir" "Anda akan dapat melihat semua pesan dari mereka lagi." "Buka blokir pengguna" 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 e7a564596d..de73bdd8b6 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -179,7 +179,9 @@ "Политика конфиденциальности" "Реакция" "Реакции" - "Ключ восстановления" + + "Ключ восстановления" + "Обновление…" "Отвечает на %1$s" "Сообщить об ошибке" @@ -249,12 +251,17 @@ "Привет, поговори со мной по %1$s: %2$s" "%1$s Android" "Встряхните устройство, чтобы сообщить об ошибке" - "Подтвердите это устройство, чтобы настроить безопасный обмен сообщениями." - "Подтвердите, что это вы" - "Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству." - "Устройство проверено" - "Используйте другое устройство" - "Ожидание на другом устройстве…" + "Откройте Element на настольном устройстве" + "Войдите в свой аккаунт еще раз" + "Когда вас попросят подтвердить устройство, выберите %1$s" + "“Сбросить все”" + "Следуйте инструкциям, чтобы создать новый ключ восстановления" + + "Сохраните новый " + "ключ восстановления" + " в менеджере паролей или зашифрованной заметке" + + "Сбросьте шифрование вашей учетной записи с помощью другого устройства." "Не удалось выбрать носитель, попробуйте еще раз." "Не удалось обработать медиафайл для загрузки, попробуйте еще раз." "Не удалось загрузить медиафайлы, попробуйте еще раз." @@ -263,6 +270,7 @@ "Заблокировать" "Заблокированные пользователи не смогут отправлять вам сообщения, а все их сообщения будут скрыты. Вы можете разблокировать их в любое время." "Заблокировать пользователя" + "Профиль" "Разблокировать" "Вы снова сможете увидеть все сообщения." "Разблокировать пользователя" 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 f49201bda1..6ebbd7b1c6 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -248,12 +248,13 @@ "Ahoj, porozprávajte sa so mnou na %1$s: %2$s" "%1$s Android" "Zúrivo potriasť pre nahlásenie chyby" - "Ak chcete nastaviť zabezpečené správy, overte toto zariadenie." - "Potvrďte, že ste to vy" - "Teraz môžete bezpečne čítať alebo odosielať správy a tomuto zariadeniu môže dôverovať aj ktokoľvek, s kým konverzujete." - "Zariadenie overené" - "Použiť iné zariadenie" - "Čaká sa na druhom zariadení…" + "Otvoriť Element v stolnom počítači" + "Znova sa prihláste do svojho účtu" + "Keď sa zobrazí výzva na overenie vášho zariadenia, vyberte %1$s" + "\"Obnoviť všetko\"" + "Postupujte podľa pokynov na vytvorenie nového kľúča na obnovenie" + "Uložte si nový kľúč na obnovenie do správcu hesiel alebo do zašifrovanej poznámky" + "Obnovte šifrovanie vášho účtu pomocou iného zariadenia" "Nepodarilo sa vybrať médium, skúste to prosím znova." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa nahrať médiá, skúste to prosím znova." @@ -262,6 +263,7 @@ "Zablokovať" "Blokovaní používatelia vám nebudú môcť posielať správy a všetky ich správy budú skryté. Môžete ich kedykoľvek odblokovať." "Zablokovať používateľa" + "Profil" "Odblokovať" "Všetky správy od nich budete môcť opäť vidieť." "Odblokovať používateľa" 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 b7029fa178..a7b1f295f3 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -96,6 +96,7 @@ "Redigerar" "* %1$s %2$s" "Kryptering aktiverad" + "Ange din PIN-kod" "Fel" "Alla" "Fil" @@ -143,6 +144,7 @@ "Riktextredigerare" "Rumsnamn" "t.ex. ditt projektnamn" + "Skärmlås" "Sök efter någon" "Sökresultat" "Säkerhet" @@ -164,6 +166,7 @@ "Kan inte avkryptera" "Inbjudan kunde inte skickas till en eller flera användare." "Kunde inte skicka inbjudningar" + "Lås upp" "Avtysta" "Händelse som inte stöds" "Användarnamn" 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 6dafa61fc9..4082bfb6b0 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -249,10 +249,6 @@ "Привіт, пишіть мені за адресою %1$s: %2$s" "%1$s Android" "Повідомити про ваду за допомогою Rageshake" - "Перевірте цей пристрій, щоб налаштувати безпечний обмін повідомленнями." - "Підтвердіть, що це ви" - "Тепер ви можете безпечно читати або надсилати повідомлення, і кожен, з ким ви спілкуєтесь, також може довіряти цьому пристрою." - "Пристрій перевірено" "Не вдалося вибрати медіафайл, спробуйте ще раз." "Не вдалося обробити медіафайл для завантаження, спробуйте ще раз." "Не вдалося завантажити медіафайл, спробуйте ще раз." 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 872f21e82e..6f8df97530 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 @@ -227,9 +227,6 @@ "有些訊息尚未傳送" "嘿,來 %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 97ddec4b3b..c64ca34ba6 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -245,22 +245,22 @@ "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" - "Verify this device to set up secure messaging." - "Confirm that it\'s you" - "Now you can read or send messages securely, and anyone you chat with can also trust this device." - "Device verified" - "Use another device" - "Waiting on other device…" + "Open Element in a desktop device" + "Sign into your account again" + "When asked to verify your device, select %1$s" + "“Reset all”" + "Follow the instructions to create a new recovery key" + "Save your new recovery key in a password manager or encrypted note" + "Reset the encryption for your account using another device" "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." - "Failed loading" - "Room directory" "Failed processing media to upload, please try again." "Could not retrieve user details" "Block" "Blocked users won\'t be able to send you messages and all their messages will be hidden. You can unblock them anytime." "Block user" + "Profile" "Unblock" "You\'ll be able to see all messages from them again." "Unblock user" diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,de].png index 50c4629129..a5842d8949 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_10,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:728219bfadff0fd21612e1138d0fab253c48e5ed4519cfe3c1464f6f409822f5 -size 32534 +oid sha256:fe992d1369d3a248768364b60fffa62306bd66bb3e7a5ac41cc4ec62073da608 +size 37944 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,de].png index 8b1638689e..7ef01ff89b 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ffea61ef0170d2d7486d1df32d7fe30782ddee9ce4d38dbc83b95f275583825 -size 43423 +oid sha256:64aec3a65c1ba5e0dc924dacc9fbeb54168cb1175f8d928e450fb3d46b546cba +size 48495 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,de].png index 56bea9fdc6..d301549e63 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67418f15f47631ad73d2b5ed61e7fd782b56ee952d5cdd12e53950de58200349 -size 43973 +oid sha256:9576f73065257426d939cfff6a408cdd726852c93b924003315529fbe2d39697 +size 49068 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,de].png index 460071abb3..bdae6a804f 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_4,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70129fc1a4cf8dd1407de2f42fd7c72d57e09853d66dc1c19e32903844b9046b -size 44456 +oid sha256:285fa01b64cc273806fe4e730a5f567d2f3304f7e7f6ecbb57282195f8bdf576 +size 49623 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,de].png index 2f31bcf4d3..c42915ecd4 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_5,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbe8b24943045e3b3f042e16647dd6de60fe661ce14e012679ac7fbd2619ff49 -size 44094 +oid sha256:35657810c246fc3cce580e455ae95dd721064641e08ad358e80cbd48091055d4 +size 49203 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,de].png index 8dab166e88..45b6da3076 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_6,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddd2210781391c12dcb67f1e2ec02ad5396a02212a9fa598ab3138a355450237 -size 44671 +oid sha256:6aec759b1855e1af48229b717a6c405206b8bcbc8306ed01f07cd5294f1fc0e5 +size 49869 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,de].png index 17e78be76c..3f666c9660 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_7,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15baabcbe95da428dd360153397b90aca1acb263c079811b3890c8befd452e91 -size 45148 +oid sha256:0edc2123a648e859334a131c4f4a477e6e4843c0c83d052cf84b9e3843a534c6 +size 50393 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,de].png index 5d271f0dbc..e0563748bf 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_8,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d17f08528c1f5e1b567fb3ec9302dfe571df6da7a0752e17d5686fb0b729778 -size 45389 +oid sha256:08703feba620c75bb0b09e565f907cedce0cabfe713bd285e30c3bb3b1ecbf11 +size 50673 diff --git a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,de].png index 37dbf17158..5fe1177ab1 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.actionlist_SheetContent_null_SheetContent-Day-1_1_null_9,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:079286f3b6a64c5162e7b7284f95df08ce51ae90eef58cb2403880e640d24024 -size 32638 +oid sha256:62da25de040bf0365765d3bf379f20b03eadff507abc7a323906459873e4e135 +size 37848 diff --git a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,de].png deleted file mode 100644 index 4cb65cc05e..0000000000 --- a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,de].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fda9297ea4cb7d4155250e69449b639fd0ff3207593b396eb4d749de7b6a6610 -size 17815 diff --git a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,de].png index 4cb65cc05e..647a20dcbf 100644 --- a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fda9297ea4cb7d4155250e69449b639fd0ff3207593b396eb4d749de7b6a6610 -size 17815 +oid sha256:75e762734dc8987ebe94bbe1b0be26febc3250ed001b5c140beca45b1b50b3ef +size 19548 diff --git a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_1,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,de].png deleted file mode 100644 index c940b4c0a9..0000000000 --- a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,de].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:68127db252601c41e441936db57b2508961fa2ce2d686426b7d51af73e508d08 -size 24865 diff --git a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,de].png deleted file mode 100644 index 2349388fce..0000000000 --- a/screenshots/de/ui_T_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,de].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:483a7fbb9d53f7fe0102f690dc75693a311dc733b25865285a8ef222e23a52bf -size 16219 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,de].png deleted file mode 100644 index dfde20564b..0000000000 --- a/screenshots/de/ui_T_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,de].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78b94b76cae3c8fa8c1a6ffe3091d341c1091060704511533b1953a76a51b396 -size 45500 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,de].png index 5a0c83f920..f11863d7fd 100644 --- a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bbda51850ec23c8c42491514119c3a0d538a32741811117c07c719f4e965857 -size 74496 +oid sha256:7017409c08b0b6514662e196af52899964d402ae633b96349e41a48b2b1209b0 +size 78040 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,de].png index 8efef115f6..7671323196 100644 --- a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e800c4dad8273d778203549cb87938fa816a77dc95a6069308e004de98c451c8 -size 65231 +oid sha256:f4f2b3bacf7ab1dc5cc1bae40435657667c57d281d222361d0735c574dd69840 +size 68245 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,de].png index bec7bc96c4..2d98885ee5 100644 --- a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60f3ff7b535f42e340c6433fef396a2404f1a7a2f4ca5b8963e5d42db38a43b0 -size 63875 +oid sha256:f014ca9304e364df7abd6b02fbbe675d5c441c91d010401afb2d32459a2479bc +size 66888 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..f594c73c43 --- /dev/null +++ b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0cd4e3d402a0c63c544810d7cff3feb69e0d744d4025fab5d4f143ee5a1692c +size 51253 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..88cc7fee19 --- /dev/null +++ b/screenshots/de/ui_T_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:195dd966b8f49562ab3d4889fe69b15836eb40950bd3942d8361db5655d3eb2d +size 46561 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,de].png index 7e990491d5..0e49c40ccc 100644 --- a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2ca4270fd5b0f00d1216bfae06f2fccfa9f90496b0d85635553f440384ebb25 -size 40244 +oid sha256:23f2fc8ed789f22d63925e23ade89babed788e34fd6ff1c8dc08be3b66895833 +size 37976 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,de].png index c0b56ea96d..52bdb05f22 100644 --- a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewDark_null_PreferencesRootViewDark--1_1_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f8345064c114e7878e7013442fd05293ca1bd07ffbbdc1d0abe581853048eba -size 39866 +oid sha256:7773e3f5084a8d220874491b3a054dbf31fb009238f5449808e09b5116e4dd07 +size 37633 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,de].png index 37da265dde..f23ef4afd3 100644 --- a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3339478f9d1fe1b6358e791bf44090584d1148da812e6f6d70a3f7b19b0715f -size 42241 +oid sha256:b54582b2e22a65ba2821dc235170643d958926f95d38352309c116d3ee2f7eb1 +size 39765 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,de].png index 2eab0820ae..b45c5e1ddc 100644 --- a/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.preferences.impl.root_PreferencesRootViewLight_null_PreferencesRootViewLight--0_0_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fed8a895dd45102ad84a2376f94e04441d4506e82cb7ef76fc6163566482f3be -size 42184 +oid sha256:b365970794bf20e9b18885293fd9b200177790e197f988f2957ce66108ac3c53 +size 39713 diff --git a/screenshots/de/ui_T_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,de].png index c3f21c27af..e9304a0c6d 100644 --- a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94ef2559a5dc4ea875bfaa17a7d624a38f67531d2170bc0ac8d4ddf62a3e3408 -size 49340 +oid sha256:41a58b2117148c08802623a3349e2d90a75de1729f24188fe5ce3fbe4e8b28ff +size 49356 diff --git a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,de].png index 20f0ddea08..b187ebb903 100644 --- a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2ce6422668e29806424288ae87866514854113c7126356420fd43b096c4582a -size 50140 +oid sha256:d71540b3cf7c4bf556f3302df8491e4813a091ce2545e59b9d384f56f662a00e +size 50167 diff --git a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,de].png index 8e4d955cff..f91a8d5b0f 100644 --- a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a8b38d9d8c8900fd40db8f6de74a35cbdd7103f54b290a78d99b66ceda6cc21 -size 49238 +oid sha256:dbe7a2f33256517f91f6987c7337f4bc76fa411fbdec79b538ab16541383bf44 +size 49244 diff --git a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,de].png index 528c103c51..fff7896550 100644 --- a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ded9cc9e3ae1cdccd3131b332f5c30dd5b4a69317e0fcb989769f12629da147a -size 46428 +oid sha256:420ec56dfbe5948890ee7467b5803bf9ed10cb141143880e7ff1ee7f4f7d3db6 +size 46409 diff --git a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,de].png index f10530582c..282bd17123 100644 --- a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54bf9f8c0fcb9efed97939c95b3f533aad9e0adb5da3bd66e2543069dd021425 -size 46855 +oid sha256:1431c75b6105069c619b11f4f50206c0ff48732c5007234953654bbdd5ef147e +size 46847 diff --git a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,de].png index da24ac7b3b..fe1351eba4 100644 --- a/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cfc7b57fa6b9882a578380a6a634ddb8e360662df03088daff63dca5d91489e -size 56272 +oid sha256:9e86f66ffe933c56af20be8301c880950536f420fd99d6e89ffae6162352a86d +size 56256 diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-8_9_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-8_9_null,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-7_8_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-7_8_null,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,de].png deleted file mode 100644 index 553fb2a5ec..0000000000 --- a/screenshots/de/ui_T_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,de].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86a3af68fabec8f77e76b8832f20db7919b8ed6d5401b826abd3a2ed1adc0893 -size 34620 diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_1,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_1,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_1,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_3,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_3,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_3,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_4,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_4,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_4,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_0,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_1,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-12_13_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-12_13_null,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,de].png similarity index 100% rename from screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,de].png rename to screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,de].png diff --git a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,de].png index 72013a011f..db9841dce2 100644 --- a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71ea12d0197945317a28e01e8b5b4bb198df0a6905014cc92fa0ca89b06cd991 -size 94061 +oid sha256:ce710e354bfdf64f656d728995ed163bca87ba69f325f583d76d6160682647e9 +size 96407 diff --git a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,de].png index db9841dce2..6c30d2bd81 100644 --- a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce710e354bfdf64f656d728995ed163bca87ba69f325f583d76d6160682647e9 -size 96407 +oid sha256:9ecc13b764d283ac5818273f96dc11d655c8e27fd914c79a9c36ebf99bcdf338 +size 59999 diff --git a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,de].png deleted file mode 100644 index 6c30d2bd81..0000000000 --- a/screenshots/de/ui_T_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,de].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9ecc13b764d283ac5818273f96dc11d655c8e27fd914c79a9c36ebf99bcdf338 -size 59999 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,de].png index 0699bd5473..a710f79532 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef5aedf5673d9a2345089e6632460cb12434cc6901e0d1cb86155e5b6dfe5968 -size 47359 +oid sha256:38a94d2276af933d4c123a2a00728db01be7d6665311217ca7f0124612b60105 +size 58662 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,de].png index f1eabc0045..d89ff64056 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6cd0d7eb7c90cf8a72818c1179e0c296db3eaeec82bf869f86170813cb4b002 -size 58807 +oid sha256:8e7e7263a120db2e187828203198188a87dd507701dc42b281d1c3d017454f00 +size 70414 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,de].png index df01ad7a03..186132354d 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fba1838d134534f42f30e22a1c4ac1eb3f8cebbf73aa45dd142cc52c313f369 -size 57250 +oid sha256:9fe482de11f11ff7f0bc4050b7ff143497e85ae7c6d12ba73ddfae3c32249e38 +size 68729 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,de].png index 0e7f4d6b6c..e250398dc2 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6d85294b3b206b14878d038efe0a9d8cee63659113fbe47816531962a180b08 -size 47712 +oid sha256:15c41ad7fa311752c3fc7b6d6e27f4f80944c6430fb6f23c659b9d795ec0cca0 +size 56295 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_0,NEXUS_5,1.0,de].png index c290043657..8822a44d49 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dde7780e24eb315b8671ada81dfc93397a2c7f324198d66cd17046d12d1ac3e0 -size 29115 +oid sha256:4ee264d6715fe4d8e8c0ff5ba07afcd33409d131d27effa77e07bbdeef47e23b +size 31034 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_1,NEXUS_5,1.0,de].png index 041e38b060..4741997df4 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ca3b63c4d12ef8d6436586043a3fd3cf80203c0c5d37f30c7027a3b11046188 -size 26185 +oid sha256:f4001ba3ca38363142f096aa4b7a052a8b7641982994698b5dd3134d0a79b65f +size 27951 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,de].png index 98522d2806..a40adf773a 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f21aac270d15f5c09cddfdc7dbd797b64acf612b979f1e4a629bb0f960d512a -size 26997 +oid sha256:1b1de5b9084a41c3e97d21aeb24ae14f761410084c950946085f69510da0bbc4 +size 39496 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,de].png index dec734c242..6c35e48ccb 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e232511aef5dd5ba6485876fbda79396ab292ca73fa0e88f73818bb562a5f4d8 -size 25036 +oid sha256:dce9eb56dc9c07027064147cd73adea4625e997575d7c4afaa453ede7c7a383c +size 36921 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_2,NEXUS_5,1.0,de].png index d7e0800c88..793849862c 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bba005d1b6341246ac3ddc03e1dcd7abecf9f80fca5bbf12e1b88d8a8ff8352d -size 28283 +oid sha256:8a736d35d4a326dfa007026ab1fdabf4d0b65c28f0bf2267c707362b55add6d6 +size 29891 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_3,NEXUS_5,1.0,de].png index d7e0800c88..793849862c 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bba005d1b6341246ac3ddc03e1dcd7abecf9f80fca5bbf12e1b88d8a8ff8352d -size 28283 +oid sha256:8a736d35d4a326dfa007026ab1fdabf4d0b65c28f0bf2267c707362b55add6d6 +size 29891 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_4,NEXUS_5,1.0,de].png index 9edbce8972..1f9fc0a2a7 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_4,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_4,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ffef63d7b63588193e346713f4898bd5b7cf5e8103a9440662f56e7b4897d38 -size 30826 +oid sha256:3cad5a954b9cfe40b1be19770e9f721d8d8d7f1f86d8c837c770ff4676bdab0c +size 33060 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_5,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_5,NEXUS_5,1.0,de].png index 041e38b060..4741997df4 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_5,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_5,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ca3b63c4d12ef8d6436586043a3fd3cf80203c0c5d37f30c7027a3b11046188 -size 26185 +oid sha256:f4001ba3ca38363142f096aa4b7a052a8b7641982994698b5dd3134d0a79b65f +size 27951 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_6,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_6,NEXUS_5,1.0,de].png index d7e0800c88..793849862c 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_6,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_6,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bba005d1b6341246ac3ddc03e1dcd7abecf9f80fca5bbf12e1b88d8a8ff8352d -size 28283 +oid sha256:8a736d35d4a326dfa007026ab1fdabf4d0b65c28f0bf2267c707362b55add6d6 +size 29891 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_7,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_7,NEXUS_5,1.0,de].png index d7e0800c88..793849862c 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_7,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_7,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bba005d1b6341246ac3ddc03e1dcd7abecf9f80fca5bbf12e1b88d8a8ff8352d -size 28283 +oid sha256:8a736d35d4a326dfa007026ab1fdabf4d0b65c28f0bf2267c707362b55add6d6 +size 29891 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,de].png index 56921f903c..51cf877cf8 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d43b1d6fb6b0c5bd0e7543f2eb27dec8361e2a8125c1935312cc58dedec3903c -size 16357 +oid sha256:994c6c3f0698d90e42bc5db279ad717a30b37a4bdcb068cfa22998f524dcf7aa +size 28167 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,de].png index a3b697b5ca..c6a3f32d68 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69fef43cf9f89ef995ae5679bd52178ee294ee604a2607eb6b9dd42e3817879b -size 16059 +oid sha256:bd781c1b0b295f8bc18db6ef031f37ad69cb3af4f905bc82859d9678fc43a58c +size 28005 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_0,NEXUS_5,1.0,de].png index 9348b1ee75..0a0bcdc725 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d6170a0b30d26402d4a3ad0b03726bd1b6516b8be11df9afdc1f0694b335e3d -size 71555 +oid sha256:fceb004153e45a443c2c3757e40e9743b0036f6a5fbd859d5b2ae9c1c3ba8903 +size 73614 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_1,NEXUS_5,1.0,de].png index 049e722748..de891bf928 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:097cb77dfb0a94e55df368d1d9048ec644e6dd3e7c82e8d214f80deb1e2fbffa -size 69111 +oid sha256:a831a3d420e4915afb0491d970080546e64c377bf0282d323583f600a6555503 +size 71210 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_2,NEXUS_5,1.0,de].png index 798dc4d146..756033069b 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3e472b79bebfdc8b8c3ba87a1f1f8fa7570fd2f782dbb3b890f50f5845140aa -size 70567 +oid sha256:5bb0151e365ea9b716b6e5ccf6c8306d666bc356b23f290694fd1d6113482d25 +size 72598 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_3,NEXUS_5,1.0,de].png index 798dc4d146..756033069b 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3e472b79bebfdc8b8c3ba87a1f1f8fa7570fd2f782dbb3b890f50f5845140aa -size 70567 +oid sha256:5bb0151e365ea9b716b6e5ccf6c8306d666bc356b23f290694fd1d6113482d25 +size 72598 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_0,NEXUS_5,1.0,de].png index 80f7d4f7b0..2c33e75e06 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bee775c1a68bd219ce0b64556d6ce2b03f62daff98c11e4a28666387b89dbbb -size 74456 +oid sha256:495fb8d17b8cae38ea7c3861014a96cff4b30200d537061b867aa9fc868c8adf +size 76560 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_1,NEXUS_5,1.0,de].png index 0a5aabccec..7a85cfc26a 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afcc930123227d55b6151258f6fbad80e06ab0e81cad5361c58b1ab62a75e7ad -size 71030 +oid sha256:75003b0a3d79c90823f2f5509faa8461c51d00e740d787c26621d3125b14e88f +size 73120 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_2,NEXUS_5,1.0,de].png index 798dc4d146..756033069b 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3e472b79bebfdc8b8c3ba87a1f1f8fa7570fd2f782dbb3b890f50f5845140aa -size 70567 +oid sha256:5bb0151e365ea9b716b6e5ccf6c8306d666bc356b23f290694fd1d6113482d25 +size 72598 diff --git a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_3,NEXUS_5,1.0,de].png index 798dc4d146..756033069b 100644 --- a/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3e472b79bebfdc8b8c3ba87a1f1f8fa7570fd2f782dbb3b890f50f5845140aa -size 70567 +oid sha256:5bb0151e365ea9b716b6e5ccf6c8306d666bc356b23f290694fd1d6113482d25 +size 72598 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,de].png index efdb436f69..689b45e144 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3bb899e721e1480c992b6291668735f94feca41683412684584cd6e25e70cf0 -size 32778 +oid sha256:75cc1c4a706be9cd7faf01eb32a4a052fac47cf1c4895a1dce1e1e6e829dc156 +size 36294 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,de].png index 348738d731..ff32bc7d30 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d12e9eac6b07bbed59da78f5efd7047c719ff1290e712f25b5f6e3a0a6c034f3 -size 33270 +oid sha256:0456bc9ec15195f7c16d46289e8fc13f19a739ba422e1ca10888a4ecc06a1632 +size 30540 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,de].png index 232063f4dd..d85f60f604 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2f9c4f6f3cd5c24f2bf1492552b0a22aee3cc0ddb0e7a85b5c4b2ff4756a5c1 -size 58247 +oid sha256:7023f599e76cb8cf68825de152d5a60c8fbeedd6993c2cfbe57eadcbbb970642 +size 57857 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,de].png index ca35ca6ed9..230db21507 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:880745e2ef6e19abb927e6ca7e20b68f6d851d6ac4cab1eb6c9c6b4cff08dea1 -size 59568 +oid sha256:821a476d4c2c78316d9bf16ef5843609a9b6ae31880cb1440fb94c081a651055 +size 59640 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,de].png index 65b40482ff..abc3b326d9 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7af6f637922df62df93b983d3d890cdd7bb0c504c1c02ca97057e148bb3d5c0 -size 39498 +oid sha256:8fa22e63f524bde8bf0f9c725f5fd2fe7f9535ffdd7ee9870988af34977116ff +size 38591 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,de].png index 4085ba2d44..591213011a 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f763f7577ada3240f81004180bd9fb45b3cb06ddb002bb086301e474e7c279c2 -size 22660 +oid sha256:88ae7a0c26283298fed235e8f1e743df177660925e9a7c5ce668f52be6a4c55e +size 24420 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,de].png index 37d64cbcd6..0c6ed24723 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b31e2bcae16f93242a749bb1ce084376029ca2d048505fef25d7d146eb43b25c -size 39761 +oid sha256:35b2b295cf16494a9f84cb27d172db25295e4c924f55d5d33642db4914ff12a5 +size 39248 diff --git a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,de].png index a358b43d8d..689b45e144 100644 --- a/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,de].png +++ b/screenshots/de/ui_T_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,de].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d23113da0ef829afc1157c5482a44839aa8a2bcc023a54cae1ad3ddbe813592d -size 39994 +oid sha256:75cc1c4a706be9cd7faf01eb32a4a052fac47cf1c4895a1dce1e1e6e829dc156 +size 36294 diff --git a/screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png deleted file mode 100644 index fc8e76dc89..0000000000 --- a/screenshots/de/ui_T_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,de].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:01404e25f56b60dcc84b0bbdd6711f0e7ed6b58078743bf7e919d6766842c92f -size 81164 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..a4ab15b039 --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7372c66ad820f04a14c61fa942cb9a7870f56e46dec4599bda70a64df874879 +size 38440 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..9c6d0c83cb --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5785b28544fc4dafe0ad17a91b55adc6c7a2c75f0da8c0e6e1def25773fe30bc +size 21937 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..29e7b23b6b --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8acbb368bd44d83ca168a55e4e6b94905aeb19efbf41dd4266dcfcf95cff383 +size 22298 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..b6904bbfbc --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1efede30d3c36c9a1fa2f1e0cae6ff22a38dfb52050444e7a51d46aba5d838d2 +size 35318 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..b265245f27 --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4282811a5864f9d41502b7108888bda5b63fd3b0f258ad2b74f10d44ba77d7e +size 30210 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..6e491c3eb5 --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca34ce2d563084f808a5ba09e6ca2dd677b266be079a71ed3c204df7138c27ec +size 47196 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..b2dfd012bf --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0175a2cfae9b0b26872f637bc840ca65d54e9e55d4e3e1be89f97ebda29f4f8f +size 29466 diff --git a/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,de].png b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,de].png new file mode 100644 index 0000000000..53f805a796 --- /dev/null +++ b/screenshots/de/ui_T_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,de].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c16acd3ecb3b0548e28ffde3c9a4bfba87a1f7d50458a662c80ef51cbd5daf3f +size 28075 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index d6d5573390..f388d4bad6 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -199,9 +199,9 @@ export const screenshots = [ ["ui_S_t[f.logout.impl.direct_DefaultDirectLogoutView_null_DefaultDirectLogoutView-Day-1_2_null_2,NEXUS_5,1.0,en]","ui_S_t[f.logout.impl.direct_DefaultDirectLogoutView_null_DefaultDirectLogoutView-Night-1_3_null_2,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.logout.impl.direct_DefaultDirectLogoutView_null_DefaultDirectLogoutView-Day-1_2_null_3,NEXUS_5,1.0,en]","ui_S_t[f.logout.impl.direct_DefaultDirectLogoutView_null_DefaultDirectLogoutView-Night-1_3_null_3,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.logout.impl.direct_DefaultDirectLogoutView_null_DefaultDirectLogoutView-Day-1_2_null_4,NEXUS_5,1.0,en]","ui_S_t[f.logout.impl.direct_DefaultDirectLogoutView_null_DefaultDirectLogoutView-Night-1_3_null_4,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-8_10_null,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-8_9_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-8_10_null,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-7_8_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-7_9_null,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Day-7_8_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBarWithIndicator_null_DefaultRoomListTopBarWithIndicator-Night-7_9_null,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Day-6_7_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_DefaultRoomListTopBar_null_DefaultRoomListTopBar-Night-6_8_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-4_5_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-4_6_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-4_5_null_1,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-4_6_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Day-4_5_null_2,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.developer_DeveloperSettingsView_null_DeveloperSettingsView-Night-4_6_null_2,NEXUS_5,1.0,en]",1,], @@ -216,21 +216,19 @@ export const screenshots = [ ["ui_S_t[l.designsystem.text_DpScale_1_0f__null_DpScale_1_0f__0_null,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.text_DpScale_1_5f__null_DpScale_1_5f__0_null,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.theme.components_DropdownMenuItem_null_Menus_DropdownMenuItem_0_null,NEXUS_5,1.0,en]","",0,], -["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_0,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_1,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_2,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_3,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_4,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-11_13_null_0,NEXUS_5,1.0,en]",1,], -["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Night_1_null,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_12_null_0,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLargeNoBlurShadow_null_ElementLogoAtomLargeNoBlurShadow-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLarge_null_ElementLogoAtomLarge-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomLarge_null_ElementLogoAtomLarge-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_null_ElementLogoAtomMediumNoBlurShadow-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_null_ElementLogoAtomMediumNoBlurShadow-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMedium_null_ElementLogoAtomMedium-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.atoms_ElementLogoAtomMedium_null_ElementLogoAtomMedium-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.messages.impl.timeline.components.customreaction_EmojiItem_null_EmojiItem-Day-31_31_null,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.customreaction_EmojiItem_null_EmojiItem-Night-31_32_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.messages.impl.timeline.components.customreaction_EmojiPicker_null_EmojiPicker-Day-32_32_null,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.customreaction_EmojiPicker_null_EmojiPicker-Night-32_33_null,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_0,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_0,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_1,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_1,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null_2,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null_2,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Day-57_57_null,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.virtual_EncryptedHistoryBannerView_null_EncryptedHistoryBannerView-Night-57_58_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[l.designsystem.components.dialogs_ErrorDialogContent_null_Dialogs_ErrorDialogContent_0_null,NEXUS_5,1.0,en]","",1,], ["ui_S_t[l.designsystem.components.dialogs_ErrorDialog_null_ErrorDialog-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.components.dialogs_ErrorDialog_null_ErrorDialog-Night_1_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Day-62_62_null,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.debug_EventDebugInfoView_null_EventDebugInfoView-Night-62_63_null,NEXUS_5,1.0,en]",0,], @@ -250,6 +248,7 @@ export const screenshots = [ ["ui_S_t[l.designsystem.theme.components_IconButton_null_Buttons_IconButton_0_null,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.theme.components_IconImageVector_null_Icons_IconImageVector_0_null,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.atomic.molecules_IconTitlePlaceholdersRowMolecule_null_IconTitlePlaceholdersRowMolecule-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.molecules_IconTitlePlaceholdersRowMolecule_null_IconTitlePlaceholdersRowMolecule-Night_1_null,NEXUS_5,1.0,en]",0,], +["ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMoleculeWithResIcon_null_IconTitleSubtitleMoleculeWithResIcon-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMolecule_null_IconTitleSubtitleMolecule-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.molecules_IconTitleSubtitleMolecule_null_IconTitleSubtitleMolecule-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.theme.components_IconToggleButton_null_Toggles_IconToggleButton_0_null,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_0,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_0,NEXUS_5,1.0,en]",0,], @@ -260,7 +259,6 @@ export const screenshots = [ ["ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Day_0_null_0,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.icons_IconsOther_null_IconsOther-Night_1_null_0,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Night_1_null,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.invitelist.impl_InviteListView_null_InviteListView-Day-0_1_null_0,NEXUS_5,1.0,en]","ui_S_t[f.invitelist.impl_InviteListView_null_InviteListView-Night-0_2_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.invitelist.impl_InviteListView_null_InviteListView-Day-0_1_null_1,NEXUS_5,1.0,en]","ui_S_t[f.invitelist.impl_InviteListView_null_InviteListView-Night-0_2_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.invitelist.impl_InviteListView_null_InviteListView-Day-0_1_null_2,NEXUS_5,1.0,en]","ui_S_t[f.invitelist.impl_InviteListView_null_InviteListView-Night-0_2_null_2,NEXUS_5,1.0,en]",1,], @@ -414,7 +412,7 @@ export const screenshots = [ ["ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_7,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_7,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_8,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_8,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_9,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_9,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-12_13_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-12_14_null,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Day-11_12_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.migration_MigrationView_null_MigrationView-Night-11_13_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[l.designsystem.theme.components_ModalBottomSheetDark_null_BottomSheets_ModalBottomSheetDark_0_null,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.theme.components_ModalBottomSheetLayoutDark_null_BottomSheets_ModalBottomSheetLayoutDark_0_null,NEXUS_5,1.0,en]","",0,], ["ui_S_t[l.designsystem.theme.components_ModalBottomSheetLayoutLight_null_BottomSheets_ModalBottomSheetLayoutLight_0_null,NEXUS_5,1.0,en]","",0,], @@ -427,6 +425,8 @@ export const screenshots = [ ["ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en]","ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.login.impl.oidc.webview_OidcView_null_OidcView-Day-3_4_null_0,NEXUS_5,1.0,en]","ui_S_t[f.login.impl.oidc.webview_OidcView_null_OidcView-Night-3_5_null_0,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.login.impl.oidc.webview_OidcView_null_OidcView-Day-3_4_null_1,NEXUS_5,1.0,en]","ui_S_t[f.login.impl.oidc.webview_OidcView_null_OidcView-Night-3_5_null_1,NEXUS_5,1.0,en]",0,], @@ -532,10 +532,9 @@ export const screenshots = [ ["ui_S_t[f.messages.impl.report_ReportMessageView_null_ReportMessageView-Day-7_7_null_3,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.report_ReportMessageView_null_ReportMessageView-Night-7_8_null_3,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.messages.impl.report_ReportMessageView_null_ReportMessageView-Day-7_7_null_4,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.report_ReportMessageView_null_ReportMessageView-Night-7_8_null_4,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.messages.impl.report_ReportMessageView_null_ReportMessageView-Day-7_7_null_5,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.report_ReportMessageView_null_ReportMessageView-Night-7_8_null_5,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Day-5_6_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RequestVerificationHeader_null_RequestVerificationHeader-Night-5_7_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[l.designsystem.components.dialogs_RetryDialogContent_null_Dialogs_RetryDialogContent_0_null,NEXUS_5,1.0,en]","",1,], ["ui_S_t[l.designsystem.components.dialogs_RetryDialog_null_RetryDialog-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.components.dialogs_RetryDialog_null_RetryDialog-Night_1_null,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_0,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_0,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_0,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Day-56_56_null_1,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.timeline.components.retrysendmenu_RetrySendMessageMenu_null_RetrySendMessageMenu-Night-56_57_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Day-8_9_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.rolesandpermissions_RolesAndPermissionView_null_RolesAndPermissionView-Night-8_10_null_1,NEXUS_5,1.0,en]",1,], @@ -585,24 +584,23 @@ export const screenshots = [ ["ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_5,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_5,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_6,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_6,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_7,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_7,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_0,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_1,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_2,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_3,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_3,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-6_7_null_4,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-6_8_null_4,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_0,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-11_12_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-11_13_null_1,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_0,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_1,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_2,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_3,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_3,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Day-5_6_null_4,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomListContentView_null_RoomListContentView-Night-5_7_null_4,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_0,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Day-10_11_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.filters_RoomListFiltersView_null_RoomListFiltersView-Night-10_12_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Day-2_3_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContentForDm_null_RoomListModalBottomSheetContentForDm-Night-2_4_null,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Day-1_2_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListModalBottomSheetContent_null_RoomListModalBottomSheetContent-Night-1_3_null,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_0,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_1,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-13_14_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-13_15_null_2,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_0,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_1,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Day-12_13_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.search_RoomListSearchResultContent_null_RoomListSearchResultContent-Night-12_14_null_2,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_0,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_1,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_13,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_13,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_10,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_10,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_11,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_11,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_12,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_12,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_2,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_3,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_3,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_4,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_4,NEXUS_5,1.0,en]",1,], @@ -610,7 +608,7 @@ export const screenshots = [ ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_6,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_6,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_7,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_7,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_8,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_8,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en]",1,], +["ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Day-3_4_null_9,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl_RoomListView_null_RoomListView-Night-3_5_null_9,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.roomdetails.impl.members_RoomMemberBannedList_null_RoomMemberBannedList-Day-3_4_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.members_RoomMemberBannedList_null_RoomMemberBannedList-Night-3_5_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.members_RoomMemberBannedList_null_RoomMemberBannedList-Day-3_4_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.members_RoomMemberBannedList_null_RoomMemberBannedList-Night-3_5_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.roomdetails.impl.members_RoomMemberBannedList_null_RoomMemberBannedList-Day-3_4_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomdetails.impl.members_RoomMemberBannedList_null_RoomMemberBannedList-Night-3_5_null_2,NEXUS_5,1.0,en]",1,], @@ -660,35 +658,35 @@ export const screenshots = [ ["ui_S_t[l.roomselect.impl_RoomSelectView_null_RoomSelectView-Day-0_1_null_2,NEXUS_5,1.0,en]","ui_S_t[l.roomselect.impl_RoomSelectView_null_RoomSelectView-Night-0_2_null_2,NEXUS_5,1.0,en]",1,], ["ui_S_t[l.roomselect.impl_RoomSelectView_null_RoomSelectView-Day-0_1_null_3,NEXUS_5,1.0,en]","ui_S_t[l.roomselect.impl_RoomSelectView_null_RoomSelectView-Night-0_2_null_3,NEXUS_5,1.0,en]",1,], ["ui_S_t[l.roomselect.impl_RoomSelectView_null_RoomSelectView-Day-0_1_null_4,NEXUS_5,1.0,en]","ui_S_t[l.roomselect.impl_RoomSelectView_null_RoomSelectView-Night-0_2_null_4,NEXUS_5,1.0,en]",1,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-9_10_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-9_11_null,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_0,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_1,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_10,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_10,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_11,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_11,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_12,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_12,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_13,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_13,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_14,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_14,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_15,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_15,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_16,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_16,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_17,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_17,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_18,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_18,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_19,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_19,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_2,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_20,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_20,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_21,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_21,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_22,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_22,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_23,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_23,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_24,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_24,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_25,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_25,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_26,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_26,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_27,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_27,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_3,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_3,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_4,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_4,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_5,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_5,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_6,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_6,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_7,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_7,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_8,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_8,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-10_11_null_9,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-10_12_null_9,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Day-8_9_null,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryPlaceholderRow_null_RoomSummaryPlaceholderRow-Night-8_10_null,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_0,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_0,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_1,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_1,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_10,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_10,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_11,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_11,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_12,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_12,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_13,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_13,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_14,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_14,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_15,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_15,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_16,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_16,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_17,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_17,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_18,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_18,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_19,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_19,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_2,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_2,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_20,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_20,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_21,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_21,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_22,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_22,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_23,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_23,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_24,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_24,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_25,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_25,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_26,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_26,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_27,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_27,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_3,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_3,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_4,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_4,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_5,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_5,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_6,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_6,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_7,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_7,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_8,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_8,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Day-9_10_null_9,NEXUS_5,1.0,en]","ui_S_t[f.roomlist.impl.components_RoomSummaryRow_null_RoomSummaryRow-Night-9_11_null_9,NEXUS_5,1.0,en]",0,], ["ui_S_t[appnav.root_Root_null_Root-Day-3_3_null_0,NEXUS_5,1.0,en]","ui_S_t[appnav.root_Root_null_Root-Night-3_4_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[appnav.root_Root_null_Root-Day-3_3_null_1,NEXUS_5,1.0,en]","ui_S_t[appnav.root_Root_null_Root-Night-3_4_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[appnav.root_Root_null_Root-Day-3_3_null_2,NEXUS_5,1.0,en]","ui_S_t[appnav.root_Root_null_Root-Night-3_4_null_2,NEXUS_5,1.0,en]",1,], @@ -941,6 +939,14 @@ export const screenshots = [ ["ui_S_t[l.designsystem.components_TitleWithIconFull_null_TitleWithIconFull-Day_0_null_4,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.components_TitleWithIconFull_null_TitleWithIconFull-Night_1_null_4,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.components_TitleWithIconMinimal_null_TitleWithIconMinimal-Day_0_null,NEXUS_5,1.0,en]","ui_S_t[l.designsystem.components_TitleWithIconMinimal_null_TitleWithIconMinimal-Night_1_null,NEXUS_5,1.0,en]",0,], ["ui_S_t[l.designsystem.theme.components_TopAppBar_null_AppBars_TopAppBar_0_null,NEXUS_5,1.0,en]","",0,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en]",1,], +["ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en]","ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Day-64_64_null_0,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Night-64_65_null_0,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Day-64_64_null_1,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Night-64_65_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Day-64_64_null_2,NEXUS_5,1.0,en]","ui_S_t[f.messages.impl.typing_TypingNotificationView_null_TypingNotificationView-Night-64_65_null_2,NEXUS_5,1.0,en]",1,], @@ -964,9 +970,9 @@ export const screenshots = [ ["ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_6,NEXUS_5,1.0,en]","ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_6,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_7,NEXUS_5,1.0,en]","ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_7,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_8,NEXUS_5,1.0,en]","ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_8,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_0,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_1,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_1,NEXUS_5,1.0,en]",0,], -["ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_2,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_2,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_0,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_0,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_1,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_1,NEXUS_5,1.0,en]",0,], +["ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_2,NEXUS_5,1.0,en]","ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_2,NEXUS_5,1.0,en]",0,], ["ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en]","ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en]","ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en]",1,], ["ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en]","ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en]",1,], diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png index c149b61eba..8eec21f31b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92f023f5edc4c050932f8a9e3a3f886285ef9cc716e999cc66f32101a58f295d -size 33356 +oid sha256:0a9321b5a4d15d815a690d422652d641deb24c06d49e99c5b28d3975544ec256 +size 40446 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png index 5d53c122c6..57e55da467 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:380e3d8df049edf96053533d8d22b8bd46215a6961c6d2b6ca6d151b47c2201e -size 45361 +oid sha256:644e4ac9a9cdaf68cf3fe8b0d39fd46435d2074c384dbc68638703a6242af65d +size 52111 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png index 3a7c3ae48c..5e0e0776e4 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:713d30ae1e8896a410396335ad72dbf75d207096a59caf90e6d3135d4cd55254 -size 43747 +oid sha256:b48d49a2c89c827ba3041738a01c4a665f694d904a1a4c8ad1aa6fbfcdc624de +size 50508 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png index 7e4da73521..5762a4ee9e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3f640e29d7029a278172b45e0597e45653beb58f195bcf71f3fc31c8832708b -size 42973 +oid sha256:573fd927ea37f9c3cf309dd805bf52c06e98e7b058b6f1a80bf8b19bb3ecd35e +size 46028 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png index 11bf947e5e..36699ada63 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58529ffb570bf24dffc2bbc019de2d108467d577ce5b699a09ab62ee22882bdc -size 31854 +oid sha256:1f0d40a93506c47240496c4fc7a1883aa69a964eb754f33c24b14cf877bc83cf +size 38240 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png index c1f40a5686..68f3875592 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:933b83af0d608e39b8d97a3bedee82040a5a13889e23dce7802955488e0fe227 -size 42290 +oid sha256:3556be4af59bd9815b6d8e57b1e7fb8b8e7985afe94cbf745e004f19373590b3 +size 48525 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png index 47008a7b67..a53a3b3114 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62ae02108b8bf661d7f799b64d5e6c1794acb00f1c91a50086fd279fe6ea62d4 -size 40908 +oid sha256:cadda348b54958eb917c64ae03770c622b503393d2db9ae0af9e3abe9ee31d15 +size 47066 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png index 52594d6e32..fc7ecc3279 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24285b1233ff3695d940ee3025b45e83a2b6094f17c9c6b65e8f1bbdccc7b7c8 -size 37961 +oid sha256:6dcceb3e818fe738f6a20dc8dffb006f46c4884e615ee9c00e845d7d9327af66 +size 40894 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png index 61e0ff549c..8a9f1b5935 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b30edf234beec3069a3b0ad051f3c166cd0b9d266491a0dcf5f62a1f5c4ce3fb -size 24123 +oid sha256:e643a714d2c527aa31ca716b3f4d9cc2423ad07b3615bbc274755e2ff90a73e4 +size 29035 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png index bbc5691f3f..cba9218d61 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd8bf20f556a3fe2decdbb5e1f471ed9de9545d39ef5651c5c7b915d3562210f -size 22167 +oid sha256:4d28f1cb062b500452b353f35e8889a260e946807d8f1b5a04f5846ac4ad6482 +size 27511 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png index 4743f2e9d8..05b6946c0a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:340de66e7f38fc545a0cd153c26c8f46c085fef9a6cd3ea51d63d7b4da74d498 -size 12690 +oid sha256:05ee5bc12b1766b9aff8a1c0352e7f4cf2144487672add3fdb9d4c1d0f0b3c0f +size 18954 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png index cb977e1604..b84ecb8f22 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:94181e4ad63d57a52d5726440268a2e1c85d8a653c44fd01cc3750f2d2ee2802 -size 12491 +oid sha256:6a6675dc88dabfc68848505be1115d25c34243b2bef0c11443c5bee1ae4b178c +size 18760 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png index a3f80cbf24..9872ca7ef9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:192a4633b725c9bc24c1782cecb4f4e7bf1266372af7ebacf64637aca943a260 -size 22553 +oid sha256:bfe63f8ce9fcaeb7047cf0ae5e843151983f2f6edb10fff502c11589007c31b3 +size 27747 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png index 3937240dd6..336ced708b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce05cc46725f3f283067284b79c7187ec1f6491618d29636b37e55eef4ce39d2 -size 20567 +oid sha256:b6b4f5b67afae571b66cc0ad1bcdc5bca1c3f9903152897816011b8a7c87828e +size 26009 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png index 106212d72b..164a09669f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5f8a85ddac57eec6d51a0f51188ab0111686982d94769cdcd378653417b41f1 -size 12303 +oid sha256:23c85dd33bd5f865685545b2fa4682081eaff194da333a6801b46011d78a168a +size 18313 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png index 2fc240d96f..03941972b1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37ee8dbcecc2ee4c3fd10e5086b4f2833eb878cf9beb09515527e8657a56a55e -size 12019 +oid sha256:b8159ab16ad6e5ac1cdd4a604455084814369af2dc72e4e506eaf81799c234bc +size 18030 From d5a5aafcda21a8a6df39f44928dd1922ebcfae6a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 09:46:06 +0200 Subject: [PATCH 103/124] Update sqldelight to v2.0.2 (#2665) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d773fa88aa..e7088e72ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ dependencyAnalysis = "1.30.0" serialization_json = "1.6.3" showkase = "1.0.2" appyx = "1.4.0" -sqldelight = "2.0.1" +sqldelight = "2.0.2" wysiwyg = "2.36.0" telephoto = "0.9.0" From e4b76e0e898446f2a233e772881c7d8ceca6c521 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Mon, 8 Apr 2024 09:59:02 +0200 Subject: [PATCH 104/124] Add back debugPlaceholderAvatar for inspection mode --- .../designsystem/components/avatar/Avatar.kt | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index 5a63eaa7ec..173716aee3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -28,11 +28,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage import coil.compose.AsyncImagePainter import coil.compose.SubcomposeAsyncImage import coil.compose.SubcomposeAsyncImageContent @@ -40,6 +42,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.colors.AvatarColorsProvider import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.preview.debugPlaceholderAvatar import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.components.Text import timber.log.Timber @@ -73,21 +76,32 @@ private fun ImageAvatar( modifier: Modifier = Modifier, contentDescription: String? = null, ) { - SubcomposeAsyncImage( - model = avatarData, - contentDescription = contentDescription, - contentScale = ContentScale.Crop, - modifier = modifier - ) { - when (val state = painter.state) { - is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent() - is AsyncImagePainter.State.Error -> { - SideEffect { - Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}") + if (LocalInspectionMode.current) { + // For compose previews, use debugPlaceholderAvatar() + // instead of falling back to initials avatar on load failure + AsyncImage( + model = avatarData, + contentDescription = contentDescription, + placeholder = debugPlaceholderAvatar(), + modifier = modifier + ) + } else { + SubcomposeAsyncImage( + model = avatarData, + contentDescription = contentDescription, + contentScale = ContentScale.Crop, + modifier = modifier + ) { + when (val state = painter.state) { + is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent() + is AsyncImagePainter.State.Error -> { + SideEffect { + Timber.e(state.result.throwable, "Error loading avatar $state\n${state.result}") + } + InitialsAvatar(avatarData = avatarData) } - InitialsAvatar(avatarData = avatarData) + else -> InitialsAvatar(avatarData = avatarData) } - else -> InitialsAvatar(avatarData = avatarData) } } } From 192a1d2107710aba5dceb0578a66673891b7d361 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:28:42 +0200 Subject: [PATCH 105/124] Update dependency io.element.android:compound-android to v0.0.6 (#2670) * Update dependency io.element.android:compound-android to v0.0.6 * Update screenshots --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: ElementBot --- gradle/libs.versions.toml | 2 +- ...mpound_null_IconsCompound-Day_0_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...mpound_null_IconsCompound-Day_0_null_4,NEXUS_5,1.0,en].png | 4 ++-- ...ound_null_IconsCompound-Night_1_null_3,NEXUS_5,1.0,en].png | 4 ++-- ...ound_null_IconsCompound-Night_1_null_4,NEXUS_5,1.0,en].png | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7088e72ee..946e25bfa8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -144,7 +144,7 @@ coil = { module = "io.coil-kt:coil", version.ref = "coil" } coil_compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil_gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" } coil_test = { module = "io.coil-kt:coil-test", version.ref = "coil" } -compound = { module = "io.element.android:compound-android", version = "0.0.5" } +compound = { module = "io.element.android:compound-android", version = "0.0.6" } datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } serialization_json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_json" } kotlinx_collections_immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7" diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_3,NEXUS_5,1.0,en].png index cb034df363..0a79fb8359 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9da1acd875be13ce9c7bed7de1b1eebf9858ea9ce076ba5a0319922d9f0e4f2f -size 74409 +oid sha256:cb7bef38ce2fa1c442811d78d6cf86a0e7b363f7896fc719a40b8c8e9d6a24dc +size 75609 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_4,NEXUS_5,1.0,en].png index b8078360dd..554f1677aa 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Day_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c743a9b3c170fc6290584d6823156c0a56fc319b78a459784d2c9a3e1a583413 -size 58288 +oid sha256:42c6bee9a251f7052a9243c95f21d25b2e240983a1d3760425404762bb4408e1 +size 60068 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_3,NEXUS_5,1.0,en].png index 849de43d06..c988e24e01 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d208b98f67c6ff538624a72c782f50362bd2eb2d789237cee4b833bcbff694f -size 70801 +oid sha256:b04f471a108f8b7c5134584618d600e6a8613cbeb7e0dbe09341133268bec8e3 +size 71888 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_4,NEXUS_5,1.0,en].png index 3c578fbc70..9245997646 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.icons_IconsCompound_null_IconsCompound-Night_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b592fd9d733c2bbf7b59cbddfecab371b459f855d9421acaabd3ed97d39a2c3 -size 55536 +oid sha256:87a3cdfa5c3c1e6f01baf60ffeebddb76c1ae040bbed15fb9e79a0227c2c20a1 +size 56518 From 8e2f7a35cb74ed1ffbd9ffc9b9a635ae6d6f65f6 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 8 Apr 2024 10:54:38 +0200 Subject: [PATCH 106/124] Categorise members by role in the ChangeRoles screen (#2595) * Categorise members by role in the ChangeRoles screen * Fix automatic reload of member list when either the membership or power levels change * Replace empty space with disabled checkbox * Add 'pending' label to members who are in invited state * Implement new designs * Fix string issue in confirm recovery key screen * Update screenshots --------- Co-authored-by: ElementBot --- changelog.d/2593.misc | 1 + .../changeroles/ChangeRolesNode.kt | 2 +- .../changeroles/ChangeRolesPresenter.kt | 15 +- .../changeroles/ChangeRolesState.kt | 22 +- .../changeroles/ChangeRolesStateProvider.kt | 19 +- .../changeroles/ChangeRolesView.kt | 219 +++++++++++++---- .../changeroles/ChangeRolesPresenterTests.kt | 28 ++- .../changeroles/ChangeRolesViewTest.kt | 221 ++++++++++-------- .../changeroles/MembersByRoleTest.kt | 80 +++++++ .../src/main/res/values-de/translations.xml | 2 + .../impl/setup/views/RecoveryKeyView.kt | 5 +- .../src/main/res/values-de/translations.xml | 4 + .../matrix/impl/room/RustMatrixRoom.kt | 11 +- .../src/main/res/values-de/translations.xml | 4 +- ...esView-Day-9_10_null_0,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_1,NEXUS_5,1.0,en].png | 4 +- ...sView-Day-9_10_null_10,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_2,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_3,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_4,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_5,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_6,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_7,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_8,NEXUS_5,1.0,en].png | 4 +- ...esView-Day-9_10_null_9,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_0,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_1,NEXUS_5,1.0,en].png | 4 +- ...iew-Night-9_11_null_10,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_2,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_3,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_4,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_5,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_6,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_7,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_8,NEXUS_5,1.0,en].png | 4 +- ...View-Night-9_11_null_9,NEXUS_5,1.0,en].png | 4 +- ...ongName-Day-10_11_null,NEXUS_5,1.0,en].png | 3 + ...gName-Night-10_12_null,NEXUS_5,1.0,en].png | 3 + ...View-Day-11_12_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Day-11_12_null_1,NEXUS_5,1.0,en].png} | 0 ...View-Day-11_12_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Day-11_12_null_3,NEXUS_5,1.0,en].png} | 0 ...View-Day-11_12_null_4,NEXUS_5,1.0,en].png} | 0 ...View-Day-11_12_null_5,NEXUS_5,1.0,en].png} | 0 ...View-Day-11_12_null_6,NEXUS_5,1.0,en].png} | 0 ...ew-Night-11_13_null_0,NEXUS_5,1.0,en].png} | 0 ...ew-Night-11_13_null_1,NEXUS_5,1.0,en].png} | 0 ...ew-Night-11_13_null_2,NEXUS_5,1.0,en].png} | 0 ...ew-Night-11_13_null_3,NEXUS_5,1.0,en].png} | 0 ...ew-Night-11_13_null_4,NEXUS_5,1.0,en].png} | 0 ...ew-Night-11_13_null_5,NEXUS_5,1.0,en].png} | 0 ...ew-Night-11_13_null_6,NEXUS_5,1.0,en].png} | 0 ...KeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-2_4_null_0,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-2_4_null_1,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-2_4_null_2,NEXUS_5,1.0,en].png | 4 +- ...eyView-Day-6_7_null_10,NEXUS_5,1.0,en].png | 4 +- ...eyView-Day-6_7_null_11,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png | 4 +- ...KeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png | 4 +- ...View-Night-6_8_null_10,NEXUS_5,1.0,en].png | 4 +- ...View-Night-6_8_null_11,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-6_8_null_8,NEXUS_5,1.0,en].png | 4 +- ...yView-Night-6_8_null_9,NEXUS_5,1.0,en].png | 4 +- 66 files changed, 542 insertions(+), 241 deletions(-) create mode 100644 changelog.d/2593.misc create mode 100644 features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/MembersByRoleTest.kt create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Day-10_11_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Night-10_12_null,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_6,NEXUS_5,1.0,en].png} (100%) diff --git a/changelog.d/2593.misc b/changelog.d/2593.misc new file mode 100644 index 0000000000..e67bfc68e5 --- /dev/null +++ b/changelog.d/2593.misc @@ -0,0 +1 @@ +Categorise members by role in change roles screen. diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt index 291beebe99..acd0d7c20c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesNode.kt @@ -65,7 +65,7 @@ class ChangeRolesNode @AssistedInject constructor( ChangeRolesView( modifier = modifier, state = state, - onBackPressed = this::navigateUp, + navigateUp = this::navigateUp, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt index 403b3f3e5d..b4522367ec 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt @@ -75,7 +75,7 @@ class ChangeRolesPresenter @AssistedInject constructor( var query by rememberSaveable { mutableStateOf(null) } var searchActive by rememberSaveable { mutableStateOf(false) } var searchResults by remember { - mutableStateOf>>(SearchBarResultState.Initial()) + mutableStateOf>(SearchBarResultState.Initial()) } val selectedUsers = remember { mutableStateOf>(persistentListOf()) @@ -91,7 +91,7 @@ class ChangeRolesPresenter @AssistedInject constructor( // Users who were selected but didn't have the role, so their role change was pending val toAdd = selectedUsers.value.filter { user -> users.none { it.userId == user.userId } && previous.none { it.userId == user.userId } } // Users who no longer have the role - val toRemove = previous.filter { user -> users.none { it.userId == user.userId } } + val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet() selectedUsers.value = (users + toAdd - toRemove).toImmutableList() } .launchIn(this) @@ -103,8 +103,9 @@ class ChangeRolesPresenter @AssistedInject constructor( LaunchedEffect(query, roomMemberState) { val results = dataSource .search(query.orEmpty()) - .sorted() + .groupedByRole() + println(results) searchResults = if (results.isEmpty()) { SearchBarResultState.NoResultsFound() } else { @@ -181,6 +182,14 @@ class ChangeRolesPresenter @AssistedInject constructor( ) } + private fun List.groupedByRole(): MembersByRole { + return MembersByRole( + admins = filter { it.role == RoomMember.Role.ADMIN }.sorted(), + moderators = filter { it.role == RoomMember.Role.MODERATOR }.sorted(), + members = filter { it.role == RoomMember.Role.USER }.sorted(), + ) + } + private fun Iterable.sorted(): ImmutableList { return sortedWith(PowerLevelRoomMemberComparator()).toImmutableList() } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt index 79a955df13..973363ae6a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesState.kt @@ -16,18 +16,20 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles +import io.element.android.features.roomdetails.impl.members.PowerLevelRoomMemberComparator 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 import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList data class ChangeRolesState( val role: RoomMember.Role, val query: String?, val isSearchActive: Boolean, - val searchResults: SearchBarResultState>, + val searchResults: SearchBarResultState, val selectedUsers: ImmutableList, val hasPendingChanges: Boolean, val exitState: AsyncAction, @@ -35,3 +37,21 @@ data class ChangeRolesState( val canChangeMemberRole: (UserId) -> Boolean, val eventSink: (ChangeRolesEvent) -> Unit, ) + +data class MembersByRole( + val admins: ImmutableList, + val moderators: ImmutableList, + val members: ImmutableList, +) { + constructor(members: List) : this( + admins = members.filter { it.role == RoomMember.Role.ADMIN }.sorted(), + moderators = members.filter { it.role == RoomMember.Role.MODERATOR }.sorted(), + members = members.filter { it.role == RoomMember.Role.USER }.sorted(), + ) + + fun isEmpty() = admins.isEmpty() && moderators.isEmpty() && members.isEmpty() +} + +private fun Iterable.sorted(): ImmutableList { + return sortedWith(PowerLevelRoomMemberComparator()).toImmutableList() +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt index 8cd5db01d6..c0e1a2c135 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesStateProvider.kt @@ -22,6 +22,7 @@ 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 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.user.MatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import kotlinx.collections.immutable.ImmutableList @@ -32,7 +33,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aChangeRolesState(), - aChangeRolesState(role = RoomMember.Role.MODERATOR), + aChangeRolesStateWithSelectedUsers().copy(role = RoomMember.Role.MODERATOR), aChangeRolesStateWithSelectedUsers().copy(hasPendingChanges = false), aChangeRolesStateWithSelectedUsers(), aChangeRolesStateWithSelectedUsers().copy( @@ -41,7 +42,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider { aChangeRolesStateWithSelectedUsers().copy( query = "Alice", isSearchActive = true, - searchResults = SearchBarResultState.Results(aRoomMemberList().take(1).toImmutableList()), + searchResults = SearchBarResultState.Results(MembersByRole(aRoomMemberList().take(1).toImmutableList())), selectedUsers = aMatrixUserList().take(1).toImmutableList(), ), aChangeRolesStateWithSelectedUsers().copy(exitState = AsyncAction.Confirming), @@ -56,7 +57,7 @@ internal fun aChangeRolesState( role: RoomMember.Role = RoomMember.Role.ADMIN, query: String? = null, isSearchActive: Boolean = false, - searchResults: SearchBarResultState> = SearchBarResultState.NoResultsFound(), + searchResults: SearchBarResultState = SearchBarResultState.NoResultsFound(), selectedUsers: ImmutableList = persistentListOf(), hasPendingChanges: Boolean = false, exitState: AsyncAction = AsyncAction.Uninitialized, @@ -78,7 +79,17 @@ internal fun aChangeRolesState( internal fun aChangeRolesStateWithSelectedUsers() = aChangeRolesState( selectedUsers = aMatrixUserList().toImmutableList(), - searchResults = SearchBarResultState.Results(aRoomMemberList().toImmutableList()), + searchResults = SearchBarResultState.Results( + MembersByRole( + members = aRoomMemberList().mapIndexed { index, roomMember -> + if (index % 2 == 0) { + roomMember.copy(membership = RoomMembershipState.INVITE) + } else { + roomMember + } + } + ) + ), hasPendingChanges = true, canRemoveMember = { it != UserId("@alice:server.org") }, ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt index f24ee3b0f1..a498456aec 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt @@ -26,8 +26,10 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.systemBarsPadding @@ -36,12 +38,16 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme @@ -52,6 +58,9 @@ import io.element.android.libraries.designsystem.components.async.AsyncActionVie import io.element.android.libraries.designsystem.components.async.AsyncIndicator import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState +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.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog @@ -67,22 +76,22 @@ import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.UserId 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.getBestName import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.ui.components.MatrixUserRow import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf @OptIn(ExperimentalMaterial3Api::class) @Composable fun ChangeRolesView( state: ChangeRolesState, - onBackPressed: () -> Unit, + navigateUp: () -> Unit, modifier: Modifier = Modifier, ) { - val updatedOnBackPressed by rememberUpdatedState(newValue = onBackPressed) + val updatedNavigateUp by rememberUpdatedState(newValue = navigateUp) BackHandler(enabled = !state.isSearchActive) { state.eventSink(ChangeRolesEvent.Exit) } @@ -136,7 +145,7 @@ fun ChangeRolesView( resultState = state.searchResults, ) { members -> SearchResultsList( - isSearchActive = true, + currentRole = state.role, lazyListState = lazyListState, searchResults = members, selectedUsers = state.selectedUsers, @@ -152,9 +161,9 @@ fun ChangeRolesView( ) { Column { SearchResultsList( - isSearchActive = false, + currentRole = state.role, lazyListState = lazyListState, - searchResults = (state.searchResults as? SearchBarResultState.Results)?.results ?: persistentListOf(), + searchResults = (state.searchResults as? SearchBarResultState.Results)?.results ?: MembersByRole(emptyList()), selectedUsers = state.selectedUsers, canRemoveMember = state.canChangeMemberRole, onSelectionToggled = { state.eventSink(ChangeRolesEvent.UserSelectionToggled(it.toMatrixUser())) }, @@ -179,7 +188,7 @@ fun ChangeRolesView( AsyncActionView( async = state.exitState, - onSuccess = { updatedOnBackPressed() }, + onSuccess = { updatedNavigateUp() }, confirmationDialog = { ConfirmationDialog( title = stringResource(CommonStrings.dialog_unsaved_changes_title), @@ -227,8 +236,8 @@ fun ChangeRolesView( @OptIn(ExperimentalFoundationApi::class) @Composable private fun SearchResultsList( - isSearchActive: Boolean, - searchResults: ImmutableList, + currentRole: RoomMember.Role, + searchResults: MembersByRole, selectedUsers: ImmutableList, canRemoveMember: (UserId) -> Boolean, onSelectionToggled: (RoomMember) -> Unit, @@ -241,43 +250,145 @@ private fun SearchResultsList( item { selectedUsersList(selectedUsers) } - stickyHeader { - val textResId = if (isSearchActive) { - CommonStrings.common_search_results - } else { - R.string.screen_room_member_list_room_members_header_title - } - Text( - modifier = Modifier - .background(ElementTheme.colors.bgCanvasDefault) - .padding(horizontal = 16.dp, vertical = 8.dp) - .fillMaxWidth(), - text = stringResource(textResId), - style = ElementTheme.typography.fontBodyLgMedium, - ) - } - items(searchResults, key = { it.userId }) { roomMember -> - val canToggle = canRemoveMember(roomMember.userId) - val trailingContent: @Composable (() -> Unit)? = if (canToggle) { - { - Checkbox( - checked = selectedUsers.any { it.userId == roomMember.userId }, - onCheckedChange = { onSelectionToggled(roomMember) }, + if (searchResults.admins.isNotEmpty()) { + stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_admins)) } + // Add a footer for the admin section in change role to moderator screen + if (currentRole == RoomMember.Role.MODERATOR) { + item { + Text( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 8.dp), + text = stringResource(R.string.screen_room_change_role_moderators_admin_section_footer), + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodySmRegular, ) } - } else { - null } - MatrixUserRow( - modifier = Modifier.clickable(enabled = canToggle, onClick = { onSelectionToggled(roomMember) }), - matrixUser = MatrixUser( - userId = roomMember.userId, - displayName = roomMember.displayName, - avatarUrl = roomMember.avatarUrl, - ), - trailingContent = trailingContent, - ) + items(searchResults.admins, key = { it.userId }) { roomMember -> + ListMemberItem( + roomMember = roomMember, + canRemoveMember = canRemoveMember, + onSelectionToggled = onSelectionToggled, + selectedUsers = selectedUsers + ) + } } + if (searchResults.moderators.isNotEmpty()) { + stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_moderators)) } + items(searchResults.moderators, key = { it.userId }) { roomMember -> + ListMemberItem( + roomMember = roomMember, + canRemoveMember = canRemoveMember, + onSelectionToggled = onSelectionToggled, + selectedUsers = selectedUsers + ) + } + } + if (searchResults.admins.isNotEmpty()) { + stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_member_list_mode_members)) } + items(searchResults.members, key = { it.userId }) { roomMember -> + ListMemberItem( + roomMember = roomMember, + canRemoveMember = canRemoveMember, + onSelectionToggled = onSelectionToggled, + selectedUsers = selectedUsers + ) + } + } + } +} + +@Composable +private fun ListSectionHeader(text: String) { + Text( + modifier = Modifier + .background(ElementTheme.colors.bgCanvasDefault) + .padding(horizontal = 16.dp, vertical = 8.dp) + .fillMaxWidth(), + text = text, + style = ElementTheme.typography.fontBodyLgMedium, + ) +} + +@Composable +private fun ListMemberItem( + roomMember: RoomMember, + canRemoveMember: (UserId) -> Boolean, + onSelectionToggled: (RoomMember) -> Unit, + selectedUsers: ImmutableList, +) { + val canToggle = canRemoveMember(roomMember.userId) + val trailingContent: @Composable (() -> Unit) = { + Checkbox( + checked = selectedUsers.any { it.userId == roomMember.userId }, + onCheckedChange = { onSelectionToggled(roomMember) }, + enabled = canToggle, + ) + } + MemberRow( + modifier = Modifier.clickable(enabled = canToggle, onClick = { onSelectionToggled(roomMember) }), + avatarData = AvatarData(roomMember.userId.value, roomMember.displayName, roomMember.avatarUrl, AvatarSize.UserListItem), + name = roomMember.getBestName(), + userId = roomMember.userId.value.takeIf { roomMember.displayName?.isNotBlank() == true }, + isPending = roomMember.membership == RoomMembershipState.INVITE, + trailingContent = trailingContent, + ) +} + +@Composable +private fun MemberRow( + avatarData: AvatarData, + name: String, + userId: String?, + isPending: Boolean, + modifier: Modifier = Modifier, + trailingContent: @Composable (() -> Unit)? = null, +) { + Row( + modifier = modifier + .fillMaxWidth() + .heightIn(min = 56.dp) + .padding(start = 16.dp, top = 4.dp, end = 16.dp, bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Avatar(avatarData) + Column( + modifier = Modifier + .padding(start = 12.dp) + .weight(1f), + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + // Name + Text( + modifier = Modifier.weight(1f, fill = false), + text = name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.primary, + style = ElementTheme.typography.fontBodyLgRegular, + ) + // Invitation pending marker + if (isPending) { + Text( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(id = R.string.screen_room_member_list_pending_header_title), + style = ElementTheme.typography.fontBodySmRegular.copy(fontStyle = FontStyle.Italic), + color = MaterialTheme.colorScheme.secondary + ) + } + } + // Id + userId?.let { + Text( + text = userId, + color = MaterialTheme.colorScheme.secondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = ElementTheme.typography.fontBodySmRegular, + ) + } + } + trailingContent?.invoke() } } @@ -287,7 +398,27 @@ internal fun ChangeRolesViewPreview(@PreviewParameter(ChangeRolesStateProvider:: ElementPreview { ChangeRolesView( state = state, - onBackPressed = {}, + navigateUp = {}, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun PendingMemberRowWithLongNamePreview() { + ElementPreview { + MemberRow( + avatarData = AvatarData("userId", "A very long name that should be truncated", "https://example.com/avatar.png", AvatarSize.UserListItem), + name = "A very long name that should be truncated", + userId = "@alice:matrix.org", + isPending = true, + trailingContent = { + Checkbox( + checked = true, + onCheckedChange = {}, + enabled = true, + ) + } ) } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt index 1dbce1fc53..e8ff232222 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTests.kt @@ -106,15 +106,19 @@ class ChangeRolesPresenterTests { presenter.present() }.test { val initialState = awaitItem() - val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results.orEmpty() - assertThat(initialResults).hasSize(10) + val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results + assertThat(initialResults?.members).hasSize(8) + assertThat(initialResults?.moderators).hasSize(1) + assertThat(initialResults?.admins).hasSize(1) initialState.eventSink(ChangeRolesEvent.QueryChanged("Alice")) skipItems(1) - val searchResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results.orEmpty() - assertThat(searchResults).hasSize(1) - assertThat(searchResults.firstOrNull()?.userId).isEqualTo(A_USER_ID) + val searchResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results + assertThat(searchResults?.admins).hasSize(1) + assertThat(searchResults?.moderators).isEmpty() + assertThat(searchResults?.members).isEmpty() + assertThat(searchResults?.admins?.firstOrNull()?.userId).isEqualTo(A_USER_ID) } } @@ -128,15 +132,19 @@ class ChangeRolesPresenterTests { presenter.present() }.test { skipItems(1) - val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results.orEmpty() - assertThat(initialResults).hasSize(10) + val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results + assertThat(initialResults?.members).hasSize(8) + assertThat(initialResults?.moderators).hasSize(1) + assertThat(initialResults?.admins).hasSize(1) room.givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList().take(1).toPersistentList())) skipItems(1) - val searchResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results.orEmpty() - assertThat(searchResults).hasSize(1) - assertThat(searchResults.firstOrNull()?.userId).isEqualTo(A_USER_ID) + val searchResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results + assertThat(searchResults?.admins).hasSize(1) + assertThat(searchResults?.moderators).isEmpty() + assertThat(searchResults?.members).isEmpty() + assertThat(searchResults?.admins?.firstOrNull()?.userId).isEqualTo(A_USER_ID) } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt index 037223327e..93cbad4d58 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesViewTest.kt @@ -37,178 +37,192 @@ import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressBackKey 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 +import java.lang.IllegalStateException @RunWith(AndroidJUnit4::class) class ChangeRolesViewTest { @get:Rule val rule = createAndroidComposeRule() @Test - fun `click on back icon search not active emits the expected event`() { - val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( - state = aChangeRolesState( - eventSink = eventsRecorder, - ), - ) - rule.pressBack() - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.Exit, + fun `passing a 'USER' role throws an exception`() { + val exception = runCatching { + rule.setChangeRolesContent( + state = aChangeRolesState( + role = RoomMember.Role.USER, + eventSink = EnsureNeverCalledWithParam(), + ), ) - ) + }.exceptionOrNull() + + assertThat(exception).isNotNull() } @Test - fun `click on back icon search active emits the expected event`() { + fun `back key - with search active toggles the search`() { val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( + rule.setChangeRolesContent( state = aChangeRolesState( isSearchActive = true, eventSink = eventsRecorder, ), ) - rule.pressBack() - // This event should be there, maybe a problem with the SearchBar - // It's working fine in the app, so let's ignore it for now - // eventsRecorder.assertSingle(ChangeRolesEvent.ToggleSearchActive) + + rule.pressBackKey() + + eventsRecorder.assertSingle(ChangeRolesEvent.ToggleSearchActive) } @Test - fun `click on search bar emits the expected event`() { + fun `back key - with search inactive exits the screen`() { val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( + rule.setChangeRolesContent( state = aChangeRolesState( + isSearchActive = false, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.common_search_for_someone) - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - // This event should be there, maybe a problem with the SearchBar - // It's working fine in the app, so let's ignore it for now - // ChangeRolesEvent.ToggleSearchActive, - ) - ) + + rule.pressBackKey() + + eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit)) } @Test - fun `click on save button emits the expected event`() { + fun `back button - exits the screen`() { val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( + rule.setChangeRolesContent( + state = aChangeRolesState( + isSearchActive = false, + eventSink = eventsRecorder, + ), + ) + + rule.pressBack() + + eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit)) + } + + @Test + fun `save button - with changes, it saves them`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesContent( state = aChangeRolesState( hasPendingChanges = true, eventSink = eventsRecorder, ), ) + rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.Save, - ) - ) + + eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Save)) } @Test - fun `testing exit confirmation dialog ok emits the expected event`() { + fun `save button - with no changes, does nothing`() { val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( + rule.setChangeRolesContent( state = aChangeRolesState( + hasPendingChanges = false, + eventSink = eventsRecorder, + ), + ) + + rule.clickOn(CommonStrings.action_save) + + eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""))) + } + + @Test + fun `exit confirmation dialog - submit exits the screen`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesContent( + state = aChangeRolesState( + isSearchActive = true, exitState = AsyncAction.Confirming, eventSink = eventsRecorder, ), ) + rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.Exit, - ) - ) + + eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test - fun `testing exit confirmation dialog cancel emits the expected event`() { + fun `exit confirmation dialog - cancel removes the dialog`() { val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( + rule.setChangeRolesContent( state = aChangeRolesState( + isSearchActive = true, exitState = AsyncAction.Confirming, eventSink = eventsRecorder, ), ) + rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.CancelExit - ) - ) + + eventsRecorder.assertSingle(ChangeRolesEvent.CancelExit) } @Test - fun `testing saving dialog failure OK emits the expected event`() { + fun `save confirmation dialog - submit saves the changes`() { val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( - state = aChangeRolesState( - savingState = AsyncAction.Failure(Exception("boom")), - eventSink = eventsRecorder, - ), - ) - rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.ClearError, - ) - ) - } - - @Test - fun `testing saving confirmation dialog for admin OK emits the expected event`() { - val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( + rule.setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.ADMIN, + isSearchActive = true, savingState = AsyncAction.Confirming, eventSink = eventsRecorder, ), ) + rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.Save, - ) - ) + + eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test - fun `testing saving confirmation dialog for admin cancel emits the expected event`() { + fun `save confirmation dialog - cancel removes the dialog`() { val eventsRecorder = EventsRecorder() - rule.setChangeRolesView( + rule.setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.ADMIN, + isSearchActive = true, savingState = AsyncAction.Confirming, eventSink = eventsRecorder, ), ) + rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertList( - listOf( - ChangeRolesEvent.QueryChanged(""), - ChangeRolesEvent.ClearError, - ) + + eventsRecorder.assertSingle(ChangeRolesEvent.ClearError) + } + + @Test + fun `error dialog - dismissing removes the dialog`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesContent( + state = aChangeRolesState( + isSearchActive = true, + savingState = AsyncAction.Failure(IllegalStateException("boom")), + eventSink = eventsRecorder, + ), ) + + rule.clickOn(CommonStrings.action_ok) + + eventsRecorder.assertSingle(ChangeRolesEvent.ClearError) } @Test @@ -217,7 +231,7 @@ class ChangeRolesViewTest { val selectedUsers = aMatrixUserList().take(2) val userToDeselect = selectedUsers[1] assertThat(userToDeselect.displayName).isEqualTo("Bob") - rule.setChangeRolesView( + rule.setChangeRolesContent( state = aChangeRolesStateWithSelectedUsers().copy( selectedUsers = selectedUsers.toImmutableList(), eventSink = eventsRecorder, @@ -235,6 +249,7 @@ class ChangeRolesViewTest { } @Test + @Config(qualifiers = "h1000dp") fun `testing adding user to the selected list emits the expected event`() { val eventsRecorder = EventsRecorder() val selectedUsers = aMatrixUserList().take(2) @@ -242,12 +257,12 @@ class ChangeRolesViewTest { selectedUsers = selectedUsers.toImmutableList(), eventSink = eventsRecorder, ) - val userToSelect = (state.searchResults as SearchBarResultState.Results).results[2].toMatrixUser() + val userToSelect = (state.searchResults as SearchBarResultState.Results).results.members.first().toMatrixUser() assertThat(userToSelect.displayName).isEqualTo("Carol") - rule.setChangeRolesView( + rule.setChangeRolesContent( state = state, ) - // Select the user from the rom list + // Select the user from the row list rule.onNodeWithText("Carol").performClick() eventsRecorder.assertList( listOf( @@ -265,9 +280,9 @@ class ChangeRolesViewTest { selectedUsers = selectedUsers.toImmutableList(), eventSink = eventsRecorder, ) - val userToSelect = (state.searchResults as SearchBarResultState.Results).results[1].toMatrixUser() + val userToSelect = (state.searchResults as SearchBarResultState.Results).results.moderators.first().toMatrixUser() assertThat(userToSelect.displayName).isEqualTo("Bob") - rule.setChangeRolesView( + rule.setChangeRolesContent( state = state, ) // Select the user from the rom list @@ -279,16 +294,16 @@ class ChangeRolesViewTest { ) ) } -} -private fun AndroidComposeTestRule.setChangeRolesView( - state: ChangeRolesState, - onBackPressed: () -> Unit = EnsureNeverCalled(), -) { - setContent { - ChangeRolesView( - state = state, - onBackPressed = onBackPressed, - ) + private fun AndroidComposeTestRule.setChangeRolesContent( + state: ChangeRolesState, + onBackPressed: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + ChangeRolesView( + state = state, + navigateUp = onBackPressed, + ) + } } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/MembersByRoleTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/MembersByRoleTest.kt new file mode 100644 index 0000000000..7819745c52 --- /dev/null +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/MembersByRoleTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.roomdetails.rolesandpermissions.changeroles + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles.MembersByRole +import io.element.android.libraries.matrix.api.room.RoomMember +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.A_USER_ID_3 +import io.element.android.libraries.matrix.test.A_USER_ID_4 +import io.element.android.libraries.matrix.test.A_USER_ID_5 +import io.element.android.libraries.matrix.test.room.aRoomMember +import kotlinx.collections.immutable.persistentListOf +import org.junit.Test + +class MembersByRoleTest { + @Test + fun `constructor - with single member list categorizes and sorts members`() { + val members = listOf( + aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.ADMIN), + aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.ADMIN), + aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.USER), + aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.USER), + aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.USER), + ) + val membersByRole = MembersByRole(members = members) + assertThat(membersByRole.admins).containsExactly( + aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.ADMIN), + aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.ADMIN), + ) + assertThat(membersByRole.moderators).isEmpty() + assertThat(membersByRole.members).containsExactly( + aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.USER), + aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.USER), + aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.USER), + ) + } + + @Test + fun `isEmpty - only returns true with no members of any role`() { + val emptyMembersByRole = MembersByRole(emptyList()) + assertThat(emptyMembersByRole.isEmpty()).isTrue() + + val membersByRoleWithAdmins = MembersByRole( + admins = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.ADMIN)), + moderators = persistentListOf(), + members = persistentListOf(), + ) + assertThat(membersByRoleWithAdmins.isEmpty()).isFalse() + + val membersByRoleWithModerators = MembersByRole( + admins = persistentListOf(), + moderators = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.MODERATOR)), + members = persistentListOf(), + ) + assertThat(membersByRoleWithModerators.isEmpty()).isFalse() + + val membersByRoleWithMembers = MembersByRole( + admins = persistentListOf(), + moderators = persistentListOf(), + members = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.USER)), + ) + assertThat(membersByRoleWithMembers.isEmpty()).isFalse() + } +} diff --git a/features/roomlist/impl/src/main/res/values-de/translations.xml b/features/roomlist/impl/src/main/res/values-de/translations.xml index a2cf494b1b..d02870ccd1 100644 --- a/features/roomlist/impl/src/main/res/values-de/translations.xml +++ b/features/roomlist/impl/src/main/res/values-de/translations.xml @@ -11,6 +11,8 @@ "In den Chat-Einstellungen kannst du einen Chat als Favorit hinzufügen. Um deine anderen Chats zu sehen wähle diesen Filter ab." "Du hast noch keine Chats als Favorit markiert." + "Einladungen" + "Du hast keine ausstehenden Einladungen." "Niedrige Priorität" "Wähle Filter ab, um Deine Chats zu sehen." "Du hast keine Chats für diese Auswahl" diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt index 7a4d92fcd3..dc8b788025 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt @@ -70,7 +70,10 @@ internal fun RecoveryKeyView( verticalArrangement = Arrangement.spacedBy(8.dp) ) { Text( - text = stringResource(id = CommonStrings.common_recovery_key), + text = when (state.recoveryKeyUserStory) { + RecoveryKeyUserStory.Enter -> stringResource(R.string.screen_recovery_key_confirm_key_label) + else -> stringResource(id = CommonStrings.common_recovery_key) + }, modifier = Modifier.padding(start = 16.dp), style = ElementTheme.typography.fontBodyMdRegular, ) diff --git a/features/securebackup/impl/src/main/res/values-de/translations.xml b/features/securebackup/impl/src/main/res/values-de/translations.xml index afb1c764d1..f2ac7c06b4 100644 --- a/features/securebackup/impl/src/main/res/values-de/translations.xml +++ b/features/securebackup/impl/src/main/res/values-de/translations.xml @@ -30,6 +30,10 @@ "Bitte versuche es noch einmal, um den Zugriff auf dein Chat-Backup zu bestätigen." "Falscher Wiederherstellungsschlüssel" "Dies funktioniert auch mit einer Wiederherstellungspassphrase oder einer geheime Passphrase/einem geheimen Schlüssel." + + "Wiederherstellungsschlüssel" + " oder Passcode" + "Eingeben…" "Wiederherstellungsschlüssel bestätigt" "Wiederherstellungsschlüssel oder Passcode bestätigen" diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index be61b06310..91e25d2498 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -71,8 +71,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.EventTimelineItem @@ -164,7 +169,11 @@ class RustMatrixRoom( override val syncUpdateFlow: StateFlow = _syncUpdateFlow.asStateFlow() init { - timeline.membershipChangeEventReceived + val powerLevelChanges = roomInfoFlow.map { it.userPowerLevels }.distinctUntilChanged() + val membershipChanges = timeline.membershipChangeEventReceived.onStart { emit(Unit) } + combine(membershipChanges, powerLevelChanges) { _, _ -> } + // Skip initial one + .drop(1) // The new events should already be in the SDK cache, no need to fetch them from the server .onEach { roomMemberListFetcher.fetchRoomMembers(source = RoomMemberListFetcher.Source.CACHE) } .launchIn(roomCoroutineScope) 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 719dcab093..8277b61731 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -175,7 +175,9 @@ "Datenschutz­erklärung" "Reaktion" "Reaktionen" - "Wiederherstellungsschlüssel oder Passcode" + + "Wiederherstellungsschlüssel" + "Wird erneuert…" "%1$s antworten" "Einen Fehler melden" diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_0,NEXUS_5,1.0,en].png index 364d5ad96e..91d3f82d6a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cf713ae66135179f17b52578bf4663bf34a102b92c0809c42e5285856d43f3b -size 18257 +oid sha256:f9eb707bd20836dd80d1939a1eb405bbeca4b58aa36e9b627f3775cdffc9dcc0 +size 15142 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_1,NEXUS_5,1.0,en].png index 6705985509..fd7266592b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7737c502737b811720b54ba8e8db4248df8e0e8577435c61270853f92e65f6bc -size 19731 +oid sha256:56873f51dd8de6630f5423f796687049a920bddafb5af17a47e4f901a5d9f360 +size 71767 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_10,NEXUS_5,1.0,en].png index 8e48dd4ae9..bb978036ab 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b0ca2b91bc525254ed4be526e622608f77d809a1c097ef02943d4cd751c65b2 -size 55941 +oid sha256:34efa87358c4529d0f67729fd417a3d52dc5a0031192dbf123bcc667d6e78391 +size 56803 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_2,NEXUS_5,1.0,en].png index 205b4fe623..05468c5c69 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e96824d27d0fda0b183e8c90d659406d012861ff520f81cfa60374424137bbdb -size 66079 +oid sha256:4818ed45a985359112ee92c38148f1030647ead270727525fed1cf1f5c493180 +size 66293 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_3,NEXUS_5,1.0,en].png index d24aa84010..35c6953ca9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b00d18ba4fa132e32c6580682c6b78d754d330735afcf8e4242ee0def213ba44 -size 66046 +oid sha256:57b0cd541704cd82962e03d2173bb37350d2f91bf1f3d6121314bdccad50c8e6 +size 66255 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_4,NEXUS_5,1.0,en].png index 0eb57ff9d0..69680bab61 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eeabb0a4ed645c7f47da62e7bafaca54ab1d3c5c48a195b48c3f807e5137d39c -size 59992 +oid sha256:ebd6484e81803758298b4ff0e19a042918d85c29e3828a561c83328a9b3e4d73 +size 60644 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png index 40bc944f5a..43f6b4f658 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5199d1491a315d7653b5e09049ead901650332deb50ec4d24742ac7f3f7ff535 -size 15618 +oid sha256:9e07c6ee5a1e9ebedc98ed8daca1c7e81c877102e24d9cc7a21834ce24df7d1d +size 16758 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_6,NEXUS_5,1.0,en].png index 11c7296257..f5a3dad0cb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54461c606debb6867bd48a054f551dd56e2895530f06e6890b44ad7b1eeb9219 -size 65289 +oid sha256:77bca4e3380c63dba4e7def0937770c6d5b1ae5bde61e4743ecde035431fb8be +size 64625 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_7,NEXUS_5,1.0,en].png index b5a534c0d2..ac13b7852a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66707e11931cc5824142a5f42ed95c4becd5621ea3a9c9cac3df5f0435b3883b -size 66191 +oid sha256:b265ee7f4eb6c56e531004af6daae9ad89167b027d089fa960fc17553ec80131 +size 66783 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_8,NEXUS_5,1.0,en].png index d74825b1b7..996e749c1c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31dcb5cef9f857e2b96631b4dd23a59a200c9174f8cc0e406d2a859497a8f893 -size 58934 +oid sha256:eb629e088198030a3a9a09c8cfc815a87e831dc6f5e26b225d17c61883585e50 +size 58902 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_9,NEXUS_5,1.0,en].png index 7f712e5d7d..0f822efc96 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56e0b337fc6afdadf477785df479478ae24dd7c9cb1f98bedf33b920ad374e78 -size 69899 +oid sha256:f4dd6cbcd17c5c97c240a1244f3233f0f228612b317cc8f6b3f39a0d0d2b05fd +size 70120 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_0,NEXUS_5,1.0,en].png index 9c99ff605b..95f73d3ca5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a99d564c528dbcc2080b6f83ca09a97bd518412019d0f32b76b80086af4f17e -size 16966 +oid sha256:4e47b753ec51c5cdc4640b9883bd9bbcb68af0c0ceacbc31020132213fb9bfbc +size 14223 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_1,NEXUS_5,1.0,en].png index 5e3ca5e603..eb5a850bea 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb3c08d58fd0dfa60ad1549b26a53f02239f1ca43aeda9e52350952d86d5fa74 -size 18178 +oid sha256:9d1cf36a65fc7e916a15a0a6eab4e5eaba47f8df5d39d5cb9fe75b137138a96d +size 69609 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_10,NEXUS_5,1.0,en].png index 9cd1a27f5a..c34a2b7cf5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f86fad7b780fb7fc0b36377724c6830d86c5d41dcfd50f8de8cbe9f40473e68d -size 52719 +oid sha256:fa0b65d6cd2eac2746a2ba1598f075a6d5d144cfc62fbde8e7e17c5b69d85f1c +size 53497 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_2,NEXUS_5,1.0,en].png index afbfa647bf..37e994e196 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00c4dba57e32a87d6acde138625214bb795c7e7c8987561d6ddd53fbf26674b5 -size 64446 +oid sha256:417ca17376f6e296a8955390c800191ebdccf8b358dbc738f438b70c2568606e +size 64439 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_3,NEXUS_5,1.0,en].png index cc77b40b16..a3490db6e3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3e270adac07c800c5ab1a124d564beee96b2e39e9473a69b8b90ff647bfd6df -size 64184 +oid sha256:a73ae445a25011b1c376f93b7e7203b023e23a027aa2a44d109008a1cca98406 +size 64193 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_4,NEXUS_5,1.0,en].png index 7615e4d3de..34fda705fa 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6490597e92bd937451dbcf7d516f43525b5d18835cefec4f825fdf90ee6dc626 -size 57891 +oid sha256:a98762c06334da0cc53d04826623850b15d3fc5fdf7cdf8f50f896574a048d0d +size 58356 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png index d0d83c7d16..b05af8072a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e088b944b00b462fcbaa16033a786835b8a09dd26f8a9b946580a237d2b363e -size 15064 +oid sha256:b8188aab8aaa9d7685e6936883e635f097bc3b9b8fdd5bb778bc7baae3f97ea1 +size 16308 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_6,NEXUS_5,1.0,en].png index 4eaec1e29e..9f39eacc46 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afac0f9958320d5c1f12d9b6647a15608eff51627dcb82d35ec9524a579f2637 -size 61111 +oid sha256:b6a02199d5c7b5bf4ff0251d437f071e2ce54440b1e0b4519305e0080f7ed6ec +size 60375 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_7,NEXUS_5,1.0,en].png index aad0cbf857..85da1ba694 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3991def6f61df49c73e025a417680f1bd409d8e2aff41ba2434e3a5a50914665 -size 62128 +oid sha256:5898156b9c4ce053ab24728b0679bf6d2d0873459a7b1e4951f8f9ce646e4074 +size 62587 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_8,NEXUS_5,1.0,en].png index 247ab0e9a6..b06975b046 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af3c14da68316feb82b4ae905868116239e43cb8b0e5393246efe0edd61f1bc2 -size 56497 +oid sha256:6648e7dc5c97e27efbf347380fc3ae3c67866595b086e1adefe36a274bf3afc2 +size 56314 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_9,NEXUS_5,1.0,en].png index ad76d6153e..cf1ca92dc6 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1979b5b0dff66a05765a1ecd03d9a896d2aaf6dffc686ffd27bba3d5a0ac927a -size 67254 +oid sha256:5d984c253b4990220acebfe91d33fb7f4201fb7a976bea48b2bc04e4788ddee2 +size 67263 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Day-10_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Day-10_11_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7f2ece3429 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Day-10_11_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31deb74551ce3e14d7fdbb6fad4bd1dd9c4124d50a39dcef572c1092eac75561 +size 16580 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Night-10_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Night-10_12_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f33d0577c4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_PendingMemberRowWithLongName_null_PendingMemberRowWithLongName-Night-10_12_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e091e28939405c49558f40dbd291ace6280152b84e10676188b51f0ddc5a662d +size 16104 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-10_11_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Day-11_12_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-10_12_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_null_ChangeRoomPermissionsView-Night-11_13_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png index 8eec21f31b..dfe108c2a8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a9321b5a4d15d815a690d422652d641deb24c06d49e99c5b28d3975544ec256 -size 40446 +oid sha256:bd35705e28bd0b46d2ca8752c3f338c883d5bc2e8c78af26c35eda435a6cf8cf +size 42542 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png index 57e55da467..d772d032e7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:644e4ac9a9cdaf68cf3fe8b0d39fd46435d2074c384dbc68638703a6242af65d -size 52111 +oid sha256:5bf4f3ff7ed970ba7eef2119525e800f80bfc056ccbd5345c1c610257180b50d +size 53908 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png index 5e0e0776e4..bd69817bb8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b48d49a2c89c827ba3041738a01c4a665f694d904a1a4c8ad1aa6fbfcdc624de -size 50508 +oid sha256:f963d37ac30795df5de921cef04b17de6baf45160716533984d08f6e543fb1e5 +size 52566 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png index 36699ada63..70ffe6c1d0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f0d40a93506c47240496c4fc7a1883aa69a964eb754f33c24b14cf877bc83cf -size 38240 +oid sha256:1d1cd406230b94ba11e414aedfbe49d903d7aa54791c4dbd93616a9f16ca7068 +size 39917 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png index 68f3875592..712ed72f35 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3556be4af59bd9815b6d8e57b1e7fb8b8e7985afe94cbf745e004f19373590b3 -size 48525 +oid sha256:7f9dbe41267a197887860cda4cef70138fd1e1b79497f396d1535725bc6219f4 +size 50262 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png index a53a3b3114..a4af367f65 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cadda348b54958eb917c64ae03770c622b503393d2db9ae0af9e3abe9ee31d15 -size 47066 +oid sha256:02ebc69b55046c946142bac74f299d191b4dba2e175a670a67ed1e91dcaf51a8 +size 48753 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png index 8a9f1b5935..80a305b663 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e643a714d2c527aa31ca716b3f4d9cc2423ad07b3615bbc274755e2ff90a73e4 -size 29035 +oid sha256:2c4aeacb175ef566e8093a9d7e297640739c71e488f0fe56f75803c2b98b09d2 +size 31137 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png index cba9218d61..f5d703685d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d28f1cb062b500452b353f35e8889a260e946807d8f1b5a04f5846ac4ad6482 -size 27511 +oid sha256:e7837d958cae6c7627a57a27880a00747dcebea7f00a8a29e4a0dcf67b5fae47 +size 29014 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png index 05b6946c0a..d053778c55 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05ee5bc12b1766b9aff8a1c0352e7f4cf2144487672add3fdb9d4c1d0f0b3c0f -size 18954 +oid sha256:e7731621308a3392d0b940ce881171e909852d24787b63fbdbfea69e238c9f18 +size 20983 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png index b84ecb8f22..9d0b5ff5d9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a6675dc88dabfc68848505be1115d25c34243b2bef0c11443c5bee1ae4b178c -size 18760 +oid sha256:13e9687d249fd366e7d39cb20a49664ae661453f4a4333c3eb6334065211aa58 +size 20794 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png index 9872ca7ef9..cb364295aa 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfe63f8ce9fcaeb7047cf0ae5e843151983f2f6edb10fff502c11589007c31b3 -size 27747 +oid sha256:ec7487af11bc617f04296a895351dd82e79a9acd2272225d2cf84a735a5bb30b +size 29005 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png index 336ced708b..bc7f0feec3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6b4f5b67afae571b66cc0ad1bcdc5bca1c3f9903152897816011b8a7c87828e -size 26009 +oid sha256:4120ab56b29285d4e2576563c521f59541d4368b9adcdc6c0dd997046df8a385 +size 27504 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png index 164a09669f..5207d34551 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23c85dd33bd5f865685545b2fa4682081eaff194da333a6801b46011d78a168a -size 18313 +oid sha256:910d080a5abbba99a1347c77d70df982fe520239700cfbd04af460d33f5a0037 +size 20043 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png index 03941972b1..b10ede074e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8159ab16ad6e5ac1cdd4a604455084814369af2dc72e4e506eaf81799c234bc -size 18030 +oid sha256:a77b0bb03a6941cf821420266c4922f9ef88c775ee6f8771aa17e966b3d86584 +size 19769 From b6ceb8c69795a8fbc0dde16a228144b776250de9 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 8 Apr 2024 10:58:37 +0200 Subject: [PATCH 107/124] Improve UI for notification permission screen in onboarding (#2660) * Improve UI for notification permission screen in onboarding * Update screenshots --------- Co-authored-by: ElementBot --- .../io/element/android/x/MainActivity.kt | 4 +- changelog.d/2581.misc | 1 + .../notifications/NotificationsOptInView.kt | 15 ++-- .../atomic/pages/HeaderFooterPage.kt | 37 ++++---- .../components/OnboardingBackground.kt | 86 +++++++++++++++++++ ...tInView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 4 +- ...gBackground-Day_0_null,NEXUS_5,1.0,en].png | 3 + ...ackground-Night_1_null,NEXUS_5,1.0,en].png | 3 + 9 files changed, 129 insertions(+), 28 deletions(-) create mode 100644 changelog.d/2581.misc create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/OnboardingBackground.kt create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Day_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Night_1_null,NEXUS_5,1.0,en].png diff --git a/app/src/main/kotlin/io/element/android/x/MainActivity.kt b/app/src/main/kotlin/io/element/android/x/MainActivity.kt index 99d940c5f5..ee277a40cf 100644 --- a/app/src/main/kotlin/io/element/android/x/MainActivity.kt +++ b/app/src/main/kotlin/io/element/android/x/MainActivity.kt @@ -19,6 +19,7 @@ package io.element.android.x import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -31,7 +32,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.core.view.WindowCompat import com.bumble.appyx.core.integration.NodeHost import com.bumble.appyx.core.integrationpoint.NodeActivity import com.bumble.appyx.core.plugin.NodeReadyObserver @@ -60,7 +60,7 @@ class MainActivity : NodeActivity() { super.onCreate(savedInstanceState) appBindings = bindings() appBindings.lockScreenService().handleSecureFlag(this) - WindowCompat.setDecorFitsSystemWindows(window, false) + enableEdgeToEdge() setContent { MainContent(appBindings) } diff --git a/changelog.d/2581.misc b/changelog.d/2581.misc new file mode 100644 index 0000000000..6d38fdb682 --- /dev/null +++ b/changelog.d/2581.misc @@ -0,0 +1 @@ +Improve UI for notification permission screen in onboarding. diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt index 924824a2ec..b9f902202b 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt @@ -26,7 +26,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable @@ -40,8 +40,10 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.ftue.impl.R import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule -import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.OnboardingBackground +import io.element.android.libraries.designsystem.components.PageTitle 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 @@ -62,8 +64,9 @@ fun NotificationsOptInView( HeaderFooterPage( modifier = modifier - .systemBarsPadding() + .statusBarsPadding() .fillMaxSize(), + background = { OnboardingBackground() }, header = { NotificationsOptInHeader(modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)) }, footer = { NotificationsOptInFooter(state) }, ) { @@ -75,11 +78,11 @@ fun NotificationsOptInView( private fun NotificationsOptInHeader( modifier: Modifier = Modifier, ) { - IconTitleSubtitleMolecule( + PageTitle( modifier = modifier, title = stringResource(R.string.screen_notification_optin_title), - subTitle = stringResource(R.string.screen_notification_optin_subtitle), - iconImageVector = CompoundIcons.NotificationsSolid(), + subtitle = stringResource(R.string.screen_notification_optin_subtitle), + iconStyle = BigIcon.Style.Default(CompoundIcons.NotificationsSolid()), ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt index 6b99537dc9..b2cf88b8bc 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.designsystem.theme.components.Text /** * @param modifier Classical modifier. + * @param background optional background component. * @param topBar optional topBar. * @param header optional header. * @param footer optional footer. @@ -42,6 +43,7 @@ import io.element.android.libraries.designsystem.theme.components.Text @Composable fun HeaderFooterPage( modifier: Modifier = Modifier, + background: @Composable () -> Unit = {}, topBar: @Composable () -> Unit = {}, header: @Composable () -> Unit = {}, footer: @Composable () -> Unit = {}, @@ -51,25 +53,28 @@ fun HeaderFooterPage( modifier = modifier, topBar = topBar, ) { padding -> - Column( - modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) - .padding(all = 20.dp), - ) { - // Header - header() - // Content + Box { + background() Column( modifier = Modifier - .weight(1f) - .fillMaxWidth(), + .padding(all = 20.dp) + .padding(padding) + .consumeWindowInsets(padding) ) { - content() - } - // Footer - Box(modifier = Modifier.padding(horizontal = 16.dp)) { - footer() + // Header + header() + // Content + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + ) { + content() + } + // Footer + Box(modifier = Modifier.padding(horizontal = 16.dp)) { + footer() + } } } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/OnboardingBackground.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/OnboardingBackground.kt new file mode 100644 index 0000000000..5dbced7417 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/OnboardingBackground.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight + +/** + * Gradient background for FTUE (onboarding) screens. + */ +@Suppress("ModifierMissing") +@Composable +fun OnboardingBackground() { + Box(modifier = Modifier.fillMaxSize()) { + val isLightTheme = ElementTheme.isLightTheme + Canvas( + modifier = Modifier + .fillMaxWidth() + .height(220.dp) + .align(Alignment.BottomCenter) + ) { + val gradientBrush = ShaderBrush( + LinearGradientShader( + from = Offset(0f, size.height / 2f), + to = Offset(size.width, size.height / 2f), + colors = listOf( + Color(0xFF0DBDA8), + if (isLightTheme) Color(0xC90D5CBD) else Color(0xFF0D5CBD), + ) + ) + ) + val eraseBrush = ShaderBrush( + LinearGradientShader( + from = Offset(size.width / 2f, 0f), + to = Offset(size.width / 2f, size.height * 2f), + colors = listOf( + Color(0xFF000000), + Color(0x00000000), + ) + ) + ) + drawWithLayer { + drawRect(brush = gradientBrush, size = size) + drawRect(brush = gradientBrush, size = size, blendMode = BlendMode.Overlay) + drawRect(brush = eraseBrush, size = size, blendMode = BlendMode.DstOut) + } + } + } +} + +@PreviewsDayNight +@Composable +internal fun OnboardingBackgroundPreview() { + ElementPreview { + OnboardingBackground() + } +} diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png index 72a7b12e96..62cab21d5f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdc15ae26f55ce303a0eb02be9533d96f843bc0173560e487f601dd1a7c80670 -size 37180 +oid sha256:c769d9116f4173b7e97b9ecb73f14166e8ae21b968c13df779aaa4d4a12c7d8b +size 74024 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png index eeafd919fa..63d4e3e7d8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.notifications_NotificationsOptInView_null_NotificationsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56b977a2c064e7753d8af2739055b89f19fd5031e5e7df92d348e8ec5e8f2c29 -size 33718 +oid sha256:2c9c2253982480ccf9558f2a1b6eaac478ddb3f7b178b39648871eb74aa877b1 +size 66490 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Day_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..89932eb28e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Day_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4df7164831cfda683ae0f312130f5e1222439000df456975e6c16567dbd4888d +size 56615 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Night_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..17cb751284 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components_OnboardingBackground_null_OnboardingBackground-Night_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:070dd4ea130ee33d04b1d2e83facbc36be3126b21abfff5c78446fe6120f175b +size 51628 From 649204238c50780e0f406559de667ad3e746ca15 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 5 Apr 2024 14:59:36 +0200 Subject: [PATCH 108/124] Add functions trackRecentlyVisitedRoom and getRecentlyVisitedRooms --- .../android/libraries/matrix/api/MatrixClient.kt | 3 +++ .../libraries/matrix/impl/RustMatrixClient.kt | 12 ++++++++++++ .../libraries/matrix/test/FakeMatrixClient.kt | 12 ++++++++++++ 3 files changed, 27 insertions(+) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index e6c536ee72..fa5f083722 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -91,4 +91,7 @@ interface MatrixClient : Closeable { fun roomMembershipObserver(): RoomMembershipObserver fun isMe(userId: UserId?) = userId == sessionId + + suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result + suspend fun getRecentlyVisitedRooms(): Result> } 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 546acc6e84..cc1b4a0888 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 @@ -438,6 +438,18 @@ class RustMatrixClient( } } + override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { + runCatching { + client.trackRecentlyVisitedRoom(roomId.value) + } + } + + override suspend fun getRecentlyVisitedRooms(): Result> = withContext(sessionDispatcher) { + runCatching { + client.getRecentlyVisitedRooms().map(::RoomId) + } + } + override fun syncService(): SyncService = rustSyncService override fun sessionVerificationService(): SessionVerificationService = verificationService diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 247bc4bbe9..b485257815 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -255,4 +255,16 @@ class FakeMatrixClient( fun givenRemoveAvatarResult(result: Result) { removeAvatarResult = result } + + private val visitedRoomsId: MutableList = mutableListOf() + + override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result { + visitedRoomsId.removeAll { it == roomId } + visitedRoomsId.add(0, roomId) + return Result.success(Unit) + } + + override suspend fun getRecentlyVisitedRooms(): Result> { + return Result.success(visitedRoomsId) + } } From 1c66254e7405194fdc32fe487b81b957de550437 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Apr 2024 11:41:31 +0200 Subject: [PATCH 109/124] Add suggestion of users when starting a Chat #2634 --- .../android/appnav/room/RoomLoadedFlowNode.kt | 7 ++ .../android/appnav/RoomFlowNodeTest.kt | 2 + changelog.d/2634.misc | 1 + .../AddPeopleUserListStateProvider.kt | 10 ++- .../impl/addpeople/AddPeopleView.kt | 19 ++---- .../impl/components/UserListView.kt | 47 ++++++++++++++ .../impl/root/CreateRoomRootStateProvider.kt | 27 ++++++-- .../impl/root/CreateRoomRootView.kt | 60 +++++++++++++---- .../impl/userlist/DefaultUserListPresenter.kt | 9 +++ .../createroom/impl/userlist/UserListState.kt | 2 + .../impl/userlist/UserListStateProvider.kt | 62 +++++++++++++----- .../userlist/DefaultUserListPresenterTests.kt | 7 ++ .../libraries/matrix/api/room/MatrixRoom.kt | 6 ++ .../api/room/recent/RecentDirectRoom.kt | 65 +++++++++++++++++++ .../matrix/impl/room/RustMatrixRoom.kt | 10 +++ .../matrix/test/room/FakeMatrixRoom.kt | 4 ++ 16 files changed, 285 insertions(+), 53 deletions(-) create mode 100644 changelog.d/2634.misc create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index 21e412ef63..b9a537dc24 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -40,6 +40,7 @@ import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.DaggerComponentOwner import io.element.android.libraries.di.SessionScope +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.MatrixRoom @@ -61,6 +62,7 @@ class RoomLoadedFlowNode @AssistedInject constructor( private val roomDetailsEntryPoint: RoomDetailsEntryPoint, private val appNavigationStateService: AppNavigationStateService, private val appCoroutineScope: CoroutineScope, + private val matrixClient: MatrixClient, roomComponentFactory: RoomComponentFactory, roomMembershipObserver: RoomMembershipObserver, ) : BaseFlowNode( @@ -92,6 +94,7 @@ class RoomLoadedFlowNode @AssistedInject constructor( Timber.v("OnCreate => ${inputs.room.roomId}") appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId) fetchRoomMembers() + trackVisitedRoom() }, onResume = { appCoroutineScope.launch { @@ -117,6 +120,10 @@ class RoomLoadedFlowNode @AssistedInject constructor( inputs() } + private fun trackVisitedRoom() = lifecycleScope.launch { + matrixClient.trackRecentlyVisitedRoom(inputs.room.roomId) + } + private fun fetchRoomMembers() = lifecycleScope.launch { inputs.room.updateMembers() } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt index ecc6a5dad0..0595ebdbea 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt @@ -33,6 +33,7 @@ import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.childNode import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import kotlinx.coroutines.CoroutineScope @@ -101,6 +102,7 @@ class RoomFlowNodeTest { roomMembershipObserver = RoomMembershipObserver(), appCoroutineScope = coroutineScope, roomComponentFactory = FakeRoomComponentFactory(), + matrixClient = FakeMatrixClient(), ) @Test diff --git a/changelog.d/2634.misc b/changelog.d/2634.misc new file mode 100644 index 0000000000..0ec68a2115 --- /dev/null +++ b/changelog.d/2634.misc @@ -0,0 +1 @@ +Show users from last visited DM as suggestion when starting a Chat or when creating a Room. diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt index 4fb1149a01..63e2417113 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleUserListStateProvider.kt @@ -19,6 +19,7 @@ package io.element.android.features.createroom.impl.addpeople import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.createroom.impl.userlist.SelectionMode import io.element.android.features.createroom.impl.userlist.UserListState +import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList import io.element.android.features.createroom.impl.userlist.aUserListState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.ui.components.aMatrixUserList @@ -29,13 +30,13 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider get() = sequenceOf( aUserListState(), - aUserListState().copy( + aUserListState( searchResults = SearchBarResultState.Results(aMatrixUserList().toImmutableList()), selectedUsers = aMatrixUserList().toImmutableList(), isSearchActive = false, selectionMode = SelectionMode.Multiple, ), - aUserListState().copy( + aUserListState( searchResults = SearchBarResultState.Results( aMatrixUserList() .mapIndexed { index, matrixUser -> @@ -46,6 +47,9 @@ open class AddPeopleUserListStateProvider : PreviewParameterProvider - Column( + UserListView( modifier = Modifier .fillMaxSize() .padding(padding) .consumeWindowInsets(padding), - ) { - UserListView( - modifier = Modifier - .fillMaxWidth(), - state = state, - showBackButton = false, - onUserSelected = { }, - onUserDeselected = {}, - ) - } + state = state, + showBackButton = false, + onUserSelected = {}, + onUserDeselected = {}, + ) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt index 3cc009989a..0e1c448015 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt @@ -19,17 +19,27 @@ package io.element.android.features.createroom.impl.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable 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.features.createroom.impl.userlist.UserListEvents import io.element.android.features.createroom.impl.userlist.UserListState import io.element.android.features.createroom.impl.userlist.UserListStateProvider +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.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.components.CheckableUserRow +import io.element.android.libraries.matrix.ui.components.CheckableUserRowData import io.element.android.libraries.matrix.ui.components.SelectedUsersRowList +import io.element.android.libraries.matrix.ui.model.getAvatarData +import io.element.android.libraries.matrix.ui.model.getBestName +import io.element.android.libraries.ui.strings.CommonStrings @Composable fun UserListView( @@ -74,6 +84,43 @@ fun UserListView( }, ) } + if (!state.isSearchActive && state.recentDirectRooms.isNotEmpty()) { + LazyColumn { + item { + ListSectionHeader( + title = stringResource(id = CommonStrings.common_suggestions), + hasDivider = false, + ) + } + state.recentDirectRooms.forEachIndexed { index, recentDirectRoom -> + item { + val isSelected = state.selectedUsers.any { + recentDirectRoom.matrixUser.userId == it.userId + } + CheckableUserRow( + checked = isSelected, + onCheckedChange = { + if (isSelected) { + state.eventSink(UserListEvents.RemoveFromSelection(recentDirectRoom.matrixUser)) + onUserDeselected(recentDirectRoom.matrixUser) + } else { + state.eventSink(UserListEvents.AddToSelection(recentDirectRoom.matrixUser)) + onUserSelected(recentDirectRoom.matrixUser) + } + }, + data = CheckableUserRowData.Resolved( + avatarData = recentDirectRoom.matrixUser.getAvatarData(AvatarSize.UserListItem), + name = recentDirectRoom.matrixUser.getBestName(), + subtext = recentDirectRoom.matrixUser.userId.value, + ), + ) + if (index < state.recentDirectRooms.lastIndex) { + HorizontalDivider() + } + } + } + } + } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt index 723c650793..0638d8abbb 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt @@ -17,9 +17,12 @@ package io.element.android.features.createroom.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.createroom.impl.userlist.UserListState +import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList import io.element.android.features.createroom.impl.userlist.aUserListState 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.components.aMatrixUser import io.element.android.libraries.usersearch.api.UserSearchResult import kotlinx.collections.immutable.persistentListOf @@ -28,7 +31,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider get() = sequenceOf( aCreateRoomRootState(), - aCreateRoomRootState().copy( + aCreateRoomRootState( startDmAction = AsyncAction.Loading, userListState = aMatrixUser().let { aUserListState().copy( @@ -39,7 +42,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, + eventSink: (CreateRoomRootEvents) -> Unit = {}, +) = CreateRoomRootState( + applicationName = applicationName, + userListState = userListState, + startDmAction = startDmAction, + eventSink = eventSink, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt index 4f874c57dd..33707896fa 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -46,11 +47,14 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.Icon +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.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.ui.components.MatrixUserRow import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.persistentListOf @Composable fun CreateRoomRootView( @@ -77,7 +81,11 @@ fun CreateRoomRootView( ) { UserListView( modifier = Modifier.fillMaxWidth(), - state = state.userListState, + // Do not render suggestions in this case, the suggestion will be rendered + // by CreateRoomActionButtonsList + state = state.userListState.copy( + recentDirectRooms = persistentListOf(), + ), onUserSelected = { state.eventSink(CreateRoomRootEvents.StartDM(it)) }, @@ -89,6 +97,7 @@ fun CreateRoomRootView( state = state, onNewRoomClicked = onNewRoomClicked, onInvitePeopleClicked = onInviteFriendsClicked, + onDmClicked = onOpenDM, ) } } @@ -106,7 +115,7 @@ fun CreateRoomRootView( onRetry = { state.userListState.selectedUsers.firstOrNull() ?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) } - // Cancel start DM if there is no more selected user (should not happen) + // Cancel start DM if there is no more selected user (should not happen) ?: state.eventSink(CreateRoomRootEvents.CancelStartDM) }, onErrorDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) }, @@ -139,18 +148,43 @@ private fun CreateRoomActionButtonsList( state: CreateRoomRootState, onNewRoomClicked: () -> Unit, onInvitePeopleClicked: () -> Unit, + onDmClicked: (RoomId) -> Unit, ) { - Column { - CreateRoomActionButton( - iconRes = CompoundDrawables.ic_compound_plus, - text = stringResource(id = R.string.screen_create_room_action_create_room), - onClick = onNewRoomClicked, - ) - CreateRoomActionButton( - iconRes = CompoundDrawables.ic_compound_share_android, - text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName), - onClick = onInvitePeopleClicked, - ) + LazyColumn { + item { + CreateRoomActionButton( + iconRes = CompoundDrawables.ic_compound_plus, + text = stringResource(id = R.string.screen_create_room_action_create_room), + onClick = onNewRoomClicked, + ) + } + item { + CreateRoomActionButton( + iconRes = CompoundDrawables.ic_compound_share_android, + text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName), + onClick = onInvitePeopleClicked, + ) + } + if (state.userListState.recentDirectRooms.isNotEmpty()) { + item { + ListSectionHeader( + title = stringResource(id = CommonStrings.common_suggestions), + hasDivider = false, + ) + } + state.userListState.recentDirectRooms.forEach { recentDirectRoom -> + item { + MatrixUserRow( + modifier = Modifier.clickable( + onClick = { + onDmClicked(recentDirectRoom.roomId) + } + ), + matrixUser = recentDirectRoom.matrixUser, + ) + } + } + } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt index a210a6debd..31daf62513 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt @@ -30,6 +30,9 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.recent.RecentDirectRoom +import io.element.android.libraries.matrix.api.room.recent.getRecentDirectRooms import io.element.android.libraries.usersearch.api.UserRepository import io.element.android.libraries.usersearch.api.UserSearchResult import kotlinx.collections.immutable.ImmutableList @@ -41,6 +44,7 @@ class DefaultUserListPresenter @AssistedInject constructor( @Assisted val args: UserListPresenterArgs, @Assisted val userRepository: UserRepository, @Assisted val userListDataStore: UserListDataStore, + private val matrixClient: MatrixClient, ) : UserListPresenter { @AssistedFactory @ContributesBinding(SessionScope::class) @@ -54,6 +58,10 @@ class DefaultUserListPresenter @AssistedInject constructor( @Composable override fun present(): UserListState { + var recentDirectRooms by remember { mutableStateOf(emptyList()) } + LaunchedEffect(Unit) { + recentDirectRooms = matrixClient.getRecentDirectRooms() + } var isSearchActive by rememberSaveable { mutableStateOf(false) } val selectedUsers by userListDataStore.selectedUsers().collectAsState(emptyList()) var searchQuery by rememberSaveable { mutableStateOf("") } @@ -82,6 +90,7 @@ class DefaultUserListPresenter @AssistedInject constructor( isSearchActive = isSearchActive, showSearchLoader = showSearchLoader, selectionMode = args.selectionMode, + recentDirectRooms = recentDirectRooms.toImmutableList(), eventSink = { event -> when (event) { is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt index a27191881e..b7b61fbe52 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt @@ -17,6 +17,7 @@ package io.element.android.features.createroom.impl.userlist 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 import io.element.android.libraries.usersearch.api.UserSearchResult import kotlinx.collections.immutable.ImmutableList @@ -28,6 +29,7 @@ data class UserListState( val selectedUsers: ImmutableList, val isSearchActive: Boolean, val selectionMode: SelectionMode, + val recentDirectRooms: ImmutableList, val eventSink: (UserListEvents) -> Unit, ) { val isMultiSelectionEnabled = selectionMode == SelectionMode.Multiple diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt index 193fa7e71f..fc46ae1953 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt @@ -18,54 +18,82 @@ package io.element.android.features.createroom.impl.userlist 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 +import io.element.android.libraries.matrix.api.room.recent.RecentDirectRoom +import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.usersearch.api.UserSearchResult -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList open class UserListStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aUserListState(), - aUserListState().copy( + aUserListState( isSearchActive = false, selectedUsers = aListOfSelectedUsers(), selectionMode = SelectionMode.Multiple, ), - aUserListState().copy(isSearchActive = true), - aUserListState().copy(isSearchActive = true, searchQuery = "someone"), - aUserListState().copy(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Multiple), - aUserListState().copy( + aUserListState(isSearchActive = true), + aUserListState(isSearchActive = true, searchQuery = "someone"), + aUserListState(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Multiple), + aUserListState( isSearchActive = true, searchQuery = "@someone:matrix.org", selectedUsers = aMatrixUserList().toImmutableList(), searchResults = SearchBarResultState.Results(aListOfUserSearchResults()), ), - aUserListState().copy( + aUserListState( isSearchActive = true, searchQuery = "@someone:matrix.org", selectionMode = SelectionMode.Multiple, selectedUsers = aMatrixUserList().toImmutableList(), searchResults = SearchBarResultState.Results(aListOfUserSearchResults()), ), - aUserListState().copy( + aUserListState( isSearchActive = true, searchQuery = "something-with-no-results", searchResults = SearchBarResultState.NoResultsFound() ), - aUserListState().copy(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Single), + aUserListState( + isSearchActive = true, + searchQuery = "someone", + selectionMode = SelectionMode.Single, + ), + aUserListState( + recentDirectRooms = aRecentDirectRoomList(), + ), ) } -fun aUserListState() = UserListState( - isSearchActive = false, - searchQuery = "", - searchResults = SearchBarResultState.Initial(), - selectedUsers = persistentListOf(), - selectionMode = SelectionMode.Single, - showSearchLoader = false, - eventSink = {} +fun aUserListState( + searchQuery: String = "", + isSearchActive: Boolean = false, + searchResults: SearchBarResultState> = SearchBarResultState.Initial(), + selectedUsers: List = emptyList(), + showSearchLoader: Boolean = false, + selectionMode: SelectionMode = SelectionMode.Single, + recentDirectRooms: List = emptyList(), + eventSink: (UserListEvents) -> Unit = {}, +) = UserListState( + searchQuery = searchQuery, + isSearchActive = isSearchActive, + searchResults = searchResults, + selectedUsers = selectedUsers.toImmutableList(), + showSearchLoader = showSearchLoader, + selectionMode = selectionMode, + recentDirectRooms = recentDirectRooms.toImmutableList(), + eventSink = eventSink ) fun aListOfSelectedUsers() = aMatrixUserList().take(6).toImmutableList() fun aListOfUserSearchResults() = aMatrixUserList().take(6).map { UserSearchResult(it) }.toImmutableList() + +fun aRecentDirectRoomList( + count: Int = 5 +): List = aMatrixUserList() + .take(count) + .map { + RecentDirectRoom(RoomId("!aRoom:id"), it) + } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt index 579bd175f5..c4d6d9ab00 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt @@ -21,6 +21,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.usersearch.api.UserSearchResult @@ -45,6 +46,7 @@ class DefaultUserListPresenterTests { UserListPresenterArgs(selectionMode = SelectionMode.Single), userRepository, UserListDataStore(), + FakeMatrixClient(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -66,6 +68,7 @@ class DefaultUserListPresenterTests { UserListPresenterArgs(selectionMode = SelectionMode.Multiple), userRepository, UserListDataStore(), + FakeMatrixClient(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -87,6 +90,7 @@ class DefaultUserListPresenterTests { UserListPresenterArgs(selectionMode = SelectionMode.Single), userRepository, UserListDataStore(), + FakeMatrixClient(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -123,6 +127,7 @@ class DefaultUserListPresenterTests { ), userRepository, UserListDataStore(), + FakeMatrixClient(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -175,6 +180,7 @@ class DefaultUserListPresenterTests { ), userRepository, UserListDataStore(), + FakeMatrixClient(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -200,6 +206,7 @@ class DefaultUserListPresenterTests { UserListPresenterArgs(selectionMode = SelectionMode.Single), userRepository, UserListDataStore(), + FakeMatrixClient(), ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index fef657571c..07b5b310bc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -82,6 +82,12 @@ interface MatrixRoom : Closeable { */ suspend fun updateMembers() + /** + * Get the members of the room. Note: generally this should not be used, please use + * [membersStateFlow] and [updateMembers] instead. + */ + suspend fun getMembers(limit: Int = 5): Result> + /** * Will return an updated member or an error. */ 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 new file mode 100644 index 0000000000..c2fb147aa0 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package 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.CurrentUserMembership +import io.element.android.libraries.matrix.api.room.MatrixRoom +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 + +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() + 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 + } + } + } + } + return result +} + +suspend fun MatrixRoom.isJoined(): Boolean { + return roomInfoFlow.first().currentUserMembership == CurrentUserMembership.JOINED +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 91e25d2498..0531c59d4a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -237,6 +237,16 @@ class RustMatrixRoom( roomMemberListFetcher.fetchRoomMembers(source = source) } + override suspend fun getMembers(limit: Int) = withContext(roomDispatcher) { + runCatching { + innerRoom.members().use { + it.nextChunk(limit.toUInt()).orEmpty().map { roomMember -> + RoomMemberMapper.map(roomMember) + } + } + } + } + override suspend fun getUpdatedMember(userId: UserId): Result = withContext(roomDispatcher) { runCatching { RoomMemberMapper.map(innerRoom.member(userId.value)) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index f2395241b5..11582433d5 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -201,6 +201,10 @@ class FakeMatrixRoom( return getRoomMemberResult } + override suspend fun getMembers(limit: Int): Result> { + return Result.success(emptyList()) + } + override suspend fun updateRoomNotificationSettings(): Result = simulateLongTask { val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow() roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings) From 15f3f0ecfcf4515c22fa1f79917b1ed961b3b0f5 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 8 Apr 2024 12:03:05 +0000 Subject: [PATCH 110/124] Update screenshots --- ...eView_null_AddPeopleView-Day-0_1_null_3,NEXUS_5,1.0,en].png | 3 +++ ...iew_null_AddPeopleView-Night-0_2_null_3,NEXUS_5,1.0,en].png | 3 +++ ...stView_null_UserListView-Day-2_3_null_9,NEXUS_5,1.0,en].png | 3 +++ ...View_null_UserListView-Night-2_4_null_9,NEXUS_5,1.0,en].png | 3 +++ ..._null_CreateRoomRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png | 3 +++ ...ull_CreateRoomRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png | 3 +++ 6 files changed, 18 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Day-0_1_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Night-0_2_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_9,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_9,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Day-0_1_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c12833586d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9828a78e660d9dfa53fcdcb5febfd45e2f112404e24ba8126e6a910254a064fc +size 45359 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Night-0_2_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2c66880825 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.addpeople_AddPeopleView_null_AddPeopleView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a7a6d9290e356382d127fa54d445a25fb5baea30689ded2999dfe199f593bb6 +size 43446 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..028a8e9204 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb2fe3d47c7af259c25e0d640d60cdbd904b0c99a6e75ff773136d55b3986db9 +size 40818 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..62e97c8adb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b7a41ea00505a0b6263ce4e6f906be24df830e83864a81b54baafa3a0434666 +size 39453 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f63426add5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b7aa4ae1aca7c541a0c8a7bc535f7ff98dd3b24127d0dcd9e76ef2a889e6ab4 +size 52249 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..70a363efb8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.root_CreateRoomRootView_null_CreateRoomRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:780070d15ef80d123a41f071dd5d13404cf65950944a94be364539607c3cba2a +size 49687 From 6c135a842bb7aa342b8c47168c8cad32e4a0c721 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 12:43:00 +0000 Subject: [PATCH 111/124] Update dependency io.sentry:sentry-android to v7.7.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 946e25bfa8..aec27d4877 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -176,7 +176,7 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0" # Analytics posthog = "com.posthog:posthog-android:3.1.16" -sentry = "io.sentry:sentry-android:7.6.0" +sentry = "io.sentry:sentry-android:7.7.0" # Note: only 0.19.0 will compile properly # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.15.0" From 470e8ff58195259ee8cba6d0524894a43b260eec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Apr 2024 15:28:48 +0200 Subject: [PATCH 112/124] Add UI test on AddPeopleView --- features/createroom/impl/build.gradle.kts | 2 + .../impl/addpeople/AddPeopleViewTest.kt | 98 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleViewTest.kt diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index 6cd9b30741..11f87a6d9f 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -69,6 +69,8 @@ dependencies { testImplementation(projects.libraries.usersearch.test) testImplementation(projects.features.createroom.test) testImplementation(projects.tests.testutils) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) ksp(libs.showkase.processor) } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleViewTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleViewTest.kt new file mode 100644 index 0000000000..36741347e5 --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleViewTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.impl.addpeople + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.createroom.impl.userlist.UserListEvents +import io.element.android.features.createroom.impl.userlist.UserListState +import io.element.android.features.createroom.impl.userlist.aUserListState +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 org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AddPeopleViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on back invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnce { + rule.setAddPeopleView( + aUserListState( + eventSink = eventsRecorder, + ), + onBackPressed = it + ) + rule.pressBack() + } + eventsRecorder.assertSingle(UserListEvents.UpdateSearchQuery("")) + } + + @Test + fun `clicking on back during search emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setAddPeopleView( + aUserListState( + isSearchActive = true, + eventSink = eventsRecorder, + ), + ) + rule.pressBack() + eventsRecorder.assertSingle(UserListEvents.OnSearchActiveChanged(false)) + } + + @Test + fun `clicking on skip invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnce { + rule.setAddPeopleView( + aUserListState( + eventSink = eventsRecorder, + ), + onNextPressed = it + ) + rule.clickOn(CommonStrings.action_skip) + } + eventsRecorder.assertSingle(UserListEvents.UpdateSearchQuery("")) + } +} + +private fun AndroidComposeTestRule.setAddPeopleView( + state: UserListState, + onBackPressed: () -> Unit = EnsureNeverCalled(), + onNextPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + AddPeopleView( + state = state, + onBackPressed = onBackPressed, + onNextPressed = onNextPressed, + ) + } +} From 523b1644a2d654ad0724ef10a323a90ba12df672 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Apr 2024 15:42:31 +0200 Subject: [PATCH 113/124] Add UI test on CreateRoomRootView --- .../impl/root/CreateRoomRootViewTest.kt | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt new file mode 100644 index 0000000000..dcb2e02347 --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootViewTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.impl.root + +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.features.createroom.impl.R +import io.element.android.features.createroom.impl.userlist.aRecentDirectRoomList +import io.element.android.features.createroom.impl.userlist.aUserListState +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.ui.model.getBestName +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +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.pressBack +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 CreateRoomRootViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on back invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setCreateRoomRootView( + aCreateRoomRootState( + eventSink = eventsRecorder, + ), + onClosePressed = it + ) + rule.pressBack() + } + } + + @Test + fun `clicking on New room invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setCreateRoomRootView( + aCreateRoomRootState( + eventSink = eventsRecorder, + ), + onNewRoomClicked = it + ) + rule.clickOn(R.string.screen_create_room_action_create_room) + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on Invite people invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setCreateRoomRootView( + aCreateRoomRootState( + applicationName = "test", + eventSink = eventsRecorder, + ), + onInviteFriendsClicked = it + ) + val text = rule.activity.getString(CommonStrings.action_invite_friends_to_app, "test") + rule.onNodeWithText(text).performClick() + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on a user suggestion invokes the expected callback`() { + val recentDirectRoomList = aRecentDirectRoomList() + val firstRoom = recentDirectRoomList[0] + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnceWithParam(firstRoom.roomId) { + rule.setCreateRoomRootView( + aCreateRoomRootState( + userListState = aUserListState( + recentDirectRooms = recentDirectRoomList + ), + eventSink = eventsRecorder, + ), + onOpenDM = it + ) + rule.onNodeWithText(firstRoom.matrixUser.getBestName()).performClick() + } + } +} + +private fun AndroidComposeTestRule.setCreateRoomRootView( + state: CreateRoomRootState, + onClosePressed: () -> Unit = EnsureNeverCalled(), + onNewRoomClicked: () -> Unit = EnsureNeverCalled(), + onOpenDM: (RoomId) -> Unit = EnsureNeverCalledWithParam(), + onInviteFriendsClicked: () -> Unit = EnsureNeverCalled(), +) { + setContent { + CreateRoomRootView( + state = state, + onClosePressed = onClosePressed, + onNewRoomClicked = onNewRoomClicked, + onOpenDM = onOpenDM, + onInviteFriendsClicked = onInviteFriendsClicked, + ) + } +} From 16d289e1805f94defc54af05f4ac3594c3225c74 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 8 Apr 2024 16:20:42 +0200 Subject: [PATCH 114/124] Exclude `fun ContentToPreview()` from coverage, this is not production code. --- .../impl/timeline/components/TimelineItemReactionsView.kt | 2 ++ .../features/preferences/impl/root/PreferencesRootView.kt | 2 ++ .../roomdetails/impl/members/details/RoomMemberDetailsView.kt | 2 ++ .../libraries/designsystem/atomic/atoms/ElementLogoAtom.kt | 2 ++ .../designsystem/components/preferences/PreferenceText.kt | 2 ++ .../libraries/designsystem/theme/components/SearchBar.kt | 2 ++ 6 files changed, 12 insertions(+) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt index b91699a5ee..ba357ab573 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt @@ -31,6 +31,7 @@ import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -166,6 +167,7 @@ internal fun TimelineItemReactionsViewOutgoingPreview() = ElementPreview { ) } +@ExcludeFromCoverage @Composable private fun ContentToPreview( reactions: ImmutableList, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index c6283158f2..21132df5c9 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -30,6 +30,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.user.UserPreferences +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -212,6 +213,7 @@ internal fun PreferencesRootViewLightPreview(@PreviewParameter(MatrixUserProvide internal fun PreferencesRootViewDarkPreview(@PreviewParameter(MatrixUserProvider::class) matrixUser: MatrixUser) = ElementPreviewDark { ContentToPreview(matrixUser) } +@ExcludeFromCoverage @Composable private fun ContentToPreview(matrixUser: MatrixUser) { PreferencesRootView( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt index 7c75a9e369..01f3ca2ea0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt @@ -33,6 +33,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.roomdetails.impl.R import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.button.BackButton @@ -127,6 +128,7 @@ internal fun RoomMemberDetailsViewLightPreview(@PreviewParameter(RoomMemberDetai internal fun RoomMemberDetailsViewDarkPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) = ElementPreviewDark { ContentToPreview(state) } +@ExcludeFromCoverage @Composable private fun ContentToPreview(state: RoomMemberDetailsState) { RoomMemberDetailsView( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt index 338ad05349..63a5150531 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.R import io.element.android.libraries.designsystem.modifiers.blurCompat import io.element.android.libraries.designsystem.modifiers.blurredShapeShadow @@ -171,6 +172,7 @@ internal fun ElementLogoAtomLargeNoBlurShadowPreview() = ElementPreview { ContentToPreview(ElementLogoAtomSize.Large, useBlurredShadow = false) } +@ExcludeFromCoverage @Composable private fun ContentToPreview(elementLogoAtomSize: ElementLogoAtomSize, useBlurredShadow: Boolean = true) { Box( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt index 33f5e3b6bb..e8a859b54a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceText.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.tooling.preview.Preview 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.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom import io.element.android.libraries.designsystem.components.preferences.components.PreferenceIcon import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -162,6 +163,7 @@ internal fun PreferenceTextWithEndBadgeDarkPreview() = ElementPreviewDark { ContentToPreview(showEndBadge = true) } +@ExcludeFromCoverage @Composable private fun ContentToPreview(showEndBadge: Boolean) { Column( 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 42289ad783..bddf614e68 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 @@ -43,6 +43,7 @@ import androidx.compose.ui.unit.Dp 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.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup @@ -269,6 +270,7 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview { @OptIn(ExperimentalMaterial3Api::class) @Composable +@ExcludeFromCoverage private fun ContentToPreview( query: String = "", active: Boolean = false, From 05ab649b71be953bde6929dcf4640d3a4cd08fce Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 20:05:37 +0000 Subject: [PATCH 115/124] Update peaceiris/actions-gh-pages action to v4 --- .github/workflows/generate_github_pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_github_pages.yml b/.github/workflows/generate_github_pages.yml index 56f5f1219b..3780c7a87d 100644 --- a/.github/workflows/generate_github_pages.yml +++ b/.github/workflows/generate_github_pages.yml @@ -32,7 +32,7 @@ jobs: mkdir -p screenshots/en cp tests/uitests/src/test/snapshots/images/* screenshots/en - name: Deploy GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./screenshots From 63f7defb0704b5b727de7ac94db87b408f18f80e Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 9 Apr 2024 15:08:40 +0200 Subject: [PATCH 116/124] Display members section when there are members in the room, not admins (#2679) * Display members section when there are members in the room, not admins * Update screenshots --------- Co-authored-by: ElementBot --- .../impl/rolesandpermissions/changeroles/ChangeRolesView.kt | 2 +- ...w_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png | 4 ++-- ...null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt index a498456aec..450005ae4a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesView.kt @@ -284,7 +284,7 @@ private fun SearchResultsList( ) } } - if (searchResults.admins.isNotEmpty()) { + if (searchResults.members.isNotEmpty()) { stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_member_list_mode_members)) } items(searchResults.members, key = { it.userId }) { roomMember -> ListMemberItem( diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png index 43f6b4f658..7bf27add4a 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Day-9_10_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e07c6ee5a1e9ebedc98ed8daca1c7e81c877102e24d9cc7a21834ce24df7d1d -size 16758 +oid sha256:5c78aba6e190dd73671503b4fa725c04c9bf23ffc3b7ca953670663e16fda7b7 +size 14607 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png index b05af8072a..4bb4ab76e5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.rolesandpermissions.changeroles_ChangeRolesView_null_ChangeRolesView-Night-9_11_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8188aab8aaa9d7685e6936883e635f097bc3b9b8fdd5bb778bc7baae3f97ea1 -size 16308 +oid sha256:b96e9765dc77cb7a16da3d241b9ee90a7039a5184bf90002b2856aa9a695b446 +size 14304 From 1045f99d18e1a39bd0d834db1c4b62cbd0c504be Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 9 Apr 2024 17:28:12 +0200 Subject: [PATCH 117/124] Add `SessionData.needsVerification` field (#2672) * Add `SessionData.needsVerification`: - Allows us to add a skip button for debug builds. - We can have the verification state almost instantly. - It doesn't depend on network availability to know the verification state and display the UI. * Add DB migration. - Make the skip button in the verification flow skip the whole flow including the completed screen. - Save the session as verified in `RustEncryptionService.recover(recoveryKey)`. * Enforce session verification for existing users too. * Fix verification confirmed screen subtitle (typo in id, was using the wrong string) * Update screenshots --------- Co-authored-by: ElementBot --- .maestro/tests/account/verifySession.yaml | 4 + .../FtueSessionVerificationFlowNode.kt | 8 +- .../ftue/impl/state/DefaultFtueService.kt | 10 +- .../ftue/impl/DefaultFtueServiceTests.kt | 3 +- .../signedout/impl/SignedOutStateProvider.kt | 2 + .../impl/VerifySelfSessionPresenter.kt | 24 +++- .../impl/VerifySelfSessionState.kt | 2 + .../impl/VerifySelfSessionStateProvider.kt | 8 +- .../impl/VerifySelfSessionView.kt | 37 ++++- .../impl/VerifySelfSessionViewEvents.kt | 1 + .../impl/VerifySelfSessionPresenterTests.kt | 78 +++++++++-- .../impl/VerifySelfSessionViewTest.kt | 53 ++++++++ .../SessionVerificationService.kt | 16 +++ .../libraries/matrix/impl/RustMatrixClient.kt | 4 + .../auth/RustMatrixAuthenticationService.kt | 4 +- .../impl/encryption/RustEncryptionService.kt | 7 +- .../libraries/matrix/impl/mapper/Session.kt | 2 + .../RustSessionVerificationService.kt | 128 ++++++++++-------- .../FakeSessionVerificationService.kt | 15 +- .../sessionstorage/api/SessionData.kt | 16 +++ .../sessionstorage/impl/SessionDataMapper.kt | 2 + .../impl/src/main/sqldelight/databases/6.db | Bin 0 -> 12288 bytes .../libraries/matrix/session/SessionData.sq | 4 +- .../impl/src/main/sqldelight/migrations/5.sqm | 4 + .../impl/DatabaseSessionStoreTests.kt | 6 + .../libraries/sessionstorage/impl/Fixtures.kt | 1 + ...ionView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_1,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_2,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_3,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_4,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_5,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_6,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_7,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_8,NEXUS_5,1.0,en].png | 3 + ...nView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_1,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_2,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_3,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_4,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_5,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_6,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_7,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_8,NEXUS_5,1.0,en].png | 3 + 44 files changed, 386 insertions(+), 123 deletions(-) create mode 100644 libraries/session-storage/impl/src/main/sqldelight/databases/6.db create mode 100644 libraries/session-storage/impl/src/main/sqldelight/migrations/5.sqm create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png diff --git a/.maestro/tests/account/verifySession.yaml b/.maestro/tests/account/verifySession.yaml index 5449f4f7da..73090f42da 100644 --- a/.maestro/tests/account/verifySession.yaml +++ b/.maestro/tests/account/verifySession.yaml @@ -7,3 +7,7 @@ appId: ${MAESTRO_APP_ID} - inputText: ${MAESTRO_RECOVERY_KEY} - hideKeyboard - tapOn: "Confirm" +- extendedWaitUntil: + visible: "Device verified" + timeout: 10000 +- tapOn: "Continue" diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt index 53e7b32995..a0f71eef97 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt @@ -19,11 +19,13 @@ package io.element.android.features.ftue.impl.sessionverification import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -33,6 +35,7 @@ import io.element.android.features.verifysession.api.VerifySessionEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.di.SessionScope +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @ContributesNode(SessionScope::class) @@ -83,7 +86,10 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( .params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey)) .callback(object : SecureBackupEntryPoint.Callback { override fun onDone() { - callback.onDone() + lifecycleScope.launch { + // Move to the completed state view in the verification flow + backstack.newRoot(NavTarget.Root) + } } }) .build() diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt index 384d518452..87b7c3a7ee 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt @@ -25,7 +25,6 @@ import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.verification.SessionVerificationService -import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.permissions.api.PermissionStateProvider import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider @@ -56,7 +55,7 @@ class DefaultFtueService @Inject constructor( } init { - sessionVerificationService.sessionVerifiedStatus + sessionVerificationService.needsVerificationFlow .onEach { updateState() } .launchIn(coroutineScope) @@ -99,12 +98,8 @@ class DefaultFtueService @Inject constructor( ).any { it() } } - private fun isSessionVerificationServiceReady(): Boolean { - return sessionVerificationService.sessionVerifiedStatus.value != SessionVerifiedStatus.Unknown - } - private fun isSessionNotVerified(): Boolean { - return sessionVerificationService.sessionVerifiedStatus.value == SessionVerifiedStatus.NotVerified + return sessionVerificationService.needsVerificationFlow.value } private fun needsAnalyticsOptIn(): Boolean { @@ -132,7 +127,6 @@ class DefaultFtueService @Inject constructor( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun updateState() { state.value = when { - !isSessionVerificationServiceReady() -> FtueState.Unknown isAnyStepIncomplete() -> FtueState.Incomplete else -> FtueState.Complete } diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt index f3f21836b9..d381d945a9 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTests.kt @@ -90,6 +90,7 @@ class DefaultFtueServiceTests { fun `traverse flow`() = runTest { val sessionVerificationService = FakeSessionVerificationService().apply { givenVerifiedStatus(SessionVerifiedStatus.NotVerified) + givenNeedsVerification(true) } val analyticsService = FakeAnalyticsService() val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false) @@ -107,7 +108,7 @@ class DefaultFtueServiceTests { // Session verification steps.add(state.getNextStep(steps.lastOrNull())) - sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.Verified) + sessionVerificationService.givenNeedsVerification(false) // Notifications opt in steps.add(state.getNextStep(steps.lastOrNull())) diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt index df3549b0f8..9827d8d720 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt @@ -38,6 +38,7 @@ fun aSignedOutState() = SignedOutState( fun aSessionData( sessionId: SessionId = SessionId("@alice:server.org"), isTokenValid: Boolean = false, + needsVerification: Boolean = false, ): SessionData { return SessionData( userId = sessionId.value, @@ -51,5 +52,6 @@ fun aSessionData( isTokenValid = isTokenValid, loginType = LoginType.UNKNOWN, passphrase = null, + needsVerification = needsVerification, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt index a060469199..ecd263d47d 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt @@ -23,10 +23,14 @@ import androidx.compose.runtime.LaunchedEffect 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 com.freeletics.flowredux.compose.rememberStateAndDispatch import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -35,6 +39,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject import io.element.android.features.verifysession.impl.VerifySelfSessionStateMachine.Event as StateMachineEvent import io.element.android.features.verifysession.impl.VerifySelfSessionStateMachine.State as StateMachineState @@ -43,20 +48,28 @@ class VerifySelfSessionPresenter @Inject constructor( private val sessionVerificationService: SessionVerificationService, private val encryptionService: EncryptionService, private val stateMachine: VerifySelfSessionStateMachine, + private val buildMeta: BuildMeta, ) : Presenter { @Composable override fun present(): VerifySelfSessionState { + val coroutineScope = rememberCoroutineScope() LaunchedEffect(Unit) { // Force reset, just in case the service was left in a broken state sessionVerificationService.reset() } val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() val stateAndDispatch = stateMachine.rememberStateAndDispatch() + var skipVerification by remember { mutableStateOf(false) } + val needsVerification by sessionVerificationService.needsVerificationFlow.collectAsState() val verificationFlowStep by remember { derivedStateOf { - stateAndDispatch.state.value.toVerificationStep( - canEnterRecoveryKey = recoveryState == RecoveryState.INCOMPLETE - ) + when { + skipVerification -> VerifySelfSessionState.VerificationStep.Skipped + needsVerification -> stateAndDispatch.state.value.toVerificationStep( + canEnterRecoveryKey = recoveryState == RecoveryState.INCOMPLETE + ) + else -> VerifySelfSessionState.VerificationStep.Completed + } } } // Start this after observing state machine @@ -72,10 +85,15 @@ class VerifySelfSessionPresenter @Inject constructor( VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge) VerifySelfSessionViewEvents.Cancel -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel) VerifySelfSessionViewEvents.Reset -> stateAndDispatch.dispatchAction(StateMachineEvent.Reset) + VerifySelfSessionViewEvents.SkipVerification -> coroutineScope.launch { + sessionVerificationService.saveVerifiedState(true) + skipVerification = true + } } } return VerifySelfSessionState( verificationFlowStep = verificationFlowStep, + displaySkipButton = buildMeta.isDebuggable, eventSink = ::handleEvents, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt index fa3cb68adf..770915d9e3 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationD @Immutable data class VerifySelfSessionState( val verificationFlowStep: VerificationStep, + val displaySkipButton: Boolean, val eventSink: (VerifySelfSessionViewEvents) -> Unit, ) { @Stable @@ -34,5 +35,6 @@ data class VerifySelfSessionState( data object Ready : VerificationStep data class Verifying(val data: SessionVerificationData, val state: AsyncData) : VerificationStep data object Completed : VerificationStep + data object Skipped : VerificationStep } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt index 59d42f11cd..c69d09acd7 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt @@ -24,7 +24,7 @@ import io.element.android.libraries.matrix.api.verification.VerificationEmoji open class VerifySelfSessionStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aVerifySelfSessionState(), + aVerifySelfSessionState(displaySkipButton = true), aVerifySelfSessionState( verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse ), @@ -46,6 +46,10 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider Unit = {}, ) = VerifySelfSessionState( verificationFlowStep = verificationFlowStep, + displaySkipButton = displaySkipButton, eventSink = eventSink, ) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt index 7a1ad12b09..8709411952 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt @@ -28,8 +28,12 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -51,11 +55,13 @@ 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.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.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep as FlowStep +@OptIn(ExperimentalMaterial3Api::class) @Composable fun VerifySelfSessionView( state: VerifySelfSessionState, @@ -66,6 +72,12 @@ fun VerifySelfSessionView( fun resetFlow() { state.eventSink(VerifySelfSessionViewEvents.Reset) } + val updatedOnFinished by rememberUpdatedState(newValue = onFinished) + LaunchedEffect(state.verificationFlowStep, updatedOnFinished) { + if (state.verificationFlowStep is FlowStep.Skipped) { + updatedOnFinished() + } + } BackHandler { when (state.verificationFlowStep) { is FlowStep.Canceled -> resetFlow() @@ -81,6 +93,19 @@ fun VerifySelfSessionView( val verificationFlowStep = state.verificationFlowStep HeaderFooterPage( modifier = modifier, + topBar = { + TopAppBar( + title = {}, + actions = { + if (state.displaySkipButton && state.verificationFlowStep != FlowStep.Completed) { + TextButton( + text = stringResource(CommonStrings.action_skip), + onClick = { state.eventSink(VerifySelfSessionViewEvents.SkipVerification) } + ) + } + } + ) + }, header = { HeaderContent(verificationFlowStep = verificationFlowStep) }, @@ -104,6 +129,7 @@ private fun HeaderContent(verificationFlowStep: FlowStep) { FlowStep.Canceled -> BigIcon.Style.AlertSolid FlowStep.Ready, is FlowStep.Verifying -> BigIcon.Style.Default(CompoundIcons.Reaction()) FlowStep.Completed -> BigIcon.Style.SuccessSolid + is FlowStep.Skipped -> return } val titleTextId = when (verificationFlowStep) { is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_identity_confirmation_title @@ -114,20 +140,21 @@ private fun HeaderContent(verificationFlowStep: FlowStep) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_title is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_title } + is FlowStep.Skipped -> return } val subtitleTextId = when (verificationFlowStep) { is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse -> R.string.screen_identity_confirmation_subtitle FlowStep.Canceled -> R.string.screen_session_verification_cancelled_subtitle FlowStep.Ready -> R.string.screen_session_verification_ready_subtitle - FlowStep.Completed -> R.string.screen_identity_confirmation_subtitle + FlowStep.Completed -> R.string.screen_identity_confirmed_subtitle is FlowStep.Verifying -> when (verificationFlowStep.data) { is SessionVerificationData.Decimals -> R.string.screen_session_verification_compare_numbers_subtitle is SessionVerificationData.Emojis -> R.string.screen_session_verification_compare_emojis_subtitle } + is FlowStep.Skipped -> return } PageTitle( - modifier = Modifier.padding(top = 60.dp), iconStyle = iconStyle, title = stringResource(id = titleTextId), subtitle = stringResource(id = subtitleTextId) @@ -137,9 +164,8 @@ private fun HeaderContent(verificationFlowStep: FlowStep) { @Composable private fun Content(flowState: FlowStep) { Column(Modifier.fillMaxHeight(), verticalArrangement = Arrangement.Center) { - when (flowState) { - is FlowStep.Initial, FlowStep.AwaitingOtherDeviceResponse, FlowStep.Ready, FlowStep.Canceled, FlowStep.Completed -> Unit - is FlowStep.Verifying -> ContentVerifying(flowState) + if (flowState is FlowStep.Verifying) { + ContentVerifying(flowState) } } } @@ -264,6 +290,7 @@ private fun BottomMenu( onPositiveButtonClicked = onFinished, ) } + is FlowStep.Skipped -> return } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt index 2b86ca6f18..2c6b776f7b 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewEvents.kt @@ -23,4 +23,5 @@ sealed interface VerifySelfSessionViewEvents { data object DeclineVerification : VerifySelfSessionViewEvents data object Cancel : VerifySelfSessionViewEvents data object Reset : VerifySelfSessionViewEvents + data object SkipVerification : VerifySelfSessionViewEvents } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index 025e782e8f..249dbdddab 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt @@ -23,15 +23,19 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.SessionVerificationService +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -48,7 +52,21 @@ class VerifySelfSessionPresenterTests { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + awaitItem().run { + assertThat(verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + assertThat(displaySkipButton).isTrue() + } + } + } + + @Test + fun `present - hides skip verification button on non-debuggable builds`() = runTest { + val buildMeta = aBuildMeta(isDebuggable = false) + val presenter = createVerifySelfSessionPresenter(buildMeta = buildMeta) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitItem().displaySkipButton).isFalse() } } @@ -68,7 +86,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - Handles requestVerification`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -79,7 +97,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - Handles startSasVerification`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -113,7 +131,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - A failure when verifying cancels it`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -130,7 +148,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - A fail when requesting verification resets the state to the initial one`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -145,7 +163,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - Canceling the flow once it's verifying cancels it`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -158,7 +176,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - When verifying, if we receive another challenge we ignore it`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -171,7 +189,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - Restart after cancelation returns to requesting verification`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -188,7 +206,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - Go back after cancelation returns to initial state`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -208,7 +226,7 @@ class VerifySelfSessionPresenterTests { val emojis = listOf( VerificationEmoji(number = 30, emoji = "😀", description = "Smiley") ) - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -230,7 +248,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - When verification is declined, the flow is canceled`() = runTest { - val service = FakeSessionVerificationService() + val service = unverifiedSessionService() val presenter = createVerifySelfSessionPresenter(service) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -247,6 +265,33 @@ class VerifySelfSessionPresenterTests { } } + @Test + fun `present - Skip event skips the flow`() = runTest { + val service = unverifiedSessionService() + val presenter = createVerifySelfSessionPresenter(service) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = requestVerificationAndAwaitVerifyingState(service) + state.eventSink(VerifySelfSessionViewEvents.SkipVerification) + service.saveVerifiedStateResult.assertions().isCalledOnce().with(value(true)) + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Skipped) + } + } + + @Test + fun `present - When verification is not needed, the flow is completed`() = runTest { + val service = FakeSessionVerificationService().apply { + givenNeedsVerification(false) + } + val presenter = createVerifySelfSessionPresenter(service) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Completed) + } + } + private suspend fun ReceiveTurbine.requestVerificationAndAwaitVerifyingState( fakeService: FakeSessionVerificationService, sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()), @@ -271,14 +316,23 @@ class VerifySelfSessionPresenterTests { return state } + private fun unverifiedSessionService(): FakeSessionVerificationService { + return FakeSessionVerificationService().apply { + givenVerifiedStatus(SessionVerifiedStatus.NotVerified) + givenNeedsVerification(true) + } + } + private fun createVerifySelfSessionPresenter( - service: SessionVerificationService = FakeSessionVerificationService(), + service: SessionVerificationService = unverifiedSessionService(), encryptionService: EncryptionService = FakeEncryptionService(), + buildMeta: BuildMeta = aBuildMeta(), ): VerifySelfSessionPresenter { return VerifySelfSessionPresenter( sessionVerificationService = service, encryptionService = encryptionService, stateMachine = VerifySelfSessionStateMachine(service, encryptionService), + buildMeta = buildMeta, ) } } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt index 1ca5bca32f..854ccac8af 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt @@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.ensureCalledOnce @@ -126,6 +127,23 @@ class VerifySelfSessionViewTest { eventsRecorder.assertEmpty() } + @Test + fun `back key pressed - on Completed step does nothing`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed, + eventSink = eventsRecorder + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), + ) + } + rule.pressBackKey() + eventsRecorder.assertEmpty() + } + @Test fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() { val eventsRecorder = EventsRecorder(expectEvents = false) @@ -202,4 +220,39 @@ class VerifySelfSessionViewTest { rule.clickOn(R.string.screen_session_verification_they_dont_match) eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification) } + + @Test + fun `clicking on 'Skip' emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = true), + displaySkipButton = true, + eventSink = eventsRecorder + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = EnsureNeverCalled(), + ) + } + rule.clickOn(CommonStrings.action_skip) + eventsRecorder.assertSingle(VerifySelfSessionViewEvents.SkipVerification) + } + + @Test + fun `on Skipped step - onFinished callback is called immediately`() { + ensureCalledOnce { callback -> + rule.setContent { + VerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Skipped, + displaySkipButton = true, + eventSink = EnsureNeverCalledWithParam(), + ), + onEnterRecoveryKey = EnsureNeverCalled(), + onFinished = callback, + ) + } + } + } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index 8d22fc174e..b82dd23188 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -21,6 +21,17 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow interface SessionVerificationService { + /** + * This flow stores the local verification status of the current session. + * + * We should ideally base the verified status in the Rust SDK info, but there are several issues with that approach: + * + * - The SDK takes a while to report this value, resulting in a delay of 1-2s in displaying the UI. + * - We need to add a 'Skip' option for testing purposes, which would not be possible if we relied only on the SDK. + * - The SDK sometimes doesn't report the verification state if there is no network connection when the app boots. + */ + val needsVerificationFlow: StateFlow + /** * State of the current verification flow ([VerificationFlowState.Initial] if not started). */ @@ -72,6 +83,11 @@ interface SessionVerificationService { * Returns the verification service state to the initial step. */ suspend fun reset() + + /** + * Saves the current session state as [verified]. + */ + suspend fun saveVerifiedState(verified: Boolean) } /** Verification status of the current session. */ 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 cc1b4a0888..f67548afae 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 @@ -150,6 +150,7 @@ class RustMatrixClient( syncService = rustSyncService, sessionCoroutineScope = sessionCoroutineScope, dispatchers = dispatchers, + sessionStore = sessionStore, ) private val roomDirectoryService = RustRoomDirectoryService( @@ -177,6 +178,7 @@ class RustMatrixClient( isTokenValid = false, loginType = existingData.loginType, passphrase = existingData.passphrase, + needsVerification = existingData.needsVerification, ) sessionStore.updateData(newData) Timber.d("Removed session data with token: '...$anonymizedToken'.") @@ -204,6 +206,7 @@ class RustMatrixClient( isTokenValid = true, loginType = existingData.loginType, passphrase = existingData.passphrase, + needsVerification = existingData.needsVerification, ) sessionStore.updateData(newData) Timber.d("Saved new session data with token: '...$anonymizedToken'.") @@ -229,6 +232,7 @@ class RustMatrixClient( client = client, isSyncServiceReady = rustSyncService.syncState.map { it == SyncState.Running }, sessionCoroutineScope = sessionCoroutineScope, + sessionStore = sessionStore, ) private val eventFilters = TimelineConfig.excludedEvents diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 29dd327ae2..ad34e30d18 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -148,6 +148,7 @@ class RustMatrixAuthenticationService @Inject constructor( isTokenValid = true, loginType = LoginType.PASSWORD, passphrase = pendingPassphrase, + needsVerification = true, ) } sessionStore.storeData(sessionData) @@ -195,7 +196,8 @@ class RustMatrixAuthenticationService @Inject constructor( it.session().toSessionData( isTokenValid = true, loginType = LoginType.OIDC, - passphrase = pendingPassphrase + passphrase = pendingPassphrase, + needsVerification = true, ) } pendingOidcAuthenticationData?.close() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index f5a6390989..25881c2174 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.impl.sync.RustSyncService +import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.currentCoroutineContext @@ -48,10 +49,11 @@ import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecover import org.matrix.rustcomponents.sdk.SteadyStateException as RustSteadyStateException internal class RustEncryptionService( - client: Client, + private val client: Client, syncService: RustSyncService, sessionCoroutineScope: CoroutineScope, private val dispatchers: CoroutineDispatchers, + private val sessionStore: SessionStore, ) : EncryptionService { private val service: Encryption = client.encryption() @@ -186,6 +188,9 @@ internal class RustEncryptionService( override suspend fun recover(recoveryKey: String): Result = withContext(dispatchers.io) { runCatching { service.recover(recoveryKey) + val existingSession = sessionStore.getSession(client.userId()) + ?: error("Failed to save verification state. No session with id ${client.userId()}") + sessionStore.updateData(existingSession.copy(needsVerification = false)) }.mapFailure { it.mapRecoveryException() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt index aea838b705..3c1e3c40ec 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt @@ -25,6 +25,7 @@ internal fun Session.toSessionData( isTokenValid: Boolean, loginType: LoginType, passphrase: String?, + needsVerification: Boolean, ) = SessionData( userId = userId, deviceId = deviceId, @@ -37,4 +38,5 @@ internal fun Session.toSessionData( isTokenValid = isTokenValid, loginType = loginType, passphrase = passphrase, + needsVerification = needsVerification, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index bb0821570b..3d2fe0cc40 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.verification +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -23,108 +24,97 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.impl.util.cancelAndDestroy +import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.Encryption import org.matrix.rustcomponents.sdk.RecoveryState import org.matrix.rustcomponents.sdk.RecoveryStateListener import org.matrix.rustcomponents.sdk.SessionVerificationController import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate -import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.VerificationState import org.matrix.rustcomponents.sdk.VerificationStateListener import org.matrix.rustcomponents.sdk.use import timber.log.Timber +import kotlin.time.Duration.Companion.seconds import org.matrix.rustcomponents.sdk.SessionVerificationData as RustSessionVerificationData class RustSessionVerificationService( - client: Client, + private val client: Client, isSyncServiceReady: Flow, - sessionCoroutineScope: CoroutineScope, + private val sessionCoroutineScope: CoroutineScope, + private val sessionStore: SessionStore, ) : SessionVerificationService, SessionVerificationControllerDelegate { - private var verificationStateListenerTaskHandle: TaskHandle? = null - private var recoveryStateListenerTaskHandle: TaskHandle? = null private val encryptionService: Encryption = client.encryption() private lateinit var verificationController: SessionVerificationController + // Listen for changes in verification status and update accordingly + private val verificationStateListenerTaskHandle = encryptionService.verificationStateListener(object : VerificationStateListener { + override fun onUpdate(status: VerificationState) { + Timber.d("New verification state: $status") + updateVerificationStatus(status) + } + }) + + // In case we enter the recovery key instead we check changes in the recovery state, since the listener above won't be triggered + private val recoveryStateListenerTaskHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { + override fun onUpdate(status: RecoveryState) { + Timber.d("New recovery state: $status") + // We could check the `RecoveryState`, but it's easier to just use the verification state directly + updateVerificationStatus(encryptionService.verificationState()) + } + }) + + override val needsVerificationFlow: StateFlow = sessionStore.sessionsFlow() + .map { sessions -> sessions.firstOrNull { it.userId == client.userId() }?.needsVerification.orFalse() } + .distinctUntilChanged() + .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) + private val _verificationFlowState = MutableStateFlow(VerificationFlowState.Initial) override val verificationFlowState = _verificationFlowState.asStateFlow() private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus.asStateFlow() - override val isReady = MutableStateFlow(false) + override val isReady = isSyncServiceReady.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) override val canVerifySessionFlow = combine(sessionVerifiedStatus, isReady) { verificationStatus, isReady -> isReady && verificationStatus == SessionVerifiedStatus.NotVerified } init { - isSyncServiceReady - .onEach { syncServiceReady -> - if (syncServiceReady) { - isReady.value = true - runCatching { - // If the controller was failed to initialize before, we try to get it again - if (!this::verificationController.isInitialized) { - verificationController = client.getSessionVerificationController() - } - } - .onFailure { - isReady.value = false - Timber.e(it, "Failed to get verification controller. Trying again in next sync.") - } - } else { - isReady.value = false - } - } - .launchIn(sessionCoroutineScope) - isReady.onEach { isReady -> if (isReady) { Timber.d("Starting verification service") - // Setup delegate - verificationController.setDelegate(this) - // Immediate status update updateVerificationStatus(encryptionService.verificationState()) - - // Listen for changes in verification status and update accordingly - verificationStateListenerTaskHandle?.cancelAndDestroy() - verificationStateListenerTaskHandle = encryptionService.verificationStateListener(object : VerificationStateListener { - override fun onUpdate(status: VerificationState) { - Timber.d("New verification state: $status") - updateVerificationStatus(status) - } - }) - - // In case we enter the recovery key instead we check changes in the recovery state, since the listener above won't be triggered - recoveryStateListenerTaskHandle?.cancelAndDestroy() - recoveryStateListenerTaskHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { - override fun onUpdate(status: RecoveryState) { - Timber.d("New recovery state: $status") - // We could check the `RecoveryState`, but it's easier to just use the verification state directly - updateVerificationStatus(encryptionService.verificationState()) - } - }) } else { Timber.d("Stopping verification service") - if (this::verificationController.isInitialized) { - verificationController.setDelegate(null) - } } } .launchIn(sessionCoroutineScope) } override suspend fun requestVerification() = tryOrFail { + if (!this::verificationController.isInitialized) { + verificationController = client.getSessionVerificationController() + verificationController.setDelegate(this) + } verificationController.requestVerification() } @@ -164,9 +154,26 @@ class RustSessionVerificationService( } override fun didFinish() { - _verificationFlowState.value = VerificationFlowState.Finished - // Ideally this should be `verificationController?.isVerified().orFalse()` but for some reason it always returns false - updateVerificationStatus(VerificationState.VERIFIED) + sessionCoroutineScope.launch { + // Ideally this should be `verificationController?.isVerified().orFalse()` but for some reason it returns false if run immediately + // It also sometimes unexpectedly fails to report the session as verified, so we have to handle that possibility and fail if needed + runCatching { + withTimeout(30.seconds) { + while (!verificationController.isVerified()) { + delay(100) + } + } + } + .onSuccess { + saveVerifiedState(true) + updateVerificationStatus(VerificationState.VERIFIED) + _verificationFlowState.value = VerificationFlowState.Finished + } + .onFailure { + Timber.e(it, "Verification finished, but the Rust SDK still reports the session as unverified.") + didFail() + } + } } override fun didReceiveVerificationData(data: RustSessionVerificationData) { @@ -188,12 +195,21 @@ class RustSessionVerificationService( _verificationFlowState.value = VerificationFlowState.Initial } + override suspend fun saveVerifiedState(verified: Boolean) = tryOrFail { + val existingSession = sessionStore.getSession(client.userId()) + ?: error("Failed to save verification state. No session with id ${client.userId()}") + sessionStore.updateData(existingSession.copy(needsVerification = !verified)) + // Wait until the new state is saved + needsVerificationFlow.first { needsVerification -> !needsVerification } + } + fun destroy() { Timber.d("Destroying RustSessionVerificationService") - recoveryStateListenerTaskHandle?.cancelAndDestroy() + verificationStateListenerTaskHandle.cancelAndDestroy() + recoveryStateListenerTaskHandle.cancelAndDestroy() if (this::verificationController.isInitialized) { verificationController.setDelegate(null) - (verificationController as? SessionVerificationController)?.destroy() + verificationController.destroy() } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 64c7c8ab97..7823910263 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -20,17 +20,22 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationD import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationFlowState +import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder +import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class FakeSessionVerificationService : SessionVerificationService { +class FakeSessionVerificationService( + var saveVerifiedStateResult: LambdaOneParamRecorder = lambdaRecorder {} +) : SessionVerificationService { private val _isReady = MutableStateFlow(false) private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) private var _verificationFlowState = MutableStateFlow(VerificationFlowState.Initial) private var _canVerifySessionFlow = MutableStateFlow(true) var shouldFail = false + override val needsVerificationFlow: MutableStateFlow = MutableStateFlow(false) override val verificationFlowState: StateFlow = _verificationFlowState override val sessionVerifiedStatus: StateFlow = _sessionVerifiedStatus override val canVerifySessionFlow: Flow = _canVerifySessionFlow @@ -89,7 +94,15 @@ class FakeSessionVerificationService : SessionVerificationService { _isReady.value = value } + fun givenNeedsVerification(value: Boolean) { + needsVerificationFlow.value = value + } + override suspend fun reset() { _verificationFlowState.value = VerificationFlowState.Initial } + + override suspend fun saveVerifiedState(verified: Boolean) { + saveVerifiedStateResult(verified) + } } diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt index 7189442716..dffc1d1bbb 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt @@ -18,16 +18,32 @@ package io.element.android.libraries.sessionstorage.api import java.util.Date +/** + * Data class representing the session data to store locally. + */ data class SessionData( + /** The user ID of the logged in user. */ val userId: String, + /** The device ID of the session. */ val deviceId: String, + /** The current access token of the session. */ val accessToken: String, + /** The optional current refresh token of the session. */ val refreshToken: String?, + /** The homeserver URL of the session. */ val homeserverUrl: String, + /** The Open ID Connect info for this session, if any. */ val oidcData: String?, + /** The Sliding Sync Proxy URL for this session, if any. */ val slidingSyncProxy: String?, + /** The timestamp of the last login. May be `null` in very old sessions. */ val loginTimestamp: Date?, + /** Whether the [accessToken] is valid or not. */ val isTokenValid: Boolean, + /** The login type used to authenticate the session. */ val loginType: LoginType, + /** The optional passphrase used to encrypt data in the SDK local store. */ val passphrase: String?, + /** Whether the session needs verification. */ + val needsVerification: Boolean, ) diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt index 3824def48c..6b79e867d0 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt @@ -34,6 +34,7 @@ internal fun SessionData.toDbModel(): DbSessionData { isTokenValid = if (isTokenValid) 1L else 0L, loginType = loginType.name, passphrase = passphrase, + needsVerification = if (needsVerification) 1L else 0L, ) } @@ -50,5 +51,6 @@ internal fun DbSessionData.toApiModel(): SessionData { isTokenValid = isTokenValid == 1L, loginType = LoginType.fromName(loginType ?: LoginType.UNKNOWN.name), passphrase = passphrase, + needsVerification = needsVerification == 1L, ) } diff --git a/libraries/session-storage/impl/src/main/sqldelight/databases/6.db b/libraries/session-storage/impl/src/main/sqldelight/databases/6.db new file mode 100644 index 0000000000000000000000000000000000000000..8bf785b442728b0ce8c65ba21f79985209ae3320 GIT binary patch literal 12288 zcmeI#&u`N(6bEppAV8I>?Y0xrlX?Nc5Mzup9m}SQFh-kB>{OZfb+t&7VrSKQ=YY8G zFXF%Ai0x3V{8H}YYsqQd_nv=z+8#W7m*|RVQkt2pXjg2CC=xG;gbMEv1Rwwb2tWV=5P$##AOL~?NZ|9M=Nqtp(^on_RoFZ2F4Q_ zdL5rV4q1v9I^*B->X^(jw{lv3agL7VLQE!*B?*I8M~zyY(^&kTb<3d(#833 zS!5@syj+Hyx^{b;q*AO%7vv`Jr>19NpLuz%i&VR*D><7}|2U10;=#YGc0*OERp!|m zmUteLb$!%R>Gro;;9Bk=KJ1N>ly>Sh>-{X}+%WZCn0hX)ollLlP3bQ}dcmyhDH}b} znXEj*&-4Ddou}=Gd#yWL{ca?5q4?5%%H3^#EHA1ujK^y~jPcIez~Afd`TtbMKQ{dX z4FV8=00bZa0SG_<0uX=z1Rwx`n<~)u2gCjUrhdIR7X%;x0SG_<0uX=z1Rwwb2tXhR F`~X5r!8HH? literal 0 HcmV?d00001 diff --git a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq index c33b4d7c7e..56b036ec96 100644 --- a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq +++ b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq @@ -23,7 +23,9 @@ CREATE TABLE SessionData ( isTokenValid INTEGER NOT NULL DEFAULT 1, loginType TEXT, -- added in version 5 - passphrase TEXT + passphrase TEXT, + -- added in version 6 + needsVerification INTEGER NOT NULL DEFAULT 0 ); diff --git a/libraries/session-storage/impl/src/main/sqldelight/migrations/5.sqm b/libraries/session-storage/impl/src/main/sqldelight/migrations/5.sqm new file mode 100644 index 0000000000..22797f1049 --- /dev/null +++ b/libraries/session-storage/impl/src/main/sqldelight/migrations/5.sqm @@ -0,0 +1,4 @@ +-- Migrate DB from version 5 +-- For users migrating previously logged in sessions, we force them to verify them too + +ALTER TABLE SessionData ADD COLUMN needsVerification INTEGER NOT NULL DEFAULT 1; diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt index 46e90f6d52..df35ec5944 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt @@ -144,6 +144,7 @@ class DatabaseSessionStoreTests { isTokenValid = 1, loginType = null, passphrase = "aPassphrase", + needsVerification = 1L, ) val secondSessionData = SessionData( userId = "userId", @@ -157,6 +158,7 @@ class DatabaseSessionStoreTests { isTokenValid = 1, loginType = null, passphrase = "aPassphraseAltered", + needsVerification = 0L, ) assertThat(firstSessionData.userId).isEqualTo(secondSessionData.userId) assertThat(firstSessionData.loginTimestamp).isNotEqualTo(secondSessionData.loginTimestamp) @@ -177,6 +179,7 @@ class DatabaseSessionStoreTests { assertThat(alteredSession.loginTimestamp).isEqualTo(firstSessionData.loginTimestamp) assertThat(alteredSession.oidcData).isEqualTo(secondSessionData.oidcData) assertThat(alteredSession.passphrase).isEqualTo(secondSessionData.passphrase) + assertThat(alteredSession.needsVerification).isEqualTo(secondSessionData.needsVerification) } @Test @@ -193,6 +196,7 @@ class DatabaseSessionStoreTests { isTokenValid = 1, loginType = null, passphrase = "aPassphrase", + needsVerification = 1L, ) val secondSessionData = SessionData( userId = "userIdUnknown", @@ -206,6 +210,7 @@ class DatabaseSessionStoreTests { isTokenValid = 1, loginType = null, passphrase = "aPassphraseAltered", + needsVerification = 0L, ) assertThat(firstSessionData.userId).isNotEqualTo(secondSessionData.userId) @@ -224,5 +229,6 @@ class DatabaseSessionStoreTests { assertThat(notAlteredSession.loginTimestamp).isEqualTo(firstSessionData.loginTimestamp) assertThat(notAlteredSession.oidcData).isEqualTo(firstSessionData.oidcData) assertThat(notAlteredSession.passphrase).isEqualTo(firstSessionData.passphrase) + assertThat(notAlteredSession.needsVerification).isEqualTo(firstSessionData.needsVerification) } } diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt index 341e5e0e92..1397e260b9 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt @@ -31,4 +31,5 @@ internal fun aSessionData() = SessionData( isTokenValid = 1, loginType = LoginType.UNKNOWN.name, passphrase = null, + needsVerification = 0L, ) diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png index 87165509bc..5fe4883b6f 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ef711f98b345d4913e930a4ef2794d1d4ae2f19e29f842548664713dbc2f82c -size 27670 +oid sha256:3fedeb54cfb4b08b3a75dc528b518966938ac9504471741cd9c0bcb90bab08f1 +size 28636 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png index cdd4902766..d22bcffed1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97b2c76523b5475a45d7023960356feee4748ef3be6fa6a33b0f727b327dc667 -size 25541 +oid sha256:761bb174a847aa9fd33f3bbba08473d36fb8319259dbef89d6f907581600042a +size 25323 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png index dddcf52c2f..431dc18371 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f6269ea5f1be9794e768671ba27d44932ce8d7a2d2d599ad2a9837e6346d7ba -size 50761 +oid sha256:c5cd4953883ddc280d12986db679383d69f9fd491e25e6719a309d0e891d6f6b +size 50793 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png index ff7200bbe7..d1d24b44f5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1ba54ee6e97535ca0a350f687ea64e826c1c590e67fddf95ff5e6f85c9ca0ea -size 52342 +oid sha256:50d3fb9d04f37ef7b78c6f2736d627da26e137317720e9b1de8d773e3ca12dd6 +size 52382 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png index eeb7a029bb..5a77351c06 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb6e3df51c248b69357ec7dd3dd8c5ba8bb8c579c8f08e5279039c78d50c541e -size 31024 +oid sha256:909c750c58d01cff00802db2218c52fc7c13f3733996b6c4e5d204daf464ae1e +size 30718 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png index 771889d3c2..45b14790fc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa8ed00e0184ac4636fbf26b42dd10893e1bc9aa64b6107922d7ad97e7304d80 -size 21927 +oid sha256:2994892e230bf79fd9dc6eed9c21854c4ee441fef1b87e65207504de125c0e74 +size 21847 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png index e37ce30fa7..0559f67af1 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae000f0f2e0f81cf75588d800f3b623f875db1dee7d5459abebfb1cbb67d5da1 -size 34937 +oid sha256:0dc7d7114d4365aa6f6a5fd645fd448e6088b2fd618efa49543eb2dbe0a1b394 +size 34779 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png index 87165509bc..042131d574 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ef711f98b345d4913e930a4ef2794d1d4ae2f19e29f842548664713dbc2f82c -size 27670 +oid sha256:92d98ccd5cf7f64cdeb531f0bc4f48589688661a6338de0628c2815b12e59d86 +size 27456 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9bef6f55d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37e571dda745d7d71df2d421651fbed56a8e698583b6b2684633574ffc8c56a9 +size 28880 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png index e2a4d4b4b3..b3cddf5860 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8cb1d19005b68d1f9f09240f58937bdf30efd00bf87758693791bda6627191f -size 25745 +oid sha256:abecf71fc305a7fd44f791ca72072f34601a49dcd1eafd4fbfa34fa58cf3db2a +size 26800 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png index 74ea9fb0bc..2d08d6b799 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1141421ad8fc3ebc2e321a45604dd1d6dc4494a9ae7b860e7853e4a0cd610d5f -size 23697 +oid sha256:55decf810e2d313554710cab91decd8dbd11ee2d18eeeec760f327b0bcc24063 +size 23507 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png index a3bd57d7a0..f3c45f52d3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a1b73e341ce8c0932f1253b9ee159a219e1b64abef54f14b286ac551b2542e4 -size 48479 +oid sha256:4d5458b28ad6ba716752234a8439b72fed9133c1e4b2b3fa3df2fe87f03df6ae +size 48369 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png index 1ccd2010c9..cd697f8dbf 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f06386b912ef02aa34cd1806588c065e76ab1373892cd62ded705537dd593a81 -size 50027 +oid sha256:c7662be2a6450384daa9e3d92c15891e53043b3181c026f4051443fb62334c96 +size 49923 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png index 62977b86c3..472438841e 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:118b4d64a5977842c3d65d27bcf545cef8b5bf62f8417ab66934553fd3985b67 -size 29534 +oid sha256:de24ec97169e51b1d820e2eccdd283e7cc666635159384d8a6e62704b168ed6f +size 29404 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png index e00771d913..e38d40d6fb 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35409dad83ef81e0aaa74a36b350e028d07a5d90cdcc75c29f9156fbdeb2e3c1 -size 20771 +oid sha256:23009d617608b10fe8a4ad6363c0a4feebb49ff160c0bd4c308eda1b2fc258a2 +size 20608 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png index 507ff9cd8e..59ee74b726 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5da97854c7dd24f5f890ff1bcce8e8061892f2a66b919caf254c85ecf1831c38 -size 32717 +oid sha256:971e3b14de6ed22d1c15687fdfe3bd2a56b5665ac250ae868ed7afe427778d1f +size 32510 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png index e2a4d4b4b3..1abc88027b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8cb1d19005b68d1f9f09240f58937bdf30efd00bf87758693791bda6627191f -size 25745 +oid sha256:5ab2e5a17ffb9ae5fa5b62a9562920281af9e2d9d2c51b80fae4c973497d118a +size 25545 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2b9295e65e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c39cda078df3c3ac3297c86d4b3da332f2793b53d62a49ed7b3f6349b9059404 +size 27770 From ecd2974fef23a6d0937e3ae591a9395cf004359d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:43:05 +0200 Subject: [PATCH 118/124] Update dependency io.sentry:sentry-android to v7.8.0 (#2680) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d24ea12e9a..8474ae9c43 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -176,7 +176,7 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0" # Analytics posthog = "com.posthog:posthog-android:3.1.16" -sentry = "io.sentry:sentry-android:7.7.0" +sentry = "io.sentry:sentry-android:7.8.0" # Note: only 0.19.0 will compile properly # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.15.0" From d0f26777da29077c7477f95532f87ab62d3dd4d2 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 9 Apr 2024 17:53:07 +0200 Subject: [PATCH 119/124] Remove Room moderation feature flag (#2678) * Remove the feature flag * Add changelog * Increase login timeout for Maestro --- .../assertSessionVerificationDisplayed.yaml | 2 +- changelog.d/2678.misc | 1 + .../roomdetails/impl/RoomDetailsPresenter.kt | 6 +----- .../impl/members/RoomMemberListPresenter.kt | 13 +------------ .../DefaultRoomMembersModerationPresenter.kt | 8 ++------ .../members/RoomMemberListPresenterTests.kt | 5 ----- .../DefaultRoomMembersModerationPresenterTests.kt | 11 ----------- .../libraries/featureflag/api/FeatureFlags.kt | 7 ------- .../featureflag/impl/StaticFeatureFlagProvider.kt | 1 - 9 files changed, 6 insertions(+), 48 deletions(-) create mode 100644 changelog.d/2678.misc diff --git a/.maestro/tests/assertions/assertSessionVerificationDisplayed.yaml b/.maestro/tests/assertions/assertSessionVerificationDisplayed.yaml index 85785ac968..6690dfddb4 100644 --- a/.maestro/tests/assertions/assertSessionVerificationDisplayed.yaml +++ b/.maestro/tests/assertions/assertSessionVerificationDisplayed.yaml @@ -2,4 +2,4 @@ appId: ${MAESTRO_APP_ID} --- - extendedWaitUntil: visible: "Confirm that it's you" - timeout: 10000 + timeout: 20000 diff --git a/changelog.d/2678.misc b/changelog.d/2678.misc new file mode 100644 index 0000000000..0113a26372 --- /dev/null +++ b/changelog.d/2678.misc @@ -0,0 +1 @@ +Enable room moderation feature. diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 9e4030201a..f0c96e74e2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -78,10 +78,6 @@ class RoomDetailsPresenter @Inject constructor( val roomTopic by remember { derivedStateOf { roomInfo?.topic ?: room.topic } } val isFavorite by remember { derivedStateOf { roomInfo?.isFavorite.orFalse() } } - val isRoomModerationEnabled by produceState(initialValue = false) { - value = featureFlagService.isFeatureEnabled(FeatureFlags.RoomModeration) - } - LaunchedEffect(Unit) { canShowNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings) if (canShowNotificationSettings.value) { @@ -147,7 +143,7 @@ class RoomDetailsPresenter @Inject constructor( leaveRoomState = leaveRoomState, roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), isFavorite = isFavorite, - displayRolesAndPermissionsSettings = isRoomModerationEnabled && !room.isDm && isUserAdmin, + displayRolesAndPermissionsSettings = !room.isDm && isUserAdmin, eventSink = ::handleEvents, ) } 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 c05388bfc7..305393c822 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 @@ -34,8 +34,6 @@ import io.element.android.features.roomdetails.impl.members.moderation.RoomMembe import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState @@ -50,7 +48,6 @@ class RoomMemberListPresenter @AssistedInject constructor( private val room: MatrixRoom, private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, - private val featureFlagService: FeatureFlagService, private val roomMembersModerationPresenter: RoomMembersModerationPresenter, @Assisted private val navigator: RoomMemberListNavigator, ) : Presenter { @@ -74,15 +71,7 @@ class RoomMemberListPresenter @AssistedInject constructor( value = room.canInvite().getOrElse { false } } - val isRoomModerationEnabled by produceState(initialValue = false) { - value = featureFlagService.isFeatureEnabled(FeatureFlags.RoomModeration) - } - - val roomModerationState = if (isRoomModerationEnabled) { - roomMembersModerationPresenter.present() - } else { - remember { roomMembersModerationPresenter.dummyState() } - } + val roomModerationState = roomMembersModerationPresenter.present() // Ensure we load the latest data when entering this screen LaunchedEffect(Unit) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt index ad18c07eb4..f35aa1f211 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/DefaultRoomMembersModerationPresenter.kt @@ -31,8 +31,6 @@ import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.finally import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember @@ -51,7 +49,6 @@ import javax.inject.Inject @ContributesBinding(RoomScope::class) class DefaultRoomMembersModerationPresenter @Inject constructor( private val room: MatrixRoom, - private val featureFlagService: FeatureFlagService, private val dispatchers: CoroutineDispatchers, private val analyticsService: AnalyticsService, ) : RoomMembersModerationPresenter { @@ -61,9 +58,8 @@ class DefaultRoomMembersModerationPresenter @Inject constructor( private suspend fun canKick() = room.canKick().getOrDefault(false) override suspend fun canDisplayModerationActions(): Boolean { - val isRoomModerationEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.RoomModeration) val isDm = room.isDm && room.isEncrypted - return isRoomModerationEnabled && !isDm && (canBan() || canKick()) + return !isDm && (canBan() || canKick()) } @Composable @@ -76,7 +72,7 @@ class DefaultRoomMembersModerationPresenter @Inject constructor( val unbanUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } val canDisplayBannedUsers by produceState(initialValue = false) { - value = featureFlagService.isFeatureEnabled(FeatureFlags.RoomModeration) && !room.isDm && canBan() + value = !room.isDm && canBan() } fun handleEvent(event: RoomMembersModerationEvents) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt index 33f0b3fec0..243b6c0c4d 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt @@ -32,9 +32,6 @@ import io.element.android.features.roomdetails.impl.members.moderation.aRoomMemb import io.element.android.features.roomdetails.members.moderation.FakeRoomMembersModerationPresenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState @@ -241,14 +238,12 @@ private fun TestScope.createPresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), matrixRoom: MatrixRoom = FakeMatrixRoom(), roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers), - featureFlagService: FeatureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.RoomModeration.key to true)), moderationPresenter: FakeRoomMembersModerationPresenter = FakeRoomMembersModerationPresenter(), navigator: RoomMemberListNavigator = object : RoomMemberListNavigator { } ) = RoomMemberListPresenter( room = matrixRoom, roomMemberListDataSource = roomMemberListDataSource, coroutineDispatchers = coroutineDispatchers, - featureFlagService = featureFlagService, roomMembersModerationPresenter = moderationPresenter, navigator = navigator ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTests.kt index 8d49eed353..e972c37ae4 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTests.kt @@ -28,8 +28,6 @@ import io.element.android.features.roomdetails.impl.members.moderation.Moderatio import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.featureflag.api.FeatureFlags -import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState @@ -45,13 +43,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultRoomMembersModerationPresenterTests { - @Test - fun `canDisplayModerationActions - when feature flag is disabled returns false`() = runTest { - val featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.RoomModeration.key to false)) - val presenter = createDefaultRoomMembersModerationPresenter(featureFlagService = featureFlagService) - assertThat(presenter.canDisplayModerationActions()).isFalse() - } - @Test fun `canDisplayModerationActions - when room is DM is false`() = runTest { val room = FakeMatrixRoom(isDirect = true, isPublic = true, isOneToOne = true).apply { @@ -309,13 +300,11 @@ class DefaultRoomMembersModerationPresenterTests { private fun TestScope.createDefaultRoomMembersModerationPresenter( matrixRoom: FakeMatrixRoom = FakeMatrixRoom(), - featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.RoomModeration.key to true)), dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ): DefaultRoomMembersModerationPresenter { return DefaultRoomMembersModerationPresenter( room = matrixRoom, - featureFlagService = featureFlagService, dispatchers = dispatchers, analyticsService = analyticsService, ) diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index 5642eefbd2..765e97f851 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -82,13 +82,6 @@ enum class FeatureFlags( defaultValue = true, isFinished = false, ), - RoomModeration( - key = "feature.roomModeration", - title = "Room moderation", - description = "Add moderation features to the room for users with permissions", - defaultValue = true, - isFinished = false, - ), RoomDirectorySearch( key = "feature.roomdirectorysearch", title = "Room directory search", diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt index e442a1e960..43fc0f0823 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/StaticFeatureFlagProvider.kt @@ -41,7 +41,6 @@ class StaticFeatureFlagProvider @Inject constructor() : FeatureFlags.Mentions -> true FeatureFlags.MarkAsUnread -> true FeatureFlags.RoomListFilters -> true - FeatureFlags.RoomModeration -> false FeatureFlags.RoomDirectorySearch -> false } } else { From cf072fa1e1147cf476ff062847b17702ed70c5ad Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Tue, 9 Apr 2024 19:01:06 +0200 Subject: [PATCH 120/124] Improve session recovery screens (#2657) * Improve enter recovery key screen UI * Add instructions to reset the encryption of the logged in account. * Update screenshots * Fix maestro flow --------- Co-authored-by: ElementBot --- .maestro/tests/account/verifySession.yaml | 2 +- changelog.d/2579.feature | 1 + .../FtueSessionVerificationFlowNode.kt | 37 ++- .../src/main/res/values-be/translations.xml | 7 + .../src/main/res/values-ru/translations.xml | 7 + .../impl/src/main/res/values/localazy.xml | 7 + .../src/main/res/values-sv/translations.xml | 1 - .../src/main/res/values-be/translations.xml | 2 - .../src/main/res/values-cs/translations.xml | 3 - .../src/main/res/values-de/translations.xml | 3 - .../src/main/res/values-hu/translations.xml | 3 - .../src/main/res/values-in/translations.xml | 3 - .../src/main/res/values-it/translations.xml | 2 +- .../src/main/res/values-ru/translations.xml | 3 - .../src/main/res/values-sk/translations.xml | 3 - .../src/main/res/values-it/translations.xml | 5 - .../src/main/res/values-be/translations.xml | 2 - .../src/main/res/values-cs/translations.xml | 2 - .../src/main/res/values-de/translations.xml | 2 - .../src/main/res/values-hu/translations.xml | 2 - .../src/main/res/values-in/translations.xml | 2 - .../src/main/res/values-it/translations.xml | 1 - .../src/main/res/values-ru/translations.xml | 7 +- .../src/main/res/values-sk/translations.xml | 2 - .../api/SecureBackupEntryPoint.kt | 4 + .../securebackup/impl/SecureBackupFlowNode.kt | 8 + .../createkey/CreateNewRecoveryKeyNode.kt | 41 +++ .../createkey/CreateNewRecoveryKeyView.kt | 135 ++++++++++ .../enter/SecureBackupEnterRecoveryKeyView.kt | 2 +- .../src/main/res/values-be/translations.xml | 9 +- .../src/main/res/values-cs/translations.xml | 9 +- .../src/main/res/values-de/translations.xml | 22 +- .../src/main/res/values-hu/translations.xml | 7 + .../src/main/res/values-in/translations.xml | 7 + .../src/main/res/values-ru/translations.xml | 13 +- .../src/main/res/values-sk/translations.xml | 7 + .../impl/src/main/res/values/localazy.xml | 9 +- .../api/VerifySessionEntryPoint.kt | 1 + .../impl/VerifySelfSessionNode.kt | 17 +- .../impl/VerifySelfSessionPresenter.kt | 2 +- .../impl/VerifySelfSessionState.kt | 2 +- .../impl/VerifySelfSessionStateProvider.kt | 22 +- .../impl/VerifySelfSessionView.kt | 25 +- .../impl/VerifySelfSessionPresenterTests.kt | 29 +- .../impl/VerifySelfSessionViewTest.kt | 249 +++++++++--------- .../modifiers/SquareSizeModifier.kt | 188 +++++++++++++ .../src/main/res/values-be/translations.xml | 9 +- .../src/main/res/values-cs/translations.xml | 7 - .../src/main/res/values-de/translations.xml | 20 -- .../src/main/res/values-hu/translations.xml | 7 - .../src/main/res/values-in/translations.xml | 7 - .../src/main/res/values-ru/translations.xml | 11 - .../src/main/res/values-sk/translations.xml | 7 - .../src/main/res/values/localazy.xml | 7 - ...ryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png | 3 + ...KeyView-Night-0_2_null,NEXUS_5,1.0,en].png | 3 + ...leView-Day-1_2_null_0,NEXUS_5,1.0,en].png} | 0 ...leView-Day-1_2_null_1,NEXUS_5,1.0,en].png} | 0 ...leView-Day-1_2_null_2,NEXUS_5,1.0,en].png} | 0 ...leView-Day-1_2_null_3,NEXUS_5,1.0,en].png} | 0 ...View-Night-1_3_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Night-1_3_null_1,NEXUS_5,1.0,en].png} | 0 ...View-Night-1_3_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Night-1_3_null_3,NEXUS_5,1.0,en].png} | 0 ...leView-Day-2_3_null_0,NEXUS_5,1.0,en].png} | 0 ...leView-Day-2_3_null_1,NEXUS_5,1.0,en].png} | 0 ...leView-Day-2_3_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Night-2_4_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Night-2_4_null_1,NEXUS_5,1.0,en].png} | 0 ...View-Night-2_4_null_2,NEXUS_5,1.0,en].png} | 0 ...KeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png | 3 - ...KeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png | 3 - ...KeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png | 3 - ...KeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png | 3 - ...KeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png | 3 + ...KeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png | 3 + ...KeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png | 3 + ...KeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png | 3 + ...yView-Night-2_4_null_0,NEXUS_5,1.0,en].png | 3 - ...yView-Night-2_4_null_1,NEXUS_5,1.0,en].png | 3 - ...yView-Night-2_4_null_2,NEXUS_5,1.0,en].png | 3 - ...yView-Night-2_4_null_3,NEXUS_5,1.0,en].png | 3 - ...yView-Night-3_5_null_0,NEXUS_5,1.0,en].png | 3 + ...yView-Night-3_5_null_1,NEXUS_5,1.0,en].png | 3 + ...yView-Night-3_5_null_2,NEXUS_5,1.0,en].png | 3 + ...yView-Night-3_5_null_3,NEXUS_5,1.0,en].png | 3 + ...otView-Day-4_5_null_0,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_1,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_2,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_3,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_4,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_5,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_6,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_7,NEXUS_5,1.0,en].png} | 0 ...otView-Day-4_5_null_8,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_1,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_3,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_4,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_5,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_6,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_7,NEXUS_5,1.0,en].png} | 0 ...View-Night-4_6_null_8,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-6_7_null_10,NEXUS_5,1.0,en].png | 3 - ...eyView-Day-6_7_null_11,NEXUS_5,1.0,en].png | 3 - ...KeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png | 3 - ...KeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png | 3 - ...eyView-Day-7_8_null_0,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-7_8_null_1,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-7_8_null_10,NEXUS_5,1.0,en].png | 3 + ...eyView-Day-7_8_null_11,NEXUS_5,1.0,en].png | 3 + ...eyView-Day-7_8_null_2,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-7_8_null_3,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-7_8_null_4,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-7_8_null_5,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-7_8_null_6,NEXUS_5,1.0,en].png} | 0 ...eyView-Day-7_8_null_7,NEXUS_5,1.0,en].png} | 0 ...KeyView-Day-7_8_null_8,NEXUS_5,1.0,en].png | 3 + ...KeyView-Day-7_8_null_9,NEXUS_5,1.0,en].png | 3 + ...View-Night-6_8_null_10,NEXUS_5,1.0,en].png | 3 - ...View-Night-6_8_null_11,NEXUS_5,1.0,en].png | 3 - ...yView-Night-6_8_null_8,NEXUS_5,1.0,en].png | 3 - ...yView-Night-6_8_null_9,NEXUS_5,1.0,en].png | 3 - ...View-Night-7_9_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Night-7_9_null_1,NEXUS_5,1.0,en].png} | 0 ...View-Night-7_9_null_10,NEXUS_5,1.0,en].png | 3 + ...View-Night-7_9_null_11,NEXUS_5,1.0,en].png | 3 + ...View-Night-7_9_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Night-7_9_null_3,NEXUS_5,1.0,en].png} | 0 ...View-Night-7_9_null_4,NEXUS_5,1.0,en].png} | 0 ...View-Night-7_9_null_5,NEXUS_5,1.0,en].png} | 0 ...View-Night-7_9_null_6,NEXUS_5,1.0,en].png} | 0 ...View-Night-7_9_null_7,NEXUS_5,1.0,en].png} | 0 ...yView-Night-7_9_null_8,NEXUS_5,1.0,en].png | 3 + ...yView-Night-7_9_null_9,NEXUS_5,1.0,en].png | 3 + ...Change-Day-6_7_null_0,NEXUS_5,1.0,en].png} | 0 ...Change-Day-6_7_null_1,NEXUS_5,1.0,en].png} | 0 ...Change-Day-6_7_null_2,NEXUS_5,1.0,en].png} | 0 ...Change-Day-6_7_null_3,NEXUS_5,1.0,en].png} | 0 ...Change-Day-6_7_null_4,NEXUS_5,1.0,en].png} | 0 ...ange-Night-6_8_null_0,NEXUS_5,1.0,en].png} | 0 ...ange-Night-6_8_null_1,NEXUS_5,1.0,en].png} | 0 ...ange-Night-6_8_null_2,NEXUS_5,1.0,en].png} | 0 ...ange-Night-6_8_null_3,NEXUS_5,1.0,en].png} | 0 ...ange-Night-6_8_null_4,NEXUS_5,1.0,en].png} | 0 ...upView-Day-5_6_null_0,NEXUS_5,1.0,en].png} | 0 ...upView-Day-5_6_null_1,NEXUS_5,1.0,en].png} | 0 ...upView-Day-5_6_null_2,NEXUS_5,1.0,en].png} | 0 ...upView-Day-5_6_null_3,NEXUS_5,1.0,en].png} | 0 ...upView-Day-5_6_null_4,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_0,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_1,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_2,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_3,NEXUS_5,1.0,en].png} | 0 ...View-Night-5_7_null_4,NEXUS_5,1.0,en].png} | 0 ...ionView-Day-0_1_null_8,NEXUS_5,1.0,en].png | 4 +- ...ionView-Day-0_1_null_9,NEXUS_5,1.0,en].png | 3 + ...nView-Night-0_2_null_8,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_9,NEXUS_5,1.0,en].png | 3 + ...ierInsideSquare_0_null,NEXUS_5,1.0,en].png | 3 + ...fierLargeHeight_0_null,NEXUS_5,1.0,en].png | 3 + ...ifierLargeWidth_0_null,NEXUS_5,1.0,en].png | 3 + tools/localazy/config.json | 6 +- 164 files changed, 765 insertions(+), 358 deletions(-) create mode 100644 changelog.d/2579.feature delete mode 100644 features/roomdirectory/impl/src/main/res/values-it/translations.xml create mode 100644 features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyNode.kt create mode 100644 features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png} (100%) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_7,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_8,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_8,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_7,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_8,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_8,NEXUS_5,1.0,en].png} (100%) delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_1,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_10,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_11,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_7,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_8,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_9,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png delete mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_1,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_10,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_11,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_5,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_5,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_6,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_6,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_7,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_7,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_8,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_9,NEXUS_5,1.0,en].png rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_0,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_1,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_2,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_3,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png} (100%) rename tests/uitests/src/test/snapshots/images/{ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_4,NEXUS_5,1.0,en].png => ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png} (100%) create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_9,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_9,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierInsideSquare_null_SquareSizeModifierInsideSquare_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeHeight_null_SquareSizeModifierLargeHeight_0_null,NEXUS_5,1.0,en].png create mode 100644 tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeWidth_null_SquareSizeModifierLargeWidth_0_null,NEXUS_5,1.0,en].png diff --git a/.maestro/tests/account/verifySession.yaml b/.maestro/tests/account/verifySession.yaml index 73090f42da..efc45ac3ef 100644 --- a/.maestro/tests/account/verifySession.yaml +++ b/.maestro/tests/account/verifySession.yaml @@ -6,7 +6,7 @@ appId: ${MAESTRO_APP_ID} id: "verification-recovery_key" - inputText: ${MAESTRO_RECOVERY_KEY} - hideKeyboard -- tapOn: "Confirm" +- tapOn: "Continue" - extendedWaitUntil: visible: "Device verified" timeout: 10000 diff --git a/changelog.d/2579.feature b/changelog.d/2579.feature new file mode 100644 index 0000000000..19cda6dc8e --- /dev/null +++ b/changelog.d/2579.feature @@ -0,0 +1 @@ +Move session recovery to the login flow. diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt index a0f71eef97..f59c76199a 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt @@ -58,13 +58,27 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( @Parcelize data object EnterRecoveryKey : NavTarget + + @Parcelize + data object CreateNewRecoveryKey : NavTarget } interface Callback : Plugin { fun onDone() } - private val callback = plugins().first() + private val secureBackupEntryPointCallback = object : SecureBackupEntryPoint.Callback { + override fun onCreateNewRecoveryKey() { + backstack.push(NavTarget.CreateNewRecoveryKey) + } + + override fun onDone() { + lifecycleScope.launch { + // Move to the completed state view in the verification flow + backstack.newRoot(NavTarget.Root) + } + } + } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { @@ -75,8 +89,12 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( backstack.push(NavTarget.EnterRecoveryKey) } + override fun onCreateNewRecoveryKey() { + backstack.push(NavTarget.CreateNewRecoveryKey) + } + override fun onDone() { - callback.onDone() + plugins().forEach { it.onDone() } } }) .build() @@ -84,14 +102,13 @@ class FtueSessionVerificationFlowNode @AssistedInject constructor( is NavTarget.EnterRecoveryKey -> { secureBackupEntryPoint.nodeBuilder(this, buildContext) .params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey)) - .callback(object : SecureBackupEntryPoint.Callback { - override fun onDone() { - lifecycleScope.launch { - // Move to the completed state view in the verification flow - backstack.newRoot(NavTarget.Root) - } - } - }) + .callback(secureBackupEntryPointCallback) + .build() + } + is NavTarget.CreateNewRecoveryKey -> { + secureBackupEntryPoint.nodeBuilder(this, buildContext) + .params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.CreateNewRecoveryKey)) + .callback(secureBackupEntryPointCallback) .build() } } diff --git a/features/ftue/impl/src/main/res/values-be/translations.xml b/features/ftue/impl/src/main/res/values-be/translations.xml index 413bad9132..0b46417dd3 100644 --- a/features/ftue/impl/src/main/res/values-be/translations.xml +++ b/features/ftue/impl/src/main/res/values-be/translations.xml @@ -2,6 +2,13 @@ "Вы можаце змяніць налады пазней." "Дазвольце апавяшчэнні і ніколі не прапускайце іх" + "Адкрыйце Element на настольнай прыладзе" + "Націсніце на свой аватар" + "Выберыце %1$s" + "“Звязаць новую прыладу”" + "Выберыце %1$s" + "“Паказаць QR-код”" + "Адкрыйце Element на іншай прыладзе, каб атрымаць QR-код" "Званкі, апытанні, пошук і многае іншае будзе дададзена пазней у гэтым годзе." "Гісторыя паведамленняў для зашыфраваных пакояў пакуль недаступна." "Мы будзем рады пачуць вашае меркаванне, паведаміце нам аб гэтым праз старонку налад." diff --git a/features/ftue/impl/src/main/res/values-ru/translations.xml b/features/ftue/impl/src/main/res/values-ru/translations.xml index 6ddceef57f..895c7068ff 100644 --- a/features/ftue/impl/src/main/res/values-ru/translations.xml +++ b/features/ftue/impl/src/main/res/values-ru/translations.xml @@ -2,6 +2,13 @@ "Вы можете изменить настройки позже." "Разрешите уведомления и никогда не пропустите сообщение" + "Откройте Element на настольном устройстве" + "Нажмите на свое изображение" + "Выбрать %1$s" + "\"Привязать новое устройство\"" + "Выбрать %1$s" + "\"Показать QR-код\"" + "Откройте Element на другом устройстве, чтобы получить QR-код" "Звонки, опросы, поиск и многое другое будут добавлены позже в этом году." "История сообщений для зашифрованных комнат в этом обновлении будет недоступна." "Мы будем рады услышать ваше мнение, сообщите нам об этом через страницу настроек." diff --git a/features/ftue/impl/src/main/res/values/localazy.xml b/features/ftue/impl/src/main/res/values/localazy.xml index 3e8c86b761..ba227878c1 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -2,6 +2,13 @@ "You can change your settings later." "Allow notifications and never miss a message" + "Open Element on a desktop device" + "Click on your avatar" + "Select %1$s" + "“Link new device”" + "Select %1$s" + "“Show QR code”" + "Open Element on another device to get the QR code" "Calls, polls, search and more will be added later this year." "Message history for encrypted rooms isn’t available yet." "We’d love to hear from you, let us know what you think via the settings page." diff --git a/features/lockscreen/impl/src/main/res/values-sv/translations.xml b/features/lockscreen/impl/src/main/res/values-sv/translations.xml index f8c420da9d..c0ffcd9cdf 100644 --- a/features/lockscreen/impl/src/main/res/values-sv/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-sv/translations.xml @@ -3,7 +3,6 @@ "Byt PIN-kod" "Tillåt biometrisk upplåsning" "Ta bort PIN-kod" - "Är du säker på att du vill ta bort PIN-koden?" "Ta bort PIN-koden?" "Loggar ut …" diff --git a/features/roomdetails/impl/src/main/res/values-be/translations.xml b/features/roomdetails/impl/src/main/res/values-be/translations.xml index 7980d6d41b..be2ac558fd 100644 --- a/features/roomdetails/impl/src/main/res/values-be/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-be/translations.xml @@ -31,7 +31,6 @@ "Панізіць сябе?" "%1$s (У чаканні)" "(У чаканні)" - "Адміністратары аўтаматычна маюць права мадэратара" "Рэдагаваць мадэратараў" "Адміністратары" "Мадэратары" @@ -59,7 +58,6 @@ "Назва пакоя" "Бяспека" "Падзяліцца пакоем" - "Інфармацыя аб пакоі" "Тэма" "Ідзе абнаўленне пакоя…" "Заблакіраваць" diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index f44cdd210a..93e7fcf389 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -30,8 +30,6 @@ "Tuto změnu nebudete moci vrátit zpět, protože sami degradujete, pokud jste posledním privilegovaným uživatelem v místnosti, nebude možné znovu získat oprávnění." "Degradovat se?" "%1$s (čekající)" - "(Čeká na vyřízení)" - "Správci mají automaticky oprávnění moderátora" "Upravit moderátory" "Správci" "Moderátoři" @@ -59,7 +57,6 @@ "Název místnosti" "Zabezpečení" "Sdílet místnost" - "Informace o místnosti" "Téma" "Aktualizace místnosti…" "Vykázat" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index 025e07a8bd..980f149bad 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -30,8 +30,6 @@ "Du stufst dich selbst herab. Diese Änderung kann nicht rückgängig gemacht werden. Wenn du der letzte Benutzer mit dieser Rolle bist, ist es nicht möglich, diese Rolle wiederzuerlangen." "Möchtest Du Dich selbst herabstufen?" "%1$s (Ausstehend)" - "(Ausstehend)" - "Administratoren haben automatisch Moderatorenrechte" "Moderatoren bearbeiten" "Administratoren" "Moderatoren" @@ -59,7 +57,6 @@ "Raumname" "Sicherheit" "Raum teilen" - "Raum Informationen" "Thema" "Raum wird aktualisiert…" "Sperren" 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 6cbbe35d90..66c05c38a7 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -30,8 +30,6 @@ "Ezt a változtatást nem fogja tudni visszavonni, mivel lefokozza magát, ha Ön az utolsó jogosultságokkal rendelkező felhasználó a szobában, akkor lehetetlen lesz visszaszerezni a jogosultságokat." "Lefokozza magát?" "%1$s (függőben)" - "(Függőben)" - "Az adminisztrátorok automatikusan moderátori jogosultságokkal rendelkeznek" "Moderátorok szerkesztése" "Rendszergazdák" "Moderátorok" @@ -59,7 +57,6 @@ "Szoba neve" "Biztonság" "Szoba megosztása" - "Szobainformációk" "Téma" "Szoba frissítése…" "Kitiltás" 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 c9aedeefd1..ddfd4859ea 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -30,8 +30,6 @@ "Anda tidak akan dapat mengurungkan perubahan ini karena Anda sedang menurunkan Anda sendiri, jika Anda merupakan pengguna dengan hak khusus dalam ruangan maka tidak akan memungkinkan untuk mendapatkan hak tersebut lagi." "Turunkan Anda sendiri?" "%1$s (Tertunda)" - "(Tertunda)" - "Admin secara otomatis memiliki hak moderator" "Sunting Moderator" "Admin" "Moderator" @@ -59,7 +57,6 @@ "Nama ruangan" "Keamanan" "Bagikan ruangan" - "Info ruangan" "Topik" "Memperbarui ruangan…" "Cekal" diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml index 8326d592e8..77b46cdeda 100644 --- a/features/roomdetails/impl/src/main/res/values-it/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -11,7 +11,7 @@ "Sondaggi" "Solo amministratori" "Escludi membri" - "Rimuovi messaggi" + "Eliminare messaggi" "Tutti" "Invitare persone" "Moderazione dei membri" 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 9e6b963efb..200b2ef3c7 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -30,8 +30,6 @@ "Вы не сможете отменить это изменение, так как понижаете себя статус. Если вы являетесь последним привилегированным пользователем в комнате, восстановить привилегии будет невозможно." "Понизить свой уровень?" "%1$s (Ожидание)" - "(В ожидании)" - "Администраторы автоматически получают права модератора" "Редактировать роль модераторов" "Администраторы" "Модераторы" @@ -59,7 +57,6 @@ "Название комнаты" "Безопасность" "Поделиться комнатой" - "Информация о комнате" "Тема" "Обновление комнаты…" "Заблокировать" 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 7feb1d4e41..c85b04017c 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -30,8 +30,6 @@ "Túto zmenu nebudete môcť vrátiť späť, pretože znižujete svoju úroveň. Ak ste posledným privilegovaným používateľom v miestnosti, nebude možné získať znova oprávnenia." "Znížiť svoju úroveň?" "%1$s (Čaká sa)" - "(Čaká sa)" - "Správcovia majú automaticky oprávnenia moderátora" "Upraviť moderátorov" "Správcovia" "Moderátori" @@ -59,7 +57,6 @@ "Názov miestnosti" "Bezpečnosť" "Zdieľať miestnosť" - "Informácie o miestnosti" "Téma" "Aktualizácia miestnosti…" "Zakázať" diff --git a/features/roomdirectory/impl/src/main/res/values-it/translations.xml b/features/roomdirectory/impl/src/main/res/values-it/translations.xml deleted file mode 100644 index 04a9602d78..0000000000 --- a/features/roomdirectory/impl/src/main/res/values-it/translations.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - "Caricamento fallito" - "Elenco delle stanze" - diff --git a/features/roomlist/impl/src/main/res/values-be/translations.xml b/features/roomlist/impl/src/main/res/values-be/translations.xml index 8da1365931..39cbd31bc6 100644 --- a/features/roomlist/impl/src/main/res/values-be/translations.xml +++ b/features/roomlist/impl/src/main/res/values-be/translations.xml @@ -11,8 +11,6 @@ "Дадаць чат у абранае можна ў наладах чата. На дадзены момант вы можаце прыбраць фільтры, каб убачыць іншыя вашыя чаты." "У вас пакуль няма абраных чатаў" - "Запрашэнні" - "У вас няма непрынятых запрашэнняў." "Нізкі прыярытэт" "Вы можаце прыбраць фільтры, каб убачыць іншыя вашыя чаты." "У вас няма чатаў для гэтай катэгорыі" diff --git a/features/roomlist/impl/src/main/res/values-cs/translations.xml b/features/roomlist/impl/src/main/res/values-cs/translations.xml index 3a91eb04dc..417e64863b 100644 --- a/features/roomlist/impl/src/main/res/values-cs/translations.xml +++ b/features/roomlist/impl/src/main/res/values-cs/translations.xml @@ -11,8 +11,6 @@ "V nastavení chatu můžete přidat chat k oblíbeným. Prozatím můžete zrušit výběr filtrů, abyste viděli své další chaty" "Zatím nemáte oblíbené chaty" - "Pozvánky" - "Nemáte žádné nevyřízené pozvánky." "Nízká priorita" "Můžete zrušit výběr filtrů, abyste viděli své další chaty" "Nemáte chaty pro tento výběr" diff --git a/features/roomlist/impl/src/main/res/values-de/translations.xml b/features/roomlist/impl/src/main/res/values-de/translations.xml index d02870ccd1..a2cf494b1b 100644 --- a/features/roomlist/impl/src/main/res/values-de/translations.xml +++ b/features/roomlist/impl/src/main/res/values-de/translations.xml @@ -11,8 +11,6 @@ "In den Chat-Einstellungen kannst du einen Chat als Favorit hinzufügen. Um deine anderen Chats zu sehen wähle diesen Filter ab." "Du hast noch keine Chats als Favorit markiert." - "Einladungen" - "Du hast keine ausstehenden Einladungen." "Niedrige Priorität" "Wähle Filter ab, um Deine Chats zu sehen." "Du hast keine Chats für diese Auswahl" diff --git a/features/roomlist/impl/src/main/res/values-hu/translations.xml b/features/roomlist/impl/src/main/res/values-hu/translations.xml index e25c493b13..559a481246 100644 --- a/features/roomlist/impl/src/main/res/values-hu/translations.xml +++ b/features/roomlist/impl/src/main/res/values-hu/translations.xml @@ -11,8 +11,6 @@ "A csevegési beállításokban csevegéseket adhat hozzá a kedvencekhez. Egyelőre törölheti a szűrőket a többi csevegés megtekintéséhez." "Még nincsenek kedvenc csevegései" - "Meghívások" - "Nincsenek függőben lévő meghívásai." "Alacsony prioritás" "Kikapcsolhatja a szűrőket a többi csevegés megtekintéséhez" "Ehhez a kiválasztáshoz nem tartoznak csevegések" diff --git a/features/roomlist/impl/src/main/res/values-in/translations.xml b/features/roomlist/impl/src/main/res/values-in/translations.xml index 8f194e1e34..74c90c1319 100644 --- a/features/roomlist/impl/src/main/res/values-in/translations.xml +++ b/features/roomlist/impl/src/main/res/values-in/translations.xml @@ -11,8 +11,6 @@ "Anda dapat menambahkan percakapan ke favorit Anda dalam pengaturan percakapan. Untuk sementara, Anda dapat membatalkan pilihan saringan untuk melihat percakapan Anda yang lain" "Anda belum memiliki percakapan favorit" - "Undangan" - "Anda tidak memiliki undangan yang tertunda." "Prioritas Rendah" "Anda dapat membatalkan pilihan saringan untuk melihat percakapan Anda yang lain" "Anda tidak memiliki percakapan untuk pemilihan ini" diff --git a/features/roomlist/impl/src/main/res/values-it/translations.xml b/features/roomlist/impl/src/main/res/values-it/translations.xml index 05e41594a2..cd1261f3b3 100644 --- a/features/roomlist/impl/src/main/res/values-it/translations.xml +++ b/features/roomlist/impl/src/main/res/values-it/translations.xml @@ -24,7 +24,6 @@ Non hai messaggi non letti!" "Tutte le conversazioni" "Segna come letto" "Segna come non letto" - "Sfoglia tutte le stanze" "Sembra che tu stia usando un nuovo dispositivo. Verificati con un altro dispositivo per accedere ai tuoi messaggi cifrati." "Verifica che sei tu" diff --git a/features/roomlist/impl/src/main/res/values-ru/translations.xml b/features/roomlist/impl/src/main/res/values-ru/translations.xml index 68cb98436d..d18fc1fd03 100644 --- a/features/roomlist/impl/src/main/res/values-ru/translations.xml +++ b/features/roomlist/impl/src/main/res/values-ru/translations.xml @@ -1,10 +1,7 @@ "В настоящее время резервная копия вашего чата не синхронизирована. Требуется подтвердить вашим ключом восстановления, чтобы сохранить доступ к резервной копии чата." - - "Введите " - "ключ восстановления" - + "Подтвердите ключ восстановления" "Это одноразовый процесс, спасибо, что подождали." "Настройка учетной записи." "Создайте новую беседу или комнату" @@ -14,8 +11,6 @@ "Добавить чат в избранное можно в настройках чата. На данный момент вы можете убрать фильтры, чтобы увидеть другие ваши чаты." "У вас пока нет избранных чатов" - "Приглашает" - "У вас нет отложенных приглашений." "Низкий приоритет" "Вы можете убрать фильтры, чтобы увидеть другие ваши чаты." "У вас нет чатов для этой подборки" diff --git a/features/roomlist/impl/src/main/res/values-sk/translations.xml b/features/roomlist/impl/src/main/res/values-sk/translations.xml index 101df1792d..08320d1c30 100644 --- a/features/roomlist/impl/src/main/res/values-sk/translations.xml +++ b/features/roomlist/impl/src/main/res/values-sk/translations.xml @@ -11,8 +11,6 @@ "Môžete pridať konverzáciu medzi obľúbené v nastaveniach konverzácie. Zatiaľ môžete zrušiť výber filtrov, aby ste videli ostatné konverzácie" "Zatiaľ nemáte obľúbené konverzácie" - "Pozvánky" - "Nemáte žiadne čakajúce pozvánky." "Nízka priorita" "Môžete zrušiť výber filtrov, aby ste videli svoje ostatné konverzácie" "Nemáte konverzácie pre tento výber" diff --git a/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt b/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt index 8a00063fef..1904ceb2ff 100644 --- a/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt +++ b/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt @@ -31,6 +31,9 @@ interface SecureBackupEntryPoint : FeatureEntryPoint { @Parcelize data object EnterRecoveryKey : InitialTarget + + @Parcelize + data object CreateNewRecoveryKey : InitialTarget } data class Params(val initialElement: InitialTarget) : NodeInputs @@ -38,6 +41,7 @@ interface SecureBackupEntryPoint : FeatureEntryPoint { fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface Callback : Plugin { + fun onCreateNewRecoveryKey() fun onDone() } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt index b698d2719f..1f22e57c03 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt @@ -30,6 +30,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.features.securebackup.api.SecureBackupEntryPoint +import io.element.android.features.securebackup.impl.createkey.CreateNewRecoveryKeyNode import io.element.android.features.securebackup.impl.disable.SecureBackupDisableNode import io.element.android.features.securebackup.impl.enable.SecureBackupEnableNode import io.element.android.features.securebackup.impl.enter.SecureBackupEnterRecoveryKeyNode @@ -50,6 +51,7 @@ class SecureBackupFlowNode @AssistedInject constructor( initialElement = when (plugins.filterIsInstance(SecureBackupEntryPoint.Params::class.java).first().initialElement) { SecureBackupEntryPoint.InitialTarget.Root -> NavTarget.Root SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey -> NavTarget.EnterRecoveryKey + SecureBackupEntryPoint.InitialTarget.CreateNewRecoveryKey -> NavTarget.CreateNewRecoveryKey }, savedStateMap = buildContext.savedStateMap, ), @@ -74,6 +76,9 @@ class SecureBackupFlowNode @AssistedInject constructor( @Parcelize data object EnterRecoveryKey : NavTarget + + @Parcelize + data object CreateNewRecoveryKey : NavTarget } private val callback = plugins().firstOrNull() @@ -134,6 +139,9 @@ class SecureBackupFlowNode @AssistedInject constructor( } createNode(buildContext, plugins = listOf(callback)) } + NavTarget.CreateNewRecoveryKey -> { + createNode(buildContext) + } } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyNode.kt new file mode 100644 index 0000000000..df1e2d9528 --- /dev/null +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyNode.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.securebackup.impl.createkey + +import androidx.compose.runtime.Composable +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 dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +class CreateNewRecoveryKeyNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : Node(buildContext, plugins = plugins) { + @Composable + override fun View(modifier: Modifier) { + CreateNewRecoveryKeyView( + modifier = modifier, + onBackClicked = ::navigateUp, + ) + } +} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt new file mode 100644 index 0000000000..ed3a5cd339 --- /dev/null +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/createkey/CreateNewRecoveryKeyView.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.securebackup.impl.createkey + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +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.securebackup.impl.R +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.PageTitle +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.modifiers.squareSize +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +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.TopAppBar + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CreateNewRecoveryKeyView( + onBackClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + topBar = { + TopAppBar(title = {}, navigationIcon = { BackButton(onClick = onBackClicked) }) + } + ) { padding -> + Column( + modifier = Modifier.padding(padding) + ) { + PageTitle( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 40.dp), + title = stringResource(R.string.screen_create_new_recovery_key_title), + iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()) + ) + Content() + } + } +} + +@Composable +private fun Content() { + Column(modifier = Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(24.dp)) { + Item(index = 1, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_1))) + Item(index = 2, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_2))) + Item( + index = 3, + text = buildAnnotatedString { + val resetAllAction = stringResource(R.string.screen_create_new_recovery_key_list_item_3_reset_all) + val text = stringResource(R.string.screen_create_new_recovery_key_list_item_3, resetAllAction) + append(text) + val start = text.indexOf(resetAllAction) + val end = start + resetAllAction.length + if (start in text.indices && end in text.indices) { + addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end) + } + } + ) + Item(index = 4, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_4))) + Item(index = 5, text = AnnotatedString(stringResource(R.string.screen_create_new_recovery_key_list_item_5))) + } +} + +@Composable +private fun Item(index: Int, text: AnnotatedString) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + ItemNumber(index = index) + Text(text = text, style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textPrimary) + } +} + +@Composable +private fun ItemNumber( + index: Int, +) { + val color = ElementTheme.colors.textPlaceholder + Box( + modifier = Modifier + .border(1.dp, color, CircleShape) + .squareSize() + ) { + Text( + modifier = Modifier.padding(1.5.dp), + text = index.toString(), + style = ElementTheme.typography.fontBodySmRegular, + color = color, + textAlign = TextAlign.Center, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun CreateNewRecoveryKeyViewPreview() { + ElementPreview { + CreateNewRecoveryKeyView( + onBackClicked = {}, + ) + } +} diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt index ad36c09726..b74078c7f2 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt @@ -83,7 +83,7 @@ private fun ColumnScope.Buttons( state: SecureBackupEnterRecoveryKeyState, ) { Button( - text = stringResource(id = CommonStrings.action_confirm), + text = stringResource(id = CommonStrings.action_continue), enabled = state.isSubmitEnabled, showProgress = state.submitAction.isLoading(), modifier = Modifier.fillMaxWidth(), diff --git a/features/securebackup/impl/src/main/res/values-be/translations.xml b/features/securebackup/impl/src/main/res/values-be/translations.xml index 2a3913043c..58011bad6e 100644 --- a/features/securebackup/impl/src/main/res/values-be/translations.xml +++ b/features/securebackup/impl/src/main/res/values-be/translations.xml @@ -9,6 +9,13 @@ "Ваша рэзервовая копія чата зараз не сінхранізавана." "Наладзьце аднаўленне" "Атрымайце доступ да зашыфраваных паведамленняў, калі вы страціце ўсе свае прылады або выйдзеце з сістэмы %1$s усюды." + "Адкрыйце Element на настольнай прыладзе" + "Увайдзіце ў свой уліковы запіс яшчэ раз" + "Калі будзе прапанавана пацвердзіць вашу прыладу, выберыце %1$s" + "“Скінуць усе”" + "Выконвайце інструкцыі, каб стварыць новы ключ аднаўлення" + "Захавайце новы ключ аднаўлення ў ме́неджэры пароляў або ў зашыфраванай нататке" + "Скіньце шыфраванне для вашага ўліковага запісу з дапамогай іншай прылады" "Адключыць" "Вы страціце зашыфраваныя паведамленні, калі выйдзеце з усіх прылад." "Вы ўпэўнены, што хочаце адключыць рэзервовае капіраванне?" @@ -25,7 +32,7 @@ "Пераканайцеся, што ніхто не бачыць гэты экран!" "Паўтарыце спробу, каб пацвердзіць доступ да рэзервовай копіі чата." "Няправільны ключ аднаўлення" - "Калі ў вас ёсць ключ аднаўлення або парольная фраза/ключ, гэта таксама будзе працаваць." + "Калі ў вас ёсць ключ аднаўлення або парольная фраза, гэта таксама будзе працаваць." "Ключ аднаўлення або код доступу" "Увесці…" "Ключ аднаўлення пацверджаны" diff --git a/features/securebackup/impl/src/main/res/values-cs/translations.xml b/features/securebackup/impl/src/main/res/values-cs/translations.xml index cf0302d684..f8a2fe8747 100644 --- a/features/securebackup/impl/src/main/res/values-cs/translations.xml +++ b/features/securebackup/impl/src/main/res/values-cs/translations.xml @@ -9,6 +9,13 @@ "Vaše záloha chatu není aktuálně synchronizována." "Nastavení obnovy" "Získejte přístup ke svým zašifrovaným zprávám, pokud ztratíte všechna zařízení nebo jste všude odhlášeni z %1$s." + "Otevřít Element na stolním počítači" + "Znovu se přihlaste ke svému účtu" + "Když budete vyzváni k ověření vašeho zařízení, vyberte %1$s" + "\"Resetovat vše\"" + "Postupujte podle pokynů k vytvoření nového obnovovacího klíče" + "Uložte nový klíč pro obnovení do správce hesel nebo do zašifrované poznámky" + "Obnovte šifrování účtu pomocí jiného zařízení" "Vypnout" "Pokud se odhlásíte ze všech zařízení, přijdete o zašifrované zprávy." "Opravdu chcete vypnout zálohování?" @@ -25,7 +32,7 @@ "Ujistěte se, že tuto obrazovku nikdo nevidí!" "Zkuste prosím znovu potvrdit přístup k záloze chatu." "Nesprávný klíč pro obnovení" - "Pokud máte frázi pro obnovení nebo tajnou přístupovou frázi/klíč, bude to fungovat také." + "Pokud máte bezpečnostní klíč nebo bezpečnostní frázi, bude to fungovat také." "Klíč pro obnovení nebo přístupový kód" "Zadejte…" "Klíč pro obnovení potvrzen" diff --git a/features/securebackup/impl/src/main/res/values-de/translations.xml b/features/securebackup/impl/src/main/res/values-de/translations.xml index f2ac7c06b4..7336b28b70 100644 --- a/features/securebackup/impl/src/main/res/values-de/translations.xml +++ b/features/securebackup/impl/src/main/res/values-de/translations.xml @@ -9,6 +9,26 @@ "Dein Chat-Backup ist derzeit nicht synchronisiert." "Wiederherstellung einrichten" "Erhalte Zugriff auf deine verschlüsselten Nachrichten, wenn du alle deine Geräte verlierst oder von %1$s überall abgemeldet bist." + + "Öffne " + "Element" + " auf einem " + "Desktop-Gerät" + + "Melde dich erneut bei deinem Konto an" + "Wenn du aufgefordert wirst dein Gerät zu verifizieren, wähle \"%1$s\"." + "Alles zurücksetzen" + "Folge den Anweisungen, um einen neuen Wiederherstellungsschlüssel zu erstellen" + + "Speichere deinen neuen " + "Wiederherstellungsschlüssel" + " in einem Passwortmanager oder einer verschlüsselten Notiz" + + + "Erstelle einen neuen " + "Wiederherstellungsschlüssel" + " mit einem anderen Gerät" + "Ausschalten" "Du verlierst deine verschlüsselten Nachrichten, wenn du auf allen Geräten abgemeldet bist." "Bist du sicher, dass du das Backup ausschalten willst?" @@ -29,7 +49,7 @@ "Sorge dafür, dass niemand diesen Bildschirm sehen kann!" "Bitte versuche es noch einmal, um den Zugriff auf dein Chat-Backup zu bestätigen." "Falscher Wiederherstellungsschlüssel" - "Dies funktioniert auch mit einer Wiederherstellungspassphrase oder einer geheime Passphrase/einem geheimen Schlüssel." + "Dies funktioniert auch mit einem Sicherheitsschlüssel oder Sicherheitsphrase." "Wiederherstellungsschlüssel" " oder Passcode" diff --git a/features/securebackup/impl/src/main/res/values-hu/translations.xml b/features/securebackup/impl/src/main/res/values-hu/translations.xml index 3588eddde0..1bd37c4b64 100644 --- a/features/securebackup/impl/src/main/res/values-hu/translations.xml +++ b/features/securebackup/impl/src/main/res/values-hu/translations.xml @@ -9,6 +9,13 @@ "A csevegéselőzményei nincsenek szinkronban." "Helyreállítás beállítása" "Szerezzen hozzáférést a titkosított üzeneteihez, ha elvesztette az összes eszközét, vagy ha mindenütt kijelentkezett az %1$sből." + "Nyissa meg az Elementet egy asztali eszközön" + "Jelentkezzen be újra a fiókjába" + "Amikor az eszköz ellenőrzését kéri, válassza ezt a lehetőséget: %1$s" + "„Minden visszaállítása”" + "Kövesse az utasításokat egy új helyreállítási kulcs létrehozásához" + "Mentse az új helyreállítási kulcsot egy jelszókezelőbe vagy egy titkosított jegyzetbe." + "A fiók titkosításának visszaállítása egy másik eszköz használatával" "Kikapcsolás" "Ha kijelentkezik az összes eszközéről, akkor elveszti a titkosított üzeneteit." "Biztos, hogy kikapcsolja a biztonsági mentéseket?" diff --git a/features/securebackup/impl/src/main/res/values-in/translations.xml b/features/securebackup/impl/src/main/res/values-in/translations.xml index e053f513e4..016d0e3057 100644 --- a/features/securebackup/impl/src/main/res/values-in/translations.xml +++ b/features/securebackup/impl/src/main/res/values-in/translations.xml @@ -9,6 +9,13 @@ "Pencadangan percakapan Anda saat ini tidak tersinkron." "Siapkan pemulihan" "Dapatkan akses ke pesan terenkripsi Anda jika Anda kehilangan semua perangkat Anda atau keluar dari %1$s di mana pun." + "Buka Element di perangkat desktop" + "Masuk ke akun Anda lagi" + "Saat diminta untuk memverifikasi perangkat Anda, pilih %1$s" + "“Atur ulang semua”" + "Ikuti petunjuk untuk membuat kunci pemulihan baru" + "Simpan kunci pemulihan baru Anda dalam pengelola kata sandi atau catatan terenkripsi" + "Atur ulang enkripsi untuk akun Anda menggunakan perangkat lain" "Matikan" "Anda akan kehilangan pesan terenkripsi jika Anda keluar dari semua perangkat." "Apakah Anda yakin ingin mematikan pencadangan?" diff --git a/features/securebackup/impl/src/main/res/values-ru/translations.xml b/features/securebackup/impl/src/main/res/values-ru/translations.xml index 8df45698ff..acacc8d60b 100644 --- a/features/securebackup/impl/src/main/res/values-ru/translations.xml +++ b/features/securebackup/impl/src/main/res/values-ru/translations.xml @@ -15,6 +15,17 @@ "Резервная копия чата в настоящее время не синхронизирована." "Настроить восстановление" "Получите доступ к зашифрованным сообщениям, если вы потеряете все свои устройства или выйдете из системы %1$s отовсюду." + "Откройте Element на настольном устройстве" + "Войдите в свой аккаунт еще раз" + "Когда вас попросят подтвердить устройство, выберите %1$s" + "“Сбросить все”" + "Следуйте инструкциям, чтобы создать новый ключ восстановления" + + "Сохраните новый " + "ключ восстановления" + " в менеджере паролей или зашифрованной заметке" + + "Сбросьте шифрование вашей учетной записи с помощью другого устройства." "Выключить" "Вы потеряете зашифрованные сообщения, если выйдете из всех устройств." "Вы действительно хотите отключить резервное копирование?" @@ -43,7 +54,7 @@ "Неверный " "ключ восстановления" - "Если у вас есть пароль для восстановления или секретная пароль/ключ, это тоже сработает." + "Если у вас есть пароль для восстановления или секретный пароль/ключ, это тоже сработает." "Ключ восстановления" " или пароль" diff --git a/features/securebackup/impl/src/main/res/values-sk/translations.xml b/features/securebackup/impl/src/main/res/values-sk/translations.xml index a2347cf6cd..fdc70027b3 100644 --- a/features/securebackup/impl/src/main/res/values-sk/translations.xml +++ b/features/securebackup/impl/src/main/res/values-sk/translations.xml @@ -9,6 +9,13 @@ "Vaša záloha konverzácie nie je momentálne synchronizovaná." "Nastaviť obnovovanie" "Získajte prístup k vašim šifrovaným správam aj keď stratíte všetky svoje zariadenia alebo sa odhlásite zo všetkých %1$s zariadení." + "Otvoriť Element v stolnom počítači" + "Znova sa prihláste do svojho účtu" + "Keď sa zobrazí výzva na overenie vášho zariadenia, vyberte %1$s" + "\"Obnoviť všetko\"" + "Postupujte podľa pokynov na vytvorenie nového kľúča na obnovenie" + "Uložte si nový kľúč na obnovenie do správcu hesiel alebo do zašifrovanej poznámky" + "Obnovte šifrovanie vášho účtu pomocou iného zariadenia" "Vypnúť" "Stratíte prístup k svojim zašifrovaným správam, ak sa odhlásite zo všetkých zariadení" "Ste si istí, že chcete vypnúť zálohovanie?" diff --git a/features/securebackup/impl/src/main/res/values/localazy.xml b/features/securebackup/impl/src/main/res/values/localazy.xml index 5b87567f7d..9e188d1251 100644 --- a/features/securebackup/impl/src/main/res/values/localazy.xml +++ b/features/securebackup/impl/src/main/res/values/localazy.xml @@ -9,6 +9,13 @@ "Your chat backup is currently out of sync." "Set up recovery" "Get access to your encrypted messages if you lose all your devices or are signed out of %1$s everywhere." + "Open Element in a desktop device" + "Sign into your account again" + "When asked to verify your device, select %1$s" + "“Reset all”" + "Follow the instructions to create a new recovery key" + "Save your new recovery key in a password manager or encrypted note" + "Reset the encryption for your account using another device" "Turn off" "You will lose your encrypted messages if you are signed out of all devices." "Are you sure you want to turn off backup?" @@ -25,7 +32,7 @@ "Make sure nobody can see this screen!" "Please try again to confirm access to your chat backup." "Incorrect recovery key" - "If you have a recovery passphrase or secret passphrase/key, this will work too." + "If you have a security key or security phrase, this will work too." "Recovery key or passcode" "Enter…" "Recovery key confirmed" diff --git a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt index 8d19ca5698..70600f7f7c 100644 --- a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt +++ b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/VerifySessionEntryPoint.kt @@ -31,6 +31,7 @@ interface VerifySessionEntryPoint : FeatureEntryPoint { interface Callback : Plugin { fun onEnterRecoveryKey() + fun onCreateNewRecoveryKey() fun onDone() } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt index d2e8ad39da..222683156a 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionNode.kt @@ -34,17 +34,7 @@ class VerifySelfSessionNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: VerifySelfSessionPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onEnterRecoveryKey() { - plugins().forEach { - it.onEnterRecoveryKey() - } - } - - private fun onDone() { - plugins().forEach { - it.onDone() - } - } + private val callback = plugins().first() @Composable override fun View(modifier: Modifier) { @@ -52,8 +42,9 @@ class VerifySelfSessionNode @AssistedInject constructor( VerifySelfSessionView( state = state, modifier = modifier, - onEnterRecoveryKey = ::onEnterRecoveryKey, - onFinished = ::onDone, + onEnterRecoveryKey = callback::onEnterRecoveryKey, + onCreateNewRecoveryKey = callback::onCreateNewRecoveryKey, + onFinished = callback::onDone, ) } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt index ecd263d47d..c09b48946d 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenter.kt @@ -103,7 +103,7 @@ class VerifySelfSessionPresenter @Inject constructor( ): VerifySelfSessionState.VerificationStep = when (val machineState = this) { StateMachineState.Initial, null -> { - VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = canEnterRecoveryKey) + VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = canEnterRecoveryKey, isLastDevice = encryptionService.isLastDevice.value) } StateMachineState.RequestingVerification, StateMachineState.StartingSasVerification, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt index 770915d9e3..30d91b8fa4 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionState.kt @@ -29,7 +29,7 @@ data class VerifySelfSessionState( ) { @Stable sealed interface VerificationStep { - data class Initial(val canEnterRecoveryKey: Boolean) : VerificationStep + data class Initial(val canEnterRecoveryKey: Boolean, val isLastDevice: Boolean) : VerificationStep data object Canceled : VerificationStep data object AwaitingOtherDeviceResponse : VerificationStep data object Ready : VerificationStep diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt index c69d09acd7..c066d48613 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionStateProvider.kt @@ -17,6 +17,7 @@ package io.element.android.features.verifysession.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.VerificationEmoji @@ -26,28 +27,31 @@ open class VerifySelfSessionStateProvider : PreviewParameterProvider Unit = {}, ) = VerifySelfSessionState( diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt index 8709411952..e54ba31872 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionView.kt @@ -66,6 +66,7 @@ import io.element.android.features.verifysession.impl.VerifySelfSessionState.Ver fun VerifySelfSessionView( state: VerifySelfSessionState, onEnterRecoveryKey: () -> Unit, + onCreateNewRecoveryKey: () -> Unit, onFinished: () -> Unit, modifier: Modifier = Modifier, ) { @@ -114,6 +115,7 @@ fun VerifySelfSessionView( screenState = state, goBack = ::resetFlow, onEnterRecoveryKey = onEnterRecoveryKey, + onCreateNewRecoveryKey = onCreateNewRecoveryKey, onFinished = onFinished, ) } @@ -226,6 +228,7 @@ private fun EmojiItemView(emoji: VerificationEmoji, modifier: Modifier = Modifie private fun BottomMenu( screenState: VerifySelfSessionState, onEnterRecoveryKey: () -> Unit, + onCreateNewRecoveryKey: () -> Unit, goBack: () -> Unit, onFinished: () -> Unit, ) { @@ -236,12 +239,21 @@ private fun BottomMenu( when (verificationViewState) { is FlowStep.Initial -> { - BottomMenu( - positiveButtonTitle = stringResource(R.string.screen_identity_use_another_device), - onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.RequestVerification) }, - negativeButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key), - onNegativeButtonClicked = onEnterRecoveryKey, - ) + if (verificationViewState.isLastDevice) { + BottomMenu( + positiveButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key), + onPositiveButtonClicked = onEnterRecoveryKey, + negativeButtonTitle = stringResource(R.string.screen_identity_confirmation_create_new_recovery_key), + onNegativeButtonClicked = onCreateNewRecoveryKey, + ) + } else { + BottomMenu( + positiveButtonTitle = stringResource(R.string.screen_identity_use_another_device), + onPositiveButtonClicked = { eventSink(VerifySelfSessionViewEvents.RequestVerification) }, + negativeButtonTitle = stringResource(R.string.screen_session_verification_enter_recovery_key), + onNegativeButtonClicked = onEnterRecoveryKey, + ) + } } is FlowStep.Canceled -> { BottomMenu( @@ -334,6 +346,7 @@ internal fun VerifySelfSessionViewPreview(@PreviewParameter(VerifySelfSessionSta VerifySelfSessionView( state = state, onEnterRecoveryKey = {}, + onCreateNewRecoveryKey = {}, onFinished = {}, ) } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index 249dbdddab..06f69e1628 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt @@ -53,7 +53,7 @@ class VerifySelfSessionPresenterTests { presenter.present() }.test { awaitItem().run { - assertThat(verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + assertThat(verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) assertThat(displaySkipButton).isTrue() } } @@ -80,7 +80,22 @@ class VerifySelfSessionPresenterTests { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true)) + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true, false)) + } + } + + @Test + fun `present - Initial state is received, can use recovery key and is last device`() = runTest { + val presenter = createVerifySelfSessionPresenter( + encryptionService = FakeEncryptionService().apply { + emitIsLastDevice(true) + emitRecoveryState(RecoveryState.INCOMPLETE) + } + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(true, true)) } } @@ -103,7 +118,7 @@ class VerifySelfSessionPresenterTests { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) val eventSink = initialState.eventSink eventSink(VerifySelfSessionViewEvents.StartSasVerification) // Await for other device response: @@ -122,7 +137,7 @@ class VerifySelfSessionPresenterTests { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) val eventSink = initialState.eventSink eventSink(VerifySelfSessionViewEvents.Cancel) expectNoEvents() @@ -157,7 +172,7 @@ class VerifySelfSessionPresenterTests { awaitItem().eventSink(VerifySelfSessionViewEvents.RequestVerification) service.shouldFail = false assertThat(awaitItem().verificationFlowStep).isInstanceOf(VerificationStep.AwaitingOtherDeviceResponse::class.java) - assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) } } @@ -216,7 +231,7 @@ class VerifySelfSessionPresenterTests { assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled) state.eventSink(VerifySelfSessionViewEvents.Reset) // Went back to initial state - assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) cancelAndIgnoreRemainingEvents() } } @@ -297,7 +312,7 @@ class VerifySelfSessionPresenterTests { sessionVerificationData: SessionVerificationData = SessionVerificationData.Emojis(emptyList()), ): VerifySelfSessionState { var state = awaitItem() - assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial(false)) + assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial(false, false)) state.eventSink(VerifySelfSessionViewEvents.RequestVerification) // Await for other device response: state = awaitItem() diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt index 854ccac8af..4d5f67f0b1 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionViewTest.kt @@ -17,6 +17,7 @@ package io.element.android.features.verifysession.impl import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.libraries.architecture.AsyncData @@ -29,6 +30,7 @@ import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBackKey import org.junit.Rule import org.junit.Test +import org.junit.rules.TestRule import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -39,16 +41,12 @@ class VerifySelfSessionViewTest { @Test fun `back key pressed - when canceled resets the flow`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled, - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Canceled, + eventSink = eventsRecorder + ), + ) rule.pressBackKey() eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Reset) } @@ -56,16 +54,12 @@ class VerifySelfSessionViewTest { @Test fun `back key pressed - when awaiting response cancels the verification`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse, - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.AwaitingOtherDeviceResponse, + eventSink = eventsRecorder + ), + ) rule.pressBackKey() eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel) } @@ -73,16 +67,12 @@ class VerifySelfSessionViewTest { @Test fun `back key pressed - when ready to verify cancels the verification`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready, - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Ready, + eventSink = eventsRecorder + ), + ) rule.pressBackKey() eventsRecorder.assertSingle(VerifySelfSessionViewEvents.Cancel) } @@ -90,19 +80,15 @@ class VerifySelfSessionViewTest { @Test fun `back key pressed - when verifying and not loading declines the verification`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( - data = aEmojisSessionVerificationData(), - state = AsyncData.Uninitialized, - ), - eventSink = eventsRecorder + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( + data = aEmojisSessionVerificationData(), + state = AsyncData.Uninitialized, ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + eventSink = eventsRecorder + ), + ) rule.pressBackKey() eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification) } @@ -110,19 +96,15 @@ class VerifySelfSessionViewTest { @Test fun `back key pressed - when verifying and loading does nothing`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( - data = aEmojisSessionVerificationData(), - state = AsyncData.Loading(), - ), - eventSink = eventsRecorder + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( + data = aEmojisSessionVerificationData(), + state = AsyncData.Loading(), ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + eventSink = eventsRecorder + ), + ) rule.pressBackKey() eventsRecorder.assertEmpty() } @@ -130,16 +112,12 @@ class VerifySelfSessionViewTest { @Test fun `back key pressed - on Completed step does nothing`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed, - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed, + eventSink = eventsRecorder + ), + ) rule.pressBackKey() eventsRecorder.assertEmpty() } @@ -148,16 +126,13 @@ class VerifySelfSessionViewTest { fun `when flow is completed and the user clicks on the continue button, the expected callback is invoked`() { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed, - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = callback, - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed, + eventSink = eventsRecorder + ), + onFinished = callback, + ) rule.clickOn(CommonStrings.action_continue) } } @@ -167,36 +142,45 @@ class VerifySelfSessionViewTest { fun `clicking on enter recovery key calls the expected callback`() { val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true), - eventSink = eventsRecorder - ), - onEnterRecoveryKey = callback, - onFinished = EnsureNeverCalled(), - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true, false), + eventSink = eventsRecorder + ), + onEnterRecoveryKey = callback, + ) rule.clickOn(R.string.screen_session_verification_enter_recovery_key) } } + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on create new recovery key calls the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true, true), + eventSink = eventsRecorder + ), + onCreateNewRecoveryKey = callback, + ) + rule.clickOn(R.string.screen_identity_confirmation_create_new_recovery_key) + } + } + @Test fun `clicking on they match emits the expected event`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( - data = aEmojisSessionVerificationData(), - state = AsyncData.Uninitialized, - ), - eventSink = eventsRecorder + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( + data = aEmojisSessionVerificationData(), + state = AsyncData.Uninitialized, ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + eventSink = eventsRecorder + ), + ) rule.clickOn(R.string.screen_session_verification_they_match) eventsRecorder.assertSingle(VerifySelfSessionViewEvents.ConfirmVerification) } @@ -204,19 +188,15 @@ class VerifySelfSessionViewTest { @Test fun `clicking on they do not match emits the expected event`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( - data = aEmojisSessionVerificationData(), - state = AsyncData.Uninitialized, - ), - eventSink = eventsRecorder + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( + data = aEmojisSessionVerificationData(), + state = AsyncData.Uninitialized, ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + eventSink = eventsRecorder + ), + ) rule.clickOn(R.string.screen_session_verification_they_dont_match) eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification) } @@ -224,17 +204,13 @@ class VerifySelfSessionViewTest { @Test fun `clicking on 'Skip' emits the expected event`() { val eventsRecorder = EventsRecorder() - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = true), - displaySkipButton = true, - eventSink = eventsRecorder - ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = EnsureNeverCalled(), - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(canEnterRecoveryKey = true, isLastDevice = false), + displaySkipButton = true, + eventSink = eventsRecorder + ), + ) rule.clickOn(CommonStrings.action_skip) eventsRecorder.assertSingle(VerifySelfSessionViewEvents.SkipVerification) } @@ -242,17 +218,30 @@ class VerifySelfSessionViewTest { @Test fun `on Skipped step - onFinished callback is called immediately`() { ensureCalledOnce { callback -> - rule.setContent { - VerifySelfSessionView( - aVerifySelfSessionState( - verificationFlowStep = VerifySelfSessionState.VerificationStep.Skipped, - displaySkipButton = true, - eventSink = EnsureNeverCalledWithParam(), - ), - onEnterRecoveryKey = EnsureNeverCalled(), - onFinished = callback, - ) - } + rule.setVerifySelfSessionView( + aVerifySelfSessionState( + verificationFlowStep = VerifySelfSessionState.VerificationStep.Skipped, + displaySkipButton = true, + eventSink = EnsureNeverCalledWithParam(), + ), + onFinished = callback, + ) + } + } + + private fun AndroidComposeTestRule.setVerifySelfSessionView( + state: VerifySelfSessionState, + onEnterRecoveryKey: () -> Unit = EnsureNeverCalled(), + onCreateNewRecoveryKey: () -> Unit = EnsureNeverCalled(), + onFinished: () -> Unit = EnsureNeverCalled(), + ) { + rule.setContent { + VerifySelfSessionView( + state = state, + onEnterRecoveryKey = onEnterRecoveryKey, + onCreateNewRecoveryKey = onCreateNewRecoveryKey, + onFinished = onFinished, + ) } } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt new file mode 100644 index 0000000000..f2b7c49cf4 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.modifiers + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.LayoutModifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.platform.InspectorValueInfo +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.ElementPreview +import kotlin.math.max +import kotlin.math.min + +/** + * Makes the content square in size. + * + * This is achieved by cropping incoming max constraints to the largest possible square size + * and measuring the content using resulting constraints. + * Next the size of layout is decided based on largest dimension of the measured content. + * Finally the content is placed inside the square layout according to specified [position]. + * + * If no square exists that falls within the size range of the incoming constraints, + * the content will be laid out as usual, as if the modifier was not applied. + * + * @param position The fraction of the content's position inside its square layout. + * It determines the point on the axis that was extended to make a square. + * Typically you'd want to use values between `0` and `1`, inclusive, where `0` + * will place the content at the "start" of the square, `0.5` in the middle, and `1` at the "end". + */ +@Stable +fun Modifier.squareSize( + position: Float = 0.5f, +): Modifier = + this.then( + when { + position == 0.5f -> SquareSizeCenter + else -> createSquareSizeModifier(position = position) + } + ) + +private val SquareSizeCenter = createSquareSizeModifier(position = 0.5f) + +private class SquareSizeModifier( + private val position: Float, + inspectorInfo: InspectorInfo.() -> Unit, +) : LayoutModifier, InspectorValueInfo(inspectorInfo) { + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + val maxSquare = min(constraints.maxWidth, constraints.maxHeight) + val minSquare = max(constraints.minWidth, constraints.minHeight) + val squareExists = minSquare <= maxSquare + + val resolvedConstraints = constraints + .takeUnless { squareExists } + ?: constraints.copy(maxWidth = maxSquare, maxHeight = maxSquare) + + val placeable = measurable.measure(resolvedConstraints) + + return if (squareExists) { + val size = max(placeable.width, placeable.height) + layout(size, size) { + val x = ((size - placeable.width) * position).toInt() + val y = ((size - placeable.height) * position).toInt() + placeable.placeRelative(x, y) + } + } else { + layout(placeable.width, placeable.height) { + placeable.placeRelative(0, 0) + } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (other !is SquareSizeModifier) return false + + if (position != other.position) return false + + return true + } + + override fun hashCode(): Int { + return position.hashCode() + } +} + +@Suppress("ModifierFactoryExtensionFunction", "ModifierFactoryReturnType") +private fun createSquareSizeModifier( + position: Float, +) = + SquareSizeModifier( + position = position, + inspectorInfo = debugInspectorInfo { + name = "squareSize" + properties["position"] = position + }, + ) + +@Preview +@Composable +internal fun SquareSizeModifierLargeWidthPreview() { + ElementPreview { + Box( + modifier = Modifier + .padding(32.dp) + .background(Color.Gray) + .squareSize(position = 0.25f) + ) { + Box( + modifier = Modifier + .background(Color.Black) + .size(100.dp, 10.dp) + ) + } + } +} + +@Preview +@Composable +internal fun SquareSizeModifierLargeHeightPreview() { + ElementPreview { + Box( + modifier = Modifier + .padding(32.dp) + .background(Color.Gray) + .squareSize(position = 0.75f) + ) { + Box( + modifier = Modifier + .background(Color.Black) + .size(10.dp, 100.dp) + ) + } + } +} + +@Preview +@Composable +internal fun SquareSizeModifierInsideSquarePreview() { + ElementPreview { + Box( + modifier = Modifier + .padding(32.dp) + .size(120.dp) + .background(Color.Gray), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .background(Color.Black) + .width(100.dp) + .squareSize(position = 0.75f) + ) + } + } +} 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 58721077e5..659f71e754 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -202,7 +202,7 @@ "Сервер не падтрымліваецца" "URL-адрас сервера" "Налады" - "Абагуліць месцазнаходжанне" + "Абагуленае месцазнаходжанне" "Выхад" "Пачатак чата…" "Стыкер" @@ -249,13 +249,6 @@ "Гэй, пагавары са мной у %1$s: %2$s" "%1$s Android" "Паведаміць аб памылцы з дапамогай Rageshake" - "Адкрыйце Element на настольнай прыладзе" - "Увайдзіце ў свой уліковы запіс яшчэ раз" - "Калі будзе прапанавана пацвердзіць вашу прыладу, выберыце %1$s" - "“Скінуць усе”" - "Выконвайце інструкцыі, каб стварыць новы ключ аднаўлення" - "Захавайце новы ключ аднаўлення ў ме́неджэры пароляў або ў зашыфраванай нататке" - "Скіньце шыфраванне для вашага ўліковага запісу з дапамогай іншай прылады" "Не ўдалося выбраць носьбіт, паўтарыце спробу." "Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз." "Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз." 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 9f5942f5f8..b92077b657 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -249,13 +249,6 @@ "Ahoj, ozvi se mi na %1$s: %2$s" "%1$s Android" "Zatřeste zařízením pro nahlášení chyby" - "Otevřít Element na stolním počítači" - "Znovu se přihlaste ke svému účtu" - "Když budete vyzváni k ověření vašeho zařízení, vyberte %1$s" - "\"Resetovat vše\"" - "Postupujte podle pokynů k vytvoření nového obnovovacího klíče" - "Uložte nový klíč pro obnovení do správce hesel nebo do zašifrované poznámky" - "Obnovte šifrování účtu pomocí jiného zařízení" "Výběr média se nezdařil, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." "Nahrání média se nezdařilo, zkuste to prosím znovu." 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 8277b61731..b7f24c80c6 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -247,26 +247,6 @@ "Hey, sprich mit mir auf %1$s: %2$s" "%1$s Android" "Schüttel heftig zum Melden von Fehlern" - - "Öffne " - "Element" - " auf einem " - "Desktop-Gerät" - - "Melde dich erneut bei deinem Konto an" - "Wenn du aufgefordert wirst dein Gerät zu verifizieren, wähle \"%1$s\"." - "Alles zurücksetzen" - "Folge den Anweisungen, um einen neuen Wiederherstellungsschlüssel zu erstellen" - - "Speichere deinen neuen " - "Wiederherstellungsschlüssel" - " in einem Passwortmanager oder einer verschlüsselten Notiz" - - - "Erstelle einen neuen " - "Wiederherstellungsschlüssel" - " mit einem anderen Gerät" - "Medienauswahl fehlgeschlagen, bitte versuche es erneut." "Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut." "Das Hochladen der Medien ist fehlgeschlagen. Bitte versuche es erneut." 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 6d492b0889..aed5aefacf 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -245,13 +245,6 @@ "Beszélgessünk itt: %1$s, %2$s" "%1$s Android" "Az eszköz rázása a hibajelentéshez" - "Nyissa meg az Elementet egy asztali eszközön" - "Jelentkezzen be újra a fiókjába" - "Amikor az eszköz ellenőrzését kéri, válassza ezt a lehetőséget: %1$s" - "„Minden visszaállítása”" - "Kövesse az utasításokat egy új helyreállítási kulcs létrehozásához" - "Mentse az új helyreállítási kulcsot egy jelszókezelőbe vagy egy titkosított jegyzetbe." - "A fiók titkosításának visszaállítása egy másik eszköz használatával" "Nem sikerült kiválasztani a médiát, próbálja újra." "Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra." "Nem sikerült a média feltöltése, próbálja újra." 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 a6a4338f79..fdbf31da25 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -241,13 +241,6 @@ "Hai, bicaralah dengan saya di %1$s: %2$s" "%1$s Android" "Rageshake untuk melaporkan kutu" - "Buka Element di perangkat desktop" - "Masuk ke akun Anda lagi" - "Saat diminta untuk memverifikasi perangkat Anda, pilih %1$s" - "“Atur ulang semua”" - "Ikuti petunjuk untuk membuat kunci pemulihan baru" - "Simpan kunci pemulihan baru Anda dalam pengelola kata sandi atau catatan terenkripsi" - "Atur ulang enkripsi untuk akun Anda menggunakan perangkat lain" "Gagal memilih media, silakan coba lagi." "Gagal memproses media untuk diunggah, silakan coba lagi." "Gagal mengunggah media, silakan coba lagi." 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 de73bdd8b6..054d42b94f 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -251,17 +251,6 @@ "Привет, поговори со мной по %1$s: %2$s" "%1$s Android" "Встряхните устройство, чтобы сообщить об ошибке" - "Откройте Element на настольном устройстве" - "Войдите в свой аккаунт еще раз" - "Когда вас попросят подтвердить устройство, выберите %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 6ebbd7b1c6..f777ed6b31 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -248,13 +248,6 @@ "Ahoj, porozprávajte sa so mnou na %1$s: %2$s" "%1$s Android" "Zúrivo potriasť pre nahlásenie chyby" - "Otvoriť Element v stolnom počítači" - "Znova sa prihláste do svojho účtu" - "Keď sa zobrazí výzva na overenie vášho zariadenia, vyberte %1$s" - "\"Obnoviť všetko\"" - "Postupujte podľa pokynov na vytvorenie nového kľúča na obnovenie" - "Uložte si nový kľúč na obnovenie do správcu hesiel alebo do zašifrovanej poznámky" - "Obnovte šifrovanie vášho účtu pomocou iného zariadenia" "Nepodarilo sa vybrať médium, skúste to prosím znova." "Nepodarilo sa spracovať médiá na odoslanie, skúste to prosím znova." "Nepodarilo sa nahrať médiá, skúste to prosím znova." diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index c64ca34ba6..f19696065a 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -245,13 +245,6 @@ "Hey, talk to me on %1$s: %2$s" "%1$s Android" "Rageshake to report bug" - "Open Element in a desktop device" - "Sign into your account again" - "When asked to verify your device, select %1$s" - "“Reset all”" - "Follow the instructions to create a new recovery key" - "Save your new recovery key in a password manager or encrypted note" - "Reset the encryption for your account using another device" "Failed selecting media, please try again." "Failed processing media to upload, please try again." "Failed uploading media, please try again." diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ecac9d32ec --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Day-0_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e7892eb99a1307f2c34968f76e994e6b8e3916ff5c218eb9cbc81424001fd54 +size 64251 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6be4090def --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.createkey_CreateNewRecoveryKeyView_null_CreateNewRecoveryKeyView-Night-0_2_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbcf18bb210a68b0e02512abd949845a8c5a705a177ce0875599734ce46da7ff +size 57525 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-0_1_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Day-1_2_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-0_2_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.disable_SecureBackupDisableView_null_SecureBackupDisableView-Night-1_3_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-1_2_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Day-2_3_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-1_3_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enable_SecureBackupEnableView_null_SecureBackupEnableView-Night-2_4_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index dfe108c2a8..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bd35705e28bd0b46d2ca8752c3f338c883d5bc2e8c78af26c35eda435a6cf8cf -size 42542 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index d772d032e7..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5bf4f3ff7ed970ba7eef2119525e800f80bfc056ccbd5345c1c610257180b50d -size 53908 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index bd69817bb8..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f963d37ac30795df5de921cef04b17de6baf45160716533984d08f6e543fb1e5 -size 52566 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index 5762a4ee9e..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-2_3_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:573fd927ea37f9c3cf309dd805bf52c06e98e7b058b6f1a80bf8b19bb3ecd35e -size 46028 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a062477882 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6df37aa3a9d8411d3b3f9cd4dc67e48c271030350fee5cc464da8b564bea14d0 +size 40410 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..60aa757d1b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:923a6792ecb57eaefb86042327495ffa48294187eb97fdb2f36ca16b8e2d2859 +size 52163 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..43f66fe1bd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9296a3f7d3c3ee8aa4d8ee3fc310cab8c539c7adfd4b14156da65608a5e6fd10 +size 50565 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..fa793735c5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Day-3_4_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0fee5009acc5128d48791066fb45555d0fa261cf09c9b372cca8570561c3055b +size 44297 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png deleted file mode 100644 index 70ffe6c1d0..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_0,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1d1cd406230b94ba11e414aedfbe49d903d7aa54791c4dbd93616a9f16ca7068 -size 39917 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png deleted file mode 100644 index 712ed72f35..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_1,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7f9dbe41267a197887860cda4cef70138fd1e1b79497f396d1535725bc6219f4 -size 50262 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png deleted file mode 100644 index a4af367f65..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_2,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02ebc69b55046c946142bac74f299d191b4dba2e175a670a67ed1e91dcaf51a8 -size 48753 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png deleted file mode 100644 index fc7ecc3279..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-2_4_null_3,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6dcceb3e818fe738f6a20dc8dffb006f46c4884e615ee9c00e845d7d9327af66 -size 40894 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1855caae90 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b11e7ce69453d49bb916bec23ce0974e33ae788911ab12cea777ba238141bc77 +size 37965 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..68beb560ea --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aefbf69085a8279090018dd35a465614b2c876932746e650e7faf6d84f062381 +size 48364 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..65c93af2ba --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b6c6e849d68d516000644352c347587910f99ffd0d441feb8cf72a981aa30e3 +size 46849 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..26d115b0d0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_null_SecureBackupEnterRecoveryKeyView-Night-3_5_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65c1faa6875a6c2b9ebe282f83e8a82b6e358f724082b53525d31a6c1bff4b15 +size 39312 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-3_4_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Day-4_5_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_8,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-3_5_null_8,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.root_SecureBackupRootView_null_SecureBackupRootView-Night-4_6_null_8,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png deleted file mode 100644 index 80a305b663..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_10,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c4aeacb175ef566e8093a9d7e297640739c71e488f0fe56f75803c2b98b09d2 -size 31137 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png deleted file mode 100644 index f5d703685d..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_11,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7837d958cae6c7627a57a27880a00747dcebea7f00a8a29e4a0dcf67b5fae47 -size 29014 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png deleted file mode 100644 index d053778c55..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_8,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7731621308a3392d0b940ce881171e909852d24787b63fbdbfea69e238c9f18 -size 20983 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png deleted file mode 100644 index 9d0b5ff5d9..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_9,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:13e9687d249fd366e7d39cb20a49664ae661453f4a4333c3eb6334065211aa58 -size 20794 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_10,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9cb0608473 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_10,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:513dcbe90f37494156e6d25c3aa37577f2028e57baf8efc8c8f0877e4560f247 +size 28759 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_11,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..c92923ee62 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_11,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9062b0003b8d53b3cf5f3ad704f98397f195e51e12ad1c4ff942ba9660c2191c +size 27150 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-6_7_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..64b5b8c5ff --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec184cdcf450def60da5f1598a4fe1e6974dad5e57718db531f996dad7fad50c +size 18556 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a0e9ae8aa3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Day-7_8_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc55f58f8a0924f910d32e16d3a7147a1d28a9c40628f3906c6f21013771792b +size 18354 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png deleted file mode 100644 index cb364295aa..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_10,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ec7487af11bc617f04296a895351dd82e79a9acd2272225d2cf84a735a5bb30b -size 29005 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png deleted file mode 100644 index bc7f0feec3..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_11,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4120ab56b29285d4e2576563c521f59541d4368b9adcdc6c0dd997046df8a385 -size 27504 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png deleted file mode 100644 index 5207d34551..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_8,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:910d080a5abbba99a1347c77d70df982fe520239700cfbd04af460d33f5a0037 -size 20043 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png deleted file mode 100644 index b10ede074e..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_9,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a77b0bb03a6941cf821420266c4922f9ef88c775ee6f8771aa17e966b3d86584 -size 19769 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_10,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e93df38559 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_10,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fee98d6b4239b51814932af659fbd4e5ea8316c60ea94788c08f6718e36da135 +size 27212 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_11,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5535e1fa78 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_11,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a12e263b94ec603fb10e710fcfdd678d7b88a4fcc49d6db0a85d06ce1aadc85 +size 25444 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_5,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_5,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_5,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_6,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_6,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_6,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_7,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-6_8_null_7,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_7,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_8,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..750f1ab968 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_8,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:659444a228416eb4b38f9bcfec8fbf140bfbdac62ff681dd1d74d228149faa41 +size 17691 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ea7c22b95d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup.views_RecoveryKeyView_null_RecoveryKeyView-Night-7_9_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8569650805e76bc73ec054459096016b18afbe161abfa50403707e2497906fa +size 17407 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-5_6_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Day-6_7_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-5_7_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupViewChange_null_SecureBackupSetupViewChange-Night-6_8_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-4_5_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Day-5_6_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-4_6_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.securebackup.impl.setup_SecureBackupSetupView_null_SecureBackupSetupView-Night-5_7_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png index 9bef6f55d1..eabba435c0 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37e571dda745d7d71df2d421651fbed56a8e698583b6b2684633574ffc8c56a9 -size 28880 +oid sha256:409858802c14fd755380aa67fedb52f04a5668cf0999104f4fff4ffbc3711743 +size 28968 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9bef6f55d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Day-0_1_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37e571dda745d7d71df2d421651fbed56a8e698583b6b2684633574ffc8c56a9 +size 28880 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png index 2b9295e65e..312fe2c859 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c39cda078df3c3ac3297c86d4b3da332f2793b53d62a49ed7b3f6349b9059404 -size 27770 +oid sha256:a7eb330ff5a506ee6198a0ec5acc3ef1b9db58161aa8eb9dedf468719f3e55a6 +size 26778 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_9,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2b9295e65e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.verifysession.impl_VerifySelfSessionView_null_VerifySelfSessionView-Night-0_2_null_9,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c39cda078df3c3ac3297c86d4b3da332f2793b53d62a49ed7b3f6349b9059404 +size 27770 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierInsideSquare_null_SquareSizeModifierInsideSquare_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierInsideSquare_null_SquareSizeModifierInsideSquare_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3659321678 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierInsideSquare_null_SquareSizeModifierInsideSquare_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2aa126c2a29cba21d2304b551af552c0fa9de8e688375bd38721a910817c2ad4 +size 5409 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeHeight_null_SquareSizeModifierLargeHeight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeHeight_null_SquareSizeModifierLargeHeight_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5b5e11df0c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeHeight_null_SquareSizeModifierLargeHeight_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a9255aeb0888547ea5d69f2cda3af3eba38b7107c2b76f950bd0756a4943c2c +size 5499 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeWidth_null_SquareSizeModifierLargeWidth_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeWidth_null_SquareSizeModifierLargeWidth_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..2e98190424 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.modifiers_SquareSizeModifierLargeWidth_null_SquareSizeModifierLargeWidth_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b268b1a973f0743bcc328bcbe843b01061209b2d7bc80fe735090820d9a9705 +size 4855 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 566ea071e0..ac9c773e1a 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -179,7 +179,8 @@ "name" : ":features:ftue:impl", "includeRegex" : [ "screen_welcome_.*", - "screen_notification_optin_.*" + "screen_notification_optin_.*", + "screen_qr_code_login_.+" ] }, { @@ -195,7 +196,8 @@ "includeRegex" : [ "screen_chat_backup_.*", "screen_key_backup_disable_.*", - "screen_recovery_key_.*" + "screen_recovery_key_.*", + "screen_create_new_recovery_key_.*" ] }, { From dafde3ce79df54f1e9df1754a20eb38a36d0916f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Apr 2024 05:17:41 +0000 Subject: [PATCH 121/124] Update android.gradle.plugin to v8.3.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8474ae9c43..ac8dd451dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ [versions] # Project -android_gradle_plugin = "8.3.1" +android_gradle_plugin = "8.3.2" kotlin = "1.9.23" ksp = "1.9.23-1.0.19" firebaseAppDistribution = "4.2.0" From 6db513c1258f896a0e78063cda9b923e302f08af Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 10 Apr 2024 10:28:05 +0200 Subject: [PATCH 122/124] Improve designs of FTUE analytics opt-in screen (#2684) * Improve designs of FTUE analytics opt-in screen * Update screenshots --------- Co-authored-by: ElementBot --- changelog.d/2684.misc | 1 + .../analytics/impl/AnalyticsOptInView.kt | 43 ++++++------------- .../atomic/molecules/InfoListItemMolecule.kt | 4 +- ...tInView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 4 +- ...nView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 4 +- ...lcomeView-Day-1_2_null,NEXUS_5,1.0,en].png | 4 +- ...omeView-Night-1_3_null,NEXUS_5,1.0,en].png | 4 +- ...OutView-Day-0_1_null_0,NEXUS_5,1.0,en].png | 4 +- ...tView-Night-0_2_null_0,NEXUS_5,1.0,en].png | 4 +- ...temMolecule-Day_0_null,NEXUS_5,1.0,en].png | 4 +- ...mMolecule-Night_1_null,NEXUS_5,1.0,en].png | 4 +- ...istOrganism-Day_0_null,NEXUS_5,1.0,en].png | 4 +- ...tOrganism-Night_1_null,NEXUS_5,1.0,en].png | 4 +- 13 files changed, 36 insertions(+), 52 deletions(-) create mode 100644 changelog.d/2684.misc diff --git a/changelog.d/2684.misc b/changelog.d/2684.misc new file mode 100644 index 0000000000..6604e938fd --- /dev/null +++ b/changelog.d/2684.misc @@ -0,0 +1 @@ +Improve analytics opt-in screen UI. diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index a51ce30f9d..08095a4e33 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -17,19 +17,14 @@ package io.element.android.features.analytics.impl import androidx.activity.compose.BackHandler -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.ClickableText -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Poll import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -45,18 +40,18 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.analytics.api.AnalyticsOptInEvents import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule -import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.OnboardingBackground +import io.element.android.libraries.designsystem.components.PageTitle import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ButtonSize -import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.TextButton -import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.persistentListOf @@ -82,6 +77,7 @@ fun AnalyticsOptInView( .fillMaxSize() .systemBarsPadding() .imePadding(), + background = { OnboardingBackground() }, header = { AnalyticsOptInHeader(state, onClickTerms) }, content = { AnalyticsOptInContent() }, footer = { @@ -103,11 +99,11 @@ private fun AnalyticsOptInHeader( Column( horizontalAlignment = Alignment.CenterHorizontally, ) { - IconTitleSubtitleMolecule( + PageTitle( modifier = Modifier.padding(top = 60.dp, bottom = 12.dp), title = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName), - subTitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve), - iconImageVector = Icons.Filled.Poll + subtitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve), + iconStyle = BigIcon.Style.Default(CompoundIcons.Chart()) ) val text = buildAnnotatedStringWithStyledPart( R.string.screen_analytics_prompt_read_terms, @@ -136,19 +132,6 @@ private fun AnalyticsOptInHeader( } } -@Composable -private fun CheckIcon() { - Icon( - modifier = Modifier - .size(20.dp) - .background(color = MaterialTheme.colorScheme.background, shape = CircleShape) - .padding(2.dp), - imageVector = CompoundIcons.Check(), - contentDescription = null, - tint = ElementTheme.colors.textActionAccent, - ) -} - @Composable private fun AnalyticsOptInContent() { Box( @@ -162,20 +145,20 @@ private fun AnalyticsOptInContent() { items = persistentListOf( InfoListItem( message = stringResource(id = R.string.screen_analytics_prompt_data_usage), - iconComposable = { CheckIcon() }, + iconVector = CompoundIcons.CheckCircle(), ), InfoListItem( message = stringResource(id = R.string.screen_analytics_prompt_third_party_sharing), - iconComposable = { CheckIcon() }, + iconVector = CompoundIcons.CheckCircle(), ), InfoListItem( message = stringResource(id = R.string.screen_analytics_prompt_settings), - iconComposable = { CheckIcon() }, + iconVector = CompoundIcons.CheckCircle(), ), ), - textStyle = ElementTheme.typography.fontBodyMdMedium, - iconTint = ElementTheme.colors.textPrimary, - backgroundColor = ElementTheme.colors.temporaryColorBgSpecial + textStyle = ElementTheme.typography.fontBodyLgMedium, + iconTint = ElementTheme.colors.iconSuccessPrimary, + backgroundColor = ElementTheme.colors.bgActionSecondaryHovered, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt index f8e0b9baa8..5c25593184 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt @@ -59,8 +59,8 @@ fun InfoListItemMolecule( color = backgroundColor, shape = backgroundShape, ) - .padding(vertical = 12.dp, horizontal = 20.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp), + .padding(vertical = 12.dp, horizontal = 18.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp), ) { icon() message() diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png index 59d9ccc85d..211571ab7c 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bfd87b963e38343c0b9da618414b7bdc1fe79fbb3f48727916a1d20868eb7483 -size 50628 +oid sha256:1d5426156630c38e8222a52c7e78a30db19a5ebc3f39cfb671bef1d1bd7b9576 +size 97356 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png index fee4ff1799..d4e8907ab9 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.analytics.impl_AnalyticsOptInView_null_AnalyticsOptInView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:97f2610be9d2c2351dea5c7935e5d6bcb7213780280c506253ade9a16868a093 -size 49519 +oid sha256:6d8b5a13e95a7c9091275809337ad9b55058d4846d4d2ed5a2fffae8e1a0287d +size 91071 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-1_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-1_2_null,NEXUS_5,1.0,en].png index ab74556c3d..b9b2597ddc 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-1_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Day-1_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de05e9bffd5210895933179d1a331a768e61b7d398a26e93dfd8dc65bdd01c1f -size 286642 +oid sha256:93a96978e4575891ff2577c2ecca801cad9e95ddda4ff21e5c0d28f5ee256df6 +size 286706 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-1_3_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-1_3_null,NEXUS_5,1.0,en].png index 90be31dea2..72fa53cd51 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-1_3_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.ftue.impl.welcome_WelcomeView_null_WelcomeView-Night-1_3_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5fec377a61aa10b3f23eb28aeeec2871a7b08bf6da2e586b01aae153821fdf5 -size 390188 +oid sha256:994cbbdebe72e7385eb8c8ff98312510c0e8610a40dc204ce26e37acb36c0dd6 +size 390300 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_1_null_0,NEXUS_5,1.0,en].png index fd4d7fc97b..f145a1f1b2 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bbb1f43f85ef3ac2c24182ad91629e5779d0e812d8fb2714f3c497bd6fca1cd -size 60462 +oid sha256:67c0ab8e3553db828c81ea56b2397b3a3f9a2fe8ff6f668ecdc460ecfadb4726 +size 60375 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_2_null_0,NEXUS_5,1.0,en].png index 9817a5e985..105c9840f8 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.signedout.impl_SignedOutView_null_SignedOutView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b452dd45e6581d3661724eb537d4020b73cdef47775a04d26e28d449f3d8a9b -size 58730 +oid sha256:f1b0d0d4cb8eafdd9eafa64f4788f32052542036757698200ef16ff0e20562c5 +size 58668 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Day_0_null,NEXUS_5,1.0,en].png index f87e41e29f..967a869007 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Day_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Day_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d3e7c8e6fa22c73e34ef27c7b6dc2c5780f48ad9b0680e5a0310896c0f11512 -size 19510 +oid sha256:4621864865a1acb0a4d8d502f1a75a3a888b6e6db92fa74fc24c4d93fd72cc28 +size 19488 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Night_1_null,NEXUS_5,1.0,en].png index ebf76be6fc..3678bc1d31 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Night_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.molecules_InfoListItemMolecule_null_InfoListItemMolecule-Night_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:240f32ad0c193c37d1533dd12500cad19ea52b5f7c36555dc734b371488eb750 -size 18820 +oid sha256:f1f088b90404dd0905e6799ea29912387c50262856ea25ad2f265c3ae0f0eacc +size 18833 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Day_0_null,NEXUS_5,1.0,en].png index b98fc16eeb..a2016c4e6d 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Day_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Day_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:897ee73ef00a3a322a5ab99b6b3d39945e0b62fea72f1099ace542e4cff893cf -size 13104 +oid sha256:6f4fcfd8a51f291510c25abaec2437ca46bd7bd70e83a4f77fb304210854181e +size 13141 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Night_1_null,NEXUS_5,1.0,en].png index d703c086a7..3b617db3ce 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Night_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.atomic.organisms_InfoListOrganism_null_InfoListOrganism-Night_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:770f21753ce0d8f85e8e20b7d74f4ff903da693e5ca9704ef6f2430895656454 -size 12437 +oid sha256:bfe2fb59100daf7b057e6c6b128b3684d4956331f7faaa2324e10d435c9f8234 +size 12463 From 249609edf8cdb1ac8bca689a8d19108c40632d72 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 10 Apr 2024 15:47:28 +0200 Subject: [PATCH 123/124] Changelog for version 0.4.8 --- CHANGES.md | 27 +++++++++++++++++++++++++++ changelog.d/2579.feature | 1 - changelog.d/2580.feature | 1 - changelog.d/2581.misc | 1 - changelog.d/2593.misc | 1 - changelog.d/2601.feature | 1 - changelog.d/2608.misc | 1 - changelog.d/2612.bugfix | 1 - changelog.d/2619.bugfix | 1 - changelog.d/2625.bugfix | 1 - changelog.d/2634.misc | 1 - changelog.d/2650.feature | 1 - changelog.d/2667.bugfix | 1 - changelog.d/2678.misc | 1 - changelog.d/2684.misc | 1 - 15 files changed, 27 insertions(+), 14 deletions(-) delete mode 100644 changelog.d/2579.feature delete mode 100644 changelog.d/2580.feature delete mode 100644 changelog.d/2581.misc delete mode 100644 changelog.d/2593.misc delete mode 100644 changelog.d/2601.feature delete mode 100644 changelog.d/2608.misc delete mode 100644 changelog.d/2612.bugfix delete mode 100644 changelog.d/2619.bugfix delete mode 100644 changelog.d/2625.bugfix delete mode 100644 changelog.d/2634.misc delete mode 100644 changelog.d/2650.feature delete mode 100644 changelog.d/2667.bugfix delete mode 100644 changelog.d/2678.misc delete mode 100644 changelog.d/2684.misc diff --git a/CHANGES.md b/CHANGES.md index ebb1bbf863..cdb2e4006e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,30 @@ +Changes in Element X v0.4.8 (2024-04-10) +======================================== + +Features ✨ +---------- + - Move session recovery to the login flow. ([#2579](https://github.com/element-hq/element-x-android/issues/2579)) + - Move session verification to the after login flow and make it mandatory. ([#2580](https://github.com/element-hq/element-x-android/issues/2580)) + - Add a notification troubleshoot screen ([#2601](https://github.com/element-hq/element-x-android/issues/2601)) + - Add action to copy permalink ([#2650](https://github.com/element-hq/element-x-android/issues/2650)) + +Bugfixes 🐛 +---------- + - Fix analytics issue around room considered as space by mistake. ([#2612](https://github.com/element-hq/element-x-android/issues/2612)) + - Fix crash observed when going back to the room list. ([#2619](https://github.com/element-hq/element-x-android/issues/2619)) + - Hide Event org.matrix.msc3401.call.member on the timeline. ([#2625](https://github.com/element-hq/element-x-android/issues/2625)) + - Fall back to name-based generated avatars when image avatars don't load. ([#2667](https://github.com/element-hq/element-x-android/issues/2667)) + +Other changes +------------- + - Improve UI for notification permission screen in onboarding. ([#2581](https://github.com/element-hq/element-x-android/issues/2581)) + - Categorise members by role in change roles screen. ([#2593](https://github.com/element-hq/element-x-android/issues/2593)) + - Make completed poll more clearly visible ([#2608](https://github.com/element-hq/element-x-android/issues/2608)) + - Show users from last visited DM as suggestion when starting a Chat or when creating a Room. ([#2634](https://github.com/element-hq/element-x-android/issues/2634)) + - Enable room moderation feature. ([#2678](https://github.com/element-hq/element-x-android/issues/2678)) + - Improve analytics opt-in screen UI. ([#2684](https://github.com/element-hq/element-x-android/issues/2684)) + + Changes in Element X v0.4.7 (2024-03-26) ======================================== diff --git a/changelog.d/2579.feature b/changelog.d/2579.feature deleted file mode 100644 index 19cda6dc8e..0000000000 --- a/changelog.d/2579.feature +++ /dev/null @@ -1 +0,0 @@ -Move session recovery to the login flow. diff --git a/changelog.d/2580.feature b/changelog.d/2580.feature deleted file mode 100644 index 8d3ede2ccc..0000000000 --- a/changelog.d/2580.feature +++ /dev/null @@ -1 +0,0 @@ -Move session verification to the after login flow and make it mandatory. diff --git a/changelog.d/2581.misc b/changelog.d/2581.misc deleted file mode 100644 index 6d38fdb682..0000000000 --- a/changelog.d/2581.misc +++ /dev/null @@ -1 +0,0 @@ -Improve UI for notification permission screen in onboarding. diff --git a/changelog.d/2593.misc b/changelog.d/2593.misc deleted file mode 100644 index e67bfc68e5..0000000000 --- a/changelog.d/2593.misc +++ /dev/null @@ -1 +0,0 @@ -Categorise members by role in change roles screen. diff --git a/changelog.d/2601.feature b/changelog.d/2601.feature deleted file mode 100644 index ecfc26061d..0000000000 --- a/changelog.d/2601.feature +++ /dev/null @@ -1 +0,0 @@ - Add a notification troubleshoot screen diff --git a/changelog.d/2608.misc b/changelog.d/2608.misc deleted file mode 100644 index aa2f48b5cc..0000000000 --- a/changelog.d/2608.misc +++ /dev/null @@ -1 +0,0 @@ - Make completed poll more clearly visible diff --git a/changelog.d/2612.bugfix b/changelog.d/2612.bugfix deleted file mode 100644 index fa6a0667e9..0000000000 --- a/changelog.d/2612.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix analytics issue around room considered as space by mistake. diff --git a/changelog.d/2619.bugfix b/changelog.d/2619.bugfix deleted file mode 100644 index a9d7b3f497..0000000000 --- a/changelog.d/2619.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash observed when going back to the room list. diff --git a/changelog.d/2625.bugfix b/changelog.d/2625.bugfix deleted file mode 100644 index 48fea40258..0000000000 --- a/changelog.d/2625.bugfix +++ /dev/null @@ -1 +0,0 @@ -Hide Event org.matrix.msc3401.call.member on the timeline. diff --git a/changelog.d/2634.misc b/changelog.d/2634.misc deleted file mode 100644 index 0ec68a2115..0000000000 --- a/changelog.d/2634.misc +++ /dev/null @@ -1 +0,0 @@ -Show users from last visited DM as suggestion when starting a Chat or when creating a Room. diff --git a/changelog.d/2650.feature b/changelog.d/2650.feature deleted file mode 100644 index 4287d42ac7..0000000000 --- a/changelog.d/2650.feature +++ /dev/null @@ -1 +0,0 @@ -Add action to copy permalink diff --git a/changelog.d/2667.bugfix b/changelog.d/2667.bugfix deleted file mode 100644 index f72b79503f..0000000000 --- a/changelog.d/2667.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fall back to name-based generated avatars when image avatars don't load. diff --git a/changelog.d/2678.misc b/changelog.d/2678.misc deleted file mode 100644 index 0113a26372..0000000000 --- a/changelog.d/2678.misc +++ /dev/null @@ -1 +0,0 @@ -Enable room moderation feature. diff --git a/changelog.d/2684.misc b/changelog.d/2684.misc deleted file mode 100644 index 6604e938fd..0000000000 --- a/changelog.d/2684.misc +++ /dev/null @@ -1 +0,0 @@ -Improve analytics opt-in screen UI. From 14a25d317b88cba1e31d73b2e3985aa0d32249c6 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 10 Apr 2024 15:48:43 +0200 Subject: [PATCH 124/124] Adding fastlane file for version 0.4.8 --- fastlane/metadata/android/en-US/changelogs/40004080.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40004080.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40004080.txt b/fastlane/metadata/android/en-US/changelogs/40004080.txt new file mode 100644 index 0000000000..06f69e53ea --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40004080.txt @@ -0,0 +1,2 @@ +Main changes in this version: Enable room moderation feature. +Full changelog: https://github.com/element-hq/element-x-android/releases