diff --git a/.github/ISSUE_TEMPLATE/story.yml b/.github/ISSUE_TEMPLATE/story.yml index 9aa7bbddc5..436b92b507 100644 --- a/.github/ISSUE_TEMPLATE/story.yml +++ b/.github/ISSUE_TEMPLATE/story.yml @@ -6,25 +6,28 @@ labels: [T-Story] body: - type: textarea attributes: - label: A story should take roughly a week or a sprint to finish. Each story is usually made up of a number of tasks that take half to a full day. + label: Story + description: A story should take roughly a week or a sprint to finish. Each story is usually made up of a number of tasks that take half to a full day. value: | As a user… I want to… so that I can… ## Scope - + ```[tasklist] ### Tasklist - [ ] Task 1 + ``` + - [ ] QA signoff on completion - [ ] Design signoff on completion - [ ] Product signoff on completion - ``` + ## Stretch goals None at this time - + ## Out of scope - diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index e131315720..389feb0665 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -50,4 +50,6 @@ jobs: USERNAME=maestroelement PASSWORD=${{ secrets.MATRIX_MAESTRO_ACCOUNT_PASSWORD }} ROOM_NAME=MyRoom + INVITEE1_MXID=@maestroelement2:matrix.org + INVITEE2_MXID=@maestroelement3:matrix.org APP_ID=io.element.android.x.debug diff --git a/.maestro/README.md b/.maestro/README.md index 3926dcdf56..cd1f0658a7 100644 --- a/.maestro/README.md +++ b/.maestro/README.md @@ -23,9 +23,11 @@ From root dir of the project ```shell maestro test \ -e APP_ID=io.element.android.x.debug \ - -e USERNAME=user \ + -e USERNAME=user1 \ -e PASSWORD=123 \ -e ROOM_NAME="MyRoom" \ + -e INVITEE1_MXID=user2 \ + -e INVITEE2_MXID=user3 \ .maestro/allTests.yaml ``` diff --git a/.maestro/tests/roomList/createAndDeleteDM.yaml b/.maestro/tests/roomList/createAndDeleteDM.yaml new file mode 100644 index 0000000000..f79a7418e4 --- /dev/null +++ b/.maestro/tests/roomList/createAndDeleteDM.yaml @@ -0,0 +1,13 @@ +appId: ${APP_ID} +--- +# Purpose: Test the creation and deletion of a DM room. +- tapOn: "Create a new conversation or room" +- tapOn: "Search for someone" +- inputText: ${INVITEE1_MXID} +- tapOn: + text: ${INVITEE1_MXID} + index: 1 +- takeScreenshot: build/maestro/330-createAndDeleteDM +- tapOn: "maestroelement2" +- tapOn: "Leave room" +- tapOn: "Leave" diff --git a/.maestro/tests/roomList/createAndDeleteRoom.yaml b/.maestro/tests/roomList/createAndDeleteRoom.yaml new file mode 100644 index 0000000000..b2b7c1da0b --- /dev/null +++ b/.maestro/tests/roomList/createAndDeleteRoom.yaml @@ -0,0 +1,33 @@ +appId: ${APP_ID} +--- +# Purpose: Test the creation and deletion of a room +- tapOn: "Create a new conversation or room" +- tapOn: "New room" +- tapOn: "Search for someone" +- inputText: ${INVITEE1_MXID} +- tapOn: + text: ${INVITEE1_MXID} + index: 1 +- tapOn: "Next" +- tapOn: "e.g. your project name" +- inputText: "aRoomName" +- tapOn: "What is this room about?" +- inputText: "aRoomTopic" +- tapOn: "Create" +- takeScreenshot: build/maestro/320-createAndDeleteRoom +- tapOn: "aRoomName" +- tapOn: "Invite people" +# assert there's 1 memeber and 1 invitee +- tapOn: "Search for someone" +- inputText: ${INVITEE2_MXID} +- tapOn: + text: ${INVITEE2_MXID} + index: 1 +- tapOn: "Send" +- tapOn: "Back" +- tapOn: "aRoomName" +- tapOn: "People" +# assert there's 1 memeber and 2 invitees +- tapOn: "Back" +- tapOn: "Leave room" +- tapOn: "Leave" diff --git a/.maestro/tests/roomList/roomContextMenu.yaml b/.maestro/tests/roomList/roomContextMenu.yaml new file mode 100644 index 0000000000..c2a8764558 --- /dev/null +++ b/.maestro/tests/roomList/roomContextMenu.yaml @@ -0,0 +1,14 @@ +appId: ${APP_ID} +--- +# Purpose: Test the context menu of a room in the room list +- longPressOn: ${ROOM_NAME} +- takeScreenshot: build/maestro/310-RoomList-ContextMenu +- tapOn: + text: "Settings" + index: 0 +- tapOn: "Back" +- longPressOn: ${ROOM_NAME} +- tapOn: + text: "Leave room" + index: 0 +- tapOn: "Cancel" diff --git a/.maestro/tests/roomList/roomList.yaml b/.maestro/tests/roomList/roomList.yaml index 3a26048791..6365759e72 100644 --- a/.maestro/tests/roomList/roomList.yaml +++ b/.maestro/tests/roomList/roomList.yaml @@ -1,6 +1,8 @@ appId: ${APP_ID} --- -- takeScreenshot: build/maestro/300-RoomList - runFlow: searchRoomList.yaml +- takeScreenshot: build/maestro/300-RoomList - runFlow: timeline/timeline.yaml - +- runFlow: roomContextMenu.yaml +- runFlow: createAndDeleteRoom.yaml +- runFlow: createAndDeleteDM.yaml diff --git a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt index a8a4d3f64c..36b267debb 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt @@ -18,6 +18,7 @@ package io.element.android.appnav import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.NewRoot +import com.bumble.appyx.navmodel.backstack.operation.Remove /** * Don't process NewRoot if the nav target already exists in the stack. @@ -29,3 +30,14 @@ fun BackStack.safeRoot(element: T) { if (containsRoot) return accept(NewRoot(element)) } + +/** + * Remove the last element on the backstack equals to the given one. + */ +fun BackStack.removeLast(element: T) { + val lastExpectedNavElement = elements.value.lastOrNull { + it.key.navTarget == element + } ?: return + accept(Remove(lastExpectedNavElement.key)) +} + 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 432b94e744..9411ceb4a5 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -58,7 +58,6 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient @@ -93,7 +92,7 @@ class LoggedInFlowNode @AssistedInject constructor( snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( backstack = BackStack( - initialElement = NavTarget.SplashScreen, + initialElement = NavTarget.RoomList, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -105,22 +104,14 @@ class LoggedInFlowNode @AssistedInject constructor( .distinctUntilChanged() .onEach { isConsentAsked -> if (isConsentAsked) { - switchToRoomList() + backstack.removeLast(NavTarget.AnalyticsOptIn) } else { - switchToAnalytics() + backstack.push(NavTarget.AnalyticsOptIn) } } .launchIn(lifecycleScope) } - private fun switchToRoomList() { - backstack.safeRoot(NavTarget.RoomList) - } - - private fun switchToAnalytics() { - backstack.safeRoot(NavTarget.AnalyticsSettings) - } - interface Callback : Plugin { fun onOpenBugReport() = Unit } @@ -196,9 +187,6 @@ class LoggedInFlowNode @AssistedInject constructor( } sealed interface NavTarget : Parcelable { - @Parcelize - object SplashScreen : NavTarget - @Parcelize object Permanent : NavTarget @@ -224,12 +212,11 @@ class LoggedInFlowNode @AssistedInject constructor( object InviteList : NavTarget @Parcelize - object AnalyticsSettings : NavTarget + object AnalyticsOptIn : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.SplashScreen -> splashNode(buildContext) NavTarget.Permanent -> { createNode(buildContext) } @@ -322,7 +309,7 @@ class LoggedInFlowNode @AssistedInject constructor( .callback(callback) .build() } - NavTarget.AnalyticsSettings -> { + NavTarget.AnalyticsOptIn -> { analyticsOptInEntryPoint.createNode(this, buildContext) } } @@ -341,12 +328,6 @@ class LoggedInFlowNode @AssistedInject constructor( } } - private fun splashNode(buildContext: BuildContext) = node(buildContext) { - Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - } - @Composable override fun View(modifier: Modifier) { Box(modifier = modifier) { 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 733485fcef..12e54b2ea5 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -31,7 +31,6 @@ 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.pop import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted @@ -42,6 +41,7 @@ import io.element.android.appnav.intent.IntentResolver import io.element.android.appnav.intent.ResolvedIntent import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootView +import io.element.android.features.login.api.LoginUserStory import io.element.android.features.login.api.oidc.OidcAction import io.element.android.features.login.api.oidc.OidcActionFlow import io.element.android.features.preferences.api.CacheService @@ -49,19 +49,23 @@ import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler 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.theme.components.CircularProgressIndicator import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.matrix.api.core.UserId -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.parcelize.Parcelize import timber.log.Timber +import java.util.UUID @ContributesNode(AppScope::class) class RootFlowNode @AssistedInject constructor( @@ -74,6 +78,7 @@ class RootFlowNode @AssistedInject constructor( private val bugReportEntryPoint: BugReportEntryPoint, private val intentResolver: IntentResolver, private val oidcActionFlow: OidcActionFlow, + private val loginUserStory: LoginUserStory, ) : BackstackNode( backstack = BackStack( @@ -90,21 +95,15 @@ class RootFlowNode @AssistedInject constructor( } private fun observeLoggedInState() { - authenticationService.isLoggedIn() - .distinctUntilChanged() - .combine( - cacheService.cacheIndex().onEach { - Timber.v("cacheIndex=$it") - matrixClientsHolder.removeAll() - } - ) { isLoggedIn, cacheIdx -> isLoggedIn to cacheIdx } - .onEach { pair -> - val isLoggedIn = pair.first - val cacheIndex = pair.second - Timber.v("isLoggedIn=$isLoggedIn, cacheIndex=$cacheIndex") + combine( + cacheService.onClearedCacheEventFlow(), + isUserLoggedInFlow(), + ) { _, isLoggedIn -> isLoggedIn } + .onEach { isLoggedIn -> + Timber.v("isLoggedIn=$isLoggedIn") if (isLoggedIn) { tryToRestoreLatestSession( - onSuccess = { switchToLoggedInFlow(it, cacheIndex) }, + onSuccess = { switchToLoggedInFlow(it) }, onFailure = { switchToNotLoggedInFlow() } ) } else { @@ -114,8 +113,19 @@ class RootFlowNode @AssistedInject constructor( .launchIn(lifecycleScope) } - private fun switchToLoggedInFlow(sessionId: SessionId, cacheIndex: Int) { - backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex)) + + private fun switchToLoggedInFlow(sessionId: SessionId) { + backstack.safeRoot(NavTarget.LoggedInFlow(sessionId)) + } + + private fun isUserLoggedInFlow(): Flow { + return combine( + authenticationService.isLoggedIn(), + loginUserStory.loginFlowIsDone + ) { isLoggedIn, loginFlowIsDone -> + isLoggedIn && loginFlowIsDone + } + .distinctUntilChanged() } private fun switchToNotLoggedInFlow() { @@ -123,28 +133,38 @@ class RootFlowNode @AssistedInject constructor( backstack.safeRoot(NavTarget.NotLoggedInFlow) } + private suspend fun restoreSessionIfNeeded( + sessionId: SessionId, + onFailure: () -> Unit = {}, + onSuccess: (SessionId) -> Unit = {}, + ) { + // If the session is already known it'll be restored by the node hierarchy + if (matrixClientsHolder.knowSession(sessionId)) { + Timber.v("Session $sessionId already alive, no need to restore.") + return + } + authenticationService.restoreSession(sessionId) + .onSuccess { matrixClient -> + matrixClientsHolder.add(matrixClient) + Timber.v("Succeed to restore session $sessionId") + onSuccess(sessionId) + } + .onFailure { + Timber.v("Failed to restore session $sessionId") + onFailure() + } + } + private suspend fun tryToRestoreLatestSession( - onSuccess: (UserId) -> Unit = {}, + onSuccess: (SessionId) -> Unit = {}, onFailure: () -> Unit = {} ) { - val latestKnownUserId = authenticationService.getLatestSessionId() - if (latestKnownUserId == null) { + val latestSessionId = authenticationService.getLatestSessionId() + if (latestSessionId == null) { onFailure() return } - if (matrixClientsHolder.knowSession(latestKnownUserId)) { - onSuccess(latestKnownUserId) - return - } - authenticationService.restoreSession(UserId(latestKnownUserId.value)) - .onSuccess { matrixClient -> - matrixClientsHolder.add(matrixClient) - onSuccess(matrixClient.sessionId) - } - .onFailure { - Timber.v("Failed to restore session...") - onFailure() - } + restoreSessionIfNeeded(latestSessionId, onFailure, onSuccess) } private fun onOpenBugReport() { @@ -175,7 +195,10 @@ class RootFlowNode @AssistedInject constructor( object NotLoggedInFlow : NavTarget @Parcelize - data class LoggedInFlow(val sessionId: SessionId, val cacheIndex: Int) : NavTarget + data class LoggedInFlow( + val sessionId: SessionId, + val navId: UUID = UUID.randomUUID(), + ) : NavTarget @Parcelize object BugReport : NavTarget @@ -186,7 +209,6 @@ class RootFlowNode @AssistedInject constructor( is NavTarget.LoggedInFlow -> { val matrixClient = matrixClientsHolder.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also { Timber.w("Couldn't find any session, go through SplashScreen") - backstack.newRoot(NavTarget.SplashScreen) } val inputs = LoggedInFlowNode.Inputs(matrixClient) val callback = object : LoggedInFlowNode.Callback { @@ -247,9 +269,16 @@ class RootFlowNode @AssistedInject constructor( } private suspend fun attachSession(sessionId: SessionId): LoggedInFlowNode { - val cacheIndex = cacheService.cacheIndex().first() - return attachChild { - backstack.newRoot(NavTarget.LoggedInFlow(sessionId, cacheIndex)) + //TODO handle multi-session + return waitForChildAttached { navTarget -> + navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId } } + + private fun CacheService.onClearedCacheEventFlow(): Flow { + return clearedCacheEventFlow + .onEach { sessionId -> matrixClientsHolder.remove(sessionId) } + .map { } + .onStart { emit((Unit)) } + } } diff --git a/build.gradle.kts b/build.gradle.kts index 6150822ade..bb112659b1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -249,6 +249,7 @@ koverMerged { excludes += "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState" excludes += "io.element.android.features.messages.impl.media.local.LocalMediaViewState" excludes += "io.element.android.features.location.impl.map.MapState" + excludes += "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*" } bound { minValue = 90 diff --git a/changelog.d/712.bugfix b/changelog.d/712.bugfix new file mode 100644 index 0000000000..7a3115a610 --- /dev/null +++ b/changelog.d/712.bugfix @@ -0,0 +1 @@ +Fix actions for redacted, not sent and media messages diff --git a/changelog.d/792.bugfix b/changelog.d/792.bugfix new file mode 100644 index 0000000000..07bac8d78e --- /dev/null +++ b/changelog.d/792.bugfix @@ -0,0 +1 @@ +Use the `Outlined` version of M3 textfields for the login screen. diff --git a/features/location/api/build.gradle.kts b/features/location/api/build.gradle.kts index 4d593e1c53..0e517fd3e6 100644 --- a/features/location/api/build.gradle.kts +++ b/features/location/api/build.gradle.kts @@ -17,6 +17,7 @@ plugins { id("io.element.android-compose-library") alias(libs.plugins.ksp) + id("kotlin-parcelize") } android { diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/GeoUris.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/Location.kt similarity index 60% rename from features/location/api/src/main/kotlin/io/element/android/features/location/api/GeoUris.kt rename to features/location/api/src/main/kotlin/io/element/android/features/location/api/Location.kt index 2986d50591..d09e163c30 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/GeoUris.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/Location.kt @@ -16,19 +16,25 @@ package io.element.android.features.location.api +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + private const val GEO_URI_REGEX = """geo:(?-?\d+(?:\.\d+)?),(?-?\d+(?:\.\d+)?)(?:;u=(?\d+(?:\.\d+)?))?""" +@Parcelize data class Location( val lat: Double, val lon: Double, val accuracy: Float, -) - -fun parseGeoUri(geoUri: String): Location? { - val result = Regex(GEO_URI_REGEX).matchEntire(geoUri) ?: return null - return Location ( - lat = result.groups["latitude"]?.value?.toDoubleOrNull() ?: return null, - lon = result.groups["longitude"]?.value?.toDoubleOrNull() ?: return null, - accuracy = result.groups["uncertainty"]?.value?.toFloatOrNull() ?: 0f, - ) +) : Parcelable { + companion object { + fun fromGeoUri(geoUri: String): Location? { + val result = Regex(GEO_URI_REGEX).matchEntire(geoUri) ?: return null + return Location( + lat = result.groups["latitude"]?.value?.toDoubleOrNull() ?: return null, + lon = result.groups["longitude"]?.value?.toDoubleOrNull() ?: return null, + accuracy = result.groups["uncertainty"]?.value?.toFloatOrNull() ?: 0f, + ) + } + } } diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt new file mode 100644 index 0000000000..3c429dfa63 --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt @@ -0,0 +1,29 @@ +/* + * 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.location.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.architecture.NodeInputs + +interface ShowLocationEntryPoint : FeatureEntryPoint { + + data class Inputs(val location: Location, val description: String?) : NodeInputs + + fun createNode(parentNode: Node, buildContext: BuildContext, inputs: Inputs): Node +} diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/GeoUrisKtTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt similarity index 53% rename from features/location/api/src/test/kotlin/io/element/android/features/location/api/GeoUrisKtTest.kt rename to features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt index 1c591bb2bb..61d9bd0351 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/GeoUrisKtTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt @@ -19,57 +19,57 @@ package io.element.android.features.location.api import com.google.common.truth.Truth.assertThat import org.junit.Test -internal class GeoUrisKtTest { +internal class LocationKtTest { @Test fun `parseGeoUri - returns null for invalid urls`() { - assertThat(parseGeoUri("")).isNull() - assertThat(parseGeoUri("http://example.com/")).isNull() - assertThat(parseGeoUri("geo:")).isNull() - assertThat(parseGeoUri("geo:1.234")).isNull() - assertThat(parseGeoUri("geo:1.234,")).isNull() - assertThat(parseGeoUri("geo:,1.234")).isNull() - assertThat(parseGeoUri("notgeo:1.234,5.678")).isNull() - assertThat(parseGeoUri("geo:+1.234,5.678")).isNull() - assertThat(parseGeoUri("geo:+1.234,*5.678")).isNull() - assertThat(parseGeoUri("geo:not,good")).isNull() - assertThat(parseGeoUri("geo:1.234,5.678;u=wrong")).isNull() - assertThat(parseGeoUri("geo:1.234,5.678trailing")).isNull() + assertThat(Location.fromGeoUri("")).isNull() + assertThat(Location.fromGeoUri("http://example.com/")).isNull() + assertThat(Location.fromGeoUri("geo:")).isNull() + assertThat(Location.fromGeoUri("geo:1.234")).isNull() + assertThat(Location.fromGeoUri("geo:1.234,")).isNull() + assertThat(Location.fromGeoUri("geo:,1.234")).isNull() + assertThat(Location.fromGeoUri("notgeo:1.234,5.678")).isNull() + assertThat(Location.fromGeoUri("geo:+1.234,5.678")).isNull() + assertThat(Location.fromGeoUri("geo:+1.234,*5.678")).isNull() + assertThat(Location.fromGeoUri("geo:not,good")).isNull() + assertThat(Location.fromGeoUri("geo:1.234,5.678;u=wrong")).isNull() + assertThat(Location.fromGeoUri("geo:1.234,5.678trailing")).isNull() } @Test fun `parseGeoUri - returns location for valid urls`() { - assertThat(parseGeoUri("geo:1.234,5.678")).isEqualTo(Location( + assertThat(Location.fromGeoUri("geo:1.234,5.678")).isEqualTo(Location( lat = 1.234, lon = 5.678, accuracy = 0f, )) - assertThat(parseGeoUri("geo:1,5")).isEqualTo(Location( + assertThat(Location.fromGeoUri("geo:1,5")).isEqualTo(Location( lat = 1.0, lon = 5.0, accuracy = 0f, )) - assertThat(parseGeoUri("geo:1.234,5.678;u=3000")).isEqualTo(Location( + assertThat(Location.fromGeoUri("geo:1.234,5.678;u=3000")).isEqualTo(Location( lat = 1.234, lon = 5.678, accuracy = 3000f, )) - assertThat(parseGeoUri("geo:1,5;u=3000")).isEqualTo(Location( + assertThat(Location.fromGeoUri("geo:1,5;u=3000")).isEqualTo(Location( lat = 1.0, lon = 5.0, accuracy = 3000f, )) - assertThat(parseGeoUri("geo:-1.234,-5.678;u=9.10")).isEqualTo(Location( + assertThat(Location.fromGeoUri("geo:-1.234,-5.678;u=9.10")).isEqualTo(Location( lat = -1.234, lon = -5.678, accuracy = 9.10f, )) - assertThat(parseGeoUri("geo:-1,-5;u=9.10")).isEqualTo(Location( + assertThat(Location.fromGeoUri("geo:-1,-5;u=9.10")).isEqualTo(Location( lat = -1.0, lon = -5.0, accuracy = 9.10f, diff --git a/features/location/impl/build.gradle.kts b/features/location/impl/build.gradle.kts index eed060cbf8..dfb5192bea 100644 --- a/features/location/impl/build.gradle.kts +++ b/features/location/impl/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.core) implementation(projects.libraries.matrixui) + implementation(projects.services.analytics.api) implementation(libs.maplibre) implementation(libs.maplibre.annotation) implementation(projects.libraries.uiStrings) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationPresenter.kt index 1be82525e9..ce5eb10817 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/SendLocationPresenter.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.location.AssetType import kotlinx.coroutines.launch import javax.inject.Inject @@ -62,6 +63,9 @@ class SendLocationPresenter @Inject constructor( room.sendLocation( body = "Location at latitude: ${event.lat}, longitude: ${event.lng}", geoUri = "geo:${event.lat},${event.lng}", + description = null, + zoomLevel = 15, // Send default zoom level for now. + assetType = AssetType.PIN, ) } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowImpl.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowImpl.kt index b650fd51de..f4fcd25de1 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowImpl.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowImpl.kt @@ -24,6 +24,7 @@ import androidx.core.content.getSystemService import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationManagerCompat import androidx.core.location.LocationRequestCompat +import io.element.android.features.location.api.Location import io.element.android.libraries.core.coroutine.CoroutineDispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt index b3104d1ed9..18d568d4a4 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/map/MapView.kt @@ -19,6 +19,7 @@ package io.element.android.features.location.impl.map import android.annotation.SuppressLint import android.view.Gravity import androidx.annotation.DrawableRes +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable @@ -29,7 +30,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.tooling.preview.Preview @@ -44,10 +48,12 @@ import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.Style import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions +import com.mapbox.mapboxsdk.style.layers.Property.ICON_ANCHOR_BOTTOM +import io.element.android.features.location.api.Location import io.element.android.features.location.api.internal.buildTileServerUrl -import io.element.android.features.location.impl.location.Location import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -69,7 +75,11 @@ fun MapView( // When in preview, early return a Box with the received modifier preserving layout if (LocalInspectionMode.current) { @Suppress("ModifierReused") // False positive, the modifier is not reused due to the early return. - Box(modifier = modifier) + Box( + modifier = modifier.background(Color.DarkGray) + ) { + Text("[MapView]", modifier = Modifier.align(Alignment.Center)) + } return } @@ -80,11 +90,14 @@ fun MapView( } var mapRefs by remember { mutableStateOf(null) } + val attributionColour = ElementTheme.colors.iconPrimary + // Build map LaunchedEffect(darkMode) { mapView.awaitMap().let { map -> map.uiSettings.apply { attributionGravity = Gravity.TOP + setAttributionTintColor(attributionColour.toArgb()) logoGravity = Gravity.TOP isCompassEnabled = false isRotateGesturesEnabled = false @@ -155,7 +168,7 @@ fun MapView( .withLatLng(LatLng(location.lat, location.lon)) .withIconImage("pin") .withIconSize(1.3f) - .withIconOffset(arrayOf(0f, 0.5f)) + .withIconAnchor(ICON_ANCHOR_BOTTOM) ) Timber.d("Shown pin at location: $location") } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/AndroidLocationActions.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/AndroidLocationActions.kt new file mode 100644 index 0000000000..fb23f72557 --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/AndroidLocationActions.kt @@ -0,0 +1,65 @@ +/* + * 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.location.impl.show + +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.annotation.VisibleForTesting +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.location.api.Location +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import timber.log.Timber +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class AndroidLocationActions @Inject constructor( + @ApplicationContext private val appContext: Context +) : LocationActions { + + private var activityContext: Context? = null + + override fun share(location: Location, label: String?) { + runCatching { + val uri = Uri.parse(buildUrl(location, label)) + val showMapsIntent = Intent(Intent.ACTION_VIEW).setData(uri) + val chooserIntent = Intent.createChooser(showMapsIntent, null) + chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + appContext.startActivity(chooserIntent) + }.onSuccess { + Timber.v("Open location succeed") + }.onFailure { + Timber.e(it, "Open location failed") + } + } +} + +@VisibleForTesting +internal fun buildUrl( + location: Location, + label: String?, + urlEncoder: (String) -> String = Uri::encode +): String { + // Ref: https://developer.android.com/guide/components/intents-common#ViewMap + val base = "geo:0,0?q=%.6f,%.6f".format(location.lat, location.lon) + return if (label == null) { + base + } else { + "%s (%s)".format(base, urlEncoder(label)) + } +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/location/Location.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/LocationActions.kt similarity index 72% rename from features/location/impl/src/main/kotlin/io/element/android/features/location/impl/location/Location.kt rename to features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/LocationActions.kt index 67acf1cb9c..7e38bd65fa 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/location/Location.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/LocationActions.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -package io.element.android.features.location.impl.location +package io.element.android.features.location.impl.show -/** - * Represents a location sample emitted by the device's location subsystem. - */ -data class Location( - val lat: Double, - val lon: Double, - val accuracy: Float, -) +import io.element.android.features.location.api.Location + +interface LocationActions { + fun share(location: Location, label: String?) +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEntryPointImpl.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEntryPointImpl.kt new file mode 100644 index 0000000000..7dc1fc02f3 --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEntryPointImpl.kt @@ -0,0 +1,32 @@ +/* + * 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.location.impl.show + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.location.api.ShowLocationEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class ShowLocationEntryPointImpl @Inject constructor() : ShowLocationEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext, inputs: ShowLocationEntryPoint.Inputs): Node { + return parentNode.createNode(buildContext, listOf(inputs)) + } +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt new file mode 100644 index 0000000000..8d5b1143fb --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt @@ -0,0 +1,21 @@ +/* + * 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.location.impl.show + +sealed interface ShowLocationEvents { + object Share : ShowLocationEvents +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt new file mode 100644 index 0000000000..24094b03ca --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt @@ -0,0 +1,61 @@ +/* + * 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.location.impl.show + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.lifecycle.subscribe +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 im.vector.app.features.analytics.plan.MobileScreen +import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.location.api.ShowLocationEntryPoint +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.RoomScope +import io.element.android.services.analytics.api.AnalyticsService + +@ContributesNode(RoomScope::class) +class ShowLocationNode @AssistedInject constructor( + presenterFactory: ShowLocationPresenter.Factory, + analyticsService: AnalyticsService, + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : Node(buildContext, plugins = plugins) { + + init { + lifecycle.subscribe( + onResume = { + analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.LocationView)) + } + ) + } + + private val inputs: ShowLocationEntryPoint.Inputs = inputs() + private val presenter = presenterFactory.create(inputs.location, inputs.description) + + @Composable + override fun View(modifier: Modifier) { + ShowLocationView( + state = presenter.present(), + modifier = modifier, + onBackPressed = ::navigateUp + ) + } +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt new file mode 100644 index 0000000000..b632b0f0a8 --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt @@ -0,0 +1,48 @@ +/* + * 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.location.impl.show + +import androidx.compose.runtime.Composable +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.features.location.api.Location +import io.element.android.libraries.architecture.Presenter + +class ShowLocationPresenter @AssistedInject constructor( + private val actions: LocationActions, + @Assisted private val location: Location, + @Assisted private val description: String? +) : Presenter { + + @AssistedFactory + interface Factory { + fun create(location: Location, description: String?): ShowLocationPresenter + } + + @Composable + override fun present(): ShowLocationState { + return ShowLocationState( + location = location, + description = description + ) { + when (it) { + ShowLocationEvents.Share -> actions.share(location, description) + } + } + } +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt new file mode 100644 index 0000000000..c381acb347 --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.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.features.location.impl.show + +import io.element.android.features.location.api.Location + +data class ShowLocationState( + val location: Location, + val description: String?, + val eventSink: (ShowLocationEvents) -> Unit, +) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt new file mode 100644 index 0000000000..878cc47882 --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt @@ -0,0 +1,42 @@ +/* + * 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.location.impl.show + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.location.api.Location + +class ShowLocationStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + ShowLocationState( + Location(1.23, 2.34, 4f), + description = null, + eventSink = {}, + ), + ShowLocationState( + Location(1.23, 2.34, 4f), + description = "My favourite place!", + eventSink = {}, + ), + ShowLocationState( + Location(1.23, 2.34, 4f), + description = "For some reason I decided to write a small essay in the location description. " + + "It is so long that it will wrap onto more than two lines!", + eventSink = {}, + ), + ) +} diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt new file mode 100644 index 0000000000..25aa7fbfad --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt @@ -0,0 +1,125 @@ +/* + * 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.location.impl.show + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Share +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.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.features.location.impl.map.MapState +import io.element.android.features.location.impl.map.MapView +import io.element.android.features.location.impl.map.rememberMapState +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar +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.theme.compound.generated.TypographyTokens +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) +@Composable +fun ShowLocationView( + state: ShowLocationState, + modifier: Modifier = Modifier, + onBackPressed: () -> Unit = {}, +) { + val mapState = rememberMapState( + location = state.location, + position = MapState.CameraPosition(state.location.lat, state.location.lon, 15.0), + ) + + Scaffold(modifier, + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(CommonStrings.screen_view_location_title), + style = TypographyTokens.fontBodyLgMedium, + ) + }, + navigationIcon = { + BackButton(onClick = onBackPressed) + }, + actions = { + IconButton(onClick = { state.eventSink(ShowLocationEvents.Share) }) { + Icon(imageVector = Icons.Outlined.Share, contentDescription = stringResource(CommonStrings.action_share)) + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .consumeWindowInsets(paddingValues) + .fillMaxSize(), + ) { + state.description?.let { + Text( + text = it, + textAlign = TextAlign.Center, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = TypographyTokens.fontBodyMdRegular, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + ) + } + + MapView( + mapState = mapState, + modifier = Modifier.fillMaxSize(), + ) + } + } +} + +@Preview +@Composable +internal fun ShowLocationViewLightPreview(@PreviewParameter(ShowLocationStateProvider::class) state: ShowLocationState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +internal fun ShowLocationViewDarkPreview(@PreviewParameter(ShowLocationStateProvider::class) state: ShowLocationState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: ShowLocationState) { + ShowLocationView( + state = state, + onBackPressed = {}, + ) +} diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowFake.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowFake.kt index ca88468c0d..861657a7e7 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowFake.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/location/LocationUpdatesFlowFake.kt @@ -16,18 +16,19 @@ package io.element.android.features.location.impl.location +import io.element.android.features.location.api.Location import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -fun fakeLocationUpdatesFlow(): Flow = flow { +fun fakeLocationUpdatesFlow(): Flow = flow { while (true) { delay(1_000) emit(aLocation()) } } -private fun aLocation() = io.element.android.features.location.impl.location.Location( +private fun aLocation() = Location( lat = 51.49404, lon = -0.25484, accuracy = 5f diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/AndroidLocationActionsTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/AndroidLocationActionsTest.kt new file mode 100644 index 0000000000..14dd983f34 --- /dev/null +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/AndroidLocationActionsTest.kt @@ -0,0 +1,70 @@ +/* + * 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.location.impl.show + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.location.api.Location +import org.junit.Test +import java.net.URLEncoder + +internal class AndroidLocationActionsTest { + + // We use an Android-native encoder in the actual app, switch to an equivalent JVM one for the tests + private fun urlEncoder(input: String) = URLEncoder.encode(input, "US-ASCII") + + @Test + fun `buildUrl - truncates excessive decimals to 6dp`() { + val location = Location( + lat = 1.234567890123, + lon = 123.456789012345, + accuracy = 0f + ) + + val actual = buildUrl(location, null, ::urlEncoder) + val expected = "geo:0,0?q=1.234568,123.456789" + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `buildUrl - appends label if set`() { + val location = Location( + lat = 1.000001, + lon = 2.000001, + accuracy = 0f + ) + + val actual = buildUrl(location, "point", ::urlEncoder) + val expected = "geo:0,0?q=1.000001,2.000001 (point)" + + assertThat(actual).isEqualTo(expected) + } + + @Test + fun `buildUrl - URL encodes label`() { + val location = Location( + lat = 1.000001, + lon = 2.000001, + accuracy = 0f + ) + + val actual = buildUrl(location, "(weird/stuff here)", ::urlEncoder) + val expected = "geo:0,0?q=1.000001,2.000001 (%28weird%2Fstuff+here%29)" + + assertThat(actual).isEqualTo(expected) + } +} diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/FakeLocationActions.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/FakeLocationActions.kt new file mode 100644 index 0000000000..411863f725 --- /dev/null +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/FakeLocationActions.kt @@ -0,0 +1,33 @@ +/* + * 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.location.impl.show + +import io.element.android.features.location.api.Location + +class FakeLocationActions : LocationActions { + + var sharedLocation: Location? = null + private set + + var sharedLabel: String? = null + private set + + override fun share(location: Location, label: String?) { + sharedLocation = location + sharedLabel = label + } +} diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt new file mode 100644 index 0000000000..5ff323f463 --- /dev/null +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenterTest.kt @@ -0,0 +1,72 @@ +/* + * 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.location.impl.show + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth +import io.element.android.features.location.api.Location +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ShowLocationPresenterTest { + + private val actions = FakeLocationActions() + private val location = Location(1.23, 4.56, 7.8f) + + @Test + fun `emits initial state`() = runTest { + val presenter = ShowLocationPresenter( + actions, + location, + A_DESCRIPTION, + ) + + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + Truth.assertThat(initialState.location).isEqualTo(location) + Truth.assertThat(initialState.description).isEqualTo(A_DESCRIPTION) + } + } + + @Test + fun `uses action to share location`() = runTest { + val presenter = ShowLocationPresenter( + actions, + location, + A_DESCRIPTION, + ) + + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(ShowLocationEvents.Share) + + Truth.assertThat(actions.sharedLocation).isEqualTo(location) + Truth.assertThat(actions.sharedLabel).isEqualTo(A_DESCRIPTION) + } + } + + companion object { + private const val A_DESCRIPTION = "My happy place" + } + +} diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginUserStory.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginUserStory.kt new file mode 100644 index 0000000000..3a4cc54563 --- /dev/null +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginUserStory.kt @@ -0,0 +1,23 @@ +/* + * 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.login.api + +import kotlinx.coroutines.flow.StateFlow + +interface LoginUserStory { + val loginFlowIsDone: StateFlow +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginUserStory.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginUserStory.kt new file mode 100644 index 0000000000..26b00068bc --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginUserStory.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.features.login.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.login.api.LoginUserStory +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultLoginUserStory @Inject constructor() : LoginUserStory { + // True by default, will be set to false when the login user story is started, and set to true again once it's done. + override val loginFlowIsDone: MutableStateFlow = MutableStateFlow(true) + + fun setLoginFlowIsDone(value: Boolean) { + loginFlowIsDone.value = value + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index f8d965ec6d..fe47fb1b67 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -18,7 +18,6 @@ package io.element.android.features.login.impl import android.app.Activity import android.os.Parcelable -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier @@ -28,6 +27,7 @@ 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 com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.singleTop import dagger.assisted.Assisted @@ -39,8 +39,10 @@ import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler import io.element.android.features.login.impl.oidc.webview.OidcNode import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode +import io.element.android.features.login.impl.screens.loginpassword.LoginFormState import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode +import io.element.android.features.login.impl.screens.waitlistscreen.WaitListNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -58,6 +60,7 @@ class LoginFlowNode @AssistedInject constructor( private val customTabAvailabilityChecker: CustomTabAvailabilityChecker, private val customTabHandler: CustomTabHandler, private val accountProviderDataSource: AccountProviderDataSource, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.ConfirmAccountProvider, @@ -75,6 +78,11 @@ class LoginFlowNode @AssistedInject constructor( private val inputs: Inputs = inputs() + override fun onBuilt() { + super.onBuilt() + defaultLoginUserStory.setLoginFlowIsDone(false) + } + sealed interface NavTarget : Parcelable { @Parcelize object ConfirmAccountProvider : NavTarget @@ -88,6 +96,9 @@ class LoginFlowNode @AssistedInject constructor( @Parcelize object LoginPassword : NavTarget + @Parcelize + data class WaitList(val loginFormState: LoginFormState) : NavTarget + @Parcelize data class OidcView(val oidcDetails: OidcDetails) : NavTarget } @@ -144,12 +155,28 @@ class LoginFlowNode @AssistedInject constructor( createNode(buildContext, plugins = listOf(callback)) } NavTarget.LoginPassword -> { - createNode(buildContext, plugins = listOf()) + val callback = object : LoginPasswordNode.Callback { + override fun onWaitListError(loginFormState: LoginFormState) { + backstack.newRoot(NavTarget.WaitList(loginFormState)) + } + } + createNode(buildContext, plugins = listOf(callback)) } is NavTarget.OidcView -> { val input = OidcNode.Inputs(navTarget.oidcDetails) createNode(buildContext, plugins = listOf(input)) } + is NavTarget.WaitList -> { + val inputs = WaitListNode.Inputs( + loginFormState = navTarget.loginFormState, + ) + val callback = object : WaitListNode.Callback { + override fun onCancelClicked() { + navigateUp() + } + } + createNode(buildContext, plugins = listOf(callback, inputs)) + } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/WaitListError.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/WaitListError.kt new file mode 100644 index 0000000000..99060f3464 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/WaitListError.kt @@ -0,0 +1,23 @@ +/* + * 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.login.impl.error + +import io.element.android.libraries.core.bool.orFalse + +fun Throwable.isWaitListError(): Boolean { + return message?.contains("IO_ELEMENT_X_WAIT_LIST").orFalse() +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt index 3efa0be668..2bc002b3fc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -91,7 +91,7 @@ fun ConfirmAccountProviderView( text = stringResource(id = R.string.screen_account_provider_continue), showProgress = isLoading, onClick = { eventSink.invoke(ConfirmAccountProviderEvents.Continue) }, - enabled = state.submitEnabled, + enabled = state.submitEnabled || isLoading, modifier = Modifier .fillMaxWidth() .testTag(TestTags.loginContinue) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt index 630b08570c..cb5542d2eb 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt @@ -21,6 +21,7 @@ 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 @@ -33,13 +34,22 @@ class LoginPasswordNode @AssistedInject constructor( private val presenter: LoginPasswordPresenter, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onWaitListError(loginFormState: LoginFormState) + } + + private fun onWaitListError(loginFormState: LoginFormState) { + plugins().forEach { it.onWaitListError(loginFormState) } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() LoginPasswordView( state = state, modifier = modifier, - onBackPressed = ::navigateUp + onBackPressed = ::navigateUp, + onWaitListError = ::onWaitListError, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index 1fc4a10bbb..b2ea5fb985 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter @@ -36,6 +37,7 @@ import javax.inject.Inject class LoginPasswordPresenter @Inject constructor( private val authenticationService: MatrixAuthenticationService, private val accountProviderDataSource: AccountProviderDataSource, + private val defaultLoginUserStory: DefaultLoginUserStory, ) : Presenter { @Composable @@ -77,6 +79,8 @@ class LoginPasswordPresenter @Inject constructor( loggedInState.value = Async.Loading() authenticationService.login(formState.login.trim(), formState.password) .onSuccess { sessionId -> + // We will not navigate to the WaitList screen, so the login user story is done + defaultLoginUserStory.setLoginFlowIsDone(true) loggedInState.value = Async.Success(sessionId) } .onFailure { failure -> diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index 9d6fe7d1ea..e7d1019565 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -16,7 +16,6 @@ package io.element.android.features.login.impl.screens.loginpassword -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Spacer @@ -56,6 +55,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.features.login.impl.R +import io.element.android.features.login.impl.error.isWaitListError import io.element.android.features.login.impl.error.loginError import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.ElementTextStyles @@ -68,9 +68,9 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight 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.OutlinedTextField 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.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.autofill import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext @@ -82,8 +82,9 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun LoginPasswordView( state: LoginPasswordState, - modifier: Modifier = Modifier, onBackPressed: () -> Unit, + onWaitListError: (LoginFormState) -> Unit, + modifier: Modifier = Modifier, ) { val isLoading by remember(state.loginAction) { derivedStateOf { @@ -108,53 +109,60 @@ fun LoginPasswordView( ) } ) { padding -> - Box( + val scrollState = rememberScrollState() + + Column( modifier = Modifier .fillMaxSize() .imePadding() .padding(padding) .consumeWindowInsets(padding) + .verticalScroll(state = scrollState) + .padding(horizontal = 16.dp), ) { - val scrollState = rememberScrollState() - - Column( + // Title + IconTitleSubtitleMolecule( + modifier = Modifier.padding(top = 20.dp, start = 16.dp, end = 16.dp), + iconImageVector = Icons.Filled.AccountCircle, + title = stringResource( + id = R.string.screen_account_provider_signin_title, + state.accountProvider.title + ), + subTitle = stringResource(id = R.string.screen_login_subtitle) + ) + Spacer(Modifier.height(40.dp)) + LoginForm( + state = state, + isLoading = isLoading, + onSubmit = ::submit + ) + // Min spacing + Spacer(Modifier.height(24.dp)) + // Flexible spacing to keep the submit button at the bottom + Spacer(modifier = Modifier.weight(1f)) + // Submit + ButtonWithProgress( + text = stringResource(R.string.screen_login_submit), + showProgress = isLoading, + onClick = ::submit, + enabled = state.submitEnabled || isLoading, modifier = Modifier - .verticalScroll(state = scrollState) - .padding(horizontal = 16.dp), - ) { - // Title - IconTitleSubtitleMolecule( - modifier = Modifier.padding(top = 20.dp), - iconImageVector = Icons.Filled.AccountCircle, - title = stringResource( - id = R.string.screen_account_provider_signin_title, - state.accountProvider.title - ), - subTitle = stringResource(id = R.string.screen_login_form_header) - ) - Spacer(Modifier.height(32.dp)) - LoginForm(state = state, - isLoading = isLoading, - onSubmit = ::submit - ) - Spacer(Modifier.height(28.dp)) - // Submit - ButtonWithProgress( - text = stringResource(R.string.screen_login_submit), - showProgress = isLoading, - onClick = ::submit, - enabled = state.submitEnabled, - modifier = Modifier - .fillMaxWidth() - .testTag(TestTags.loginContinue) - ) - Spacer(modifier = Modifier.height(32.dp)) - } + .fillMaxWidth() + .testTag(TestTags.loginContinue) + ) + Spacer(modifier = Modifier.height(60.dp)) if (state.loginAction is Async.Failure) { - LoginErrorDialog(error = state.loginAction.error, onDismiss = { - state.eventSink(LoginPasswordEvents.ClearError) - }) + when { + state.loginAction.error.isWaitListError() -> { + onWaitListError(state.formState) + } + else -> { + LoginErrorDialog(error = state.loginAction.error, onDismiss = { + state.eventSink(LoginPasswordEvents.ClearError) + }) + } + } } } } @@ -182,7 +190,7 @@ internal fun LoginForm( ) Spacer(modifier = Modifier.height(8.dp)) - TextField( + OutlinedTextField( value = loginFieldState, readOnly = isLoading, modifier = Modifier @@ -193,7 +201,7 @@ internal fun LoginForm( loginFieldState = it eventSink(LoginPasswordEvents.SetLogin(it)) }), - label = { + placeholder = { Text(text = stringResource(R.string.screen_login_username_hint)) }, onValueChange = { @@ -225,7 +233,7 @@ internal fun LoginForm( passwordVisible = false } Spacer(Modifier.height(20.dp)) - TextField( + OutlinedTextField( value = passwordFieldState, readOnly = isLoading, modifier = Modifier @@ -240,7 +248,7 @@ internal fun LoginForm( passwordFieldState = it eventSink(LoginPasswordEvents.SetPassword(it)) }, - label = { + placeholder = { Text(text = stringResource(R.string.screen_login_password_hint)) }, visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), @@ -269,6 +277,7 @@ internal fun LoginForm( @Composable internal fun LoginErrorDialog(error: Throwable, onDismiss: () -> Unit) { ErrorDialog( + title = stringResource(id = CommonStrings.dialog_title_error), content = stringResource(loginError(error)), onDismiss = onDismiss ) @@ -288,6 +297,7 @@ internal fun LoginPasswordViewDarkPreview(@PreviewParameter(LoginPasswordStatePr private fun ContentToPreview(state: LoginPasswordState) { LoginPasswordView( state = state, - onBackPressed = {} + onBackPressed = {}, + onWaitListError = {}, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt new file mode 100644 index 0000000000..5ceee99f91 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListEvents.kt @@ -0,0 +1,23 @@ +/* + * 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.login.impl.screens.waitlistscreen + +sealed interface WaitListEvents { + object AttemptLogin : WaitListEvents + object ClearError : WaitListEvents + object Continue : WaitListEvents +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListNode.kt new file mode 100644 index 0000000000..24b5f271a0 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListNode.kt @@ -0,0 +1,62 @@ +/* + * 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.login.impl.screens.waitlistscreen + +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 io.element.android.anvilannotations.ContributesNode +import io.element.android.features.login.impl.screens.loginpassword.LoginFormState +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.AppScope + +@ContributesNode(AppScope::class) +class WaitListNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: WaitListPresenter.Factory, +) : Node(buildContext, plugins = plugins) { + + data class Inputs(val loginFormState: LoginFormState) : NodeInputs + + private val inputs: Inputs = inputs() + private val presenter = presenterFactory.create(inputs.loginFormState) + + interface Callback : Plugin { + fun onCancelClicked() + } + + private fun onCancelClicked() { + plugins().forEach { it.onCancelClicked() } + } + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + WaitListView( + state = state, + onCancelClicked = ::onCancelClicked, + modifier = modifier + ) + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt new file mode 100644 index 0000000000..9c07204ab2 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenter.kt @@ -0,0 +1,96 @@ +/* + * 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.login.impl.screens.waitlistscreen + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.features.login.impl.DefaultLoginUserStory +import io.element.android.features.login.impl.screens.loginpassword.LoginFormState +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService +import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber + +class WaitListPresenter @AssistedInject constructor( + @Assisted private val formState: LoginFormState, + private val buildMeta: BuildMeta, + private val authenticationService: MatrixAuthenticationService, + private val defaultLoginUserStory: DefaultLoginUserStory, +) : Presenter { + + @AssistedFactory + interface Factory { + fun create(loginFormState: LoginFormState): WaitListPresenter + } + + @Composable + override fun present(): WaitListState { + val coroutineScope = rememberCoroutineScope() + val homeserverUrl = remember { + authenticationService.getHomeserverDetails().value?.url ?: "server" + } + + val loginAction: MutableState> = remember { + mutableStateOf(Async.Uninitialized) + } + + val attemptNumber: MutableState = remember { mutableStateOf(0) } + + fun handleEvents(event: WaitListEvents) { + when (event) { + WaitListEvents.AttemptLogin -> { + // Do not attempt to login on first resume of the View. + attemptNumber.value++ + if (attemptNumber.value > 1) { + coroutineScope.loginAttempt(formState, loginAction) + } + } + WaitListEvents.ClearError -> loginAction.value = Async.Uninitialized + WaitListEvents.Continue -> defaultLoginUserStory.setLoginFlowIsDone(true) + } + } + + return WaitListState( + appName = buildMeta.applicationName, + serverName = homeserverUrl, + loginAction = loginAction.value, + eventSink = ::handleEvents + ) + } + + private fun CoroutineScope.loginAttempt(formState: LoginFormState, loggedInState: MutableState>) = launch { + Timber.w("Attempt to login...") + loggedInState.value = Async.Loading() + authenticationService.login(formState.login.trim(), formState.password) + .onSuccess { sessionId -> + loggedInState.value = Async.Success(sessionId) + } + .onFailure { failure -> + loggedInState.value = Async.Failure(failure) + } + } +} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListState.kt new file mode 100644 index 0000000000..f50de7e194 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListState.kt @@ -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. + */ + +package io.element.android.features.login.impl.screens.waitlistscreen + +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.SessionId + +// Do not use default value, so no member get forgotten in the presenters. +data class WaitListState( + val appName: String, + val serverName: String, + val loginAction: Async, + val eventSink: (WaitListEvents) -> Unit +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt new file mode 100644 index 0000000000..5907ff1acf --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt @@ -0,0 +1,44 @@ +/* + * 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.login.impl.screens.waitlistscreen + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.SessionId + +open class WaitListStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aWaitListState(loginAction = Async.Uninitialized), + aWaitListState(loginAction = Async.Loading()), + aWaitListState(loginAction = Async.Failure(Throwable())), + aWaitListState(loginAction = Async.Failure(Throwable(message = "IO_ELEMENT_X_WAIT_LIST"))), + aWaitListState(loginAction = Async.Success(SessionId("@alice:element.io"))), + // Add other state here + ) +} + +fun aWaitListState( + appName: String = "Element X", + serverName: String = "server.org", + loginAction: Async = Async.Uninitialized, +) = WaitListState( + appName = appName, + serverName = serverName, + loginAction = loginAction, + eventSink = {} +) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt new file mode 100644 index 0000000000..15105a8048 --- /dev/null +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt @@ -0,0 +1,266 @@ +/* + * 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.login.impl.screens.waitlistscreen + +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +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.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAbsoluteAlignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import io.element.android.features.login.impl.R +import io.element.android.features.login.impl.error.isWaitListError +import io.element.android.features.login.impl.error.loginError +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.components.dialogs.RetryDialog +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +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.designsystem.utils.OnLifecycleEvent +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings + +// Ref: https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=6761-148425 +// Only the first screen can be displayed, since once logged in, this Node will be remove by the RootNode. +@Composable +fun WaitListView( + state: WaitListState, + onCancelClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + OnLifecycleEvent { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> state.eventSink.invoke(WaitListEvents.AttemptLogin) + else -> Unit + } + } + + Box(modifier = modifier) { + WaitListBackground() + WaitListContent(state, onCancelClicked) + WaitListError(state) + } +} + +@Composable +private fun WaitListError(state: WaitListState) { + // Display a dialog for error other than the waitlist error + state.loginAction.errorOrNull()?.let { error -> + if (error.isWaitListError().not()) { + RetryDialog( + content = stringResource(id = loginError(error)), + onRetry = { + state.eventSink.invoke(WaitListEvents.AttemptLogin) + }, + onDismiss = { + state.eventSink.invoke(WaitListEvents.ClearError) + } + ) + } + } +} + +@Composable +private fun WaitListBackground( + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(0.3f) + .background(Color.White) + ) + Image( + modifier = Modifier + .fillMaxWidth(), + painter = painterResource(id = R.drawable.light_dark), + contentScale = ContentScale.Crop, + contentDescription = null, + ) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(0.7f) + .background(Color(0xFF121418)) + ) + } +} + +@Composable +private fun WaitListContent( + state: WaitListState, + onCancelClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .fillMaxSize() + .systemBarsPadding() + .padding(horizontal = 16.dp, vertical = 16.dp) + ) { + if (state.loginAction !is Async.Success) { + TextButton( + onClick = onCancelClicked, + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black, + disabledContainerColor = Color.White, + disabledContentColor = Color.Black, + ), + ) { + Text( + text = stringResource(CommonStrings.action_cancel), + style = ElementTheme.typography.fontBodyLgMedium, + ) + } + } + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = BiasAbsoluteAlignment( + horizontalBias = 0f, + verticalBias = -0.05f + ) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (state.loginAction.isLoading()) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + strokeWidth = 2.dp, + color = Color.White + ) + } else { + Spacer(modifier = Modifier.height(24.dp)) + } + Spacer(modifier = Modifier.height(18.dp)) + val titleRes = when (state.loginAction) { + is Async.Success -> R.string.screen_waitlist_title_success + else -> R.string.screen_waitlist_title + } + Text( + text = withColoredPeriod(titleRes), + style = ElementTheme.typography.fontHeadingXlBold, + textAlign = TextAlign.Center, + color = Color.White, + ) + Spacer(modifier = Modifier.height(8.dp)) + val subtitle = when (state.loginAction) { + is Async.Success -> stringResource( + id = R.string.screen_waitlist_message_success, + state.appName, + ) + else -> stringResource( + id = R.string.screen_waitlist_message, + state.appName, + state.serverName, + ) + } + Text( + modifier = Modifier.widthIn(max = 360.dp), + text = subtitle, + style = ElementTheme.typography.fontBodyLgRegular, + textAlign = TextAlign.Center, + color = Color.White, + ) + } + } + if (state.loginAction is Async.Success) { + Button( + onClick = { state.eventSink.invoke(WaitListEvents.Continue) }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = Color.Black, + disabledContainerColor = Color.White, + disabledContentColor = Color.Black, + ), + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .padding(bottom = 8.dp) + ) { + Text( + text = stringResource(id = CommonStrings.action_continue), + style = ElementTheme.typography.fontBodyLgMedium, + ) + } + } + } +} + +@Composable +private fun withColoredPeriod( + @StringRes textRes: Int, +) = buildAnnotatedString { + val text = stringResource(textRes) + append(text) + if (text.endsWith(".")) { + addStyle( + style = SpanStyle( + // Light.colorGreen700 + color = Color(0xff0bc491), + ), + start = text.length - 1, + end = text.length, + ) + } +} + +@Preview +@Composable +internal fun WaitListViewLightPreview(@PreviewParameter(WaitListStateProvider::class) state: WaitListState) = + ElementPreviewLight { ContentToPreview(state) } + +@Preview +@Composable +internal fun WaitListViewDarkPreview(@PreviewParameter(WaitListStateProvider::class) state: WaitListState) = + ElementPreviewDark { ContentToPreview(state) } + +@Composable +private fun ContentToPreview(state: WaitListState) { + WaitListView( + state = state, + onCancelClicked = {}, + ) +} diff --git a/features/login/impl/src/main/res/drawable/light_dark.png b/features/login/impl/src/main/res/drawable/light_dark.png new file mode 100644 index 0000000000..2ff7516878 Binary files /dev/null and b/features/login/impl/src/main/res/drawable/light_dark.png differ diff --git a/features/login/impl/src/main/res/values-cs/translations.xml b/features/login/impl/src/main/res/values-cs/translations.xml index 300d851693..bb67aed9d7 100644 --- a/features/login/impl/src/main/res/values-cs/translations.xml +++ b/features/login/impl/src/main/res/values-cs/translations.xml @@ -24,7 +24,6 @@ "Toto není platný identifikátor uživatele. Očekávaný formát: \'@user:homeserver.org\'" "Vybraný domovský server nepodporuje přihlášení pomocí hesla nebo OIDC. Kontaktujte prosím svého správce nebo vyberte jiný domovský server." "Zadejte své údaje" - "Kde budou vaše konverzace probíhat" "Vítejte zpět!" "Přihlaste se k %1$s" "Změnit poskytovatele účtu" @@ -33,6 +32,12 @@ "Zde budou uloženy vaše konverzace - podobně jako u poskytovatele e-mailových služeb uchováváte své e-maily." "Chystáte se přihlásit do služby %1$s" "Chystáte se vytvořit účet na %1$s" + "Na %2$s je momentálně vysoká poptávka po %1$s. Vraťte se do aplikace za pár dní a zkuste to znovu. + +Díky za trpělivost!" + "Vítá vás %1$s" + "Jste v pořadníku!" + "Jdete do toho!" "Pokračovat" "Vyberte svůj server" "Heslo" diff --git a/features/login/impl/src/main/res/values-de/translations.xml b/features/login/impl/src/main/res/values-de/translations.xml index a79ddd1c08..0ffd103acc 100644 --- a/features/login/impl/src/main/res/values-de/translations.xml +++ b/features/login/impl/src/main/res/values-de/translations.xml @@ -24,7 +24,6 @@ "Dies ist kein gültiger Benutzeridentifikator. Erwartetes Format: \'@user:homeserver.org\'" "Der ausgewählte Homeserver unterstützt kein Passwort- oder OIDC-Login. Bitte kontaktiere deinen Admin oder wähle einen anderen Homeserver." "Gib deine Daten ein" - "Wo deine Gespräche leben" "Willkommen zurück!" "Bei %1$s anmelden" "Kontoanbieter wechseln" @@ -33,6 +32,12 @@ "Hier werden deine Konversationen stattfinden — genau so wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren." "Du bist dabei dich bei %1$s anzumelden" "Du bist dabei einen Account auf %1$s zu erstellen" + "Im Moment besteht eine hohe Nachfrage nach %1$s auf %2$s. Besuche die App in ein paar Tagen wieder und versuche es erneut. + +Vielen Dank für deine Geduld!" + "Willkommen bei %1$s!" + "Du hast es fast geschafft!" + "Du bist dabei." "Weiter" "Wählen deinen Server" "Passwort" diff --git a/features/login/impl/src/main/res/values-es/translations.xml b/features/login/impl/src/main/res/values-es/translations.xml index 0e55589556..18d34d5e23 100644 --- a/features/login/impl/src/main/res/values-es/translations.xml +++ b/features/login/impl/src/main/res/values-es/translations.xml @@ -10,7 +10,6 @@ "Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'" "El servidor seleccionado no admite contraseñas ni inicio de sesión OIDC. Póngase en contacto con su administrador o elija otro homeserver." "Introduce tus datos" - "Donde viven tus conversaciones" "¡Hola de nuevo!" "Continuar" "Selecciona tu servidor" diff --git a/features/login/impl/src/main/res/values-fr/translations.xml b/features/login/impl/src/main/res/values-fr/translations.xml index e6a069bdf6..85a89ffef2 100644 --- a/features/login/impl/src/main/res/values-fr/translations.xml +++ b/features/login/impl/src/main/res/values-fr/translations.xml @@ -22,7 +22,6 @@ "Il ne s\'agit pas d\'un identifiant utilisateur valide. Format attendu : « @user:homeserver.org »" "Le serveur domestique sélectionné ne prend pas en charge le mot de passe ou la connexion OIDC. Contactez votre administrateur ou choisissez un autre serveur domestique." "Saisir vos informations personnelles" - "Où se déroulent vos conversations" "Heureux de vous revoir!" "Continuer" "Sélectionnez votre serveur" diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml index feb74db373..bb054b0577 100644 --- a/features/login/impl/src/main/res/values-it/translations.xml +++ b/features/login/impl/src/main/res/values-it/translations.xml @@ -10,7 +10,6 @@ "Questo non è un identificatore utente valido. Formato previsto: \'@user:homeserver.org\'" "L\'homeserver selezionato non supporta la password o l\'accesso OIDC. Contatta il tuo amministratore o scegli un altro homeserver." "Inserisci i tuoi dati" - "Dove vivono le tue conversazioni" "Bentornato!" "Continua" "Seleziona il tuo server" diff --git a/features/login/impl/src/main/res/values-ro/translations.xml b/features/login/impl/src/main/res/values-ro/translations.xml index 70651bc53b..123ab99dd6 100644 --- a/features/login/impl/src/main/res/values-ro/translations.xml +++ b/features/login/impl/src/main/res/values-ro/translations.xml @@ -24,7 +24,6 @@ "Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”" "Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver." "Introduceți detaliile" - "Locul unde trăiesc conversațiile tale" "Bine ați revenit!" "Conectați-vă la %1$s" "Schimbați furnizorul contului" @@ -33,6 +32,12 @@ "Aici vor trăi conversațiile dvs. - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile." "Sunteți pe cale să vă conectați la %1$s" "Sunteți pe cale să creați un cont pe %1$s" + "Există o cerere mare pentru %1$s pentru %2$s în acest moment. Reveniți la aplicație în câteva zile și încercați din nou. + +Vă mulțumim pentru răbdare!" + "Bun venit la %1$s" + "Sunteți pe lista de așteptare" + "Sunteți conectat!" "Continuați" "Selectați serverul" "Parola" diff --git a/features/login/impl/src/main/res/values-sk/translations.xml b/features/login/impl/src/main/res/values-sk/translations.xml index c593e1df54..3c1e49f732 100644 --- a/features/login/impl/src/main/res/values-sk/translations.xml +++ b/features/login/impl/src/main/res/values-sk/translations.xml @@ -24,7 +24,6 @@ "Toto nie je platný identifikátor používateľa. Očakávaný formát: \'@pouzivatel:homeserver.sk\'" "Vybraný domovský server nepodporuje prihlásenie pomocou hesla alebo OIDC. Obráťte sa na správcu alebo vyberte iný domovský server." "Zadajte svoje údaje" - "Kde žijú vaše rozhovory" "Vitajte späť!" "Prihlásiť sa do %1$s" "Zmeniť poskytovateľa účtu" @@ -33,6 +32,12 @@ "Tu budú žiť vaše konverzácie - podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov." "Chystáte sa prihlásiť do %1$s" "Chystáte sa vytvoriť účet na %1$s" + "Momentálne je veľký dopyt po %1$s na %2$s. Vráťte sa do aplikácie za pár dní a skúste to znova. + +Ďakujeme za trpezlivosť!" + "Vitajte v %1$s" + "Ste na čakanej listine!" + "Ste dnu!" "Pokračovať" "Vyberte svoj server" "Heslo" diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 55324613ed..891786b3a2 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -24,7 +24,6 @@ "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’" "The selected homeserver doesn\'t support password or OIDC login. Please contact your admin or choose another homeserver." "Enter your details" - "Where your conversations live" "Welcome back!" "Sign in to %1$s" "Change account provider" @@ -33,9 +32,16 @@ "This is where your conversations will live — just like you would use an email provider to keep your emails." "You’re about to sign in to %1$s" "You’re about to create an account on %1$s" + "There\'s a high demand for %1$s on %2$s at the moment. Come back to the app in a few days and try again. + +Thanks for your patience!" + "Welcome to %1$s!" + "You’re almost there." + "You\'re in." "Continue" "Select your server" "Password" "Continue" + "Matrix is an open network for secure, decentralised communication." "Username" diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt index c4c8a97155..afd4b542e4 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt @@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.libraries.architecture.Async @@ -38,9 +39,11 @@ class LoginPasswordPresenterTest { fun `present - initial state`() = runTest { val authenticationService = FakeAuthenticationService() val accountProviderDataSource = AccountProviderDataSource() + val loginUserStory = DefaultLoginUserStory() val presenter = LoginPasswordPresenter( authenticationService, accountProviderDataSource, + loginUserStory, ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -57,9 +60,11 @@ class LoginPasswordPresenterTest { fun `present - enter login and password`() = runTest { val authenticationService = FakeAuthenticationService() val accountProviderDataSource = AccountProviderDataSource() + val loginUserStory = DefaultLoginUserStory() val presenter = LoginPasswordPresenter( authenticationService, accountProviderDataSource, + loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) moleculeFlow(RecompositionClock.Immediate) { @@ -81,14 +86,17 @@ class LoginPasswordPresenterTest { fun `present - submit`() = runTest { val authenticationService = FakeAuthenticationService() val accountProviderDataSource = AccountProviderDataSource() + val loginUserStory = DefaultLoginUserStory().apply { setLoginFlowIsDone(false) } val presenter = LoginPasswordPresenter( authenticationService, accountProviderDataSource, + loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { + assertThat(loginUserStory.loginFlowIsDone.value).isFalse() val initialState = awaitItem() initialState.eventSink.invoke(LoginPasswordEvents.SetLogin(A_USER_NAME)) initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) @@ -99,6 +107,7 @@ class LoginPasswordPresenterTest { assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) val loggedInState = awaitItem() assertThat(loggedInState.loginAction).isEqualTo(Async.Success(A_SESSION_ID)) + assertThat(loginUserStory.loginFlowIsDone.value).isTrue() } } @@ -106,9 +115,11 @@ class LoginPasswordPresenterTest { fun `present - submit with error`() = runTest { val authenticationService = FakeAuthenticationService() val accountProviderDataSource = AccountProviderDataSource() + val loginUserStory = DefaultLoginUserStory() val presenter = LoginPasswordPresenter( authenticationService, accountProviderDataSource, + loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) moleculeFlow(RecompositionClock.Immediate) { @@ -132,9 +143,11 @@ class LoginPasswordPresenterTest { fun `present - clear error`() = runTest { val authenticationService = FakeAuthenticationService() val accountProviderDataSource = AccountProviderDataSource() + val loginUserStory = DefaultLoginUserStory() val presenter = LoginPasswordPresenter( authenticationService, accountProviderDataSource, + loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) moleculeFlow(RecompositionClock.Immediate) { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt new file mode 100644 index 0000000000..389ac52176 --- /dev/null +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListPresenterTest.kt @@ -0,0 +1,118 @@ +/* + * 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.login.impl.screens.waitlistscreen + +import app.cash.molecule.RecompositionClock +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.login.impl.DefaultLoginUserStory +import io.element.android.features.login.impl.screens.loginpassword.LoginFormState +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_HOMESERVER +import io.element.android.libraries.matrix.test.A_HOMESERVER_URL +import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService +import io.element.android.libraries.matrix.test.core.aBuildMeta +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class WaitListPresenterTest { + @Test + fun `present - initial state`() = runTest { + val authenticationService = FakeAuthenticationService().apply { + givenHomeserver(A_HOMESERVER) + } + val loginUserStory = DefaultLoginUserStory() + val presenter = WaitListPresenter( + LoginFormState.Default, + aBuildMeta(applicationName = "Application Name"), + authenticationService, + loginUserStory, + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.appName).isEqualTo("Application Name") + assertThat(initialState.serverName).isEqualTo(A_HOMESERVER_URL) + assertThat(initialState.loginAction).isEqualTo(Async.Uninitialized) + } + } + + @Test + fun `present - attempt login with error`() = runTest { + val authenticationService = FakeAuthenticationService().apply { + givenLoginError(A_THROWABLE) + } + val loginUserStory = DefaultLoginUserStory() + val presenter = WaitListPresenter( + LoginFormState.Default, + aBuildMeta(), + authenticationService, + loginUserStory, + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + // First usage of AttemptLogin, nothing should happen + initialState.eventSink.invoke(WaitListEvents.AttemptLogin) + expectNoEvents() + initialState.eventSink.invoke(WaitListEvents.AttemptLogin) + val submitState = awaitItem() + assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + val errorState = awaitItem() + assertThat(errorState.loginAction).isEqualTo(Async.Failure(A_THROWABLE)) + // Assert the error can be cleared + errorState.eventSink(WaitListEvents.ClearError) + val clearedState = awaitItem() + assertThat(clearedState.loginAction).isEqualTo(Async.Uninitialized) + } + } + + @Test + fun `present - attempt login with success`() = runTest { + val authenticationService = FakeAuthenticationService() + val loginUserStory = DefaultLoginUserStory().apply { setLoginFlowIsDone(false) } + val presenter = WaitListPresenter( + LoginFormState.Default, + aBuildMeta(), + authenticationService, + loginUserStory, + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + assertThat(loginUserStory.loginFlowIsDone.value).isFalse() + val initialState = awaitItem() + // First usage of AttemptLogin, nothing should happen + initialState.eventSink.invoke(WaitListEvents.AttemptLogin) + expectNoEvents() + initialState.eventSink.invoke(WaitListEvents.AttemptLogin) + val submitState = awaitItem() + assertThat(submitState.loginAction).isInstanceOf(Async.Loading::class.java) + val successState = awaitItem() + assertThat(successState.loginAction).isEqualTo(Async.Success(A_USER_ID)) + assertThat(loginUserStory.loginFlowIsDone.value).isFalse() + successState.eventSink.invoke(WaitListEvents.Continue) + assertThat(loginUserStory.loginFlowIsDone.value).isTrue() + } + } +} diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 44d5d34c41..7488820f7d 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -54,6 +54,8 @@ dependencies { implementation(libs.accompanist.flowlayout) implementation(libs.androidx.recyclerview) implementation(libs.jsoup) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.constraintlayout.compose) implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.ui) implementation(libs.accompanist.systemui) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 35a34638e0..1593c19afc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -29,7 +29,9 @@ 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.location.api.Location import io.element.android.features.location.api.SendLocationEntryPoint +import io.element.android.features.location.api.ShowLocationEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode @@ -41,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.debug.EventDebugInfoNo import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -59,6 +62,7 @@ class MessagesFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val sendLocationEntryPoint: SendLocationEntryPoint, + private val showLocationEntryPoint: ShowLocationEntryPoint, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.Messages, @@ -83,7 +87,10 @@ class MessagesFlowNode @AssistedInject constructor( data class AttachmentPreview(val attachment: Attachment) : NavTarget @Parcelize - data class EventDebugInfo(val eventId: EventId, val debugInfo: TimelineItemDebugInfo) : NavTarget + data class LocationViewer(val location: Location, val description: String?) : NavTarget + + @Parcelize + data class EventDebugInfo(val eventId: EventId?, val debugInfo: TimelineItemDebugInfo) : NavTarget @Parcelize data class ForwardEvent(val eventId: EventId) : NavTarget @@ -117,7 +124,7 @@ class MessagesFlowNode @AssistedInject constructor( callback?.onUserDataClicked(userId) } - override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) { + override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo)) } @@ -147,6 +154,10 @@ class MessagesFlowNode @AssistedInject constructor( val inputs = AttachmentsPreviewNode.Inputs(navTarget.attachment) createNode(buildContext, listOf(inputs)) } + is NavTarget.LocationViewer -> { + val inputs = ShowLocationEntryPoint.Inputs(navTarget.location, navTarget.description) + showLocationEntryPoint.createNode(this, buildContext, inputs) + } is NavTarget.EventDebugInfo -> { val inputs = EventDebugInfoNode.Inputs(navTarget.eventId, navTarget.debugInfo) createNode(buildContext, listOf(inputs)) @@ -213,6 +224,13 @@ class MessagesFlowNode @AssistedInject constructor( ) backstack.push(navTarget) } + is TimelineItemLocationContent -> { + val navTarget = NavTarget.LocationViewer( + location = event.content.location, + description = event.content.description, + ) + backstack.push(navTarget) + } else -> Unit } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt index 201173a0bf..a0517c59c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo interface MessagesNavigator { - fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) + fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) fun onForwardEventClicked(eventId: EventId) fun onReportContentClicked(eventId: EventId, senderId: UserId) } 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 8a44d0b748..3f201a8e4c 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 @@ -54,7 +54,7 @@ class MessagesNode @AssistedInject constructor( fun onEventClicked(event: TimelineItem.Event) fun onPreviewAttachments(attachments: ImmutableList) fun onUserDataClicked(userId: UserId) - fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) + fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) fun onForwardEventClicked(eventId: EventId) fun onReportMessage(eventId: EventId, senderId: UserId) fun onSendLocationClicked() @@ -83,7 +83,7 @@ class MessagesNode @AssistedInject constructor( private fun onUserDataClicked(userId: UserId) { callback?.onUserDataClicked(userId) } - override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) { + override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { callback?.onShowEventDebugInfoClicked(eventId, debugInfo) } @@ -94,7 +94,7 @@ class MessagesNode @AssistedInject constructor( override fun onReportContentClicked(eventId: EventId, senderId: UserId) { callback?.onReportMessage(eventId, senderId) } - + private fun onSendLocationClicked() { callback?.onSendLocationClicked() } 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 1171e4b23f..841ccb8faa 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 @@ -226,15 +226,19 @@ class MessagesPresenter @AssistedInject constructor( } private suspend fun handleActionRedact(event: TimelineItem.Event) { - if (event.eventId == null) return - room.redactEvent(event.eventId) + if (event.failedToSend) { + // If the message hasn't been sent yet, just cancel it + event.transactionId?.let { room.cancelSend(it) } + } else if (event.eventId != null) { + room.redactEvent(event.eventId) + } } private fun handleActionEdit(targetEvent: TimelineItem.Event, composerState: MessageComposerState) { - if (targetEvent.eventId == null) return val composerMode = MessageComposerMode.Edit( targetEvent.eventId, - (targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty() + (targetEvent.content as? TimelineItemTextBasedContent)?.body.orEmpty(), + targetEvent.transactionId, ) composerState.eventSink( MessageComposerEvents.SetMode(composerMode) @@ -287,7 +291,6 @@ class MessagesPresenter @AssistedInject constructor( } private fun handleShowDebugInfoAction(event: TimelineItem.Event) { - if (event.eventId == null) return navigator.onShowEventDebugInfoClicked(event.eventId, event.debugInfo) } 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 87ee434598..907a2a06a5 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 @@ -78,7 +78,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import timber.log.Timber @@ -126,6 +126,9 @@ fun MessagesView( state.eventSink(MessagesEvents.ToggleReaction(emoji, event.eventId)) } + fun onMoreReactionsClicked(event: TimelineItem.Event): Unit = + state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event.eventId)) + Scaffold( modifier = modifier, contentWindowInsets = WindowInsets.statusBars, @@ -150,12 +153,16 @@ fun MessagesView( onMessageLongClicked = ::onMessageLongClicked, onUserDataClicked = onUserDataClicked, onTimestampClicked = { event -> - if (event.sendState is EventSendState.SendingFailed) { + if (event.localSendState is LocalEventSendState.SendingFailed) { state.retrySendMenuState.eventSink(RetrySendMenuEvents.EventSelected(event)) } }, onReactionClicked = ::onEmojiReactionClicked, + onMoreReactionsClicked = ::onMoreReactionsClicked, onSendLocationClicked = onSendLocationClicked, + onSwipeToReply = { targetEvent -> + state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent)) + }, ) }, snackbarHost = { @@ -237,10 +244,12 @@ fun MessagesViewContent( onMessageClicked: (TimelineItem.Event) -> Unit, onUserDataClicked: (UserId) -> Unit, onReactionClicked: (key: String, TimelineItem.Event) -> Unit, + onMoreReactionsClicked: (TimelineItem.Event) -> Unit, onMessageLongClicked: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onSendLocationClicked: () -> Unit, modifier: Modifier = Modifier, + onSwipeToReply: (TimelineItem.Event) -> Unit, ) { Column( modifier = modifier @@ -258,6 +267,8 @@ fun MessagesViewContent( onUserDataClicked = onUserDataClicked, onTimestampClicked = onTimestampClicked, onReactionClicked = onReactionClicked, + onMoreReactionsClicked = onMoreReactionsClicked, + onSwipeToReply = onSwipeToReply, ) } if (state.userHasPermissionToSendMessage) { 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 26ab4fc217..56e9f48dde 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 @@ -18,6 +18,8 @@ package io.element.android.features.messages.impl.actionlist import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +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 @@ -28,6 +30,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.canBeCopied import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -45,6 +48,10 @@ class ActionListPresenter @Inject constructor( mutableStateOf(ActionListState.Target.None) } + val displayEmojiReactions by remember { + derivedStateOf { (target.value as? ActionListState.Target.Success)?.event?.isRemote == true } + } + fun handleEvents(event: ActionListEvents) { when (event) { ActionListEvents.Clear -> target.value = ActionListState.Target.None @@ -54,6 +61,7 @@ class ActionListPresenter @Inject constructor( return ActionListState( target = target.value, + displayEmojiReactions = displayEmojiReactions, eventSink = ::handleEvents ) } @@ -62,21 +70,28 @@ class ActionListPresenter @Inject constructor( target.value = ActionListState.Target.Loading(timelineItem) val actions = when (timelineItem.content) { - is TimelineItemRedactedContent, + is TimelineItemRedactedContent -> { + if (buildMeta.isDebuggable) { + listOf(TimelineItemAction.Developer) + } else { + emptyList() + } + } is TimelineItemStateContent -> { buildList { - if (timelineItem.content.canBeCopied()) { - add(TimelineItemAction.Copy) - } + add(TimelineItemAction.Copy) if (buildMeta.isDebuggable) { add(TimelineItemAction.Developer) } } } else -> buildList { - add(TimelineItemAction.Reply) - add(TimelineItemAction.Forward) - if (timelineItem.isMine) { + if (timelineItem.isRemote) { + // Can only reply or forward messages already uploaded to the server + add(TimelineItemAction.Reply) + add(TimelineItemAction.Forward) + } + if (timelineItem.isMine && timelineItem.isTextMessage) { add(TimelineItemAction.Edit) } if (timelineItem.content.canBeCopied()) { @@ -93,6 +108,10 @@ class ActionListPresenter @Inject constructor( } } } - target.value = ActionListState.Target.Success(timelineItem, actions.toImmutableList()) + if (actions.isNotEmpty()) { + target.value = ActionListState.Target.Success(timelineItem, actions.toImmutableList()) + } else { + target.value = ActionListState.Target.None + } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt index faf41160be..aac3469218 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt @@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList @Immutable data class ActionListState( val target: Target, + val displayEmojiReactions: Boolean, val eventSink: (ActionListEvents) -> Unit, ) { sealed interface Target { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index ee1b7de309..09213d64b3 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 @@ -19,6 +19,7 @@ package io.element.android.features.messages.impl.actionlist import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.aTimelineItemEvent +import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemLocationContent @@ -28,44 +29,67 @@ import kotlinx.collections.immutable.persistentListOf open class ActionListStateProvider : PreviewParameterProvider { override val values: Sequence - get() = sequenceOf( - anActionListState(), - anActionListState().copy(target = ActionListState.Target.Loading(aTimelineItemEvent())), - anActionListState().copy( - target = ActionListState.Target.Success( - event = aTimelineItemEvent(), - actions = aTimelineItemActionList(), - ) - ), - anActionListState().copy( - target = ActionListState.Target.Success( - event = aTimelineItemEvent(content = aTimelineItemImageContent()), - actions = aTimelineItemActionList(), - ) - ), - anActionListState().copy( - target = ActionListState.Target.Success( - event = aTimelineItemEvent(content = aTimelineItemVideoContent()), - actions = aTimelineItemActionList(), - ) - ), - anActionListState().copy( - target = ActionListState.Target.Success( - event = aTimelineItemEvent(content = aTimelineItemFileContent()), - actions = aTimelineItemActionList(), - ) - ), - anActionListState().copy( - target = ActionListState.Target.Success( - event = aTimelineItemEvent(content = aTimelineItemLocationContent()), - actions = aTimelineItemActionList(), - ) - ), - ) + get() { + val reactionsState = aTimelineItemReactions(1, isHighlighted = true) + return sequenceOf( + anActionListState(), + anActionListState().copy(target = ActionListState.Target.Loading(aTimelineItemEvent())), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent().copy( + reactionsState = reactionsState + ), + actions = aTimelineItemActionList(), + ) + ), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent(content = aTimelineItemImageContent()).copy( + reactionsState = reactionsState + ), + actions = aTimelineItemActionList(), + ) + ), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent(content = aTimelineItemVideoContent()).copy( + reactionsState = reactionsState + ), + actions = aTimelineItemActionList(), + ) + ), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent(content = aTimelineItemFileContent()).copy( + reactionsState = reactionsState + ), + actions = aTimelineItemActionList(), + ) + ), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent(content = aTimelineItemLocationContent()).copy( + reactionsState = reactionsState + ), + actions = aTimelineItemActionList(), + ) + ), + anActionListState().copy( + target = ActionListState.Target.Success( + event = aTimelineItemEvent(content = aTimelineItemLocationContent()).copy( + reactionsState = reactionsState + ), + actions = aTimelineItemActionList(), + ), + displayEmojiReactions = false, + ), + ) + } } fun anActionListState() = ActionListState( target = ActionListState.Target.None, + displayEmojiReactions = true, eventSink = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index 904313120f..20a5d0f355 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -16,6 +16,7 @@ package io.element.android.features.messages.impl.actionlist +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement @@ -46,6 +47,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -78,7 +80,9 @@ import io.element.android.libraries.designsystem.theme.components.hide import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType +import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -175,13 +179,16 @@ private fun SheetContent( Divider() } } - item { - EmojiReactionsRow( - onEmojiReactionClicked = onEmojiReactionClicked, - onCustomReactionClicked = onCustomReactionClicked, - modifier = Modifier.fillMaxWidth(), - ) - Divider() + if (state.displayEmojiReactions) { + item { + EmojiReactionsRow( + highlightedEmojis = target.event.reactionsState.highlightedKeys, + onEmojiReactionClicked = onEmojiReactionClicked, + onCustomReactionClicked = onCustomReactionClicked, + modifier = Modifier.fillMaxWidth(), + ) + Divider() + } } items( items = actions, @@ -320,6 +327,7 @@ private val emojiRippleRadius = 24.dp @Composable internal fun EmojiReactionsRow( + highlightedEmojis: ImmutableList, onEmojiReactionClicked: (String) -> Unit, onCustomReactionClicked: () -> Unit, modifier: Modifier = Modifier, @@ -333,7 +341,8 @@ internal fun EmojiReactionsRow( "👍", "👎", "🔥", "❤️", "👏" ) for (emoji in defaultEmojis) { - EmojiButton(emoji, onEmojiReactionClicked) + val isHighlighted = highlightedEmojis.contains(emoji) + EmojiButton(emoji, isHighlighted, onEmojiReactionClicked) } Icon( @@ -356,19 +365,34 @@ internal fun EmojiReactionsRow( @Composable private fun EmojiButton( emoji: String, + isHighlighted: Boolean, onClicked: (String) -> Unit, modifier: Modifier = Modifier, ) { - Text( - emoji, - fontSize = 28.dp.toSp(), - modifier = modifier.clickable( - enabled = true, - onClick = { onClicked(emoji) }, - indication = rememberRipple(bounded = false, radius = emojiRippleRadius), - interactionSource = remember { MutableInteractionSource() } + val backgroundColor = if (isHighlighted) { + ElementTheme.colors.bgActionPrimaryRest + } else { + Color.Transparent + } + Box( + modifier = modifier + .size(48.dp) + .background(backgroundColor, RoundedCornerShape(24.dp)), + contentAlignment = Alignment.Center + ) { + Text( + emoji, + fontSize = 28.dp.toSp(), + color = Color.White, + modifier = Modifier + .clickable( + enabled = true, + onClick = { onClicked(emoji) }, + indication = rememberRipple(bounded = false, radius = emojiRippleRadius), + interactionSource = remember { MutableInteractionSource() } + ) ) - ) + } } @Preview diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt index 597a38ccbb..c8324ec677 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/AttachmentsBottomSheet.kt @@ -41,6 +41,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.R import io.element.android.libraries.androidutils.ui.hideKeyboard +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text @@ -129,3 +131,12 @@ internal fun AttachmentSourcePickerMenu( ) } } + +@DayNightPreviews +@Composable +internal fun AttachmentSourcePickerMenuPreview() = ElementPreview { + AttachmentSourcePickerMenu( + eventSink = {}, + onSendLocationClicked = {}, + ) +} 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 16c86f9baa..3ad2c497ce 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 @@ -196,10 +196,11 @@ class MessageComposerPresenter @Inject constructor( composerMode.setToNormal() when (capturedMode) { is MessageComposerMode.Normal -> room.sendMessage(text) - is MessageComposerMode.Edit -> room.editMessage( - capturedMode.eventId, - text - ) + is MessageComposerMode.Edit -> { + val eventId = capturedMode.eventId + val transactionId = capturedMode.transactionId + room.editMessage(eventId, transactionId, text) + } is MessageComposerMode.Quote -> TODO() is MessageComposerMode.Reply -> room.replyMessage( 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 071dc84116..765becfd11 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 @@ -30,7 +30,9 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.core.EventId 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.timeline.MatrixTimeline +import io.element.android.libraries.matrix.ui.room.canSendEventAsState import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn @@ -44,7 +46,7 @@ private const val backPaginationPageSize = 50 class TimelinePresenter @Inject constructor( private val timelineItemsFactory: TimelineItemsFactory, - room: MatrixRoom, + private val room: MatrixRoom, ) : Presenter { private val timeline = room.timeline @@ -62,6 +64,9 @@ class TimelinePresenter @Inject constructor( val timelineItems by timelineItemsFactory.collectItemsAsState() val paginationState by timeline.paginationState.collectAsState() + val syncUpdateFlow = room.syncUpdateFlow.collectAsState() + val userHasPermissionToSendMessage by room.canSendEventAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) + fun handleEvents(event: TimelineEvents) { when (event) { TimelineEvents.LoadMore -> localCoroutineScope.loadMore(paginationState) @@ -92,6 +97,7 @@ class TimelinePresenter @Inject constructor( return TimelineState( highlightedEventId = highlightedEventId.value, + canReply = userHasPermissionToSendMessage, paginationState = paginationState, timelineItems = timelineItems, eventSink = ::handleEvents 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 0e86614bf3..0aa1bd0160 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 @@ -26,6 +26,7 @@ import kotlinx.collections.immutable.ImmutableList data class TimelineState( val timelineItems: ImmutableList, val highlightedEventId: EventId?, + val canReply: Boolean, val paginationState: MatrixTimeline.PaginationState, 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 fd7c4402f4..c3264f7a54 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 @@ -31,7 +31,7 @@ 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.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -43,6 +43,7 @@ fun aTimelineState(timelineItems: ImmutableList = persistentListOf timelineItems = timelineItems, paginationState = MatrixTimeline.PaginationState(isBackPaginating = false, canBackPaginate = true), highlightedEventId = null, + canReply = true, eventSink = {} ) @@ -58,7 +59,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList isMine = false, content = content, groupPosition = TimelineItemGroupPosition.Middle, - sendState = EventSendState.SendingFailed("Message failed to send"), + sendState = LocalEventSendState.SendingFailed("Message failed to send"), ), aTimelineItemEvent( isMine = false, @@ -81,7 +82,7 @@ internal fun aTimelineItemList(content: TimelineItemEventContent): ImmutableList isMine = true, content = content, groupPosition = TimelineItemGroupPosition.Middle, - sendState = EventSendState.SendingFailed("Message failed to send"), + sendState = LocalEventSendState.SendingFailed("Message failed to send"), ), aTimelineItemEvent( isMine = true, @@ -111,7 +112,7 @@ internal fun aTimelineItemEvent( isMine: Boolean = false, content: TimelineItemEventContent = aTimelineItemTextContent(), groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, - sendState: EventSendState = EventSendState.Sent(eventId), + sendState: LocalEventSendState = LocalEventSendState.Sent(eventId), inReplyTo: InReplyTo? = null, debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(), timelineItemReactions: TimelineItemReactions = aTimelineItemReactions(), @@ -128,7 +129,7 @@ internal fun aTimelineItemEvent( isMine = isMine, senderDisplayName = "Sender", groupPosition = groupPosition, - sendState = sendState, + localSendState = sendState, inReplyTo = inReplyTo, debugInfo = debugInfo, ) @@ -138,10 +139,12 @@ fun aTimelineItemReactions( count: Int = 1, isHighlighted: Boolean = false, ): TimelineItemReactions { + val emojis = arrayOf("👍", "😀️", "😁️", "😆️", "😅️", "🤣️", "🥰️", "😇️", "😊️", "😉️", "🙃️", "🙂️", "😍️", "🤗️", "🤭️") return TimelineItemReactions( reactions = buildList { - repeat(count) { - add(AggregatedReaction(key = "👍", count = 1 + it, isHighlighted = isHighlighted)) + repeat(count) { index -> + val key = emojis[index % emojis.size] + add(AggregatedReaction(key = key, count = 1 + index, isHighlighted = isHighlighted)) } }.toPersistentList() ) 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 ab1adb97ae..cda5eff6db 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 @@ -80,7 +80,9 @@ fun TimelineView( onMessageClicked: (TimelineItem.Event) -> Unit, onMessageLongClicked: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, + onSwipeToReply: (TimelineItem.Event) -> Unit, onReactionClicked: (emoji: String, TimelineItem.Event) -> Unit, + onMoreReactionsClicked: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, ) { fun onReachedLoadMore() { @@ -120,12 +122,15 @@ fun TimelineView( TimelineItemRow( timelineItem = timelineItem, highlightedItem = state.highlightedEventId?.value, + canReply = state.canReply, onClick = onMessageClicked, onLongClick = onMessageLongClicked, onUserDataClick = onUserDataClicked, inReplyToClick = ::inReplyToClicked, onReactionClick = onReactionClicked, + onMoreReactionsClick = onMoreReactionsClicked, onTimestampClicked = onTimestampClicked, + onSwipeToReply = onSwipeToReply, ) if (index == state.timelineItems.lastIndex) { onReachedLoadMore() @@ -145,12 +150,15 @@ fun TimelineView( fun TimelineItemRow( timelineItem: TimelineItem, highlightedItem: String?, + canReply: Boolean, onUserDataClick: (UserId) -> Unit, onClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, + onMoreReactionsClick: (TimelineItem.Event) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, + onSwipeToReply: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier ) { when (timelineItem) { @@ -161,32 +169,27 @@ fun TimelineItemRow( ) } is TimelineItem.Event -> { - fun onClick() { - onClick(timelineItem) - } - - fun onLongClick() { - onLongClick(timelineItem) - } - if (timelineItem.content is TimelineItemStateContent) { TimelineItemStateEventRow( event = timelineItem, isHighlighted = highlightedItem == timelineItem.identifier(), - onClick = ::onClick, - onLongClick = ::onLongClick, + onClick = { onClick(timelineItem) }, + onLongClick = { onLongClick(timelineItem) }, modifier = modifier, ) } else { TimelineItemEventRow( event = timelineItem, isHighlighted = highlightedItem == timelineItem.identifier(), - onClick = ::onClick, - onLongClick = ::onLongClick, + canReply = canReply, + onClick = { onClick(timelineItem) }, + onLongClick = { onLongClick(timelineItem) }, onUserDataClick = onUserDataClick, inReplyToClick = inReplyToClick, onReactionClick = onReactionClick, + onMoreReactionsClick = onMoreReactionsClick, onTimestampClicked = onTimestampClicked, + onSwipeToReply = { onSwipeToReply(timelineItem) }, modifier = modifier, ) } @@ -215,12 +218,15 @@ fun TimelineItemRow( TimelineItemRow( timelineItem = subGroupEvent, highlightedItem = highlightedItem, + canReply = false, onClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, onUserDataClick = onUserDataClick, onTimestampClicked = onTimestampClicked, onReactionClick = onReactionClick, + onMoreReactionsClick = onMoreReactionsClick, + onSwipeToReply = {}, ) } } @@ -322,5 +328,7 @@ private fun ContentToPreview(content: TimelineItemEventContent) { onUserDataClicked = {}, onMessageLongClicked = {}, onReactionClicked = { _, _ -> }, + onMoreReactionsClicked = {}, + onSwipeToReply = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesMoreReactionsButton.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesMoreReactionsButton.kt new file mode 100644 index 0000000000..7c2c1638d5 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesMoreReactionsButton.kt @@ -0,0 +1,87 @@ +/* + * 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.features.messages.impl.timeline.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.AddReaction +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Surface +import io.element.android.libraries.theme.ElementTheme + +@Composable +fun MessagesMoreReactionsButton(modifier: Modifier = Modifier, onClick: () -> Unit) { + val buttonColor = ElementTheme.colors.bgSubtleSecondary + Surface( + modifier = modifier + .background(Color.Transparent) + // Outer border, same colour as background + .border( + BorderStroke(2.dp, MaterialTheme.colorScheme.background), + shape = RoundedCornerShape(corner = CornerSize(14.dp)) + ) + .padding(vertical = 2.dp, horizontal = 2.dp) + // Clip click indicator inside the outer border + .clip(RoundedCornerShape(corner = CornerSize(12.dp))) + .clickable(onClick = onClick) + .background(buttonColor, RoundedCornerShape(corner = CornerSize(12.dp))) + .padding(vertical = 4.dp, horizontal = 10.dp), + color = buttonColor + ) { + Icon( + imageVector = Icons.Outlined.AddReaction, + contentDescription = "Add emoji", + tint = MaterialTheme.colorScheme.secondary, + modifier = Modifier + // Same size as the line height of reaction emoji text + .size(with(LocalDensity.current) { 20.sp.toDp() }) + ) + } +} + +@Preview +@Composable +internal fun MessagesMoreReactionsButtonLightPreview() = + ElementPreviewLight { ContentToPreview() } + +@Preview +@Composable +internal fun MessagesMoreReactionsButtonDarkPreview() = + ElementPreviewDark { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + MessagesMoreReactionsButton(onClick = {}) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt index 0afc1e23e2..497ed7b32d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt @@ -17,9 +17,9 @@ package io.element.android.features.messages.impl.timeline.components import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding @@ -30,6 +30,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -45,35 +46,47 @@ import io.element.android.libraries.theme.ElementTheme @Composable fun MessagesReactionButton(reaction: AggregatedReaction, modifier: Modifier = Modifier, onClick: () -> Unit) { - // First Surface is to render a border with the same background color as the background + val buttonColor = if (reaction.isHighlighted) { + ElementTheme.colors.bgSubtlePrimary + } else { + ElementTheme.colors.bgSubtleSecondary + } + val borderColor = if (reaction.isHighlighted) { + ElementTheme.colors.borderInteractivePrimary + } else { + buttonColor + } Surface( - modifier = modifier.clickable(onClick = onClick::invoke), - // TODO Should use compound.bgSubtlePrimary - color = ElementTheme.legacyColors.gray300, - border = BorderStroke(2.dp, MaterialTheme.colorScheme.background), - shape = RoundedCornerShape(corner = CornerSize(14.dp)), + modifier = modifier + .background(Color.Transparent) + // Outer border, same colour as background + .border( + BorderStroke(2.dp, MaterialTheme.colorScheme.background), + shape = RoundedCornerShape(corner = CornerSize(14.dp)) + ) + .padding(vertical = 2.dp, horizontal = 2.dp) + // Clip click indicator inside the outer border + .clip(RoundedCornerShape(corner = CornerSize(12.dp))) + .clickable(onClick = onClick) + // Inner border, to highlight when selected + .border(BorderStroke(1.dp, borderColor), RoundedCornerShape(corner = CornerSize(12.dp))) + .background(buttonColor, RoundedCornerShape(corner = CornerSize(12.dp))) + .padding(vertical = 4.dp, horizontal = 10.dp), + color = buttonColor ) { - Box(modifier = Modifier.padding(2.dp)) { - val reactionModifier = if (reaction.isHighlighted) { - Modifier - // TODO Check the color, should use compound.borderInteractivePrimary - .border(BorderStroke(1.dp, Color(0xFF808994)), RoundedCornerShape(corner = CornerSize(12.dp))) - } else { - Modifier - } - Row( - modifier = reactionModifier.padding(vertical = 4.dp, horizontal = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text(text = reaction.key, fontSize = 15.sp) - if (reaction.count > 1) { - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = reaction.count.toString(), - color = if (reaction.isHighlighted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary, - fontSize = 14.sp - ) - } + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = reaction.key, fontSize = 15.sp, lineHeight = 20.sp + ) + if (reaction.count > 1) { + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = reaction.count.toString(), + color = if (reaction.isHighlighted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary, + fontSize = 14.sp + ) } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt new file mode 100644 index 0000000000..de40ae8dc1 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt @@ -0,0 +1,77 @@ +/* + * 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 + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.VectorIcons +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Icon + +/** + * A swipe indicator that appears when swiping to reply to a message. + * + * @param swipeProgress the progress of the swipe, between 0 and X. When swipeProgress >= 1 the swipe will be detected. + * @param modifier the modifier to apply to this Composable root. + */ +@Composable +fun RowScope.ReplySwipeIndicator( + swipeProgress: () -> Float, + modifier: Modifier = Modifier, +) { + Icon( + modifier = modifier + .align(Alignment.CenterVertically) + .graphicsLayer { + translationX = 36.dp.toPx() * swipeProgress().coerceAtMost(1f) + alpha = swipeProgress() + }, + contentDescription = null, + resourceId = VectorIcons.Reply, + ) +} + +@Preview +@Composable +internal fun ReplySwipeIndicatorLightPreview() = + ElementPreviewLight { ContentToPreview() } + +@Preview +@Composable +internal fun ReplySwipeIndicatorDarkPreview() = + ElementPreviewDark { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + Column(modifier = Modifier.fillMaxWidth()) { + for (i in 0..8) { + Row { ReplySwipeIndicator(swipeProgress = { i / 8f }) } + } + Row { ReplySwipeIndicator(swipeProgress = { 1.5f }) } + Row { ReplySwipeIndicator(swipeProgress = { 2f }) } + Row { ReplySwipeIndicator(swipeProgress = { 3f }) } + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt index 80f21d6934..5c333b6789 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt @@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.ui.strings.CommonStrings @OptIn(ExperimentalFoundationApi::class) @@ -55,7 +55,7 @@ fun TimelineEventTimestampView( modifier: Modifier = Modifier, ) { val formattedTime = event.sentTime - val hasMessageSendingFailed = event.sendState is EventSendState.SendingFailed + val hasMessageSendingFailed = event.localSendState is LocalEventSendState.SendingFailed val isMessageEdited = (event.content as? TimelineItemTextBasedContent)?.isEdited.orFalse() val tint = if (hasMessageSendingFailed) MaterialTheme.colorScheme.error else null val clickModifier = if (hasMessageSendingFailed) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt index 24daed065a..7697ccf4a4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt @@ -20,19 +20,19 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState class TimelineItemEventForTimestampViewProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aTimelineItemEvent(), // Sending failed - aTimelineItemEvent().copy(sendState = EventSendState.SendingFailed("AN_ERROR")), + aTimelineItemEvent().copy(localSendState = LocalEventSendState.SendingFailed("AN_ERROR")), // Edited aTimelineItemEvent().copy(content = aTimelineItemTextContent().copy(isEdited = true)), // Sending failed + Edited (not sure this is possible IRL, but should be covered by test) aTimelineItemEvent().copy( - sendState = EventSendState.SendingFailed("AN_ERROR"), + localSendState = LocalEventSendState.SendingFailed("AN_ERROR"), content = aTimelineItemTextContent().copy(isEdited = true), ), ) 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 f2f7328821..4a20af086f 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 @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3Api::class) + package io.element.android.features.messages.impl.timeline.components import androidx.compose.foundation.Canvas @@ -33,7 +35,13 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DismissDirection +import androidx.compose.material3.DismissState +import androidx.compose.material3.DismissValue +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SwipeToDismiss +import androidx.compose.material3.rememberDismissState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -46,8 +54,13 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow 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.dp import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import androidx.constraintlayout.compose.ConstrainScope +import androidx.constraintlayout.compose.ConstraintLayout +import com.google.accompanist.flowlayout.FlowMainAxisAlignment import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView @@ -86,12 +99,15 @@ import org.jsoup.Jsoup fun TimelineItemEventRow( event: TimelineItem.Event, isHighlighted: Boolean, + canReply: Boolean, onClick: () -> Unit, onLongClick: () -> Unit, onUserDataClick: (UserId) -> Unit, inReplyToClick: (EventId) -> Unit, onTimestampClicked: (TimelineItem.Event) -> Unit, onReactionClick: (emoji: String, eventId: TimelineItem.Event) -> Unit, + onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, + onSwipeToReply: () -> Unit, modifier: Modifier = Modifier ) { val interactionSource = remember { MutableInteractionSource() } @@ -100,77 +116,55 @@ fun TimelineItemEventRow( onUserDataClick(event.senderId) } - fun onReactionClicked(emoji: String) = - onReactionClick(emoji, event) - fun inReplyToClicked() { val inReplyToEventId = (event.inReplyTo as? InReplyTo.Ready)?.eventId ?: return inReplyToClick(inReplyToEventId) } - // To avoid using negative offset, we display in this Box a column with: - // - Spacer to give room to the Sender information if they must be displayed; - // - The message bubble; - // - Spacer for the reactions if there are some. - // Then the Sender information and the reactions are displayed on top of it. - // This fixes some clickable issue and some unexpected margin on top and bottom of each message row - Box( - modifier = modifier - .fillMaxWidth() - .wrapContentHeight(), - contentAlignment = if (event.isMine) Alignment.CenterEnd else Alignment.CenterStart - ) { - Column { - if (event.showSenderInformation) { - Spacer(modifier = Modifier.height(event.senderAvatar.size.dp - 8.dp)) + if (canReply) { + val dismissState = rememberDismissState( + confirmValueChange = { + if (it == DismissValue.DismissedToEnd) { + onSwipeToReply() + } + // Do not dismiss the message, return false! + false } - val bubbleState = BubbleState( - groupPosition = event.groupPosition, - isMine = event.isMine, - isHighlighted = isHighlighted, - ) - MessageEventBubble( - state = bubbleState, - interactionSource = interactionSource, - onClick = onClick, - onLongClick = onLongClick, - ) { - MessageEventBubbleContent( + ) + SwipeToDismiss( + state = dismissState, + background = { + ReplySwipeIndicator({ dismissState.toSwipeProgress() }) + }, + directions = setOf(DismissDirection.StartToEnd), + dismissContent = { + TimelineItemEventRowContent( event = event, + isHighlighted = isHighlighted, interactionSource = interactionSource, - onMessageClick = onClick, - onMessageLongClick = onLongClick, - inReplyToClick = ::inReplyToClicked, - onTimestampClicked = { - onTimestampClicked(event) - } + onClick = onClick, + onLongClick = onLongClick, + onTimestampClicked = onTimestampClicked, + inReplyToClicked = ::inReplyToClicked, + onUserDataClicked = ::onUserDataClicked, + onReactionClicked = { emoji -> onReactionClick(emoji, event) }, + onMoreReactionsClicked = { onMoreReactionsClick(event) }, ) } - if (event.reactionsState.reactions.isNotEmpty()) { - Spacer(modifier = Modifier.height(28.dp)) - } - } - // Align to the top of the box - if (event.showSenderInformation) { - MessageSenderInformation( - event.safeSenderName, - event.senderAvatar, - Modifier - .padding(horizontal = 16.dp) - .align(Alignment.TopStart) - .clickable(onClick = ::onUserDataClicked) - ) - } - // Align to the bottom of the box - if (event.reactionsState.reactions.isNotEmpty()) { - TimelineItemReactionsView( - reactionsState = event.reactionsState, - onReactionClicked = ::onReactionClicked, - modifier = Modifier - .align(if (event.isMine) Alignment.BottomEnd else Alignment.BottomStart) - .padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp) - ) - } + ) + } else { + TimelineItemEventRowContent( + event = event, + isHighlighted = isHighlighted, + interactionSource = interactionSource, + onClick = onClick, + onLongClick = onLongClick, + onTimestampClicked = onTimestampClicked, + inReplyToClicked = ::inReplyToClicked, + onUserDataClicked = ::onUserDataClicked, + onReactionClicked = { emoji -> onReactionClick(emoji, event) }, + onMoreReactionsClicked = { onMoreReactionsClick(event) }, + ) } // This is assuming that we are in a ColumnScope, but this is OK, for both Preview and real usage. if (event.groupPosition.isNew()) { @@ -180,13 +174,113 @@ fun TimelineItemEventRow( } } +@Composable +private fun TimelineItemEventRowContent( + event: TimelineItem.Event, + isHighlighted: Boolean, + interactionSource: MutableInteractionSource, + onClick: () -> Unit, + onLongClick: () -> Unit, + onTimestampClicked: (TimelineItem.Event) -> Unit, + inReplyToClicked: () -> Unit, + onUserDataClicked: () -> Unit, + onReactionClicked: (emoji: String) -> Unit, + onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit, + modifier: Modifier = Modifier, +) { + fun ConstrainScope.linkStartOrEnd(event: TimelineItem.Event) = if (event.isMine) { + end.linkTo(parent.end) + } else { + start.linkTo(parent.start) + } + + ConstraintLayout( + modifier = modifier + .wrapContentHeight() + .fillMaxWidth(), + ) { + val (sender, message, reactions) = createRefs() + + // Sender + val avatarStrokeSize = 3.dp + if (event.showSenderInformation) { + MessageSenderInformation( + event.safeSenderName, + event.senderAvatar, + avatarStrokeSize, + Modifier + .constrainAs(sender) { + top.linkTo(parent.top) + } + .padding(horizontal = 16.dp) + .zIndex(1f) + .clickable(onClick = onUserDataClicked) + ) + } + + // Message bubble + val bubbleState = BubbleState( + groupPosition = event.groupPosition, + isMine = event.isMine, + isHighlighted = isHighlighted, + ) + MessageEventBubble( + modifier = Modifier + .constrainAs(message) { + top.linkTo(sender.bottom, margin = -avatarStrokeSize - 8.dp) + this.linkStartOrEnd(event) + }, + state = bubbleState, + interactionSource = interactionSource, + onClick = onClick, + onLongClick = onLongClick, + ) { + MessageEventBubbleContent( + event = event, + interactionSource = interactionSource, + onMessageClick = onClick, + onMessageLongClick = onLongClick, + inReplyToClick = inReplyToClicked, + onTimestampClicked = { + onTimestampClicked(event) + } + ) + } + + // Reactions + if (event.reactionsState.reactions.isNotEmpty()) { + TimelineItemReactionsView( + reactionsState = event.reactionsState, + mainAxisAlignment = if (event.isMine) FlowMainAxisAlignment.End else FlowMainAxisAlignment.Start, + onReactionClicked = onReactionClicked, + onMoreReactionsClicked = { onMoreReactionsClicked(event) }, + modifier = Modifier + .constrainAs(reactions) { + top.linkTo(message.bottom, margin = (-4).dp) + this.linkStartOrEnd(event) + } + .zIndex(1f) + .padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp) + ) + } + } +} + +private fun DismissState.toSwipeProgress(): Float { + return when (targetValue) { + DismissValue.Default -> 0f + DismissValue.DismissedToEnd -> progress * 3 + DismissValue.DismissedToStart -> progress * 3 + } +} + @Composable private fun MessageSenderInformation( sender: String, senderAvatar: AvatarData, + avatarStrokeSize: Dp, modifier: Modifier = Modifier ) { - val avatarStrokeSize = 3.dp val avatarStrokeColor = MaterialTheme.colorScheme.background val avatarSize = senderAvatar.size.dp Box( @@ -449,30 +543,36 @@ private fun ContentToPreview() { content = aTimelineItemTextContent().copy( body = "A long text which will be displayed on several lines and" + " hopefully can be manually adjusted to test different behaviors." - ) + ), ), isHighlighted = false, + canReply = true, onClick = {}, onLongClick = {}, onUserDataClick = {}, inReplyToClick = {}, onReactionClick = { _, _ -> }, + onMoreReactionsClick = {}, onTimestampClicked = {}, + onSwipeToReply = {}, ) TimelineItemEventRow( event = aTimelineItemEvent( isMine = it, content = aTimelineItemImageContent().copy( aspectRatio = 5f - ) + ), ), isHighlighted = false, + canReply = true, onClick = {}, onLongClick = {}, onUserDataClick = {}, inReplyToClick = {}, onReactionClick = { _, _ -> }, + onMoreReactionsClick = {}, onTimestampClicked = {}, + onSwipeToReply = {}, ) } } @@ -492,7 +592,7 @@ internal fun TimelineItemEventRowWithReplyDarkPreview() = private fun ContentToPreviewWithReply() { Column { sequenceOf(false, true).forEach { - val replyContent = if(it) { + val replyContent = if (it) { // Short "Message which are being replied." } else { @@ -509,12 +609,15 @@ private fun ContentToPreviewWithReply() { inReplyTo = aInReplyToReady(replyContent) ), isHighlighted = false, + canReply = true, onClick = {}, onLongClick = {}, onUserDataClick = {}, inReplyToClick = {}, onReactionClick = { _, _ -> }, + onMoreReactionsClick = {}, onTimestampClicked = {}, + onSwipeToReply = {}, ) TimelineItemEventRow( event = aTimelineItemEvent( @@ -525,12 +628,15 @@ private fun ContentToPreviewWithReply() { inReplyTo = aInReplyToReady(replyContent) ), isHighlighted = false, + canReply = true, onClick = {}, onLongClick = {}, onUserDataClick = {}, inReplyToClick = {}, onReactionClick = { _, _ -> }, + onMoreReactionsClick = {}, onTimestampClicked = {}, + onSwipeToReply = {}, ) } } @@ -578,14 +684,56 @@ private fun ContentTimestampToPreview(event: TimelineItem.Event) { senderDisplayName = if (useDocument) "Document case" else "Text case", ), isHighlighted = false, + canReply = true, onClick = {}, onLongClick = {}, onUserDataClick = {}, inReplyToClick = {}, onReactionClick = { _, _ -> }, + onMoreReactionsClick = {}, onTimestampClicked = {}, + onSwipeToReply = {}, ) } } } } + + +@Preview +@Composable +internal fun TimelineItemEventRowWithManyReactionsLightPreview() = + ElementPreviewLight { ContentWithManyReactionsToPreview() } + +@Preview +@Composable +internal fun TimelineItemEventRowWithManyReactionsDarkPreview() = + ElementPreviewDark { ContentWithManyReactionsToPreview() } + +@Composable +private fun ContentWithManyReactionsToPreview() { + Column { + listOf(false, true).forEach { isMine -> + TimelineItemEventRow( + event = aTimelineItemEvent( + isMine = isMine, + content = aTimelineItemTextContent().copy( + body = "A couple of multi-line messages with many reactions attached." + + " One sent by me and another from someone else." + ), + timelineItemReactions = aTimelineItemReactions(count = 20), + ), + isHighlighted = false, + canReply = true, + onClick = {}, + onLongClick = {}, + onUserDataClick = {}, + inReplyToClick = {}, + onReactionClick = { _, _ -> }, + onMoreReactionsClick = {}, + onSwipeToReply = {}, + onTimestampClicked = {}, + ) + } + } +} 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 e275cf0d55..2bd7108677 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 @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.google.accompanist.flowlayout.FlowMainAxisAlignment import com.google.accompanist.flowlayout.FlowRow import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions import io.element.android.features.messages.impl.timeline.model.aTimelineItemReactions @@ -29,13 +30,16 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight @Composable fun TimelineItemReactionsView( reactionsState: TimelineItemReactions, + mainAxisAlignment: FlowMainAxisAlignment, + onReactionClicked: (emoji: String) -> Unit, + onMoreReactionsClicked: () -> Unit, modifier: Modifier = Modifier, - onReactionClicked: (emoji: String) -> Unit ) { FlowRow( modifier = modifier, - mainAxisSpacing = 2.dp, - crossAxisSpacing = 8.dp, + mainAxisSpacing = 4.dp, + crossAxisSpacing = 4.dp, + mainAxisAlignment = mainAxisAlignment, ) { reactionsState.reactions.forEach { reaction -> MessagesReactionButton( @@ -43,6 +47,9 @@ fun TimelineItemReactionsView( onClick = { onReactionClicked(reaction.key) } ) } + MessagesMoreReactionsButton( + onClick = onMoreReactionsClicked + ) } } @@ -60,6 +67,8 @@ internal fun TimelineItemReactionsViewDarkPreview() = private fun ContentToPreview() { TimelineItemReactionsView( reactionsState = aTimelineItemReactions(), - onReactionClicked = { } + mainAxisAlignment = FlowMainAxisAlignment.Center, + onReactionClicked = {}, + onMoreReactionsClicked = {}, ) } 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 index e880dc7959..d941b8a814 100644 --- 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 @@ -22,7 +22,7 @@ import androidx.compose.ui.unit.TextUnit 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.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonStrings @@ -39,7 +39,7 @@ val noExtraPadding = ExtraPadding(0) @Composable fun TimelineItem.Event.toExtraPadding(): ExtraPadding { val formattedTime = sentTime - val hasMessageSendingFailed = sendState is EventSendState.SendingFailed + val hasMessageSendingFailed = localSendState is LocalEventSendState.SendingFailed val isMessageEdited = (content as? TimelineItemTextBasedContent)?.isEdited.orFalse() var strLen = 6 diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt index 28c9e4fd16..4fbc3e995e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt @@ -16,8 +16,10 @@ package io.element.android.features.messages.impl.timeline.components.event +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @@ -28,21 +30,31 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContentProvider import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Text @Composable fun TimelineItemLocationView( content: TimelineItemLocationContent, modifier: Modifier = Modifier, ) { - StaticMapView( - modifier = modifier - .fillMaxWidth() - .heightIn(max = 188.dp), - lat = content.location.lat, - lon = content.location.lon, - zoom = 15.0, - contentDescription = content.body - ) + Column(modifier = modifier.fillMaxWidth()) { + content.description?.let { + Text( + text = it, + modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 8.dp, bottom = 8.dp), + ) + } + + StaticMapView( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 188.dp), + lat = content.location.lat, + lon = content.location.lon, + zoom = 15.0, + contentDescription = content.body + ) + } } @Preview diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt index faa80d134b..b9e4a75d97 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt @@ -37,7 +37,7 @@ class EventDebugInfoNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { data class Inputs( - val eventId: EventId, + val eventId: EventId?, val timelineItemDebugInfo: TimelineItemDebugInfo, ) : NodeInputs diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt index f3fc79e0a3..e6ecab9d5a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt @@ -70,7 +70,7 @@ import io.element.android.libraries.matrix.api.core.EventId @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable fun EventDebugInfoView( - eventId: EventId, + eventId: EventId?, model: String, originalJson: String?, latestEditedJson: String?, @@ -99,7 +99,7 @@ fun EventDebugInfoView( item { Column(Modifier.padding(vertical = 10.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Text(text = "Event ID:") - CopyableText(text = eventId.value) + CopyableText(text = eventId?.value ?: "-", modifier = Modifier.fillMaxWidth()) } } item { @@ -142,7 +142,7 @@ private fun CollapsibleSection( ) } AnimatedVisibility(visible = isExpanded, enter = expandVertically(), exit = shrinkVertically()) { - CopyableText(text = text) + CopyableText(text = text, modifier = Modifier.fillMaxWidth()) } } } 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 6d70d8318b..9d4304cec0 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 @@ -16,7 +16,7 @@ package io.element.android.features.messages.impl.timeline.factories.event -import io.element.android.features.location.api.parseGeoUri +import io.element.android.features.location.api.Location import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEmoteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent @@ -68,7 +68,7 @@ class TimelineItemContentMessageFactory @Inject constructor( ) } is LocationMessageType -> { - val location = parseGeoUri(messageType.geoUri) + val location = Location.fromGeoUri(messageType.geoUri) if (location == null) { TimelineItemTextContent( body = messageType.body, @@ -79,6 +79,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemLocationContent( body = messageType.body, location = location, + description = messageType.description ) } } 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 7663c14517..6ccbf7143a 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 @@ -26,7 +26,6 @@ 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.timeline.MatrixTimelineItem -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import kotlinx.collections.immutable.toImmutableList import java.text.DateFormat @@ -83,7 +82,7 @@ class TimelineItemEventFactory @Inject constructor( sentTime = sentTime, groupPosition = groupPosition, reactionsState = currentTimelineItem.computeReactionsState(), - sendState = currentTimelineItem.event.localSendState ?: EventSendState.NotSentYet, + localSendState = currentTimelineItem.event.localSendState, inReplyTo = currentTimelineItem.event.inReplyTo(), debugInfo = currentTimelineItem.event.debugInfo, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 08f9df0535..6743651e76 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -18,12 +18,13 @@ package io.element.android.features.messages.impl.timeline.model import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.libraries.designsystem.components.avatar.AvatarData 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.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import kotlinx.collections.immutable.ImmutableList @@ -61,7 +62,7 @@ sealed interface TimelineItem { val isMine: Boolean = false, val groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.None, val reactionsState: TimelineItemReactions, - val sendState: EventSendState, + val localSendState: LocalEventSendState?, val inReplyTo: InReplyTo?, val debugInfo: TimelineItemDebugInfo, ) : TimelineItem { @@ -69,6 +70,12 @@ sealed interface TimelineItem { val showSenderInformation = groupPosition.isNew() && !isMine val safeSenderName: String = senderDisplayName ?: senderId.value + + val failedToSend: Boolean = localSendState is LocalEventSendState.SendingFailed + + val isTextMessage: Boolean = content is TimelineItemTextBasedContent + + val isRemote = eventId != null } @Immutable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt index 3912844f20..373a8009ec 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt @@ -17,7 +17,14 @@ package io.element.android.features.messages.impl.timeline.model import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList data class TimelineItemReactions( val reactions: ImmutableList -) +) { + val highlightedKeys: ImmutableList + get() = reactions + .filter { it.isHighlighted } + .map { it.key } + .toPersistentList() +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt index 52c84d31db..85b739cd80 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt @@ -29,6 +29,7 @@ class TimelineItemEventContentProvider : PreviewParameterProvider get() = sequenceOf( aTimelineItemLocationContent(), + aTimelineItemLocationContent("This is a description!"), ) } -fun aTimelineItemLocationContent() = TimelineItemLocationContent( +fun aTimelineItemLocationContent(description: String? = null) = TimelineItemLocationContent( body = "User location geo:52.2445,0.7186;u=5000", location = Location( lat = 52.2445, lon = 0.7186, accuracy = 5000f, - ) + ), + description = description, ) diff --git a/features/messages/impl/src/main/res/values/localazy.xml b/features/messages/impl/src/main/res/values/localazy.xml index 79d70cd4b7..5ba6a2ebd8 100644 --- a/features/messages/impl/src/main/res/values/localazy.xml +++ b/features/messages/impl/src/main/res/values/localazy.xml @@ -10,11 +10,14 @@ "Attachment" "Photo & Video Library" "Location" + "Message history is currently unavailable in this room" "Could not retrieve user details" "Would you like to invite them back?" "You are alone in this chat" "Message copied" "You do not have permission to post to this room" + "Show less" + "Show more" "Send again" "Your message failed to send" "Failed processing media to upload, please try again." diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/FakeMessagesNavigator.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/FakeMessagesNavigator.kt index 8a374e5bcb..bb2caa9405 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/FakeMessagesNavigator.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/FakeMessagesNavigator.kt @@ -31,7 +31,7 @@ class FakeMessagesNavigator : MessagesNavigator { var onReportContentClickedCount = 0 private set - override fun onShowEventDebugInfoClicked(eventId: EventId, debugInfo: TimelineItemDebugInfo) { + override fun onShowEventDebugInfoClicked(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { onShowEventDebugInfoClickedCount++ } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index c90a03e5bb..0aafa68100 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -25,9 +25,12 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction +import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent +import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.core.aBuildMeta import kotlinx.collections.immutable.persistentListOf @@ -62,7 +65,6 @@ class ActionListPresenterTest { ActionListState.Target.Success( messageEvent, persistentListOf( - TimelineItemAction.Copy, TimelineItemAction.Developer, ) ) @@ -88,7 +90,6 @@ class ActionListPresenterTest { ActionListState.Target.Success( messageEvent, persistentListOf( - TimelineItemAction.Copy, TimelineItemAction.Developer, ) ) @@ -184,7 +185,6 @@ class ActionListPresenterTest { persistentListOf( TimelineItemAction.Reply, TimelineItemAction.Forward, - TimelineItemAction.Edit, TimelineItemAction.Developer, TimelineItemAction.Redact, ) @@ -195,6 +195,63 @@ class ActionListPresenterTest { } } + @Test + fun `present - compute for a state item in debug build`() = runTest { + val presenter = anActionListPresenter(isBuildDebuggable = true) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val stateEvent = aTimelineItemEvent( + isMine = true, + content = aTimelineItemStateEventContent(), + ) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent)) + // val loadingState = awaitItem() + // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + stateEvent, + persistentListOf( + TimelineItemAction.Copy, + TimelineItemAction.Developer, + ) + ) + ) + initialState.eventSink.invoke(ActionListEvents.Clear) + assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) + } + } + + @Test + fun `present - compute for a state item in non-debuggable build`() = runTest { + val presenter = anActionListPresenter(isBuildDebuggable = false) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val stateEvent = aTimelineItemEvent( + isMine = true, + content = aTimelineItemStateEventContent(), + ) + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(stateEvent)) + // val loadingState = awaitItem() + // assertThat(loadingState.target).isEqualTo(ActionListState.Target.Loading(messageEvent)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + stateEvent, + persistentListOf( + TimelineItemAction.Copy, + ) + ) + ) + initialState.eventSink.invoke(ActionListEvents.Clear) + assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) + } + } + @Test fun `present - compute message in non-debuggable build`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = false) @@ -226,6 +283,62 @@ class ActionListPresenterTest { assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) } } + + @Test + fun `present - compute message with no actions`() = runTest { + val presenter = anActionListPresenter(isBuildDebuggable = false) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + isMine = true, + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false) + ) + val redactedEvent = aMessageEvent( + isMine = true, + content = TimelineItemRedactedContent, + ) + + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent)) + assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java) + + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(redactedEvent)) + awaitItem().run { + assertThat(target).isEqualTo(ActionListState.Target.None) + assertThat(displayEmojiReactions).isFalse() + } + } + } + + @Test + fun `present - compute not sent message`() = runTest { + val presenter = anActionListPresenter(isBuildDebuggable = false) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + eventId = null, // No event id, so it's not sent yet + isMine = true, + content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false), + ) + + initialState.eventSink.invoke(ActionListEvents.ComputeForMessage(messageEvent)) + val successState = awaitItem() + assertThat(successState.target).isEqualTo( + ActionListState.Target.Success( + messageEvent, + persistentListOf( + TimelineItemAction.Edit, + TimelineItemAction.Copy, + TimelineItemAction.Redact, + ) + ) + ) + assertThat(successState.displayEmojiReactions).isFalse() + } + } } private fun anActionListPresenter(isBuildDebuggable: Boolean) = ActionListPresenter(buildMeta = aBuildMeta(isDebuggable = isBuildDebuggable)) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt index 6298042e89..088df6060f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/aMessageEvent.kt @@ -24,7 +24,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_MESSAGE @@ -38,6 +38,7 @@ internal fun aMessageEvent( content: TimelineItemEventContent = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false), inReplyTo: InReplyTo? = null, debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(), + sendState: LocalEventSendState = LocalEventSendState.Sent(AN_EVENT_ID), ) = TimelineItem.Event( id = eventId?.value.orEmpty(), eventId = eventId, @@ -48,7 +49,7 @@ internal fun aMessageEvent( sentTime = "", isMine = isMine, reactionsState = aTimelineItemReactions(count = 0), - sendState = EventSendState.Sent(AN_EVENT_ID), + localSendState = sendState, inReplyTo = inReplyTo, debugInfo = debugInfo, ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index 8e96820c47..97bbf925bd 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -36,6 +36,7 @@ import io.element.android.libraries.designsystem.utils.SnackbarDispatcher 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.EventId 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.room.MatrixRoom @@ -43,6 +44,7 @@ import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_REPLY +import io.element.android.libraries.matrix.test.A_TRANSACTION_ID import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediapickers.api.PickerProvider @@ -193,7 +195,7 @@ class MessageComposerPresenterTest { } @Test - fun `present - edit message`() = runTest { + fun `present - edit sent message`() = runTest { val fakeMatrixRoom = FakeMatrixRoom() val presenter = createPresenter( this, @@ -219,7 +221,38 @@ class MessageComposerPresenterTest { val messageSentState = awaitItem() assertThat(messageSentState.text).isEqualTo(StableCharSequence("")) assertThat(messageSentState.isSendButtonVisible).isFalse() - assertThat(fakeMatrixRoom.editMessageParameter).isEqualTo(ANOTHER_MESSAGE) + assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE) + } + } + + @Test + fun `present - edit not sent message`() = runTest { + val fakeMatrixRoom = FakeMatrixRoom() + val presenter = createPresenter( + this, + fakeMatrixRoom, + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.text).isEqualTo(StableCharSequence("")) + val mode = anEditMode(eventId = null, transactionId = A_TRANSACTION_ID) + initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + skipItems(1) + val withMessageState = awaitItem() + assertThat(withMessageState.mode).isEqualTo(mode) + assertThat(withMessageState.text).isEqualTo(StableCharSequence(A_MESSAGE)) + assertThat(withMessageState.isSendButtonVisible).isTrue() + withMessageState.eventSink.invoke(MessageComposerEvents.UpdateText(ANOTHER_MESSAGE)) + val withEditedMessageState = awaitItem() + assertThat(withEditedMessageState.text).isEqualTo(StableCharSequence(ANOTHER_MESSAGE)) + withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage(ANOTHER_MESSAGE)) + skipItems(1) + val messageSentState = awaitItem() + assertThat(messageSentState.text).isEqualTo(StableCharSequence("")) + assertThat(messageSentState.isSendButtonVisible).isFalse() + assertThat(fakeMatrixRoom.editMessageCalls.first()).isEqualTo(ANOTHER_MESSAGE) } } @@ -474,6 +507,10 @@ class MessageComposerPresenterTest { ) } -fun anEditMode() = MessageComposerMode.Edit(AN_EVENT_ID, A_MESSAGE) +fun anEditMode( + eventId: EventId? = AN_EVENT_ID, + message: String = A_MESSAGE, + transactionId: String? = null, +) = MessageComposerMode.Edit(eventId, message, transactionId) fun aReplyMode() = MessageComposerMode.Reply(A_USER_NAME, null, AN_EVENT_ID, A_MESSAGE) fun aQuoteMode() = MessageComposerMode.Quote(AN_EVENT_ID, A_MESSAGE) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt index 465f8e3d33..3cab1fe44c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/groups/TimelineItemGrouperTest.kt @@ -24,7 +24,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel import io.element.android.libraries.designsystem.components.avatar.anAvatarData -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState 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 @@ -42,7 +42,7 @@ class TimelineItemGrouperTest { senderDisplayName = "", content = TimelineItemStateEventContent(body = "a state event"), reactionsState = aTimelineItemReactions(count = 0), - sendState = EventSendState.Sent(AN_EVENT_ID), + localSendState = LocalEventSendState.Sent(AN_EVENT_ID), inReplyTo = null, debugInfo = aTimelineItemDebugInfo(), ) diff --git a/features/preferences/api/build.gradle.kts b/features/preferences/api/build.gradle.kts index bf0cb4a51f..c20fe9aabb 100644 --- a/features/preferences/api/build.gradle.kts +++ b/features/preferences/api/build.gradle.kts @@ -23,4 +23,5 @@ android { dependencies { implementation(projects.libraries.architecture) + api(projects.libraries.matrix.api) } diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt index 0bc9285853..191535a3a0 100644 --- a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt @@ -16,13 +16,13 @@ package io.element.android.features.preferences.api +import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.Flow interface CacheService { /** - * Returns a flow of the current cache index, can let the app to know when the - * cache has been cleared, for instance to restart the app. - * Will be a flow of Int, starting from 0, and incrementing each time the cache is cleared. + * A flow of [SessionId], can let the app to know when the + * cache has been cleared for a given session, for instance to restart the app. */ - fun cacheIndex(): Flow + val clearedCacheEventFlow: Flow } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt index 7675ec3dd6..2ffe480518 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt @@ -20,20 +20,19 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.preferences.api.CacheService import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.MutableSharedFlow import javax.inject.Inject @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) class DefaultCacheService @Inject constructor() : CacheService { - private val cacheIndexState = MutableStateFlow(0) - override fun cacheIndex(): Flow { - return cacheIndexState - } + private val _clearedCacheEventFlow = MutableSharedFlow(0) + override val clearedCacheEventFlow: Flow = _clearedCacheEventFlow - fun incrementCacheIndex() { - cacheIndexState.value++ + suspend fun onClearedCache(sessionId: SessionId) { + _clearedCacheEventFlow.emit(sessionId) } } 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 f7b0d01130..0ef0e02558 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 @@ -27,6 +27,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import javax.inject.Inject @@ -57,6 +58,6 @@ class DefaultClearCacheUseCase @Inject constructor( // Clear app cache context.cacheDir.deleteRecursively() // Ensure the app is restarted - defaultCacheIndexProvider.incrementCacheIndex() + defaultCacheIndexProvider.onClearedCache(matrixClient.sessionId) } } diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index b2120af737..2bd6a259aa 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -13,7 +13,7 @@ "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them." "Message encryption enabled" "Invite people" - "Notification" + "Notifications" "Room name" "Share room" "Updating room…" diff --git a/features/roomlist/impl/src/main/res/values/localazy.xml b/features/roomlist/impl/src/main/res/values/localazy.xml index e18d4c9017..3a1c3cbad6 100644 --- a/features/roomlist/impl/src/main/res/values/localazy.xml +++ b/features/roomlist/impl/src/main/res/values/localazy.xml @@ -2,6 +2,6 @@ "Create a new conversation or room" "All Chats" - "Looks like you’re using a new device. Verify it’s you to access your encrypted messages." - "Access your message history" + "Looks like you’re using a new device. Verify with another device to access your encrypted messages moving forwards." + "Verify it’s you" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5346bd99c7..9038c7a72a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,11 +13,12 @@ material = "1.9.0" core = "1.10.1" datastore = "1.0.0" constraintlayout = "2.1.4" +constraintlayout_compose = "1.0.1" recyclerview = "1.3.0" lifecycle = "2.6.1" activity = "1.7.2" startup = "1.1.1" -media3 = "1.0.2" +media3 = "1.1.0" browser = "1.5.0" # Compose @@ -74,6 +75,8 @@ androidx_datastore_preferences = { module = "androidx.datastore:datastore-prefer androidx_datastore_datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } androidx_exifinterface = "androidx.exifinterface:exifinterface:1.3.6" androidx_constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } +androidx_constraintlayout_compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayout_compose" } + androidx_recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } androidx_browser = { module = "androidx.browser:browser", version.ref = "browser" } androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } @@ -142,7 +145,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.27" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.28" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } @@ -160,7 +163,7 @@ maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.0" # Analytics posthog = "com.posthog.android:posthog:2.0.3" -sentry_android = "io.sentry:sentry-android:6.24.0" +sentry_android = "io.sentry:sentry-android:6.25.0" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:42b2faa417c1e95f430bf8f6e379adba25ad5ef8" # Di diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt index b8284ff7b9..c03ff64f6c 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt @@ -16,12 +16,35 @@ package io.element.android.libraries.architecture +import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.children.nodeOrNull import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.ParentNode +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.resume fun ParentNode.childNode(navTarget: NavTarget): Node? { val childMap = children.value val key = childMap.keys.find { it.navTarget == navTarget } return childMap[key]?.nodeOrNull } + +suspend inline fun ParentNode.waitForChildAttached(crossinline predicate: (NavTarget) -> Boolean): N = + suspendCancellableCoroutine { continuation -> + lifecycleScope.launch { + children.collect { childMap -> + val expectedChildNode = childMap.entries + .map { it.key.navTarget } + .lastOrNull(predicate) + ?.let { + childNode(it) as? N + } + if (expectedChildNode != null && !continuation.isCompleted) { + continuation.resume(expectedChildNode) + } + } + }.invokeOnCompletion { + continuation.cancel() + } + } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/DayNightPreviews.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/DayNightPreviews.kt new file mode 100644 index 0000000000..201d6f7151 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/DayNightPreviews.kt @@ -0,0 +1,47 @@ +/* + * 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.preview + +import android.content.res.Configuration +import androidx.compose.ui.tooling.preview.Preview + +/** + * Marker for a night mode preview. + * + * Previews with such marker will be rendered in night mode during screenshot testing. + * + * NB: Length of this constant is kept to a minimum to avoid screenshot file names being too long. + */ +const val NIGHT_MODE_NAME = "N" + +/** + * Marker for a day mode preview. + * + * This marker is currently not used during screenshot testing, it mainly act as a counterpart to [NIGHT_MODE_NAME]. + * + * NB: Length of this constant is kept to a minimum to avoid screenshot file names being too long. + */ +const val DAY_MODE_NAME = "D" + +/** + * Generates 2 previews of the composable it is applied to: day and night mode. + * + * NB: Content should be wrapped into [ElementPreview] to apply proper theming. + */ +@Preview(name = DAY_MODE_NAME) +@Preview(name = NIGHT_MODE_NAME, uiMode = Configuration.UI_MODE_NIGHT_YES) +annotation class DayNightPreviews diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt index 84de5d58f1..3c2d61a76e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.designsystem.preview import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -28,8 +29,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.designsystem.theme.components.Surface +import io.element.android.libraries.theme.ElementTheme @Composable fun ElementPreviewLight( @@ -62,29 +63,35 @@ fun ElementThemedPreview( vertical: Boolean = true, content: @Composable () -> Unit, ) { - Box(modifier = Modifier - .background(Color.Gray) - .padding(4.dp)) { + Box( + modifier = Modifier + .background(Color.Gray) + .padding(4.dp) + ) { if (vertical) { Column { - ElementPreviewLight( + ElementPreview( + darkTheme = false, showBackground = showBackground, content = content, ) Spacer(modifier = Modifier.height(4.dp)) - ElementPreviewDark( + ElementPreview( + darkTheme = true, showBackground = showBackground, content = content ) } } else { Row { - ElementPreviewLight( + ElementPreview( + darkTheme = false, showBackground = showBackground, content = content, ) Spacer(modifier = Modifier.width(4.dp)) - ElementPreviewDark( + ElementPreview( + darkTheme = true, showBackground = showBackground, content = content ) @@ -95,18 +102,17 @@ fun ElementThemedPreview( @Composable @Suppress("ModifierMissing") -private fun ElementPreview( - darkTheme: Boolean, - showBackground: Boolean, +fun ElementPreview( + darkTheme: Boolean = isSystemInDarkTheme(), + showBackground: Boolean = true, content: @Composable () -> Unit ) { ElementTheme(darkTheme = darkTheme) { if (showBackground) { // If we have a proper contentColor applied we need a Surface instead of a Box - Surface { content() } + Surface(content = content) } else { content() } } } - diff --git a/libraries/designsystem/src/main/res/drawable-night/pin.xml b/libraries/designsystem/src/main/res/drawable-night/pin.xml new file mode 100644 index 0000000000..b527ef7f5f --- /dev/null +++ b/libraries/designsystem/src/main/res/drawable-night/pin.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/libraries/designsystem/src/main/res/drawable/pin.xml b/libraries/designsystem/src/main/res/drawable/pin.xml index 40b9d4030a..7f26c5ac4b 100644 --- a/libraries/designsystem/src/main/res/drawable/pin.xml +++ b/libraries/designsystem/src/main/res/drawable/pin.xml @@ -3,21 +3,17 @@ android:height="54dp" android:viewportWidth="50" android:viewportHeight="54"> + + + android:pathData="M13,13h24v24h-24z"/> - - - - - + android:pathData="M25,13C20.356,13 16.6,16.858 16.6,21.629C16.6,26.769 21.904,33.857 24.088,36.556C24.568,37.148 25.444,37.148 25.924,36.556C28.096,33.857 33.4,26.769 33.4,21.629C33.4,16.858 29.644,13 25,13ZM25,24.71C23.344,24.71 22,23.33 22,21.629C22,19.928 23.344,18.547 25,18.547C26.656,18.547 28,19.928 28,21.629C28,23.33 26.656,24.71 25,24.71Z" + android:fillColor="#ffffff"/> diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt index b737031302..494c63784d 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLastMessageFormatterTests.kt @@ -162,7 +162,7 @@ class DefaultRoomLastMessageFormatterTests { AudioMessageType(body, MediaSource("url"), null), ImageMessageType(body, MediaSource("url"), null), FileMessageType(body, MediaSource("url"), null), - LocationMessageType(body, "geo:1,2"), + LocationMessageType(body, "geo:1,2", null), NoticeMessageType(body, null), EmoteMessageType(body, null), ) 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 3960f74723..3ad391ead8 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 @@ -25,8 +25,8 @@ import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo 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.room.location.AssetType import io.element.android.libraries.matrix.api.timeline.MatrixTimeline -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import java.io.Closeable import java.io.File @@ -70,7 +70,7 @@ interface MatrixRoom : Closeable { suspend fun sendMessage(message: String): Result - suspend fun editMessage(originalEventId: EventId, message: String): Result + suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result suspend fun replyMessage(eventId: EventId, message: String): Result @@ -122,6 +122,16 @@ interface MatrixRoom : Closeable { * @param body A human readable textual representation of the location. * @param geoUri A geo URI (RFC 5870) representing the location e.g. `geo:51.5008,0.1247;u=35`. * Respectively: latitude, longitude, and (optional) uncertainty. + * @param description Optional description of the location to display to the user. + * @param zoomLevel Optional zoom level to display the map at. + * @param assetType Optional type of the location asset. + * Set to SENDER if sharing own location. Set to PIN if sharing any location. */ - suspend fun sendLocation(body: String, geoUri: String): Result + suspend fun sendLocation( + body: String, + geoUri: String, + description: String? = null, + zoomLevel: Int? = null, + assetType: AssetType? = null, + ): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/location/AssetType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/location/AssetType.kt new file mode 100644 index 0000000000..24375a45d4 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/location/AssetType.kt @@ -0,0 +1,22 @@ +/* + * 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.api.room.location + +enum class AssetType { + SENDER, + PIN +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index f37f7ceba2..843c232890 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -128,6 +128,7 @@ data class ImageMessageType( data class LocationMessageType( val body: String, val geoUri: String, + val description: String?, ) : MessageType data class AudioMessageType( diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 2a5c068519..e2a86fbb3c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -28,7 +28,7 @@ data class EventTimelineItem( val isLocal: Boolean, val isOwn: Boolean, val isRemote: Boolean, - val localSendState: EventSendState?, + val localSendState: LocalEventSendState?, val reactions: List, val sender: UserId, val senderProfile: ProfileTimelineDetails, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventSendState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt similarity index 82% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventSendState.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt index 0c70096b1a..3e1ee55318 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventSendState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt @@ -18,15 +18,15 @@ package io.element.android.libraries.matrix.api.timeline.item.event import io.element.android.libraries.matrix.api.core.EventId -sealed interface EventSendState { - object NotSentYet : EventSendState - object Canceled : EventSendState +sealed interface LocalEventSendState { + object NotSentYet : LocalEventSendState + object Canceled : LocalEventSendState data class SendingFailed( val error: String - ) : EventSendState + ) : LocalEventSendState data class Sent( val eventId: EventId - ) : EventSendState + ) : LocalEventSendState } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt index e6125cf69b..b62063ddc5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt @@ -32,13 +32,13 @@ class NotificationMapper { NotificationData( senderId = UserId(it.event.senderId()), eventId = EventId(it.event.eventId()), - roomId = RoomId(it.roomId), - senderAvatarUrl = it.senderAvatarUrl, - senderDisplayName = it.senderDisplayName, - roomAvatarUrl = it.roomAvatarUrl, - roomDisplayName = it.roomDisplayName, - isDirect = it.isDirect, - isEncrypted = it.isEncrypted.orFalse(), + roomId = RoomId(it.roomInfo.id), + senderAvatarUrl = it.senderInfo.avatarUrl, + senderDisplayName = it.senderInfo.displayName, + roomAvatarUrl = it.roomInfo.avatarUrl, + roomDisplayName = it.roomInfo.displayName, + isDirect = it.roomInfo.isDirect, + isEncrypted = it.roomInfo.isEncrypted.orFalse(), isNoisy = it.isNoisy, event = it.event.use { event -> timelineEventMapper.map(event) } ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index 73abea0d77..4e2d63d091 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.withTimeout import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListService -import org.matrix.rustcomponents.sdk.SlidingSync import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineListener import org.matrix.rustcomponents.sdk.genTransactionId 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 cc3a03e95f..10d9763c1f 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 @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback 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.core.UserId +import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo @@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.impl.core.toProgressWatcher +import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow @@ -202,11 +204,16 @@ class RustMatrixRoom( } } - override suspend fun editMessage(originalEventId: EventId, message: String): Result = withContext(coroutineDispatchers.io) { - val transactionId = genTransactionId() - // val content = messageEventContentFromMarkdown(message) - runCatching { - innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId) + override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result = withContext(coroutineDispatchers.io) { + if (originalEventId != null) { + runCatching { + innerRoom.edit(/* TODO use content */ message, originalEventId.value, transactionId) + } + } else { + runCatching { + transactionId?.let { cancelSend(it) } + innerRoom.send(messageEventContentFromMarkdown(message), genTransactionId()) + } } } @@ -363,13 +370,22 @@ class RustMatrixRoom( } } - //TODO expose inner parameters override suspend fun sendLocation( body: String, - geoUri: String + geoUri: String, + description: String?, + zoomLevel: Int?, + assetType: AssetType?, ): Result = withContext(coroutineDispatchers.io) { runCatching { - innerRoom.sendLocation(body, geoUri, null, null, null, genTransactionId()) + innerRoom.sendLocation( + body = body, + geoUri = geoUri, + description = description, + zoomLevel = zoomLevel?.toUByte(), + assetType = assetType?.toInner(), + txnId = genTransactionId() + ) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetType.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetType.kt new file mode 100644 index 0000000000..e886dae442 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetType.kt @@ -0,0 +1,24 @@ +/* + * 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.room.location + +import io.element.android.libraries.matrix.api.room.location.AssetType + +fun AssetType.toInner(): org.matrix.rustcomponents.sdk.AssetType = when (this) { + AssetType.SENDER -> org.matrix.rustcomponents.sdk.AssetType.SENDER + AssetType.PIN -> org.matrix.rustcomponents.sdk.AssetType.PIN +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 767a3ce444..866dbf984b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -110,4 +110,8 @@ class RustMatrixTimeline( innerRoom.sendReadReceipt(eventId = eventId.value) } } + + fun getItemById(eventId: EventId): MatrixTimelineItem.Event? { + return _timelineItems.value.firstOrNull { (it as? MatrixTimelineItem.Event)?.eventId == eventId } as? MatrixTimelineItem.Event + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index 0a76d2c4bb..c4148de745 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -55,7 +55,7 @@ class EventMessageMapper { ImageMessageType(type.content.body, type.content.source.map(), type.content.info?.map()) } is MessageType.Location -> { - LocationMessageType(type.content.body, type.content.geoUri) + LocationMessageType(type.content.body, type.content.geoUri, type.content.description) } is MessageType.Notice -> { NoticeMessageType(type.content.body, type.content.formatted?.map()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 549b0096d0..d250072267 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -20,7 +20,7 @@ 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.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails import org.matrix.rustcomponents.sdk.Reaction @@ -64,13 +64,13 @@ fun RustProfileDetails.map(): ProfileTimelineDetails { } } -fun RustEventSendState?.map(): EventSendState? { +fun RustEventSendState?.map(): LocalEventSendState? { return when (this) { null -> null - RustEventSendState.NotSentYet -> EventSendState.NotSentYet - is RustEventSendState.SendingFailed -> EventSendState.SendingFailed(error) - is RustEventSendState.Sent -> EventSendState.Sent(EventId(eventId)) - RustEventSendState.Cancelled -> EventSendState.Canceled + RustEventSendState.NotSentYet -> LocalEventSendState.NotSentYet + is RustEventSendState.SendingFailed -> LocalEventSendState.SendingFailed(error) + is RustEventSendState.Sent -> LocalEventSendState.Sent(EventId(eventId)) + RustEventSendState.Cancelled -> LocalEventSendState.Canceled } } 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 be4cd4cbed..9392fb7985 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 @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback 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.core.UserId +import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.ImageInfo @@ -79,6 +80,7 @@ class FakeMatrixRoom( private var reportContentResult = Result.success(Unit) private var sendLocationResult = Result.success(Unit) private var progressCallbackValues = emptyList>() + val editMessageCalls = mutableListOf() var sendMediaCount = 0 private set @@ -172,11 +174,8 @@ class FakeMatrixRoom( return cancelSendResult } - var editMessageParameter: String? = null - private set - - override suspend fun editMessage(originalEventId: EventId, message: String): Result { - editMessageParameter = message + override suspend fun editMessage(originalEventId: EventId?, transactionId: String?, message: String): Result { + editMessageCalls += message return Result.success(Unit) } @@ -285,7 +284,10 @@ class FakeMatrixRoom( override suspend fun sendLocation( body: String, - geoUri: String + geoUri: String, + description: String?, + zoomLevel: Int?, + assetType: AssetType?, ): Result = simulateLongTask { sendLocationCount++ return sendLocationResult diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index e8e3ff38b9..7580a32e18 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.api.room.message.RoomMessage import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction -import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState +import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails @@ -94,7 +94,7 @@ fun anEventTimelineItem( isLocal: Boolean = false, isOwn: Boolean = false, isRemote: Boolean = false, - localSendState: EventSendState? = null, + localSendState: LocalEventSendState? = null, reactions: List = emptyList(), sender: UserId = A_USER_ID, senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), diff --git a/libraries/rustsdk/.gitignore b/libraries/rustsdk/.gitignore new file mode 100644 index 0000000000..67f29a6964 --- /dev/null +++ b/libraries/rustsdk/.gitignore @@ -0,0 +1,2 @@ +# Built application files +*.aar diff --git a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt b/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt index 5539b781ea..f0ccc76f3c 100644 --- a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt +++ b/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerMode.kt @@ -25,11 +25,11 @@ sealed interface MessageComposerMode : Parcelable { @Parcelize data class Normal(val content: CharSequence?) : MessageComposerMode - sealed class Special(open val eventId: EventId, open val defaultContent: CharSequence) : + sealed class Special(open val eventId: EventId?, open val defaultContent: CharSequence) : MessageComposerMode @Parcelize - data class Edit(override val eventId: EventId, override val defaultContent: CharSequence) : + data class Edit(override val eventId: EventId?, override val defaultContent: CharSequence, val transactionId: String?) : Special(eventId, defaultContent) @Parcelize diff --git a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index b13a361188..e9710da4d8 100644 --- a/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -473,7 +473,7 @@ private fun EditContentToPreview() { TextComposer( onSendMessage = {}, onComposerTextChange = {}, - composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text"), + composerMode = MessageComposerMode.Edit(EventId("$1234"), "Some text", "1234"), onResetComposerMode = {}, composerCanSendMessage = true, composerText = "A message", 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 161acdff38..9d897db9d8 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -175,12 +175,6 @@ "Otevřít v OpenStreetMap" "Sdílet tuto polohu" "Poloha" - "Na %2$s je momentálně vysoká poptávka po %1$s. Vraťte se do aplikace za pár dní a zkuste to znovu. - -Díky za trpělivost!" - "Vítá vás %1$s" - "Jste v pořadníku!" - "Jdete do toho!" "Rageshake" "Práh detekce" "Obecné" 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 be70035c99..bb469ad0b1 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -174,12 +174,6 @@ "In OpenStreetMap öffnen" "Diesen Ort teilen" "Standort" - "Im Moment besteht eine hohe Nachfrage nach %1$s auf %2$s. Besuche die App in ein paar Tagen wieder und versuche es erneut. - -Vielen Dank für deine Geduld!" - "Willkommen bei %1$s!" - "Du hast es fast geschafft!" - "Du bist dabei." "Rageshake" "Erkennungsschwelle" "Allgemein" diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index e0e700720c..d90a7a6cfc 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -170,12 +170,6 @@ "Distribuiți locația mea" "Distribuiți această locație" "Locație" - "Există o cerere mare pentru %1$s pentru %2$s în acest moment. Reveniți la aplicație în câteva zile și încercați din nou. - -Vă mulțumim pentru răbdare!" - "Bun venit la %1$s" - "Sunteți pe lista de așteptare" - "Sunteți conectat!" "Rageshake" "Prag de detecție" "General" 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 607b3aeca9..707032b237 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -175,12 +175,6 @@ "Otvoriť v OpenStreetMap" "Zdieľajte túto polohu" "Poloha" - "Momentálne je veľký dopyt po %1$s na %2$s. Vráťte sa do aplikácie za pár dní a skúste to znova. - -Ďakujeme za trpezlivosť!" - "Vitajte v %1$s" - "Ste na čakanej listine!" - "Ste dnu!" "Zúrivé potrasenie" "Prahová hodnota detekcie" "Všeobecné" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 1a48a00c5a..315c32ecb9 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -140,7 +140,10 @@ "Travel & Places" "Symbols" "Failed creating the permalink" + "Element could not load the map. Please try again later." "Failed loading messages" + "Element could not access your location. Please try again later." + "Element does not have permission to access your location. You can enable access in Settings > Location" "Some messages have not been sent" "Sorry, an error occurred" "🔐️ Join me on %1$s" @@ -166,6 +169,11 @@ "Failed uploading media, please try again." "This is a one time process, thanks for waiting." "Setting up your account." + "Enable notifications on this device" + "To receive notifications, please change your %1$s." + "system settings" + "System notifications turned off" + "Notifications" "Check if you want to hide all current and future messages from this user" "Share location" "Share my location" @@ -180,6 +188,12 @@ Thanks for your patience!" "Welcome to %1$s!" "You’re almost there." "You\'re in." + "Calls, location sharing, search and more will be added later this year." + "Message history for encrypted rooms won’t be available in this update." + "We’d love to hear from you, let us know what you think via the settings page." + "Let\'s go!" + "Here’s what you need to know:" + "Welcome to %1$s!" "Rageshake" "Detection threshold" "General" diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt index 54bef4652b..ec4d7cf9f2 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordPresenter import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordView @@ -33,7 +34,8 @@ class LoginScreen(private val authenticationService: MatrixAuthenticationService val presenter = remember { LoginPasswordPresenter( authenticationService = authenticationService, - AccountProviderDataSource() + AccountProviderDataSource(), + DefaultLoginUserStory(), ) } @@ -46,6 +48,7 @@ class LoginScreen(private val authenticationService: MatrixAuthenticationService state = state, modifier = modifier, onBackPressed = {}, + onWaitListError = {}, ) } } diff --git a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ColorTestPreview.kt b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ColorTestPreview.kt index ae3722612b..97276e0987 100644 --- a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ColorTestPreview.kt +++ b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ColorTestPreview.kt @@ -38,5 +38,7 @@ class ColorTestPreview( ) } + override val name: String = showkaseBrowserColor.colorName + override fun toString(): String = "Color_${showkaseBrowserColor.colorGroup}_${showkaseBrowserColor.colorName}" } diff --git a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ComponentTestPreview.kt b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ComponentTestPreview.kt index d6c09608d4..8be21ba1ee 100644 --- a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ComponentTestPreview.kt +++ b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ComponentTestPreview.kt @@ -25,5 +25,7 @@ class ComponentTestPreview( @Composable override fun Content() = showkaseBrowserComponent.component() + override val name: String = showkaseBrowserComponent.componentName + override fun toString(): String = showkaseBrowserComponent.componentKey } diff --git a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ScreenshotTest.kt b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ScreenshotTest.kt index 1c8f1232d0..28593c2b07 100644 --- a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ScreenshotTest.kt +++ b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/ScreenshotTest.kt @@ -38,6 +38,7 @@ import com.airbnb.android.showkase.models.Showkase import com.android.ide.common.rendering.api.SessionParams import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector +import io.element.android.libraries.designsystem.preview.NIGHT_MODE_NAME import io.element.android.libraries.theme.ElementTheme import org.junit.Rule import org.junit.Test @@ -80,6 +81,8 @@ class ScreenshotTest { @TestParameter(value = ["1.0"/*, "1.5"*/]) fontScale: Float, @TestParameter(value = ["en" /*"fr", "de", "ru"*/]) localeStr: String, ) { + val locale = localeStr.toLocale() + Locale.setDefault(locale) // Needed for regional settings, as first day of week paparazzi.unsafeUpdateConfig( deviceConfig = baseDeviceConfig.deviceConfig.copy( softButtons = false @@ -94,7 +97,11 @@ class ScreenshotTest { fontScale = fontScale ), LocalConfiguration provides Configuration().apply { - setLocales(LocaleList(localeStr.toLocale())) + setLocales(LocaleList(locale)) + // Dark mode previews have name "N" so their component name contains "- N" + if (componentTestPreview.name.contains("- $NIGHT_MODE_NAME")){ + uiMode = Configuration.UI_MODE_NIGHT_YES + } }, // Needed so that UI that uses it don't crash during screenshot tests LocalOnBackPressedDispatcherOwner provides object : OnBackPressedDispatcherOwner { @@ -117,7 +124,7 @@ class ScreenshotTest { private fun String.toLocale(): Locale { return when (this) { - "en" -> Locale.ENGLISH + "en" -> Locale.US "fr" -> Locale.FRANCE "de" -> Locale.GERMAN else -> Locale.Builder().setLanguage(this).build() diff --git a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TestPreview.kt b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TestPreview.kt index 6892665e1a..005002f3e6 100644 --- a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TestPreview.kt +++ b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TestPreview.kt @@ -21,4 +21,6 @@ import androidx.compose.runtime.Composable interface TestPreview { @Composable fun Content() + + val name: String } diff --git a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TypographyTestPreview.kt b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TypographyTestPreview.kt index 80f27c25a0..c723f990b1 100644 --- a/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TypographyTestPreview.kt +++ b/tests/uitests/src/test/kotlin/io/element/android/tests/uitests/TypographyTestPreview.kt @@ -44,5 +44,7 @@ class TypographyTestPreview( ) } + override val name: String = showkaseBrowserTypography.typographyName + override fun toString(): String = "Typo_${showkaseBrowserTypography.typographyGroup}_${showkaseBrowserTypography.typographyName}" } diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewDarkPreview_0_null,NEXUS_5,1.0,en].png index 56501f54c9..fe31f40122 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74409b405f143793ac5641bb16f66731d7fa96d513a85e5e38c368b295b297c7 -size 4965 +oid sha256:dcd8ccab99efd822f085614edb7296e5d73f3c1ae8d84ec0ccf930ead56bfa13 +size 7803 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewLightPreview_0_null,NEXUS_5,1.0,en].png index 665c8811ac..7200bc82d1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.map_null_DefaultGroup_MapViewLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb0d3bfcfd75cbd75fd9270ff1dc27090e5dbac79ca8db8a46d91a4c12bc966b -size 4457 +oid sha256:e6040743cf442f6e3069eb77f562d6803eb9243edc959c0db7b5631ad00c583b +size 7103 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d639c16ee7 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1f40593f1075d245f98547fea35ab6861858cb02568efd1e9c6d1f1e07d85a9 +size 10193 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0b0fe1c24d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f571102bed06886257392347b27a935d3d34187de1004311395ee6f849e44a2 +size 13504 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e3e6a8641e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e13f80abff301cf0d345434a2010c7aa9e84f65514842712b1c8be64fde81160 +size 23003 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..76abe2b489 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:779f80e6cc4cb9ca9c3dd113cfaaeb528244b48d3b915f262ae5c797ad0b6524 +size 10101 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..893997718f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57852301ef65b259a7b3a28943918fc37261c3d183d5b543e246ef9fd07b542f +size 13937 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d713d3675a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f1d57e7cbabd106d236a9afb49cfbaf5546c1f2d6b490d11a5d313d101632b4 +size 24994 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 3f52a57bc3..19a09eb962 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46073600d49a438279e3fb891573a88eb17dd7c74f853078ce7d33dadc81c298 -size 14260 +oid sha256:fd4a7d61010660b1c9081c48e562b219fd454e6ef23088bf0a6a1529d5fa67f6 +size 17379 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 24dd806db1..fbffd41d56 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl_null_DefaultGroup_SendLocationViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5bd98280b4cfbd97bfebe731a772874cef0a50ceea363d0d365318d38d164774 -size 16249 +oid sha256:7204bf50f6f7352160f9f45dbbecebbb2186b0d2e59d51e4d5237b536f345867 +size 18062 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 84be6236ba..486bee7bee 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5a518949ddf27ba4a4f4139a3a227486a342a5627c08e52150e0e76fde68e7f -size 30001 +oid sha256:7628df19a3da31d3f75219c74fad7f2c813c35cec60902d760bd6895939f8e59 +size 37389 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index c9a1d8cbaa..44230acdfc 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dabfe0f8aac581d004be23d46f736d99cfe662fd4d523cb058e61605fb3e619 -size 30336 +oid sha256:4fa945bba719a8e04508f49042ee71064605c8b5315a822435f4fa15ccd8c551 +size 38570 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 84be6236ba..486bee7bee 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5a518949ddf27ba4a4f4139a3a227486a342a5627c08e52150e0e76fde68e7f -size 30001 +oid sha256:7628df19a3da31d3f75219c74fad7f2c813c35cec60902d760bd6895939f8e59 +size 37389 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index de9f59840b..256c8959b6 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f77f1cb022aba56240a6df7c040675be0bd8374e708962214e9aac9c66a9f61 -size 31771 +oid sha256:4c3eafbca803a46cbac24db0f61d51f3e14bced8fff98c0eee171f522fbd66d1 +size 39258 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 6259c15b22..e9f8677292 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b61c5af512bfd1087ba62ae24b9677225fb29201795e38354bef00e82ccd95e -size 32132 +oid sha256:40ef3fb32c107aa751304de44faf5c78f0faaf6b330c61e9d7618858498aab75 +size 40430 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index de9f59840b..256c8959b6 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.loginpassword_null_DefaultGroup_LoginPasswordViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f77f1cb022aba56240a6df7c040675be0bd8374e708962214e9aac9c66a9f61 -size 31771 +oid sha256:4c3eafbca803a46cbac24db0f61d51f3e14bced8fff98c0eee171f522fbd66d1 +size 39258 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6b5e4c405f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e97d88bef72c332cd145dc1080a989d163478c0252a9993f2f474bd51f2e4da8 +size 148762 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..151e09cfc0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8edd6d72db9efaaed76ac64f9882a5b66ac30747355815955157a3f3fc98c2c +size 149344 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d1642cd741 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e099a30e3090ffd9c0f30e095131cb60d53b615623acd50565bde8fa28e7fe74 +size 62810 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6b5e4c405f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e97d88bef72c332cd145dc1080a989d163478c0252a9993f2f474bd51f2e4da8 +size 148762 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..822450c8af --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85222447b700315e6eea458fc72d61fb741018cb80c3a4530d4efc08ac9335ac +size 129373 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6b5e4c405f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e97d88bef72c332cd145dc1080a989d163478c0252a9993f2f474bd51f2e4da8 +size 148762 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..151e09cfc0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8edd6d72db9efaaed76ac64f9882a5b66ac30747355815955157a3f3fc98c2c +size 149344 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..dfa30f1f08 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71289cbba79500c063ec80430fb46bc544ccbe6fa7035ac81c81322c9f1a300f +size 63631 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6b5e4c405f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e97d88bef72c332cd145dc1080a989d163478c0252a9993f2f474bd51f2e4da8 +size 148762 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..822450c8af --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.screens.waitlistscreen_null_DefaultGroup_WaitListViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85222447b700315e6eea458fc72d61fb741018cb80c3a4530d4efc08ac9335ac +size 129373 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 032e91c88d..d278a647ee 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ce2edea622636c3709d7da3d01c931a2ced65d31738d9cfc9e5f318a374ca30 -size 37850 +oid sha256:5ccab2f54c80d52438b71a8afee8eb2fbd755b44cf3912c7b8a6b4cce77ee2a1 +size 39327 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 90b577b08e..3f79e73675 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa58a3f5fa6ee6e9a7f89f87a64b7b43577fca75cd44193b521875c8d6a184e3 -size 43823 +oid sha256:a03f5e42a98c9664175d7630923b89ed9f657745e457a1dd3061693e523992dd +size 45340 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_4,NEXUS_5,1.0,en].png index e0cc85ab36..801b84e574 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50cbc5955836725e0f58f2058e2c43cce0639c122036470fac58b89f3208c7f2 -size 44139 +oid sha256:de5fc56ef0c5a093895cfff39972f4f472207fe39c36458c29789f34ef875f60 +size 45865 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_5,NEXUS_5,1.0,en].png index 1e81eb19d3..ddd6b1206b 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6787391e332dcf4dd0998c18447962a1ee03314e276f6b7d0a6bd6717f30f43 -size 38214 +oid sha256:2c6a00bfbe9b860736900fad85d5727e06ef2589c0ac15b8f5dc47a68685fef8 +size 39719 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_6,NEXUS_5,1.0,en].png index 17dc639971..e88a7fb8f2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32c0ace0df1b31d9e47aaa4fcfc4fa7eb0d7e7d9cde321bc3e13ffd85e991f22 -size 39371 +oid sha256:8918fa3ef7b1e73d7f9c85ae279016693c36e83271ca46f808ae6beee862d30e +size 41053 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..48fc6f0d42 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentDarkPreview_0_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c385584daf063f769882c424507a526e33c876bddd6c76126c4c7eeb55ecd370 +size 25797 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_2,NEXUS_5,1.0,en].png index ad55384d9c..f873e7c869 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acaa3c96e40cd2d2d0cace906e87a6e36801a0836b1eff059a2f0a031e41ddd8 -size 38770 +oid sha256:b683ddb79ad09ef856af89d616499790af0283baa50e93150ff96ace0eac5ab3 +size 40309 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_3,NEXUS_5,1.0,en].png index 7ae0ff866f..1177c247ab 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0ac4827780ad236721b793b1d07a3f7ee514811ca3d8f29c8d447cdb99e739e -size 45293 +oid sha256:9ddaa5bdfaddc2cb86d40782660d5617192b1826ff886e24c1c5aee92a3b443b +size 46995 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_4,NEXUS_5,1.0,en].png index 41ec9d6cba..67cb550138 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57f7684c300496d7c8ca31e500b2191b85e3bc2ecbce489e77f1c71edec3d877 -size 45790 +oid sha256:4ac90b67d42bd6bedf9c4b08ae86545b0908c5663503ac8d8b8ee71d6d373a12 +size 47432 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_5,NEXUS_5,1.0,en].png index 42bc9a2a59..fbfdf6fc25 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb973d123139c6da77e5b764cbca51866310ea81caf7057c584064677d2deff8 -size 39233 +oid sha256:ee3c88ad82154b1e8b85dd9bed76944dd5fdddec9df0f6f2d077c64360933560 +size 40865 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_6,NEXUS_5,1.0,en].png index 096e9df6c6..077b84a006 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c3a0506c870eb5f940821e5f05357f098f18fa177b143828e3e2afad09732dc -size 40704 +oid sha256:fef9feae283ba58d9819428d8e980dc56d40eb34ecd2b49070db18b60f5bd72c +size 42138 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..36b1449bc0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.actionlist_null_DefaultGroup_SheetContentLightPreview_0_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23901280b9a5f60481cf368f5fb6b122cf21da2ffa4a73367e27cdc3cf52e377 +size 27334 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.messagecomposer_null_DefaultGroup_AttachmentSourcePickerMenuPreview-D-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.messagecomposer_null_DefaultGroup_AttachmentSourcePickerMenuPreview-D-0_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6061c8021e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.messagecomposer_null_DefaultGroup_AttachmentSourcePickerMenuPreview-D-0_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52acd0adfd7998d3b31569cabde3f4ffdc962317df9d28bfdce9011002f9c55a +size 21502 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.messagecomposer_null_DefaultGroup_AttachmentSourcePickerMenuPreview-N-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.messagecomposer_null_DefaultGroup_AttachmentSourcePickerMenuPreview-N-0_2_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8df9286d82 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.messagecomposer_null_DefaultGroup_AttachmentSourcePickerMenuPreview-N-0_2_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ec0d842969c58a089fe9224946bac7d89ccb1041d3bdfaa1b2f5f8cf9a25a90 +size 19806 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3830329d1d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b14570268c5885105bb68d55e54afe83d1916e32a5a9cd8fa4084cba020b272 +size 80504 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..fbfd3b1b30 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ae5e86ad1728027f7189041745592d4ed24df07e008b5798b29eadba5449569 +size 159493 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesMoreReactionsButtonDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesMoreReactionsButtonDarkPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d5d7aa4c40 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesMoreReactionsButtonDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c8527bc63aceac59916ca5dfe9c73e9fd1309b957b26945e261849cb1b658253 +size 6143 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesMoreReactionsButtonLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesMoreReactionsButtonLightPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..860956b181 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesMoreReactionsButtonLightPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:836fc6c1689c9be78cac4bcd0cfa4cb8fb5f8ed36ace9159889b2404feb8f52e +size 6095 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 7151a85c50..59d830d27f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35ded8660ee6bf1628271ab2089e7024960ffc3bded961a5ec031960e2cd9b02 -size 6684 +oid sha256:048cafa3f3d2aa0ec9122c84a6a6fe39d16549021bbbe286c436900c81fb8873 +size 6519 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 8083b39877..db05ee276e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efbf53aa8eaec37c770281ce5c539810c0e7d081229989164d50a4420d532dce -size 7576 +oid sha256:5648d6a428f4b7fa20c9981c6e564555bc04ec6b0911028507e01f90a56a44cf +size 7414 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png index c440067555..d11f1557be 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7bff9985b4231c67489bbbd01aefde6a66e26532f595aa9033b71888c4aa123 -size 7504 +oid sha256:d76cd06cf3b36aeb3de567605f6ebc33508cb074049ad854ccec5cf5b382ff36 +size 7263 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 398fdcc391..c625fcbb59 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:913306f961ff082715c2707d467a13d7ec1b2edcbc321f5535abf6ced9e64ca8 -size 8421 +oid sha256:ab3023d14ccd579dc350bc019f94ea2ddc59ac55238d04f4a96c34a1036361df +size 8185 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png index 7fd6397cae..b8db218eba 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1eab05db3cad5c2d0d05ec01a41631b92adea5b625dd0a30c8ca70ff7edff178 -size 6648 +oid sha256:a0e5cb3791012323f9ad6352537b4e579608ebb33f7f46742241b3cac737e617 +size 6349 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png index f966a1e666..02edd283e7 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da5e0e74e13ecee477b33c63fedf5c87953facc68b674da765c65658b91ad85b -size 7527 +oid sha256:dfdd3edecb3cb2adb7686dec5043407e3a23e15c5a7911a28f5feba279532e9e +size 7261 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png index f11a913e5c..23ed8a0354 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95d693f3ebcd18df2b474175959b5dfe856b2204d2d5a9e0b95637d52e95c73a -size 7425 +oid sha256:995f7f218c005e18acd4b500a81d28784b3b3d81a12c2aadcdd9672ef18ebf28 +size 7191 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png index 6d835f3d04..d6134420b0 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionButtonLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa3529f9658c15a7de5a3b5cd6fcad1a67a302cd767177d5cfd912493df4aaaf -size 8342 +oid sha256:e08bd85cee39e0827fa2cbbee3fd60dc83d860af553f52a609a0f4d691174d2e +size 8140 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_ReplySwipeIndicatorDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_ReplySwipeIndicatorDarkPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f15ddfd706 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_ReplySwipeIndicatorDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6418097195ef3c6a166d216913469aa3adf30a4fb42fbda21bc27e321d431410 +size 9792 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_ReplySwipeIndicatorLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_ReplySwipeIndicatorLightPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8c614712a5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_ReplySwipeIndicatorLightPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f64649306919ac8dfeb6a7729f73a0617e8fc809c2d62f3aeabac6566bced1e +size 9151 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png index bec1e63b2f..94c70663d5 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f516fec77af8ffaecaa6e4eb6d23cdda607edf31321a14136893e29907758aa -size 136058 +oid sha256:6ac13d38a9aaf4341ff8845141dcbc90033f7105750e0ea212bb7b4387dd72ad +size 141063 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png index d7e6b8f42f..723764d53f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1629f7b12f7846460cbf3bfb9b0e52c2e00f97666bafdc7aff18be9b24d3761a -size 140954 +oid sha256:651535d566f1bf250b0856d3fd6d3726aa2f6714d44217701e08c805d3771347 +size 145406 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsDarkPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7581499a8f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a1c0e5249236abdde5de337f2fe8c8f58bb2ad8bf31fe2f4214dbe35f327915 +size 129834 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsLightPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f765c4672f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsLightPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1276ef584823b28330ee3facc4cbebdaf9bc6cb02f59af2c023a740677940f02 +size 133065 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png index 996beb9968..bb75c29281 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d27d08d756b084e349c5c8066ed570ea1dec1daf93aa11b85671bb631acbccce -size 116677 +oid sha256:47760aad8040e34898f8d80d207a6fdb6255255f72bcc2f8cb99801bedebabd4 +size 119160 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png index f7142fe2d2..2843666d40 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e52e57c7f09aaa43f9cf5e3b85b1e9cccacb4db2749eec4409f8653846c47cf -size 121351 +oid sha256:5896cb2b9847d2f67bc6d7c39a2499286c59945b9e2c4961e191e547739f41e7 +size 123936 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png index 93f71fa424..7e616e911f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:468a23d2d1f7a440659e2c1a8a57420b1aad2bbebd50af65bf3c98b2d4975806 -size 13906 +oid sha256:61eda889828d68c309da1ca9879e6b6436fdedb5abea86abfb227caea866efdb +size 15396 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png index 16ffe953b4..fc6fbd4e7c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2079ae564091c0f1f55f6aea107f8d65d90cc31aff6f4d5a42540e4a8905598d -size 13952 +oid sha256:e99b245b027b2610947863591f7593bf273cf328ff64344b575423635e03ac24 +size 15428 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewDark_0_null,NEXUS_5,1.0,en].png index a903cb9526..ec77ebc1ac 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15c77e539cc793ab0875813ac98eab14412a0619ca16afc90513e11f41462e07 -size 32575 +oid sha256:b28bdd7f227340b1af4456f0e24a78b8b279819b7e41edca11c0f2bc9a14a15b +size 32894 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewLight_0_null,NEXUS_5,1.0,en].png index 91e74ddeb4..b86be1ed83 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.debug_null_DefaultGroup_EventDebugInfoViewPreviewLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:047b5e935803b2c7b12ebb1d86feabccac9d6291de3c14fea1185c2279db8d57 -size 35318 +oid sha256:f7d39ebcf85878e3869d369a10f95efe6c786db44c098864473b3e5bda51bd83 +size 35141 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 1a40fbf879..afa420d3be 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76a516d4610c44cff470c8ed3dd4216eb4183a1eee37ddc2d1d50ac8388a6bf1 -size 44694 +oid sha256:0b5e8b131784aa74261ebeef065f027c8ed4ab6188b390bc9849d3d4bac747e2 +size 51648 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 17faca69c7..1ef0bab331 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c68ad8932cac922061ae8579f7f5cca844370fba127de7580804b86aa365714 -size 55592 +oid sha256:8d26f0735eda30013ad31475347978d778e231e6b2ece7a3528a0b9a2f3743c5 +size 63185 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png index 8958efde7b..63c56994c0 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0dc90137be2f7f1aa7b2ced587e0a539e33abe795260582ed418393aae6ec08c -size 58578 +oid sha256:9eba210c27876f999977e75874b4ed2d89f591e16734133ae140f5e82e7fe879 +size 49724 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png index 692731b5b7..10c9076b02 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b933ae95db4865a86cac9196e91b99bb7768271a41d8bf37e69637434bb9bb4b -size 49255 +oid sha256:e88421b82ca846b94a0eba7a00d424dd077586ebdc28c5364cdb07e4f2d6e2ee +size 66031 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f8b300ed6d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_12,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e86e8491e170371cb229f2e58a1fda5efe5ac3dc3b87b4f928bfb74d9fd3ded +size 56368 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 80b4c84a56..a9ce0876da 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c8b1af339d9f8c4a9e114523956688a48d3817f756f1e50cc63d4692de0336f -size 228299 +oid sha256:33e25642dd17c10bb39f576f33ebd673619b76c111fd4b5834cd51644cb57b8b +size 229779 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 3987234862..7c688cc77e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c7e3c7431a8bb6c75892c0db3d5c1b61fbd233c9bb98f741a2a89c486d1f53f -size 229208 +oid sha256:7ae3b529f45c9a5c739193eee0dfd9c4ad73310deae8eb50e1b0f57205de84a6 +size 230761 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index cc3178c2a4..d7b89e1cf1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5233170b8312a919fbaefb43fdb1595a59d6fb044f2cbf2401ba42226bb84e7c -size 63886 +oid sha256:eece03c2cf471a428d6f383ab3ba2e763a333f10bedf6b4d7a86efd35ffb960d +size 70996 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png index aaf9efc2e8..c171129b53 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd89b421b4bd74782f0d4c5a04df291077d5bea46b3bbf0b5962bc5c10bd0e0c -size 78874 +oid sha256:df108cf01c6ef1a061d7f84bdb283b9a7cea45ed3a6eea703c110a6981435733 +size 85260 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png index afc7d1bf63..e32ca981bb 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2812002d2aa0862b7bb71db095bde4c3a8ab5b70946609c906e253f0476489d2 -size 173384 +oid sha256:b0efa0cbf46ffc038bd7ff522b0f81587438f564dd9a7ed7b5b995ea9b3fda31 +size 174560 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png index 61513681c0..5b2f94f763 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7a1c89e89950526e65afa84a3ab60ff563cfee5a56977aacd2be6931bf36b40 -size 46101 +oid sha256:5ee5b3a256c3ebac9accb7a4361fd1e29c88e21d4c8b10be293bff368d3dd966 +size 165233 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png index f21fa9b9fd..5e9a7d353f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e2b4f39df6359c7bb1d3ce8f146a13e36197f7e2303421a809c9aedf267cac7 -size 57650 +oid sha256:538df93c96ec8d52f370607cb460c0c507cbf1d716cd8150979bc555e7c4dd92 +size 53061 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png index e019980b70..07686a9448 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewDarkPreview_0_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:374e99f565fa90cbd89c36081f372cf64af59341208a3ff32ca623727fdee9f2 -size 42850 +oid sha256:09f899772bfd8fb4fcff729ba2b4236a842b6aba27a7c7efcbe15f60379ac067 +size 65090 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 1ad326f7b1..4233397553 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49c2ab83ae91850a5047e5703e9f4f3675c551b215f2aca9bf02c3a0bde4b14a -size 46151 +oid sha256:a9cf9fd968fe92c8e5f4402182689d400a554e423905a8d69a065e8ecf7461ba +size 53294 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 8c803e123f..64fd62fe84 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1054f6cce57f6a5b73a9eddd819f374735b53864209f48a35fa3cb1010d5b3da -size 58189 +oid sha256:3565ffe08fd9472a2a3298e0d1c65b2066bbf318f7c3564d3ca8022a22815750 +size 65710 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png index 5218297559..15cae0f58c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_10,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1e2e322dbb32e5f52fed4f00e50e6d97b6149f8d2b46344e07f6c1b5816dcde -size 61344 +oid sha256:85c2a8b9910c6f751902a40702fe605b5b50ee22b9d61d258500c0c9dfed07dd +size 51179 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png index 056115ac4c..987414d5e5 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_11,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c5018384f7783297a995d72c088903606b7d092407b7dbb4934b2a678271853 -size 51363 +oid sha256:e07e00d3940bbc0f0cee49c76f82a1ad638616ecbab98599fb94c871628a8557 +size 68782 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..5c32716cb4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_12,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2858fb6aff4161239713634efe4040878a3e836173386c80675b962c9f87131f +size 58413 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 5672cd8a92..91d417a8bd 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc0e157d9838459d7859dc8ce8e4cf85a0f83c056fd9d151ea5052145c591378 -size 228765 +oid sha256:9dff8dd2a36d8cb5521e7b7f4e395f87038dbd1c4886e92c94be122d18248f17 +size 230285 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index b2550e1500..8f2f460962 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e116bbf8dd5de0ab472368088bf3823010f34fe3fd5f168256365ccb983e8080 -size 229754 +oid sha256:873e746f40b06d4cfe2af496dd4bf2d6ad761412ae3c3e72b6184282b2225346 +size 231253 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index c41de52743..c8467349a1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5844f1ef60404ba714b32294c99dd06cbdd35800ddc2758e35da654b2a2921ce -size 66692 +oid sha256:4d49e59b6ed1ccd2fbd22ed25ffd31619fda33b709a1af8546ceed2328fad6f0 +size 73767 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png index 0d00fea636..52cc82273e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:beae1875ccdce8390d3d237f5754f0aaa9fc8a0ee2e9bcc87ed9deff7602bd1e -size 83688 +oid sha256:db4b63cae9b106c125e53296875d3032ea1cd2a455c39c39bb8bd3bd514ab2ac +size 89827 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png index 54a6b55549..75e36de764 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b46199eec3d05a3ee13813c37dbb10bb8bf9d285a0bd349cda9f6956385fa5f5 -size 361988 +oid sha256:a1800f8b515a557d93f25ef1c2dae4f11b29de95f4ee9af649bb40bad1014966 +size 364477 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png index 668d744b5f..faa4e1d2ba 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_7,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebee6b6cf3756c5a4b861dd4fb4a142dd7a443faa8495a4b9c7dfa8cea3e3931 -size 47791 +oid sha256:dc59dde6b5b1f0b894de2dc91516e2e9ae35d9b1d0d523b6a488b9366c57289d +size 323483 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png index 3c46998325..4643f22f60 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_8,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2403644534084159a0f4b49f3bac626927a2bc228ef8de0f94ade921202ec40 -size 60183 +oid sha256:eaa7b8ef747034fd29d383902436d491c4544e5a14f455fa29697120bbbcb204 +size 54917 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png index fa61ea23d5..81130e6eab 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewLightPreview_0_null_9,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d0a783c549f51974beebfdac4d3bf48d98de67c57e040a9120e6cb73ea4b1707 -size 44180 +oid sha256:88788496a3be3ef0a464377869ef1ca2e7abe4eb8b96c5d69b819afed0470683 +size 67671 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 08c7c1b666..caaf8b4b45 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28894ec89a8f0d8b4ba2d4032e3f451d699c165d80cd0a6eea0367b4cde67d24 -size 45644 +oid sha256:341ead0eb1a09351740738bd540bcf8c6c96862c3370b95b764617335e5b4f22 +size 52510 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index ca3ff9976a..ea181abd06 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9f451aeb5991c6c89f50ac5579a5a67b731d2d0fe1378953c9f74c6ea94f186 -size 47234 +oid sha256:96b7a3cd0f79994d4195e4abe1ee07381f98c6c5c51760226a995efc09617a03 +size 54039 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index c6f4e7f13c..9cc792307e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:194f742f1ea4e15343656fad3acf25147e6fec367f14e2a0a26470df1f0b2443 -size 45838 +oid sha256:e8302d3b48c733500927ba7e5878f001bd7dced72ebd8386142931d557757028 +size 52706 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 7609bba02f..8343417a66 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e4af00485fddc3d8ecafbbdc2e08e8423816f7d5dbdfbb4fc0eba942a866a8e -size 48659 +oid sha256:2c6dbd33edb880fea91d290a24260c17573d4d36140f12a472c28c77441c8684 +size 55697 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index af8272f218..3c0d2139fb 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:977eb261b9658466904813bd6f238efb16b4a10d48a49780cc62bf15938e47d7 -size 45931 +oid sha256:159b54d8c45229e37a3ca9748ab52a6ef448ad93aed209b4e770e94ff5196373 +size 50706 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 3369e98a18..9a7a197623 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c1067095cd7f3990baf1a7018de2b5dc236bc2321c40a097a595492a501f806 -size 47221 +oid sha256:f1ffe365eedfe6b054a268fb3a6a10599df9152d5008359ea95ab7b90b13a33e +size 54389 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index fd1bca3bed..5793b8b379 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52d0cc874c1ecfd6125c3666dc6f7dbf8a532a337e8a1d532e1c68a3febc3b75 -size 48870 +oid sha256:2ddcc0519e33b2f6a93d873d2e6aee000ca5a38d2dbd2122869e0befa0c78c7d +size 55949 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 80b46b8968..31ba4ec4e4 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac63233b5b7e799cf1b393eb1225c680e07f44ad543ed545017649dfa56976a8 -size 47487 +oid sha256:96fdf93ee3e67f4f575928a842ce309c4675d361526b9b2714ef2c3fe7167ff4 +size 54675 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index aec32cf0e5..bc41091dd6 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c832763a985e5df02c7a1071e910456639387a1edaf2074a5915310e98958ef0 -size 50294 +oid sha256:5694004acfac3f1c573dd7929897e01ea9b7e7927cc209e6f4b9bd5568c5ed96 +size 57714 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 644fa31605..ca175169e2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37597fd224de15ded08333a593a19e9b3ce20cde8a155907f9d9bfb9ecad2d07 -size 47091 +oid sha256:d2ff9337e57cecce1404c44b7b9eb7d6fce65a22fb94326429bd25041e59a588 +size 52250 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderDark_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderDark_0_null,NEXUS_5,1.0,en].png index bcff7a70cf..26994438d1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderDark_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderDark_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:871933869e264855de16d6dc9c361f3d600e66de18f326ddcaecfc01e311c819 -size 27710 +oid sha256:fb5440a19523667659d0cfb45841ba3acfe820e0e22a47ef4bbeb2506d5ce039 +size 28898 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderLight_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderLight_0_null,NEXUS_5,1.0,en].png index 4d77fdd766..fbae3ba0fe 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderLight_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_PreviewRequestVerificationHeaderLight_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4796e01c900c7695df98f88ee79b0a6b90c43a26a18d2808b02c41982d519f2e -size 27936 +oid sha256:94c4f3ec17d3521d546b49a732c263c31ecf17b6c7a19d6820c41466dd1562bb +size 29047 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index a71910a25e..ec986c5a01 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ae5fe88c5cef58fa8714b5cdbfe03f17b105f7dab98b2efd34c41fda5a6125d -size 56790 +oid sha256:3370bdbb9e67c7223a1fa4ccde20e249f2fecac9fdb350c11c00cc0171156db7 +size 58503 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 77247628e3..2ba4427e32 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41245ea4be3afa1241d3593b9c5b7861d6a2bc547b69be82795105ff801791ba -size 59818 +oid sha256:2eb712a4523956af6c4a32020caffe3952c5395dd39effcfef3b4a3ac16f1374 +size 61866 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconDarkPreview_0_null,NEXUS_5,1.0,en].png index c1c554dd7c..830a75b9e8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bdca76cdbf676d1b5dfd4bdf3871913a3fca0a9efafbf962e461a45278c9df13 -size 5426 +oid sha256:c81885a3cffdb92e8282bfd511ac4efff4b02b1de2d6a37c01f9e41b94841e25 +size 5429 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconLightPreview_0_null,NEXUS_5,1.0,en].png index a522d00657..cf640a5e80 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components_null_DefaultGroup_PinIconLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73750e1428e85548a8a8add1193d3c32709d79ffdc6c18125e3fbbc13d078c73 -size 5683 +oid sha256:d08207b517c75145a791e0d1872bb65eb24cfd8c5c9364fb620e392ed9025c51 +size 5678 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyDarkPreview_0_null,NEXUS_5,1.0,en].png index 7c695ff8d9..5bf2f75d49 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:870b7a0002ec6fd77bfe071a64b7c5faf98062227800ca55dd1c21f517025a48 -size 77684 +oid sha256:7d83815b1834a70e52fd55ab3558cb3f705abeba74474f4b23f6aafbb028a5ff +size 77687 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyLightPreview_0_null,NEXUS_5,1.0,en].png index 63b43390cd..0c23b6b043 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.textcomposer_null_DefaultGroup_TextComposerReplyLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:582d2f55a8f6707f1e4a55b4eadb7b19f18f3f78a097300151753c66e737e8e2 -size 80326 +oid sha256:116fb7199e2577efa0d1b3de60f70054697671d554bfb2f695fb229511c03c2f +size 80331 diff --git a/tools/danger/dangerfile.js b/tools/danger/dangerfile.js index 16fbf4d31b..7270f334ae 100644 --- a/tools/danger/dangerfile.js +++ b/tools/danger/dangerfile.js @@ -132,7 +132,8 @@ if (allowList.includes(user)) { const previewAnnotations = [ 'androidx.compose.ui.tooling.preview.Preview', - 'io.element.android.libraries.designsystem.preview.LargeHeightPreview' + 'io.element.android.libraries.designsystem.preview.LargeHeightPreview', + 'io.element.android.libraries.designsystem.preview.DayNightPreviews' ] const filesWithPreviews = editedFiles.filter(file => file.endsWith(".kt")).filter(file => { diff --git a/tools/localazy/config.json b/tools/localazy/config.json index a7317af0db..3feadf7a7a 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -76,7 +76,8 @@ "screen_server_confirmation_.*", "screen_change_server_.*", "screen_change_account_provider_.*", - "screen_account_provider_.*" + "screen_account_provider_.*", + "screen_waitlist_.*" ] }, {