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/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..0eac47bf6c 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,9 @@ 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.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.tooling.preview.Preview @@ -44,10 +47,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 +74,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 } @@ -155,7 +164,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..42d57ecf7d --- /dev/null +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt @@ -0,0 +1,49 @@ +/* + * 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.runtime.rememberCoroutineScope +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/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..ef9db5fcec 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, @@ -82,6 +86,9 @@ class MessagesFlowNode @AssistedInject constructor( @Parcelize data class AttachmentPreview(val attachment: Attachment) : NavTarget + @Parcelize + data class LocationViewer(val location: Location, val description: String?) : NavTarget + @Parcelize data class EventDebugInfo(val eventId: EventId, val debugInfo: TimelineItemDebugInfo) : NavTarget @@ -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/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..64b5cd697e 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, 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.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..a237f65c56 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 +oid sha256:a90e03f54c92c91388b3218942e633ced908fd3b77defbeefc492d5c97de97f5 size 39371 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..07cd2cc9d3 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:884654e20bd77864e1f7b7f137a539f8111ea50b8dd9c17ab689e246c70b2ead +size 40705 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