Rename send location to share location

This commit is contained in:
ganfra
2026-02-18 15:20:21 +01:00
parent 1f5a628b13
commit 8c64f704cb
14 changed files with 223 additions and 223 deletions

View File

@@ -14,11 +14,11 @@ import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.matrix.api.timeline.Timeline
/**
* The "Send location" screen.
* The "Share location" screen.
*
* Allows a user to share a location message within a room.
*/
interface SendLocationEntryPoint : FeatureEntryPoint {
interface ShareLocationEntryPoint : FeatureEntryPoint {
fun createNode(
parentNode: Node,
buildContext: BuildContext,

View File

@@ -1,58 +0,0 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
private const val APP_NAME = "ApplicationName"
class SendLocationStateProvider : PreviewParameterProvider<SendLocationState> {
override val values: Sequence<SendLocationState>
get() = sequenceOf(
aSendLocationState(
permissionDialog = SendLocationState.Dialog.None,
mode = SendLocationState.Mode.PinLocation,
hasLocationPermission = false,
),
aSendLocationState(
permissionDialog = SendLocationState.Dialog.PermissionDenied,
mode = SendLocationState.Mode.PinLocation,
hasLocationPermission = false,
),
aSendLocationState(
permissionDialog = SendLocationState.Dialog.PermissionRationale,
mode = SendLocationState.Mode.PinLocation,
hasLocationPermission = false,
),
aSendLocationState(
permissionDialog = SendLocationState.Dialog.None,
mode = SendLocationState.Mode.PinLocation,
hasLocationPermission = true,
),
aSendLocationState(
permissionDialog = SendLocationState.Dialog.None,
mode = SendLocationState.Mode.SenderLocation,
hasLocationPermission = true,
),
)
}
private fun aSendLocationState(
permissionDialog: SendLocationState.Dialog,
mode: SendLocationState.Mode,
hasLocationPermission: Boolean,
): SendLocationState {
return SendLocationState(
permissionDialog = permissionDialog,
mode = mode,
hasLocationPermission = hasLocationPermission,
appName = APP_NAME,
eventSink = {}
)
}

View File

@@ -6,26 +6,26 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.features.location.api.SendLocationEntryPoint
import io.element.android.features.location.api.ShareLocationEntryPoint
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.matrix.api.timeline.Timeline
@ContributesBinding(AppScope::class)
class DefaultSendLocationEntryPoint : SendLocationEntryPoint {
class DefaultShareLocationEntryPoint : ShareLocationEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
timelineMode: Timeline.Mode,
): Node {
return parentNode.createNode<SendLocationNode>(
return parentNode.createNode<ShareLocationNode>(
buildContext = buildContext,
plugins = listOf(SendLocationNode.Inputs(timelineMode))
plugins = listOf(ShareLocationNode.Inputs(timelineMode))
)
}
}

View File

@@ -6,15 +6,15 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
import io.element.android.features.location.api.Location
sealed interface SendLocationEvents {
data class SendLocation(
sealed interface ShareLocationEvents {
data class ShareLocation(
val cameraPosition: CameraPosition,
val location: Location?,
) : SendLocationEvents {
) : ShareLocationEvents {
data class CameraPosition(
val lat: Double,
val lon: Double,
@@ -22,9 +22,9 @@ sealed interface SendLocationEvents {
)
}
data object SwitchToMyLocationMode : SendLocationEvents
data object SwitchToPinLocationMode : SendLocationEvents
data object DismissDialog : SendLocationEvents
data object RequestPermissions : SendLocationEvents
data object OpenAppSettings : SendLocationEvents
data object SwitchToMyLocationMode : ShareLocationEvents
data object SwitchToPinLocationMode : ShareLocationEvents
data object DismissDialog : ShareLocationEvents
data object RequestPermissions : ShareLocationEvents
data object OpenAppSettings : ShareLocationEvents
}

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -26,10 +26,10 @@ import io.element.android.services.analytics.api.AnalyticsService
@ContributesNode(RoomScope::class)
@AssistedInject
class SendLocationNode(
class ShareLocationNode(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
presenterFactory: SendLocationPresenter.Factory,
presenterFactory: ShareLocationPresenter.Factory,
analyticsService: AnalyticsService,
) : Node(buildContext, plugins = plugins) {
data class Inputs(
@@ -48,7 +48,7 @@ class SendLocationNode(
@Composable
override fun View(modifier: Modifier) {
SendLocationView(
ShareLocationView(
state = presenter.present(),
modifier = modifier,
navigateUp = ::navigateUp,

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -38,7 +38,7 @@ import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.coroutines.launch
@AssistedInject
class SendLocationPresenter(
class ShareLocationPresenter(
permissionsPresenterFactory: PermissionsPresenter.Factory,
private val room: JoinedRoom,
@Assisted private val timelineMode: Timeline.Mode,
@@ -46,60 +46,60 @@ class SendLocationPresenter(
private val messageComposerContext: MessageComposerContext,
private val locationActions: LocationActions,
private val buildMeta: BuildMeta,
) : Presenter<SendLocationState> {
) : Presenter<ShareLocationState> {
@AssistedFactory
fun interface Factory {
fun create(timelineMode: Timeline.Mode): SendLocationPresenter
fun create(timelineMode: Timeline.Mode): ShareLocationPresenter
}
private val permissionsPresenter = permissionsPresenterFactory.create(MapDefaults.permissions)
@Composable
override fun present(): SendLocationState {
override fun present(): ShareLocationState {
val permissionsState: PermissionsState = permissionsPresenter.present()
var mode: SendLocationState.Mode by remember {
var mode: ShareLocationState.Mode by remember {
mutableStateOf(
if (permissionsState.isAnyGranted) {
SendLocationState.Mode.SenderLocation
ShareLocationState.Mode.SenderLocation
} else {
SendLocationState.Mode.PinLocation
ShareLocationState.Mode.PinLocation
}
)
}
val appName by remember { derivedStateOf { buildMeta.applicationName } }
var permissionDialog: SendLocationState.Dialog by remember {
mutableStateOf(SendLocationState.Dialog.None)
var permissionDialog: ShareLocationState.Dialog by remember {
mutableStateOf(ShareLocationState.Dialog.None)
}
val scope = rememberCoroutineScope()
LaunchedEffect(permissionsState.permissions) {
if (permissionsState.isAnyGranted) {
mode = SendLocationState.Mode.SenderLocation
permissionDialog = SendLocationState.Dialog.None
mode = ShareLocationState.Mode.SenderLocation
permissionDialog = ShareLocationState.Dialog.None
}
}
fun handleEvent(event: SendLocationEvents) {
fun handleEvent(event: ShareLocationEvents) {
when (event) {
is SendLocationEvents.SendLocation -> scope.launch {
sendLocation(event, mode)
is ShareLocationEvents.ShareLocation -> scope.launch {
shareLocation(event, mode)
}
SendLocationEvents.SwitchToMyLocationMode -> when {
permissionsState.isAnyGranted -> mode = SendLocationState.Mode.SenderLocation
permissionsState.shouldShowRationale -> permissionDialog = SendLocationState.Dialog.PermissionRationale
else -> permissionDialog = SendLocationState.Dialog.PermissionDenied
ShareLocationEvents.SwitchToMyLocationMode -> when {
permissionsState.isAnyGranted -> mode = ShareLocationState.Mode.SenderLocation
permissionsState.shouldShowRationale -> permissionDialog = ShareLocationState.Dialog.PermissionRationale
else -> permissionDialog = ShareLocationState.Dialog.PermissionDenied
}
SendLocationEvents.SwitchToPinLocationMode -> mode = SendLocationState.Mode.PinLocation
SendLocationEvents.DismissDialog -> permissionDialog = SendLocationState.Dialog.None
SendLocationEvents.OpenAppSettings -> {
ShareLocationEvents.SwitchToPinLocationMode -> mode = ShareLocationState.Mode.PinLocation
ShareLocationEvents.DismissDialog -> permissionDialog = ShareLocationState.Dialog.None
ShareLocationEvents.OpenAppSettings -> {
locationActions.openSettings()
permissionDialog = SendLocationState.Dialog.None
permissionDialog = ShareLocationState.Dialog.None
}
SendLocationEvents.RequestPermissions -> permissionsState.eventSink(PermissionsEvents.RequestPermissions)
ShareLocationEvents.RequestPermissions -> permissionsState.eventSink(PermissionsEvents.RequestPermissions)
}
}
return SendLocationState(
return ShareLocationState(
permissionDialog = permissionDialog,
mode = mode,
hasLocationPermission = permissionsState.isAnyGranted,
@@ -108,14 +108,14 @@ class SendLocationPresenter(
)
}
private suspend fun sendLocation(
event: SendLocationEvents.SendLocation,
mode: SendLocationState.Mode,
private suspend fun shareLocation(
event: ShareLocationEvents.ShareLocation,
mode: ShareLocationState.Mode,
) {
val replyMode = messageComposerContext.composerMode as? MessageComposerMode.Reply
val inReplyToEventId = replyMode?.eventId
when (mode) {
SendLocationState.Mode.PinLocation -> {
ShareLocationState.Mode.PinLocation -> {
val geoUri = event.cameraPosition.toGeoUri()
getTimeline().flatMap {
it.sendLocation(
@@ -136,7 +136,7 @@ class SendLocationPresenter(
)
)
}
SendLocationState.Mode.SenderLocation -> {
ShareLocationState.Mode.SenderLocation -> {
val geoUri = event.toGeoUri()
getTimeline().flatMap {
it.sendLocation(
@@ -168,8 +168,8 @@ class SendLocationPresenter(
}
}
private fun SendLocationEvents.SendLocation.toGeoUri(): String = location?.toGeoUri() ?: cameraPosition.toGeoUri()
private fun ShareLocationEvents.ShareLocation.toGeoUri(): String = location?.toGeoUri() ?: cameraPosition.toGeoUri()
private fun SendLocationEvents.SendLocation.CameraPosition.toGeoUri(): String = "geo:$lat,$lon"
private fun ShareLocationEvents.ShareLocation.CameraPosition.toGeoUri(): String = "geo:$lat,$lon"
private fun generateBody(uri: String): String = "Location was shared at $uri"

View File

@@ -6,14 +6,14 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
data class SendLocationState(
data class ShareLocationState(
val permissionDialog: Dialog,
val mode: Mode,
val hasLocationPermission: Boolean,
val appName: String,
val eventSink: (SendLocationEvents) -> Unit,
val eventSink: (ShareLocationEvents) -> Unit,
) {
sealed interface Mode {
data object SenderLocation : Mode

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.share
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
private const val APP_NAME = "ApplicationName"
class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState> {
override val values: Sequence<ShareLocationState>
get() = sequenceOf(
aShareLocationState(
permissionDialog = ShareLocationState.Dialog.None,
mode = ShareLocationState.Mode.PinLocation,
hasLocationPermission = false,
),
aShareLocationState(
permissionDialog = ShareLocationState.Dialog.PermissionDenied,
mode = ShareLocationState.Mode.PinLocation,
hasLocationPermission = false,
),
aShareLocationState(
permissionDialog = ShareLocationState.Dialog.PermissionRationale,
mode = ShareLocationState.Mode.PinLocation,
hasLocationPermission = false,
),
aShareLocationState(
permissionDialog = ShareLocationState.Dialog.None,
mode = ShareLocationState.Mode.PinLocation,
hasLocationPermission = true,
),
aShareLocationState(
permissionDialog = ShareLocationState.Dialog.None,
mode = ShareLocationState.Mode.SenderLocation,
hasLocationPermission = true,
),
)
}
private fun aShareLocationState(
permissionDialog: ShareLocationState.Dialog,
mode: ShareLocationState.Mode,
hasLocationPermission: Boolean,
): ShareLocationState {
return ShareLocationState(
permissionDialog = permissionDialog,
mode = mode,
hasLocationPermission = hasLocationPermission,
appName = APP_NAME,
eventSink = {}
)
}

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@@ -56,25 +56,25 @@ import org.maplibre.android.camera.CameraPosition
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SendLocationView(
state: SendLocationState,
fun ShareLocationView(
state: ShareLocationState,
navigateUp: () -> Unit,
modifier: Modifier = Modifier,
) {
LaunchedEffect(Unit) {
state.eventSink(SendLocationEvents.RequestPermissions)
state.eventSink(ShareLocationEvents.RequestPermissions)
}
when (state.permissionDialog) {
SendLocationState.Dialog.None -> Unit
SendLocationState.Dialog.PermissionDenied -> PermissionDeniedDialog(
onContinue = { state.eventSink(SendLocationEvents.OpenAppSettings) },
onDismiss = { state.eventSink(SendLocationEvents.DismissDialog) },
ShareLocationState.Dialog.None -> Unit
ShareLocationState.Dialog.PermissionDenied -> PermissionDeniedDialog(
onContinue = { state.eventSink(ShareLocationEvents.OpenAppSettings) },
onDismiss = { state.eventSink(ShareLocationEvents.DismissDialog) },
appName = state.appName,
)
SendLocationState.Dialog.PermissionRationale -> PermissionRationaleDialog(
onContinue = { state.eventSink(SendLocationEvents.RequestPermissions) },
onDismiss = { state.eventSink(SendLocationEvents.DismissDialog) },
ShareLocationState.Dialog.PermissionRationale -> PermissionRationaleDialog(
onContinue = { state.eventSink(ShareLocationEvents.RequestPermissions) },
onDismiss = { state.eventSink(ShareLocationEvents.DismissDialog) },
appName = state.appName,
)
}
@@ -85,10 +85,10 @@ fun SendLocationView(
LaunchedEffect(state.mode) {
when (state.mode) {
SendLocationState.Mode.PinLocation -> {
ShareLocationState.Mode.PinLocation -> {
cameraPositionState.cameraMode = CameraMode.NONE
}
SendLocationState.Mode.SenderLocation -> {
ShareLocationState.Mode.SenderLocation -> {
cameraPositionState.position = CameraPosition.Builder()
.zoom(MapDefaults.DEFAULT_ZOOM)
.build()
@@ -99,7 +99,7 @@ fun SendLocationView(
LaunchedEffect(cameraPositionState.isMoving) {
if (cameraPositionState.cameraMoveStartedReason == CameraMoveStartedReason.GESTURE) {
state.eventSink(SendLocationEvents.SwitchToPinLocationMode)
state.eventSink(ShareLocationEvents.SwitchToPinLocationMode)
}
}
@@ -114,8 +114,8 @@ fun SendLocationView(
Text(
stringResource(
when (state.mode) {
SendLocationState.Mode.PinLocation -> CommonStrings.screen_share_this_location_action
SendLocationState.Mode.SenderLocation -> CommonStrings.screen_share_my_location_action
ShareLocationState.Mode.PinLocation -> CommonStrings.screen_share_this_location_action
ShareLocationState.Mode.SenderLocation -> CommonStrings.screen_share_my_location_action
}
)
)
@@ -125,8 +125,8 @@ fun SendLocationView(
enabled = cameraPositionState.position.target != null
) {
state.eventSink(
SendLocationEvents.SendLocation(
cameraPosition = SendLocationEvents.SendLocation.CameraPosition(
ShareLocationEvents.ShareLocation(
cameraPosition = ShareLocationEvents.ShareLocation.CameraPosition(
lat = cameraPositionState.position.target!!.latitude,
lon = cameraPositionState.position.target!!.longitude,
zoom = cameraPositionState.position.zoom,
@@ -190,8 +190,8 @@ fun SendLocationView(
modifier = Modifier.centerBottomEdge(this),
)
LocationFloatingActionButton(
isMapCenteredOnUser = state.mode == SendLocationState.Mode.SenderLocation,
onClick = { state.eventSink(SendLocationEvents.SwitchToMyLocationMode) },
isMapCenteredOnUser = state.mode == ShareLocationState.Mode.SenderLocation,
onClick = { state.eventSink(ShareLocationEvents.SwitchToMyLocationMode) },
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 18.dp, bottom = 72.dp + navBarPadding),
@@ -202,10 +202,10 @@ fun SendLocationView(
@PreviewsDayNight
@Composable
internal fun SendLocationViewPreview(
@PreviewParameter(SendLocationStateProvider::class) state: SendLocationState
internal fun ShareLocationViewPreview(
@PreviewParameter(ShareLocationStateProvider::class) state: ShareLocationState
) = ElementPreview {
SendLocationView(
ShareLocationView(
state = state,
navigateUp = {},
)

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.bumble.appyx.core.modality.BuildContext
@@ -22,19 +22,19 @@ import io.element.android.tests.testutils.node.TestParentNode
import org.junit.Rule
import org.junit.Test
class DefaultSendLocationEntryPointTest {
class DefaultShareLocationEntryPointTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun `test node builder`() {
val entryPoint = DefaultSendLocationEntryPoint()
val entryPoint = DefaultShareLocationEntryPoint()
val parentNode = TestParentNode.create { buildContext, plugins ->
SendLocationNode(
ShareLocationNode(
buildContext = buildContext,
plugins = plugins,
presenterFactory = { timelineMode: Timeline.Mode ->
SendLocationPresenter(
ShareLocationPresenter(
permissionsPresenterFactory = { FakePermissionsPresenter() },
room = FakeJoinedRoom(),
timelineMode = timelineMode,
@@ -53,7 +53,7 @@ class DefaultSendLocationEntryPointTest {
buildContext = BuildContext.root(null),
timelineMode = timelineMode,
)
assertThat(result).isInstanceOf(SendLocationNode::class.java)
assertThat(result.plugins).contains(SendLocationNode.Inputs(timelineMode))
assertThat(result).isInstanceOf(ShareLocationNode::class.java)
assertThat(result.plugins).contains(ShareLocationNode.Inputs(timelineMode))
}
}

View File

@@ -6,7 +6,7 @@
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.location.impl.send
package io.element.android.features.location.impl.share
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
@@ -40,7 +40,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class SendLocationPresenterTest {
class ShareLocationPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
@@ -50,9 +50,9 @@ class SendLocationPresenterTest {
private val fakeLocationActions = FakeLocationActions()
private val fakeBuildMeta = aBuildMeta(applicationName = "app name")
private fun createSendLocationPresenter(
private fun createShareLocationPresenter(
joinedRoom: JoinedRoom = FakeJoinedRoom(),
): SendLocationPresenter = SendLocationPresenter(
): ShareLocationPresenter = ShareLocationPresenter(
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
override fun create(permissions: List<String>): PermissionsPresenter = fakePermissionsPresenter
},
@@ -66,7 +66,7 @@ class SendLocationPresenterTest {
@Test
fun `initial state with permissions granted`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted,
@@ -75,25 +75,25 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation)
assertThat(initialState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.SenderLocation)
assertThat(initialState.hasLocationPermission).isTrue()
// Swipe the map to switch mode
initialState.eventSink(SendLocationEvents.SwitchToPinLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToPinLocationMode)
val myLocationState = awaitItem()
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isTrue()
}
}
@Test
fun `initial state with permissions partially granted`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.SomeGranted,
@@ -102,25 +102,25 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.SenderLocation)
assertThat(initialState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.SenderLocation)
assertThat(initialState.hasLocationPermission).isTrue()
// Swipe the map to switch mode
initialState.eventSink(SendLocationEvents.SwitchToPinLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToPinLocationMode)
val myLocationState = awaitItem()
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isTrue()
}
}
@Test
fun `initial state with permissions denied`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -129,25 +129,25 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(initialState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(initialState.hasLocationPermission).isFalse()
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionDenied)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.permissionDialog).isEqualTo(ShareLocationState.Dialog.PermissionDenied)
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
}
}
@Test
fun `initial state with permissions denied once`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -156,25 +156,25 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(initialState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(initialState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(initialState.hasLocationPermission).isFalse()
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.permissionDialog).isEqualTo(ShareLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
}
}
@Test
fun `rationale dialog dismiss`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -183,30 +183,30 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
// Skip initial state
val initialState = awaitItem()
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.permissionDialog).isEqualTo(ShareLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
// Dismiss the dialog
myLocationState.eventSink(SendLocationEvents.DismissDialog)
myLocationState.eventSink(ShareLocationEvents.DismissDialog)
val dialogDismissedState = awaitItem()
assertThat(dialogDismissedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(dialogDismissedState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(dialogDismissedState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(dialogDismissedState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
}
}
@Test
fun `rationale dialog continue`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -215,27 +215,27 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
// Skip initial state
val initialState = awaitItem()
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.permissionDialog).isEqualTo(ShareLocationState.Dialog.PermissionRationale)
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
// Continue the dialog sends permission request to the permissions presenter
myLocationState.eventSink(SendLocationEvents.RequestPermissions)
myLocationState.eventSink(ShareLocationEvents.RequestPermissions)
assertThat(fakePermissionsPresenter.events.last()).isEqualTo(PermissionsEvents.RequestPermissions)
}
}
@Test
fun `permission denied dialog dismiss`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -244,23 +244,23 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
// Skip initial state
val initialState = awaitItem()
// Click on the button to switch mode
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToMyLocationMode)
val myLocationState = awaitItem()
assertThat(myLocationState.permissionDialog).isEqualTo(SendLocationState.Dialog.PermissionDenied)
assertThat(myLocationState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(myLocationState.permissionDialog).isEqualTo(ShareLocationState.Dialog.PermissionDenied)
assertThat(myLocationState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(myLocationState.hasLocationPermission).isFalse()
// Dismiss the dialog
myLocationState.eventSink(SendLocationEvents.DismissDialog)
myLocationState.eventSink(ShareLocationEvents.DismissDialog)
val dialogDismissedState = awaitItem()
assertThat(dialogDismissedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(dialogDismissedState.mode).isEqualTo(SendLocationState.Mode.PinLocation)
assertThat(dialogDismissedState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(dialogDismissedState.mode).isEqualTo(ShareLocationState.Mode.PinLocation)
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
}
}
@@ -275,7 +275,7 @@ class SendLocationPresenterTest {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
val shareLocationPresenter = createShareLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted,
@@ -284,15 +284,15 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
// Skip initial state
val initialState = awaitItem()
// Send location
initialState.eventSink(
SendLocationEvents.SendLocation(
cameraPosition = SendLocationEvents.SendLocation.CameraPosition(
ShareLocationEvents.ShareLocation(
cameraPosition = ShareLocationEvents.ShareLocation.CameraPosition(
lat = 0.0,
lon = 1.0,
zoom = 2.0,
@@ -339,7 +339,7 @@ class SendLocationPresenterTest {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
val shareLocationPresenter = createShareLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -348,15 +348,15 @@ class SendLocationPresenterTest {
)
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
// Skip initial state
val initialState = awaitItem()
// Send location
initialState.eventSink(
SendLocationEvents.SendLocation(
cameraPosition = SendLocationEvents.SendLocation.CameraPosition(
ShareLocationEvents.ShareLocation(
cameraPosition = ShareLocationEvents.ShareLocation.CameraPosition(
lat = 0.0,
lon = 1.0,
zoom = 2.0,
@@ -403,7 +403,7 @@ class SendLocationPresenterTest {
sendLocationLambda = sendLocationResult
},
)
val sendLocationPresenter = createSendLocationPresenter(joinedRoom)
val shareLocationPresenter = createShareLocationPresenter(joinedRoom)
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -418,15 +418,15 @@ class SendLocationPresenterTest {
}
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
// Skip initial state
val initialState = awaitItem()
// Send location
initialState.eventSink(
SendLocationEvents.SendLocation(
cameraPosition = SendLocationEvents.SendLocation.CameraPosition(
ShareLocationEvents.ShareLocation(
cameraPosition = ShareLocationEvents.ShareLocation.CameraPosition(
lat = 0.0,
lon = 1.0,
zoom = 2.0,
@@ -451,7 +451,7 @@ class SendLocationPresenterTest {
@Test
fun `open settings activity`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
@@ -466,28 +466,28 @@ class SendLocationPresenterTest {
}
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
// Skip initial state
val initialState = awaitItem()
initialState.eventSink(SendLocationEvents.SwitchToMyLocationMode)
initialState.eventSink(ShareLocationEvents.SwitchToMyLocationMode)
val dialogShownState = awaitItem()
// Open settings
dialogShownState.eventSink(SendLocationEvents.OpenAppSettings)
dialogShownState.eventSink(ShareLocationEvents.OpenAppSettings)
val settingsOpenedState = awaitItem()
assertThat(settingsOpenedState.permissionDialog).isEqualTo(SendLocationState.Dialog.None)
assertThat(settingsOpenedState.permissionDialog).isEqualTo(ShareLocationState.Dialog.None)
assertThat(fakeLocationActions.openSettingsInvocationsCount).isEqualTo(1)
}
}
@Test
fun `application name is in state`() = runTest {
val sendLocationPresenter = createSendLocationPresenter()
val shareLocationPresenter = createShareLocationPresenter()
moleculeFlow(RecompositionMode.Immediate) {
sendLocationPresenter.present()
shareLocationPresenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.appName).isEqualTo("app name")

View File

@@ -10,11 +10,11 @@ package io.element.android.features.location.test
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.features.location.api.SendLocationEntryPoint
import io.element.android.features.location.api.ShareLocationEntryPoint
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.tests.testutils.lambda.lambdaError
class FakeSendLocationEntryPoint : SendLocationEntryPoint {
class FakeShareLocationEntryPoint : ShareLocationEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,

View File

@@ -30,7 +30,7 @@ import io.element.android.features.forward.api.ForwardEntryPoint
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.LocationService
import io.element.android.features.location.api.SendLocationEntryPoint
import io.element.android.features.location.api.ShareLocationEntryPoint
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
@@ -102,7 +102,7 @@ class MessagesFlowNode(
@Assisted plugins: List<Plugin>,
private val roomListService: RoomListService,
private val sessionId: SessionId,
private val sendLocationEntryPoint: SendLocationEntryPoint,
private val shareLocationEntryPoint: ShareLocationEntryPoint,
private val showLocationEntryPoint: ShowLocationEntryPoint,
private val createPollEntryPoint: CreatePollEntryPoint,
private val elementCallEntryPoint: ElementCallEntryPoint,
@@ -374,7 +374,7 @@ class MessagesFlowNode(
createNode<ReportMessageNode>(buildContext, listOf(inputs))
}
is NavTarget.SendLocation -> {
sendLocationEntryPoint.createNode(
shareLocationEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
timelineMode = navTarget.timelineMode,

View File

@@ -17,7 +17,7 @@ import io.element.android.features.call.test.FakeElementCallEntryPoint
import io.element.android.features.forward.test.FakeForwardEntryPoint
import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint
import io.element.android.features.location.test.FakeLocationService
import io.element.android.features.location.test.FakeSendLocationEntryPoint
import io.element.android.features.location.test.FakeShareLocationEntryPoint
import io.element.android.features.location.test.FakeShowLocationEntryPoint
import io.element.android.features.messages.api.MessagesEntryPoint
import io.element.android.features.messages.impl.pinned.banner.createPinnedEventsTimelineProvider
@@ -62,7 +62,7 @@ class DefaultMessagesEntryPointTest {
plugins = plugins,
roomListService = FakeRoomListService(),
sessionId = A_SESSION_ID,
sendLocationEntryPoint = FakeSendLocationEntryPoint(),
shareLocationEntryPoint = FakeShareLocationEntryPoint(),
showLocationEntryPoint = FakeShowLocationEntryPoint(),
createPollEntryPoint = FakeCreatePollEntryPoint(),
elementCallEntryPoint = FakeElementCallEntryPoint(),