change: ensure UserLocationState is computed from the Presenters instead of Views
This commit is contained in:
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import org.maplibre.compose.location.DesiredAccuracy
|
||||
import org.maplibre.compose.location.Location
|
||||
import org.maplibre.compose.location.rememberNullLocationProvider
|
||||
import org.maplibre.spatialk.units.extensions.meters
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class UserLocationState(locationState: State<Location?>) {
|
||||
val location: Location? by locationState
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Composable
|
||||
fun rememberUserLocationState(hasLocationPermission: Boolean): UserLocationState {
|
||||
val isPreview = LocalInspectionMode.current
|
||||
val locationProvider = if (isPreview || !hasLocationPermission) {
|
||||
rememberNullLocationProvider()
|
||||
} else {
|
||||
rememberPlatformLocationProvider(
|
||||
updateInterval = 5.seconds,
|
||||
desiredAccuracy = DesiredAccuracy.High,
|
||||
minDistance = 5.meters,
|
||||
)
|
||||
}
|
||||
val locationState = locationProvider.location.collectAsState()
|
||||
return remember { UserLocationState(locationState) }
|
||||
}
|
||||
+18
-36
@@ -10,9 +10,8 @@ package io.element.android.features.location.impl.common.ui
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.UserLocationState
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.location.Location
|
||||
import org.maplibre.compose.location.LocationPuck
|
||||
import org.maplibre.compose.location.LocationPuckColors
|
||||
import org.maplibre.compose.location.LocationPuckSizes
|
||||
@@ -20,40 +19,23 @@ import org.maplibre.compose.location.LocationPuckSizes
|
||||
@Composable
|
||||
fun UserLocationPuck(
|
||||
cameraState: CameraState,
|
||||
locationState: UserLocationState,
|
||||
trackUserLocation: Boolean,
|
||||
location: Location?,
|
||||
) {
|
||||
SimpleLocationTrackingEffect(
|
||||
locationState = locationState,
|
||||
enabled = trackUserLocation,
|
||||
) { currentLocation ->
|
||||
val target = currentLocation?.position?.value ?: cameraState.position.target
|
||||
val newPosition = cameraState.position.copy(
|
||||
target = target,
|
||||
// Force pointing to NORTH
|
||||
bearing = 0.0,
|
||||
zoom = cameraState.position.zoom.coerceAtLeast(MapDefaults.DEFAULT_ZOOM)
|
||||
LocationPuck(
|
||||
idPrefix = "user-location",
|
||||
location = location,
|
||||
cameraState = cameraState,
|
||||
accuracyThreshold = Float.POSITIVE_INFINITY,
|
||||
showBearingAccuracy = false,
|
||||
showBearing = false,
|
||||
sizes = LocationPuckSizes(
|
||||
dotRadius = 8.dp,
|
||||
dotStrokeWidth = 2.dp,
|
||||
),
|
||||
colors = LocationPuckColors(
|
||||
dotFillColorCurrentLocation = ElementTheme.colors.iconAccentPrimary,
|
||||
dotFillColorOldLocation = ElementTheme.colors.iconAccentTertiary,
|
||||
dotStrokeColor = ElementTheme.colors.bgCanvasDefault,
|
||||
)
|
||||
cameraState.animateTo(newPosition)
|
||||
}
|
||||
val location = locationState.location
|
||||
if (location != null) {
|
||||
LocationPuck(
|
||||
idPrefix = "user-location",
|
||||
location = location,
|
||||
cameraState = cameraState,
|
||||
accuracyThreshold = Float.POSITIVE_INFINITY,
|
||||
showBearingAccuracy = false,
|
||||
showBearing = false,
|
||||
sizes = LocationPuckSizes(
|
||||
dotRadius = 8.dp,
|
||||
dotStrokeWidth = 2.dp,
|
||||
),
|
||||
colors = LocationPuckColors(
|
||||
dotFillColorCurrentLocation = ElementTheme.colors.iconAccentPrimary,
|
||||
dotFillColorOldLocation = ElementTheme.colors.iconAccentTertiary,
|
||||
dotStrokeColor = ElementTheme.colors.bgCanvasDefault,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common.userlocation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import org.maplibre.compose.location.DesiredAccuracy
|
||||
import org.maplibre.compose.location.rememberNullLocationProvider
|
||||
import org.maplibre.spatialk.units.extensions.meters
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@ContributesBinding(RoomScope::class)
|
||||
class DefaultUserLocationStateFactory : UserLocationState.Factory {
|
||||
@Composable
|
||||
override fun create(hasLocationPermission: Boolean): UserLocationState {
|
||||
val locationProvider = if (!hasLocationPermission) {
|
||||
rememberNullLocationProvider()
|
||||
} else {
|
||||
@SuppressLint("MissingPermission")
|
||||
rememberPlatformLocationProvider(
|
||||
updateInterval = 5.seconds,
|
||||
desiredAccuracy = DesiredAccuracy.High,
|
||||
minDistance = 5.meters,
|
||||
)
|
||||
}
|
||||
val location by locationProvider.location.collectAsState()
|
||||
return UserLocationState(location)
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common.userlocation
|
||||
|
||||
import io.element.android.features.location.api.Location
|
||||
import org.maplibre.compose.location.PositionWithAccuracy
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
import org.maplibre.spatialk.units.extensions.meters
|
||||
import kotlin.time.TimeSource
|
||||
import org.maplibre.compose.location.Location as MapLibreLocation
|
||||
|
||||
fun Location.asMapLibreLocation(): MapLibreLocation {
|
||||
return MapLibreLocation(
|
||||
position = PositionWithAccuracy(
|
||||
value = Position(latitude = lat, longitude = lon),
|
||||
accuracy = accuracy?.toDouble()?.meters
|
||||
),
|
||||
// Not relevant as not used
|
||||
timestamp = TimeSource.Monotonic.markNow(),
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common
|
||||
package io.element.android.features.location.impl.common.userlocation
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common.userlocation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import org.maplibre.compose.location.Location
|
||||
|
||||
data class UserLocationState(val location: Location?) {
|
||||
fun interface Factory {
|
||||
@Composable
|
||||
fun create(hasLocationPermission: Boolean): UserLocationState
|
||||
}
|
||||
}
|
||||
+31
-5
@@ -5,15 +5,16 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common.ui
|
||||
package io.element.android.features.location.impl.common.userlocation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import io.element.android.features.location.impl.common.UserLocationState
|
||||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.location.Location
|
||||
import kotlin.math.abs
|
||||
|
||||
@@ -22,16 +23,17 @@ import kotlin.math.abs
|
||||
* TODO remove once https://github.com/maplibre/maplibre-compose/issues/808 is fixed
|
||||
*/
|
||||
@Composable
|
||||
internal fun SimpleLocationTrackingEffect(
|
||||
internal fun UserLocationTrackingEffect(
|
||||
locationState: UserLocationState,
|
||||
enabled: Boolean = true,
|
||||
precision: Double = 0.00001,
|
||||
onLocationChange: suspend (Location?) -> Unit,
|
||||
) {
|
||||
val latestOnLocationChange by rememberUpdatedState(onLocationChange)
|
||||
LaunchedEffect(locationState, enabled) {
|
||||
val latestLocationState by rememberUpdatedState(locationState)
|
||||
LaunchedEffect(enabled) {
|
||||
if (!enabled) return@LaunchedEffect
|
||||
val locationStateFlow = snapshotFlow { locationState.location }
|
||||
val locationStateFlow = snapshotFlow { latestLocationState.location }
|
||||
locationStateFlow
|
||||
.distinctUntilChanged { oldLocation, newLocation ->
|
||||
if (oldLocation != null && newLocation != null) {
|
||||
@@ -49,3 +51,27 @@ internal fun SimpleLocationTrackingEffect(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun UserLocationTrackingEffect(
|
||||
cameraState: CameraState,
|
||||
locationState: UserLocationState,
|
||||
enabled: Boolean = true,
|
||||
precision: Double = 0.00001,
|
||||
) {
|
||||
UserLocationTrackingEffect(
|
||||
locationState = locationState,
|
||||
enabled = enabled,
|
||||
precision = precision
|
||||
) { location ->
|
||||
val target = location?.position?.value ?: cameraState.position.target
|
||||
cameraState.animateTo(
|
||||
cameraState.position.copy(
|
||||
target = target,
|
||||
// Force pointing to NORTH
|
||||
bearing = 0.0,
|
||||
zoom = cameraState.position.zoom.coerceAtLeast(MapDefaults.DEFAULT_ZOOM)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -14,7 +14,7 @@ import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.ServiceCompat
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.location.impl.common.PlatformLocationProvider
|
||||
import io.element.android.features.location.impl.common.userlocation.PlatformLocationProvider
|
||||
import io.element.android.features.location.impl.di.LocationBindings
|
||||
import io.element.android.features.location.impl.live.notification.LiveLocationSharingNotificationCreator
|
||||
import io.element.android.libraries.architecture.bindings
|
||||
|
||||
+5
-1
@@ -33,6 +33,7 @@ import io.element.android.features.location.impl.common.permissions.PermissionsP
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
||||
import io.element.android.features.location.impl.common.sendLiveLocationPermissions
|
||||
import io.element.android.features.location.impl.common.toDialogState
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||
import io.element.android.features.location.impl.live.LiveLocationStore
|
||||
import io.element.android.features.messages.api.MessageComposerContext
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
@@ -70,6 +71,7 @@ class ShareLocationPresenter(
|
||||
private val durationFormatter: DurationFormatter,
|
||||
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
||||
private val liveLocationStore: LiveLocationStore,
|
||||
private val userLocationStateFactory: UserLocationState.Factory,
|
||||
) : Presenter<ShareLocationState> {
|
||||
@AssistedFactory
|
||||
fun interface Factory {
|
||||
@@ -123,6 +125,8 @@ class ShareLocationPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
val userLocationState = userLocationStateFactory.create(permissionsState.isAnyGranted)
|
||||
|
||||
LaunchedEffect(permissionsState.permissions) { checkLocationConstraints() }
|
||||
|
||||
fun handleEvent(event: ShareLocationEvent) {
|
||||
@@ -171,7 +175,7 @@ class ShareLocationPresenter(
|
||||
currentUser = currentUser,
|
||||
dialogState = dialogState,
|
||||
trackUserLocation = trackUserPosition,
|
||||
hasLocationPermission = permissionsState.isAnyGranted,
|
||||
userLocationState = userLocationState,
|
||||
canShareLiveLocation = timelineMode.canShareLiveLocation(),
|
||||
appName = appName,
|
||||
startLiveLocationAction = startLiveLocationAction.value,
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@
|
||||
package io.element.android.features.location.impl.share
|
||||
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
@@ -19,7 +20,7 @@ data class ShareLocationState(
|
||||
val currentUser: MatrixUser,
|
||||
val dialogState: Dialog,
|
||||
val trackUserLocation: Boolean,
|
||||
val hasLocationPermission: Boolean,
|
||||
val userLocationState: UserLocationState,
|
||||
val appName: String,
|
||||
val canShareLiveLocation: Boolean,
|
||||
val startLiveLocationAction: AsyncAction<Unit>,
|
||||
|
||||
+3
-11
@@ -10,6 +10,7 @@ package io.element.android.features.location.impl.share
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -26,43 +27,35 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.None,
|
||||
trackUserPosition = false,
|
||||
hasLocationPermission = false,
|
||||
),
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
||||
trackUserPosition = false,
|
||||
hasLocationPermission = false,
|
||||
),
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
||||
trackUserPosition = false,
|
||||
hasLocationPermission = false,
|
||||
),
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||
trackUserPosition = false,
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.None,
|
||||
trackUserPosition = false,
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.None,
|
||||
trackUserPosition = true,
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.None,
|
||||
trackUserPosition = true,
|
||||
hasLocationPermission = true,
|
||||
canShareLiveLocation = true,
|
||||
),
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.LiveLocationDisclaimer,
|
||||
trackUserPosition = true,
|
||||
hasLocationPermission = true,
|
||||
canShareLiveLocation = true,
|
||||
),
|
||||
aShareLocationState(
|
||||
@@ -74,7 +67,6 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
|
||||
)
|
||||
),
|
||||
trackUserPosition = true,
|
||||
hasLocationPermission = true,
|
||||
canShareLiveLocation = true,
|
||||
),
|
||||
aShareLocationState(
|
||||
@@ -88,7 +80,7 @@ fun aShareLocationState(
|
||||
currentUser: MatrixUser = MatrixUser(UserId("@user:matrix.org")),
|
||||
dialogState: ShareLocationState.Dialog = ShareLocationState.Dialog.None,
|
||||
trackUserPosition: Boolean = false,
|
||||
hasLocationPermission: Boolean = false,
|
||||
userLocationState: UserLocationState = UserLocationState(null),
|
||||
canShareLiveLocation: Boolean = false,
|
||||
appName: String = APP_NAME,
|
||||
startLiveLocationAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
@@ -99,7 +91,7 @@ fun aShareLocationState(
|
||||
currentUser = currentUser,
|
||||
dialogState = dialogState,
|
||||
trackUserLocation = trackUserPosition,
|
||||
hasLocationPermission = hasLocationPermission,
|
||||
userLocationState = userLocationState,
|
||||
canShareLiveLocation = canShareLiveLocation,
|
||||
appName = appName,
|
||||
startLiveLocationAction = startLiveLocationAction,
|
||||
|
||||
+8
-8
@@ -39,12 +39,11 @@ import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.api.internal.centerBottomEdge
|
||||
import io.element.android.features.location.impl.R
|
||||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.UserLocationState
|
||||
import io.element.android.features.location.impl.common.rememberUserLocationState
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialog
|
||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
||||
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
|
||||
import io.element.android.features.location.impl.common.ui.UserLocationPuck
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationTrackingEffect
|
||||
import io.element.android.features.location.impl.share.ShareLocationEvent.StartLiveLocationShare
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.LocationPin
|
||||
@@ -109,7 +108,6 @@ fun ShareLocationView(
|
||||
bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.Expanded)
|
||||
)
|
||||
val cameraState = rememberCameraState(firstPosition = MapDefaults.defaultCameraPosition)
|
||||
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
|
||||
|
||||
LaunchedEffect(cameraState.isCameraMoving) {
|
||||
if (cameraState.moveReason == CameraMoveReason.GESTURE) {
|
||||
@@ -136,15 +134,18 @@ fun ShareLocationView(
|
||||
BottomSheetContent(
|
||||
cameraState = cameraState,
|
||||
state = state,
|
||||
userLocationState = userLocationState,
|
||||
navigateUp = navigateUp
|
||||
)
|
||||
},
|
||||
mapContent = {
|
||||
UserLocationTrackingEffect(
|
||||
cameraState = cameraState,
|
||||
locationState = state.userLocationState,
|
||||
enabled = state.trackUserLocation,
|
||||
)
|
||||
UserLocationPuck(
|
||||
cameraState = cameraState,
|
||||
locationState = userLocationState,
|
||||
trackUserLocation = state.trackUserLocation
|
||||
location = state.userLocationState.location,
|
||||
)
|
||||
},
|
||||
overlayContent = { sheetPadding ->
|
||||
@@ -215,11 +216,10 @@ private fun StartLiveLocationActionView(
|
||||
private fun BottomSheetContent(
|
||||
cameraState: CameraState,
|
||||
state: ShareLocationState,
|
||||
userLocationState: UserLocationState,
|
||||
navigateUp: () -> Unit,
|
||||
) {
|
||||
Spacer(Modifier.height(20.dp))
|
||||
val userLocation = userLocationState.location
|
||||
val userLocation = state.userLocationState.location
|
||||
if (state.trackUserLocation && userLocation != null) {
|
||||
ShareCurrentLocationItem {
|
||||
state.eventSink(
|
||||
|
||||
+46
-39
@@ -10,12 +10,14 @@ package io.element.android.features.location.impl.show
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
@@ -23,6 +25,7 @@ import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.api.ShowLocationMode
|
||||
import io.element.android.features.location.api.live.ActiveLiveLocationShareManager
|
||||
import io.element.android.features.location.api.live.isCurrentlySharing
|
||||
import io.element.android.features.location.impl.common.LocationConstraintsCheck
|
||||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.SendLiveLocationPermissions
|
||||
@@ -33,6 +36,8 @@ import io.element.android.features.location.impl.common.permissions.PermissionsP
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
||||
import io.element.android.features.location.impl.common.toDialogState
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||
import io.element.android.features.location.impl.common.userlocation.asMapLibreLocation
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
@@ -52,8 +57,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AssistedInject
|
||||
class ShowLocationPresenter(
|
||||
@AssistedInject class ShowLocationPresenter(
|
||||
@Assisted private val mode: ShowLocationMode,
|
||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
private val locationActions: LocationActions,
|
||||
@@ -63,9 +67,9 @@ class ShowLocationPresenter(
|
||||
private val client: MatrixClient,
|
||||
private val joinedRoom: JoinedRoom,
|
||||
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
||||
private val userLocationStateFactory: UserLocationState.Factory,
|
||||
) : Presenter<ShowLocationState> {
|
||||
@AssistedFactory
|
||||
fun interface Factory {
|
||||
@AssistedFactory fun interface Factory {
|
||||
fun create(mode: ShowLocationMode): ShowLocationPresenter
|
||||
}
|
||||
|
||||
@@ -155,55 +159,58 @@ class ShowLocationPresenter(
|
||||
val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares()
|
||||
val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() }
|
||||
combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members ->
|
||||
liveShares
|
||||
.sortedWith(comparator)
|
||||
.mapNotNull { share ->
|
||||
val lastLocation = share.lastLocation ?: return@mapNotNull null
|
||||
val location = Location.fromGeoUri(lastLocation.geoUri) ?: return@mapNotNull null
|
||||
val member = members.find { it.userId == share.userId }
|
||||
val displayName = member?.getBestName() ?: share.userId.value
|
||||
val avatarUrl = member?.avatarUrl
|
||||
val relativeTime = dateFormatter.format(timestamp = lastLocation.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
||||
val formattedTimestamp = stringProvider.getString(
|
||||
CommonStrings.screen_static_location_sheet_timestamp_description,
|
||||
relativeTime
|
||||
)
|
||||
LocationShareItem(
|
||||
userId = share.userId,
|
||||
displayName = displayName,
|
||||
avatarData = AvatarData(
|
||||
id = share.userId.value,
|
||||
name = displayName,
|
||||
url = avatarUrl,
|
||||
size = AvatarSize.UserListItem,
|
||||
),
|
||||
formattedTimestamp = formattedTimestamp,
|
||||
location = location,
|
||||
isLive = true,
|
||||
assetType = lastLocation.assetType,
|
||||
isOwnUser = share.userId == joinedRoom.sessionId
|
||||
)
|
||||
}
|
||||
.toImmutableList()
|
||||
liveShares.sortedWith(comparator).mapNotNull { share ->
|
||||
val lastLocation = share.lastLocation ?: return@mapNotNull null
|
||||
val location = Location.fromGeoUri(lastLocation.geoUri) ?: return@mapNotNull null
|
||||
val member = members.find { it.userId == share.userId }
|
||||
val displayName = member?.getBestName() ?: share.userId.value
|
||||
val avatarUrl = member?.avatarUrl
|
||||
val relativeTime = dateFormatter.format(timestamp = lastLocation.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
||||
val formattedTimestamp = stringProvider.getString(
|
||||
CommonStrings.screen_static_location_sheet_timestamp_description,
|
||||
relativeTime
|
||||
)
|
||||
LocationShareItem(
|
||||
userId = share.userId,
|
||||
displayName = displayName,
|
||||
avatarData = AvatarData(
|
||||
id = share.userId.value,
|
||||
name = displayName,
|
||||
url = avatarUrl,
|
||||
size = AvatarSize.UserListItem,
|
||||
),
|
||||
formattedTimestamp = formattedTimestamp,
|
||||
location = location,
|
||||
isLive = true,
|
||||
assetType = lastLocation.assetType,
|
||||
isOwnUser = share.userId == joinedRoom.sessionId
|
||||
)
|
||||
}.toImmutableList()
|
||||
}.collect { value = it }
|
||||
}.value
|
||||
}
|
||||
}
|
||||
|
||||
val focusedLocation = when (mode) {
|
||||
is ShowLocationMode.Static -> locationShares.firstOrNull()
|
||||
is ShowLocationMode.Live -> locationShares.firstOrNull { it.userId == mode.senderId }
|
||||
val updatedLocationShares by rememberUpdatedState(locationShares)
|
||||
val focusedLocation by remember {
|
||||
derivedStateOf {
|
||||
when (mode) {
|
||||
is ShowLocationMode.Static -> updatedLocationShares.firstOrNull()
|
||||
is ShowLocationMode.Live -> updatedLocationShares.firstOrNull { it.userId == mode.senderId }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val userLocationState = userLocationStateFactory.create(hasLocationPermission = permissionsState.isAnyGranted)
|
||||
return ShowLocationState(
|
||||
customMapStyleUrl = customMapStyleUrl,
|
||||
dialogState = dialogState,
|
||||
locationShares = locationShares,
|
||||
focusedLocation = focusedLocation,
|
||||
hasLocationPermission = permissionsState.isAnyGranted,
|
||||
isTrackMyLocation = isTrackMyLocation,
|
||||
userLocationState = userLocationState,
|
||||
isLive = mode is ShowLocationMode.Live,
|
||||
appName = appName,
|
||||
hideUserLocationPuck = false,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
+3
-1
@@ -11,6 +11,7 @@ package io.element.android.features.location.impl.show
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.features.location.impl.common.ui.LocationMarkerData
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.PinVariant
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
@@ -24,9 +25,10 @@ data class ShowLocationState(
|
||||
val dialogState: LocationConstraintsDialogState,
|
||||
val locationShares: ImmutableList<LocationShareItem>,
|
||||
val focusedLocation: LocationShareItem?,
|
||||
val hasLocationPermission: Boolean,
|
||||
val isTrackMyLocation: Boolean,
|
||||
val userLocationState: UserLocationState,
|
||||
val appName: String,
|
||||
val hideUserLocationPuck: Boolean,
|
||||
val eventSink: (ShowLocationEvent) -> Unit,
|
||||
) {
|
||||
val isSheetDraggable = isLive && locationShares.isNotEmpty()
|
||||
|
||||
+7
-13
@@ -11,6 +11,7 @@ package io.element.android.features.location.impl.show
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
@@ -33,18 +34,9 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
|
||||
),
|
||||
aShowLocationState(
|
||||
constraintsDialogState = LocationConstraintsDialogState.LocationServiceDisabled,
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
aShowLocationState(
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
aShowLocationState(
|
||||
hasLocationPermission = true,
|
||||
isTrackMyLocation = true,
|
||||
),
|
||||
aShowLocationState(
|
||||
customMapStyleUrl = AsyncData.Loading(),
|
||||
),
|
||||
aShowLocationState(isTrackMyLocation = true),
|
||||
aShowLocationState(customMapStyleUrl = AsyncData.Loading()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,9 +48,10 @@ fun aShowLocationState(
|
||||
constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None,
|
||||
locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)),
|
||||
focusedLocation: LocationShareItem? = locationShares.firstOrNull(),
|
||||
hasLocationPermission: Boolean = false,
|
||||
isTrackMyLocation: Boolean = false,
|
||||
userLocationState: UserLocationState = UserLocationState(null),
|
||||
appName: String = APP_NAME,
|
||||
hideUserLocationPuck: Boolean = false,
|
||||
eventSink: (ShowLocationEvent) -> Unit = {},
|
||||
): ShowLocationState {
|
||||
return ShowLocationState(
|
||||
@@ -66,8 +59,9 @@ fun aShowLocationState(
|
||||
dialogState = constraintsDialogState,
|
||||
locationShares = locationShares.toImmutableList(),
|
||||
focusedLocation = focusedLocation,
|
||||
hasLocationPermission = hasLocationPermission,
|
||||
isTrackMyLocation = isTrackMyLocation,
|
||||
userLocationState = userLocationState,
|
||||
hideUserLocationPuck = hideUserLocationPuck,
|
||||
appName = appName,
|
||||
isLive = isLive,
|
||||
eventSink = eventSink,
|
||||
|
||||
+10
-5
@@ -37,13 +37,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.rememberUserLocationState
|
||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialog
|
||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
||||
import io.element.android.features.location.impl.common.ui.LocationPinMarkers
|
||||
import io.element.android.features.location.impl.common.ui.LocationShareRow
|
||||
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
|
||||
import io.element.android.features.location.impl.common.ui.UserLocationPuck
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationTrackingEffect
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
@@ -91,7 +91,6 @@ fun ShowLocationView(
|
||||
}
|
||||
}
|
||||
|
||||
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
|
||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = rememberStandardBottomSheetState(SheetValue.Expanded)
|
||||
)
|
||||
@@ -169,11 +168,17 @@ fun ShowLocationView(
|
||||
}
|
||||
},
|
||||
mapContent = {
|
||||
UserLocationPuck(
|
||||
UserLocationTrackingEffect(
|
||||
cameraState = cameraState,
|
||||
locationState = userLocationState,
|
||||
trackUserLocation = state.isTrackMyLocation
|
||||
locationState = state.userLocationState,
|
||||
enabled = state.isTrackMyLocation,
|
||||
)
|
||||
if (!state.hideUserLocationPuck) {
|
||||
UserLocationPuck(
|
||||
cameraState = cameraState,
|
||||
location = state.userLocationState.location,
|
||||
)
|
||||
}
|
||||
val markers = remember(state.locationShares) {
|
||||
state.locationShares.map { it.toMarkerData() }.toImmutableList()
|
||||
}
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||
|
||||
class FakeUserLocationStateFactory : UserLocationState.Factory {
|
||||
@Composable
|
||||
override fun create(hasLocationPermission: Boolean): UserLocationState {
|
||||
return UserLocationState(null)
|
||||
}
|
||||
}
|
||||
+2
@@ -11,6 +11,7 @@ package io.element.android.features.location.impl.share
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.location.impl.common.FakeUserLocationStateFactory
|
||||
import io.element.android.features.location.impl.common.actions.FakeLocationActions
|
||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||
import io.element.android.features.location.impl.live.LiveLocationStore
|
||||
@@ -56,6 +57,7 @@ class DefaultShareLocationEntryPointTest {
|
||||
preferenceDataStoreFactory = FakePreferenceDataStoreFactory(),
|
||||
sessionId = room.sessionId,
|
||||
),
|
||||
userLocationStateFactory = FakeUserLocationStateFactory(),
|
||||
)
|
||||
},
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
|
||||
+2
-5
@@ -21,6 +21,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.Composer
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.impl.aPermissionsState
|
||||
import io.element.android.features.location.impl.common.FakeUserLocationStateFactory
|
||||
import io.element.android.features.location.impl.common.actions.FakeLocationActions
|
||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
||||
@@ -102,6 +103,7 @@ class ShareLocationPresenterTest {
|
||||
durationFormatter = durationFormatter,
|
||||
liveLocationShareManager = liveLocationShareManager,
|
||||
liveLocationStore = liveLocationStore,
|
||||
userLocationStateFactory = FakeUserLocationStateFactory(),
|
||||
)
|
||||
|
||||
@Test
|
||||
@@ -118,7 +120,6 @@ class ShareLocationPresenterTest {
|
||||
val state = awaitFirstItem()
|
||||
assertThat(state.customMapStyleUrl.isLoading()).isFalse()
|
||||
assertThat(state.trackUserLocation).isTrue()
|
||||
assertThat(state.hasLocationPermission).isTrue()
|
||||
assertThat(state.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
||||
}
|
||||
}
|
||||
@@ -155,7 +156,6 @@ class ShareLocationPresenterTest {
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.trackUserLocation).isTrue()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,6 @@ class ShareLocationPresenterTest {
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.trackUserLocation).isFalse()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied)
|
||||
)
|
||||
@@ -195,7 +194,6 @@ class ShareLocationPresenterTest {
|
||||
shareLocationPresenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.trackUserLocation).isFalse()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale)
|
||||
)
|
||||
@@ -216,7 +214,6 @@ class ShareLocationPresenterTest {
|
||||
shareLocationPresenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.trackUserLocation).isFalse()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.dialogState).isEqualTo(
|
||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled)
|
||||
)
|
||||
|
||||
-2
@@ -120,7 +120,6 @@ class ShareLocationViewTest {
|
||||
setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||
hasLocationPermission = true,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
@@ -135,7 +134,6 @@ class ShareLocationViewTest {
|
||||
setShareLocationView(
|
||||
aShareLocationState(
|
||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||
hasLocationPermission = true,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
navigateUp = EnsureNeverCalled(),
|
||||
|
||||
+2
@@ -14,6 +14,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.api.ShowLocationEntryPoint
|
||||
import io.element.android.features.location.api.ShowLocationMode
|
||||
import io.element.android.features.location.impl.common.FakeUserLocationStateFactory
|
||||
import io.element.android.features.location.impl.common.actions.FakeLocationActions
|
||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
||||
@@ -51,6 +52,7 @@ class DefaultShowLocationEntryPointTest {
|
||||
joinedRoom = joinedRoom,
|
||||
client = FakeMatrixClient(),
|
||||
liveLocationShareManager = FakeActiveLiveLocationShareManager(),
|
||||
userLocationStateFactory = FakeUserLocationStateFactory(),
|
||||
)
|
||||
},
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
|
||||
+24
-57
@@ -15,6 +15,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.api.ShowLocationMode
|
||||
import io.element.android.features.location.impl.aPermissionsState
|
||||
import io.element.android.features.location.impl.common.FakeUserLocationStateFactory
|
||||
import io.element.android.features.location.impl.common.actions.FakeLocationActions
|
||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
||||
@@ -41,6 +42,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ShowLocationPresenterTest {
|
||||
@@ -76,26 +78,9 @@ class ShowLocationPresenterTest {
|
||||
joinedRoom = joinedRoom,
|
||||
client = client,
|
||||
liveLocationShareManager = liveLocationShareManager,
|
||||
userLocationStateFactory = FakeUserLocationStateFactory(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `emits initial state with no location permission`() = runTest {
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = false,
|
||||
)
|
||||
)
|
||||
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.customMapStyleUrl.isLoading()).isTrue()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - non-null customMapStyleUrl`() = runTest {
|
||||
val shareLocationPresenter = createShowLocationPresenter(
|
||||
@@ -112,35 +97,6 @@ class ShowLocationPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `emits initial state location permission denied once`() = runTest {
|
||||
fakePermissionsPresenter.givenState(
|
||||
aPermissionsState(
|
||||
permissions = PermissionsState.Permissions.NoneGranted,
|
||||
shouldShowRationale = true,
|
||||
)
|
||||
)
|
||||
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isFalse()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `emits initial state with location permission`() = runTest {
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.AllGranted))
|
||||
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `emits initial state with partial location permission`() = runTest {
|
||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.SomeGranted))
|
||||
@@ -148,7 +104,6 @@ class ShowLocationPresenterTest {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
}
|
||||
}
|
||||
@@ -176,7 +131,6 @@ class ShowLocationPresenterTest {
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||
|
||||
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
|
||||
@@ -184,7 +138,6 @@ class ShowLocationPresenterTest {
|
||||
|
||||
delay(1)
|
||||
|
||||
assertThat(trackMyLocationState.hasLocationPermission).isTrue()
|
||||
assertThat(trackMyLocationState.isTrackMyLocation).isTrue()
|
||||
|
||||
// Swipe the map to switch mode
|
||||
@@ -192,7 +145,6 @@ class ShowLocationPresenterTest {
|
||||
val trackLocationDisabledState = awaitItem()
|
||||
assertThat(trackLocationDisabledState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
||||
assertThat(trackLocationDisabledState.isTrackMyLocation).isFalse()
|
||||
assertThat(trackLocationDisabledState.hasLocationPermission).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,14 +167,12 @@ class ShowLocationPresenterTest {
|
||||
val trackLocationState = awaitItem()
|
||||
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionRationale)
|
||||
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
||||
assertThat(trackLocationState.hasLocationPermission).isFalse()
|
||||
|
||||
// Dismiss the dialog
|
||||
initialState.eventSink(ShowLocationEvent.DismissDialog)
|
||||
val dialogDismissedState = awaitItem()
|
||||
assertThat(dialogDismissedState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
||||
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
|
||||
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +194,6 @@ class ShowLocationPresenterTest {
|
||||
val trackLocationState = awaitItem()
|
||||
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionRationale)
|
||||
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
||||
assertThat(trackLocationState.hasLocationPermission).isFalse()
|
||||
|
||||
// Continue the dialog sends permission request to the permissions presenter
|
||||
trackLocationState.eventSink(ShowLocationEvent.RequestPermissions)
|
||||
@@ -271,14 +220,12 @@ class ShowLocationPresenterTest {
|
||||
val trackLocationState = awaitItem()
|
||||
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionDenied)
|
||||
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
||||
assertThat(trackLocationState.hasLocationPermission).isFalse()
|
||||
|
||||
// Dismiss the dialog
|
||||
initialState.eventSink(ShowLocationEvent.DismissDialog)
|
||||
val dialogDismissedState = awaitItem()
|
||||
assertThat(dialogDismissedState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
||||
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
|
||||
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +273,6 @@ class ShowLocationPresenterTest {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.hasLocationPermission).isTrue()
|
||||
|
||||
// Try to track location when location services are disabled
|
||||
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
|
||||
@@ -493,4 +439,25 @@ class ShowLocationPresenterTest {
|
||||
assertThat(state.isSheetDraggable).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `static mode never hides user location puck`() = runTest {
|
||||
val presenter = createShowLocationPresenter()
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(state.hideUserLocationPuck).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `live mode does not hide user location puck when not sharing`() = runTest {
|
||||
val presenter = createShowLocationPresenter(
|
||||
mode = ShowLocationMode.Live(senderId = A_USER_ID),
|
||||
)
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(state.hideUserLocationPuck).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user