mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
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.runtime.Composable
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
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.camera.CameraState
|
||||||
|
import org.maplibre.compose.location.Location
|
||||||
import org.maplibre.compose.location.LocationPuck
|
import org.maplibre.compose.location.LocationPuck
|
||||||
import org.maplibre.compose.location.LocationPuckColors
|
import org.maplibre.compose.location.LocationPuckColors
|
||||||
import org.maplibre.compose.location.LocationPuckSizes
|
import org.maplibre.compose.location.LocationPuckSizes
|
||||||
@@ -20,40 +19,23 @@ import org.maplibre.compose.location.LocationPuckSizes
|
|||||||
@Composable
|
@Composable
|
||||||
fun UserLocationPuck(
|
fun UserLocationPuck(
|
||||||
cameraState: CameraState,
|
cameraState: CameraState,
|
||||||
locationState: UserLocationState,
|
location: Location?,
|
||||||
trackUserLocation: Boolean,
|
|
||||||
) {
|
) {
|
||||||
SimpleLocationTrackingEffect(
|
LocationPuck(
|
||||||
locationState = locationState,
|
idPrefix = "user-location",
|
||||||
enabled = trackUserLocation,
|
location = location,
|
||||||
) { currentLocation ->
|
cameraState = cameraState,
|
||||||
val target = currentLocation?.position?.value ?: cameraState.position.target
|
accuracyThreshold = Float.POSITIVE_INFINITY,
|
||||||
val newPosition = cameraState.position.copy(
|
showBearingAccuracy = false,
|
||||||
target = target,
|
showBearing = false,
|
||||||
// Force pointing to NORTH
|
sizes = LocationPuckSizes(
|
||||||
bearing = 0.0,
|
dotRadius = 8.dp,
|
||||||
zoom = cameraState.position.zoom.coerceAtLeast(MapDefaults.DEFAULT_ZOOM)
|
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.
|
* 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.Manifest
|
||||||
import android.annotation.SuppressLint
|
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.
|
* 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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.snapshotFlow
|
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 kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import org.maplibre.compose.camera.CameraState
|
||||||
import org.maplibre.compose.location.Location
|
import org.maplibre.compose.location.Location
|
||||||
import kotlin.math.abs
|
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
|
* TODO remove once https://github.com/maplibre/maplibre-compose/issues/808 is fixed
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
internal fun SimpleLocationTrackingEffect(
|
internal fun UserLocationTrackingEffect(
|
||||||
locationState: UserLocationState,
|
locationState: UserLocationState,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
precision: Double = 0.00001,
|
precision: Double = 0.00001,
|
||||||
onLocationChange: suspend (Location?) -> Unit,
|
onLocationChange: suspend (Location?) -> Unit,
|
||||||
) {
|
) {
|
||||||
val latestOnLocationChange by rememberUpdatedState(onLocationChange)
|
val latestOnLocationChange by rememberUpdatedState(onLocationChange)
|
||||||
LaunchedEffect(locationState, enabled) {
|
val latestLocationState by rememberUpdatedState(locationState)
|
||||||
|
LaunchedEffect(enabled) {
|
||||||
if (!enabled) return@LaunchedEffect
|
if (!enabled) return@LaunchedEffect
|
||||||
val locationStateFlow = snapshotFlow { locationState.location }
|
val locationStateFlow = snapshotFlow { latestLocationState.location }
|
||||||
locationStateFlow
|
locationStateFlow
|
||||||
.distinctUntilChanged { oldLocation, newLocation ->
|
.distinctUntilChanged { oldLocation, newLocation ->
|
||||||
if (oldLocation != null && newLocation != null) {
|
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 android.os.IBinder
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import dev.zacsweers.metro.Inject
|
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.di.LocationBindings
|
||||||
import io.element.android.features.location.impl.live.notification.LiveLocationSharingNotificationCreator
|
import io.element.android.features.location.impl.live.notification.LiveLocationSharingNotificationCreator
|
||||||
import io.element.android.libraries.architecture.bindings
|
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.permissions.PermissionsState
|
||||||
import io.element.android.features.location.impl.common.sendLiveLocationPermissions
|
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.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.location.impl.live.LiveLocationStore
|
||||||
import io.element.android.features.messages.api.MessageComposerContext
|
import io.element.android.features.messages.api.MessageComposerContext
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
@@ -70,6 +71,7 @@ class ShareLocationPresenter(
|
|||||||
private val durationFormatter: DurationFormatter,
|
private val durationFormatter: DurationFormatter,
|
||||||
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
||||||
private val liveLocationStore: LiveLocationStore,
|
private val liveLocationStore: LiveLocationStore,
|
||||||
|
private val userLocationStateFactory: UserLocationState.Factory,
|
||||||
) : Presenter<ShareLocationState> {
|
) : Presenter<ShareLocationState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
fun interface Factory {
|
fun interface Factory {
|
||||||
@@ -123,6 +125,8 @@ class ShareLocationPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val userLocationState = userLocationStateFactory.create(permissionsState.isAnyGranted)
|
||||||
|
|
||||||
LaunchedEffect(permissionsState.permissions) { checkLocationConstraints() }
|
LaunchedEffect(permissionsState.permissions) { checkLocationConstraints() }
|
||||||
|
|
||||||
fun handleEvent(event: ShareLocationEvent) {
|
fun handleEvent(event: ShareLocationEvent) {
|
||||||
@@ -171,7 +175,7 @@ class ShareLocationPresenter(
|
|||||||
currentUser = currentUser,
|
currentUser = currentUser,
|
||||||
dialogState = dialogState,
|
dialogState = dialogState,
|
||||||
trackUserLocation = trackUserPosition,
|
trackUserLocation = trackUserPosition,
|
||||||
hasLocationPermission = permissionsState.isAnyGranted,
|
userLocationState = userLocationState,
|
||||||
canShareLiveLocation = timelineMode.canShareLiveLocation(),
|
canShareLiveLocation = timelineMode.canShareLiveLocation(),
|
||||||
appName = appName,
|
appName = appName,
|
||||||
startLiveLocationAction = startLiveLocationAction.value,
|
startLiveLocationAction = startLiveLocationAction.value,
|
||||||
|
|||||||
+2
-1
@@ -9,6 +9,7 @@
|
|||||||
package io.element.android.features.location.impl.share
|
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.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.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
@@ -19,7 +20,7 @@ data class ShareLocationState(
|
|||||||
val currentUser: MatrixUser,
|
val currentUser: MatrixUser,
|
||||||
val dialogState: Dialog,
|
val dialogState: Dialog,
|
||||||
val trackUserLocation: Boolean,
|
val trackUserLocation: Boolean,
|
||||||
val hasLocationPermission: Boolean,
|
val userLocationState: UserLocationState,
|
||||||
val appName: String,
|
val appName: String,
|
||||||
val canShareLiveLocation: Boolean,
|
val canShareLiveLocation: Boolean,
|
||||||
val startLiveLocationAction: AsyncAction<Unit>,
|
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 androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
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.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
@@ -26,43 +27,35 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
|
|||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.None,
|
dialogState = ShareLocationState.Dialog.None,
|
||||||
trackUserPosition = false,
|
trackUserPosition = false,
|
||||||
hasLocationPermission = false,
|
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
||||||
trackUserPosition = false,
|
trackUserPosition = false,
|
||||||
hasLocationPermission = false,
|
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
||||||
trackUserPosition = false,
|
trackUserPosition = false,
|
||||||
hasLocationPermission = false,
|
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||||
trackUserPosition = false,
|
trackUserPosition = false,
|
||||||
hasLocationPermission = true,
|
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.None,
|
dialogState = ShareLocationState.Dialog.None,
|
||||||
trackUserPosition = false,
|
trackUserPosition = false,
|
||||||
hasLocationPermission = true,
|
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.None,
|
dialogState = ShareLocationState.Dialog.None,
|
||||||
trackUserPosition = true,
|
trackUserPosition = true,
|
||||||
hasLocationPermission = true,
|
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.None,
|
dialogState = ShareLocationState.Dialog.None,
|
||||||
trackUserPosition = true,
|
trackUserPosition = true,
|
||||||
hasLocationPermission = true,
|
|
||||||
canShareLiveLocation = true,
|
canShareLiveLocation = true,
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.LiveLocationDisclaimer,
|
dialogState = ShareLocationState.Dialog.LiveLocationDisclaimer,
|
||||||
trackUserPosition = true,
|
trackUserPosition = true,
|
||||||
hasLocationPermission = true,
|
|
||||||
canShareLiveLocation = true,
|
canShareLiveLocation = true,
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
@@ -74,7 +67,6 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
trackUserPosition = true,
|
trackUserPosition = true,
|
||||||
hasLocationPermission = true,
|
|
||||||
canShareLiveLocation = true,
|
canShareLiveLocation = true,
|
||||||
),
|
),
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
@@ -88,7 +80,7 @@ fun aShareLocationState(
|
|||||||
currentUser: MatrixUser = MatrixUser(UserId("@user:matrix.org")),
|
currentUser: MatrixUser = MatrixUser(UserId("@user:matrix.org")),
|
||||||
dialogState: ShareLocationState.Dialog = ShareLocationState.Dialog.None,
|
dialogState: ShareLocationState.Dialog = ShareLocationState.Dialog.None,
|
||||||
trackUserPosition: Boolean = false,
|
trackUserPosition: Boolean = false,
|
||||||
hasLocationPermission: Boolean = false,
|
userLocationState: UserLocationState = UserLocationState(null),
|
||||||
canShareLiveLocation: Boolean = false,
|
canShareLiveLocation: Boolean = false,
|
||||||
appName: String = APP_NAME,
|
appName: String = APP_NAME,
|
||||||
startLiveLocationAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
startLiveLocationAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
@@ -99,7 +91,7 @@ fun aShareLocationState(
|
|||||||
currentUser = currentUser,
|
currentUser = currentUser,
|
||||||
dialogState = dialogState,
|
dialogState = dialogState,
|
||||||
trackUserLocation = trackUserPosition,
|
trackUserLocation = trackUserPosition,
|
||||||
hasLocationPermission = hasLocationPermission,
|
userLocationState = userLocationState,
|
||||||
canShareLiveLocation = canShareLiveLocation,
|
canShareLiveLocation = canShareLiveLocation,
|
||||||
appName = appName,
|
appName = appName,
|
||||||
startLiveLocationAction = startLiveLocationAction,
|
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.api.internal.centerBottomEdge
|
||||||
import io.element.android.features.location.impl.R
|
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.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.LocationConstraintsDialog
|
||||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
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.MapBottomSheetScaffold
|
||||||
import io.element.android.features.location.impl.common.ui.UserLocationPuck
|
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.features.location.impl.share.ShareLocationEvent.StartLiveLocationShare
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.LocationPin
|
import io.element.android.libraries.designsystem.components.LocationPin
|
||||||
@@ -109,7 +108,6 @@ fun ShareLocationView(
|
|||||||
bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.Expanded)
|
bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.Expanded)
|
||||||
)
|
)
|
||||||
val cameraState = rememberCameraState(firstPosition = MapDefaults.defaultCameraPosition)
|
val cameraState = rememberCameraState(firstPosition = MapDefaults.defaultCameraPosition)
|
||||||
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
|
|
||||||
|
|
||||||
LaunchedEffect(cameraState.isCameraMoving) {
|
LaunchedEffect(cameraState.isCameraMoving) {
|
||||||
if (cameraState.moveReason == CameraMoveReason.GESTURE) {
|
if (cameraState.moveReason == CameraMoveReason.GESTURE) {
|
||||||
@@ -136,15 +134,18 @@ fun ShareLocationView(
|
|||||||
BottomSheetContent(
|
BottomSheetContent(
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
state = state,
|
state = state,
|
||||||
userLocationState = userLocationState,
|
|
||||||
navigateUp = navigateUp
|
navigateUp = navigateUp
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
mapContent = {
|
mapContent = {
|
||||||
|
UserLocationTrackingEffect(
|
||||||
|
cameraState = cameraState,
|
||||||
|
locationState = state.userLocationState,
|
||||||
|
enabled = state.trackUserLocation,
|
||||||
|
)
|
||||||
UserLocationPuck(
|
UserLocationPuck(
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
locationState = userLocationState,
|
location = state.userLocationState.location,
|
||||||
trackUserLocation = state.trackUserLocation
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
overlayContent = { sheetPadding ->
|
overlayContent = { sheetPadding ->
|
||||||
@@ -215,11 +216,10 @@ private fun StartLiveLocationActionView(
|
|||||||
private fun BottomSheetContent(
|
private fun BottomSheetContent(
|
||||||
cameraState: CameraState,
|
cameraState: CameraState,
|
||||||
state: ShareLocationState,
|
state: ShareLocationState,
|
||||||
userLocationState: UserLocationState,
|
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Spacer(Modifier.height(20.dp))
|
Spacer(Modifier.height(20.dp))
|
||||||
val userLocation = userLocationState.location
|
val userLocation = state.userLocationState.location
|
||||||
if (state.trackUserLocation && userLocation != null) {
|
if (state.trackUserLocation && userLocation != null) {
|
||||||
ShareCurrentLocationItem {
|
ShareCurrentLocationItem {
|
||||||
state.eventSink(
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
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.Location
|
||||||
import io.element.android.features.location.api.ShowLocationMode
|
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.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.LocationConstraintsCheck
|
||||||
import io.element.android.features.location.impl.common.MapDefaults
|
import io.element.android.features.location.impl.common.MapDefaults
|
||||||
import io.element.android.features.location.impl.common.SendLiveLocationPermissions
|
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.permissions.PermissionsState
|
||||||
import io.element.android.features.location.impl.common.toDialogState
|
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.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.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.mapState
|
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.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject class ShowLocationPresenter(
|
||||||
class ShowLocationPresenter(
|
|
||||||
@Assisted private val mode: ShowLocationMode,
|
@Assisted private val mode: ShowLocationMode,
|
||||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||||
private val locationActions: LocationActions,
|
private val locationActions: LocationActions,
|
||||||
@@ -63,9 +67,9 @@ class ShowLocationPresenter(
|
|||||||
private val client: MatrixClient,
|
private val client: MatrixClient,
|
||||||
private val joinedRoom: JoinedRoom,
|
private val joinedRoom: JoinedRoom,
|
||||||
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
private val liveLocationShareManager: ActiveLiveLocationShareManager,
|
||||||
|
private val userLocationStateFactory: UserLocationState.Factory,
|
||||||
) : Presenter<ShowLocationState> {
|
) : Presenter<ShowLocationState> {
|
||||||
@AssistedFactory
|
@AssistedFactory fun interface Factory {
|
||||||
fun interface Factory {
|
|
||||||
fun create(mode: ShowLocationMode): ShowLocationPresenter
|
fun create(mode: ShowLocationMode): ShowLocationPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,55 +159,58 @@ class ShowLocationPresenter(
|
|||||||
val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares()
|
val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares()
|
||||||
val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() }
|
val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() }
|
||||||
combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members ->
|
combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members ->
|
||||||
liveShares
|
liveShares.sortedWith(comparator).mapNotNull { share ->
|
||||||
.sortedWith(comparator)
|
val lastLocation = share.lastLocation ?: return@mapNotNull null
|
||||||
.mapNotNull { share ->
|
val location = Location.fromGeoUri(lastLocation.geoUri) ?: return@mapNotNull null
|
||||||
val lastLocation = share.lastLocation ?: return@mapNotNull null
|
val member = members.find { it.userId == share.userId }
|
||||||
val location = Location.fromGeoUri(lastLocation.geoUri) ?: return@mapNotNull null
|
val displayName = member?.getBestName() ?: share.userId.value
|
||||||
val member = members.find { it.userId == share.userId }
|
val avatarUrl = member?.avatarUrl
|
||||||
val displayName = member?.getBestName() ?: share.userId.value
|
val relativeTime = dateFormatter.format(timestamp = lastLocation.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
||||||
val avatarUrl = member?.avatarUrl
|
val formattedTimestamp = stringProvider.getString(
|
||||||
val relativeTime = dateFormatter.format(timestamp = lastLocation.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
CommonStrings.screen_static_location_sheet_timestamp_description,
|
||||||
val formattedTimestamp = stringProvider.getString(
|
relativeTime
|
||||||
CommonStrings.screen_static_location_sheet_timestamp_description,
|
)
|
||||||
relativeTime
|
LocationShareItem(
|
||||||
)
|
userId = share.userId,
|
||||||
LocationShareItem(
|
displayName = displayName,
|
||||||
userId = share.userId,
|
avatarData = AvatarData(
|
||||||
displayName = displayName,
|
id = share.userId.value,
|
||||||
avatarData = AvatarData(
|
name = displayName,
|
||||||
id = share.userId.value,
|
url = avatarUrl,
|
||||||
name = displayName,
|
size = AvatarSize.UserListItem,
|
||||||
url = avatarUrl,
|
),
|
||||||
size = AvatarSize.UserListItem,
|
formattedTimestamp = formattedTimestamp,
|
||||||
),
|
location = location,
|
||||||
formattedTimestamp = formattedTimestamp,
|
isLive = true,
|
||||||
location = location,
|
assetType = lastLocation.assetType,
|
||||||
isLive = true,
|
isOwnUser = share.userId == joinedRoom.sessionId
|
||||||
assetType = lastLocation.assetType,
|
)
|
||||||
isOwnUser = share.userId == joinedRoom.sessionId
|
}.toImmutableList()
|
||||||
)
|
|
||||||
}
|
|
||||||
.toImmutableList()
|
|
||||||
}.collect { value = it }
|
}.collect { value = it }
|
||||||
}.value
|
}.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val focusedLocation = when (mode) {
|
val updatedLocationShares by rememberUpdatedState(locationShares)
|
||||||
is ShowLocationMode.Static -> locationShares.firstOrNull()
|
val focusedLocation by remember {
|
||||||
is ShowLocationMode.Live -> locationShares.firstOrNull { it.userId == mode.senderId }
|
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(
|
return ShowLocationState(
|
||||||
customMapStyleUrl = customMapStyleUrl,
|
customMapStyleUrl = customMapStyleUrl,
|
||||||
dialogState = dialogState,
|
dialogState = dialogState,
|
||||||
locationShares = locationShares,
|
locationShares = locationShares,
|
||||||
focusedLocation = focusedLocation,
|
focusedLocation = focusedLocation,
|
||||||
hasLocationPermission = permissionsState.isAnyGranted,
|
|
||||||
isTrackMyLocation = isTrackMyLocation,
|
isTrackMyLocation = isTrackMyLocation,
|
||||||
|
userLocationState = userLocationState,
|
||||||
isLive = mode is ShowLocationMode.Live,
|
isLive = mode is ShowLocationMode.Live,
|
||||||
appName = appName,
|
appName = appName,
|
||||||
|
hideUserLocationPuck = false,
|
||||||
eventSink = ::handleEvent,
|
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.api.Location
|
||||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
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.ui.LocationMarkerData
|
||||||
|
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.PinVariant
|
import io.element.android.libraries.designsystem.components.PinVariant
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
@@ -24,9 +25,10 @@ data class ShowLocationState(
|
|||||||
val dialogState: LocationConstraintsDialogState,
|
val dialogState: LocationConstraintsDialogState,
|
||||||
val locationShares: ImmutableList<LocationShareItem>,
|
val locationShares: ImmutableList<LocationShareItem>,
|
||||||
val focusedLocation: LocationShareItem?,
|
val focusedLocation: LocationShareItem?,
|
||||||
val hasLocationPermission: Boolean,
|
|
||||||
val isTrackMyLocation: Boolean,
|
val isTrackMyLocation: Boolean,
|
||||||
|
val userLocationState: UserLocationState,
|
||||||
val appName: String,
|
val appName: String,
|
||||||
|
val hideUserLocationPuck: Boolean,
|
||||||
val eventSink: (ShowLocationEvent) -> Unit,
|
val eventSink: (ShowLocationEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val isSheetDraggable = isLive && locationShares.isNotEmpty()
|
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 androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.location.api.Location
|
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.LocationConstraintsDialogState
|
||||||
|
import io.element.android.features.location.impl.common.userlocation.UserLocationState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
@@ -33,18 +34,9 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
|
|||||||
),
|
),
|
||||||
aShowLocationState(
|
aShowLocationState(
|
||||||
constraintsDialogState = LocationConstraintsDialogState.LocationServiceDisabled,
|
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,
|
constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None,
|
||||||
locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)),
|
locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)),
|
||||||
focusedLocation: LocationShareItem? = locationShares.firstOrNull(),
|
focusedLocation: LocationShareItem? = locationShares.firstOrNull(),
|
||||||
hasLocationPermission: Boolean = false,
|
|
||||||
isTrackMyLocation: Boolean = false,
|
isTrackMyLocation: Boolean = false,
|
||||||
|
userLocationState: UserLocationState = UserLocationState(null),
|
||||||
appName: String = APP_NAME,
|
appName: String = APP_NAME,
|
||||||
|
hideUserLocationPuck: Boolean = false,
|
||||||
eventSink: (ShowLocationEvent) -> Unit = {},
|
eventSink: (ShowLocationEvent) -> Unit = {},
|
||||||
): ShowLocationState {
|
): ShowLocationState {
|
||||||
return ShowLocationState(
|
return ShowLocationState(
|
||||||
@@ -66,8 +59,9 @@ fun aShowLocationState(
|
|||||||
dialogState = constraintsDialogState,
|
dialogState = constraintsDialogState,
|
||||||
locationShares = locationShares.toImmutableList(),
|
locationShares = locationShares.toImmutableList(),
|
||||||
focusedLocation = focusedLocation,
|
focusedLocation = focusedLocation,
|
||||||
hasLocationPermission = hasLocationPermission,
|
|
||||||
isTrackMyLocation = isTrackMyLocation,
|
isTrackMyLocation = isTrackMyLocation,
|
||||||
|
userLocationState = userLocationState,
|
||||||
|
hideUserLocationPuck = hideUserLocationPuck,
|
||||||
appName = appName,
|
appName = appName,
|
||||||
isLive = isLive,
|
isLive = isLive,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
|
|||||||
+10
-5
@@ -37,13 +37,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.location.impl.common.MapDefaults
|
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.LocationConstraintsDialog
|
||||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
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.LocationPinMarkers
|
||||||
import io.element.android.features.location.impl.common.ui.LocationShareRow
|
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.MapBottomSheetScaffold
|
||||||
import io.element.android.features.location.impl.common.ui.UserLocationPuck
|
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.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
@@ -91,7 +91,6 @@ fun ShowLocationView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
|
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||||
bottomSheetState = rememberStandardBottomSheetState(SheetValue.Expanded)
|
bottomSheetState = rememberStandardBottomSheetState(SheetValue.Expanded)
|
||||||
)
|
)
|
||||||
@@ -169,11 +168,17 @@ fun ShowLocationView(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mapContent = {
|
mapContent = {
|
||||||
UserLocationPuck(
|
UserLocationTrackingEffect(
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
locationState = userLocationState,
|
locationState = state.userLocationState,
|
||||||
trackUserLocation = state.isTrackMyLocation
|
enabled = state.isTrackMyLocation,
|
||||||
)
|
)
|
||||||
|
if (!state.hideUserLocationPuck) {
|
||||||
|
UserLocationPuck(
|
||||||
|
cameraState = cameraState,
|
||||||
|
location = state.userLocationState.location,
|
||||||
|
)
|
||||||
|
}
|
||||||
val markers = remember(state.locationShares) {
|
val markers = remember(state.locationShares) {
|
||||||
state.locationShares.map { it.toMarkerData() }.toImmutableList()
|
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 androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.actions.FakeLocationActions
|
||||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||||
import io.element.android.features.location.impl.live.LiveLocationStore
|
import io.element.android.features.location.impl.live.LiveLocationStore
|
||||||
@@ -56,6 +57,7 @@ class DefaultShareLocationEntryPointTest {
|
|||||||
preferenceDataStoreFactory = FakePreferenceDataStoreFactory(),
|
preferenceDataStoreFactory = FakePreferenceDataStoreFactory(),
|
||||||
sessionId = room.sessionId,
|
sessionId = room.sessionId,
|
||||||
),
|
),
|
||||||
|
userLocationStateFactory = FakeUserLocationStateFactory(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = FakeAnalyticsService(),
|
||||||
|
|||||||
+2
-5
@@ -21,6 +21,7 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import im.vector.app.features.analytics.plan.Composer
|
import im.vector.app.features.analytics.plan.Composer
|
||||||
import io.element.android.features.location.api.Location
|
import io.element.android.features.location.api.Location
|
||||||
import io.element.android.features.location.impl.aPermissionsState
|
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.actions.FakeLocationActions
|
||||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||||
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
||||||
@@ -102,6 +103,7 @@ class ShareLocationPresenterTest {
|
|||||||
durationFormatter = durationFormatter,
|
durationFormatter = durationFormatter,
|
||||||
liveLocationShareManager = liveLocationShareManager,
|
liveLocationShareManager = liveLocationShareManager,
|
||||||
liveLocationStore = liveLocationStore,
|
liveLocationStore = liveLocationStore,
|
||||||
|
userLocationStateFactory = FakeUserLocationStateFactory(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -118,7 +120,6 @@ class ShareLocationPresenterTest {
|
|||||||
val state = awaitFirstItem()
|
val state = awaitFirstItem()
|
||||||
assertThat(state.customMapStyleUrl.isLoading()).isFalse()
|
assertThat(state.customMapStyleUrl.isLoading()).isFalse()
|
||||||
assertThat(state.trackUserLocation).isTrue()
|
assertThat(state.trackUserLocation).isTrue()
|
||||||
assertThat(state.hasLocationPermission).isTrue()
|
|
||||||
assertThat(state.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
assertThat(state.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,7 +156,6 @@ class ShareLocationPresenterTest {
|
|||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.trackUserLocation).isTrue()
|
assertThat(initialState.trackUserLocation).isTrue()
|
||||||
assertThat(initialState.hasLocationPermission).isTrue()
|
|
||||||
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
assertThat(initialState.dialogState).isEqualTo(ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,6 @@ class ShareLocationPresenterTest {
|
|||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.trackUserLocation).isFalse()
|
assertThat(initialState.trackUserLocation).isFalse()
|
||||||
assertThat(initialState.hasLocationPermission).isFalse()
|
|
||||||
assertThat(initialState.dialogState).isEqualTo(
|
assertThat(initialState.dialogState).isEqualTo(
|
||||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied)
|
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied)
|
||||||
)
|
)
|
||||||
@@ -195,7 +194,6 @@ class ShareLocationPresenterTest {
|
|||||||
shareLocationPresenter.test {
|
shareLocationPresenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.trackUserLocation).isFalse()
|
assertThat(initialState.trackUserLocation).isFalse()
|
||||||
assertThat(initialState.hasLocationPermission).isFalse()
|
|
||||||
assertThat(initialState.dialogState).isEqualTo(
|
assertThat(initialState.dialogState).isEqualTo(
|
||||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale)
|
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale)
|
||||||
)
|
)
|
||||||
@@ -216,7 +214,6 @@ class ShareLocationPresenterTest {
|
|||||||
shareLocationPresenter.test {
|
shareLocationPresenter.test {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.trackUserLocation).isFalse()
|
assertThat(initialState.trackUserLocation).isFalse()
|
||||||
assertThat(initialState.hasLocationPermission).isTrue()
|
|
||||||
assertThat(initialState.dialogState).isEqualTo(
|
assertThat(initialState.dialogState).isEqualTo(
|
||||||
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled)
|
ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled)
|
||||||
)
|
)
|
||||||
|
|||||||
-2
@@ -120,7 +120,6 @@ class ShareLocationViewTest {
|
|||||||
setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||||
hasLocationPermission = true,
|
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
@@ -135,7 +134,6 @@ class ShareLocationViewTest {
|
|||||||
setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||||
hasLocationPermission = true,
|
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
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.Location
|
||||||
import io.element.android.features.location.api.ShowLocationEntryPoint
|
import io.element.android.features.location.api.ShowLocationEntryPoint
|
||||||
import io.element.android.features.location.api.ShowLocationMode
|
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.actions.FakeLocationActions
|
||||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||||
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
||||||
@@ -51,6 +52,7 @@ class DefaultShowLocationEntryPointTest {
|
|||||||
joinedRoom = joinedRoom,
|
joinedRoom = joinedRoom,
|
||||||
client = FakeMatrixClient(),
|
client = FakeMatrixClient(),
|
||||||
liveLocationShareManager = FakeActiveLiveLocationShareManager(),
|
liveLocationShareManager = FakeActiveLiveLocationShareManager(),
|
||||||
|
userLocationStateFactory = FakeUserLocationStateFactory(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
analyticsService = FakeAnalyticsService(),
|
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.Location
|
||||||
import io.element.android.features.location.api.ShowLocationMode
|
import io.element.android.features.location.api.ShowLocationMode
|
||||||
import io.element.android.features.location.impl.aPermissionsState
|
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.actions.FakeLocationActions
|
||||||
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
|
||||||
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
|
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 kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class ShowLocationPresenterTest {
|
class ShowLocationPresenterTest {
|
||||||
@@ -76,26 +78,9 @@ class ShowLocationPresenterTest {
|
|||||||
joinedRoom = joinedRoom,
|
joinedRoom = joinedRoom,
|
||||||
client = client,
|
client = client,
|
||||||
liveLocationShareManager = liveLocationShareManager,
|
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
|
@Test
|
||||||
fun `present - non-null customMapStyleUrl`() = runTest {
|
fun `present - non-null customMapStyleUrl`() = runTest {
|
||||||
val shareLocationPresenter = createShowLocationPresenter(
|
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
|
@Test
|
||||||
fun `emits initial state with partial location permission`() = runTest {
|
fun `emits initial state with partial location permission`() = runTest {
|
||||||
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.SomeGranted))
|
fakePermissionsPresenter.givenState(aPermissionsState(permissions = PermissionsState.Permissions.SomeGranted))
|
||||||
@@ -148,7 +104,6 @@ class ShowLocationPresenterTest {
|
|||||||
val presenter = createShowLocationPresenter()
|
val presenter = createShowLocationPresenter()
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.hasLocationPermission).isTrue()
|
|
||||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,7 +131,6 @@ class ShowLocationPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.hasLocationPermission).isTrue()
|
|
||||||
assertThat(initialState.isTrackMyLocation).isFalse()
|
assertThat(initialState.isTrackMyLocation).isFalse()
|
||||||
|
|
||||||
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
|
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
|
||||||
@@ -184,7 +138,6 @@ class ShowLocationPresenterTest {
|
|||||||
|
|
||||||
delay(1)
|
delay(1)
|
||||||
|
|
||||||
assertThat(trackMyLocationState.hasLocationPermission).isTrue()
|
|
||||||
assertThat(trackMyLocationState.isTrackMyLocation).isTrue()
|
assertThat(trackMyLocationState.isTrackMyLocation).isTrue()
|
||||||
|
|
||||||
// Swipe the map to switch mode
|
// Swipe the map to switch mode
|
||||||
@@ -192,7 +145,6 @@ class ShowLocationPresenterTest {
|
|||||||
val trackLocationDisabledState = awaitItem()
|
val trackLocationDisabledState = awaitItem()
|
||||||
assertThat(trackLocationDisabledState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
assertThat(trackLocationDisabledState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
||||||
assertThat(trackLocationDisabledState.isTrackMyLocation).isFalse()
|
assertThat(trackLocationDisabledState.isTrackMyLocation).isFalse()
|
||||||
assertThat(trackLocationDisabledState.hasLocationPermission).isTrue()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,14 +167,12 @@ class ShowLocationPresenterTest {
|
|||||||
val trackLocationState = awaitItem()
|
val trackLocationState = awaitItem()
|
||||||
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionRationale)
|
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionRationale)
|
||||||
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
||||||
assertThat(trackLocationState.hasLocationPermission).isFalse()
|
|
||||||
|
|
||||||
// Dismiss the dialog
|
// Dismiss the dialog
|
||||||
initialState.eventSink(ShowLocationEvent.DismissDialog)
|
initialState.eventSink(ShowLocationEvent.DismissDialog)
|
||||||
val dialogDismissedState = awaitItem()
|
val dialogDismissedState = awaitItem()
|
||||||
assertThat(dialogDismissedState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
assertThat(dialogDismissedState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
||||||
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
|
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
|
||||||
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +194,6 @@ class ShowLocationPresenterTest {
|
|||||||
val trackLocationState = awaitItem()
|
val trackLocationState = awaitItem()
|
||||||
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionRationale)
|
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionRationale)
|
||||||
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
||||||
assertThat(trackLocationState.hasLocationPermission).isFalse()
|
|
||||||
|
|
||||||
// Continue the dialog sends permission request to the permissions presenter
|
// Continue the dialog sends permission request to the permissions presenter
|
||||||
trackLocationState.eventSink(ShowLocationEvent.RequestPermissions)
|
trackLocationState.eventSink(ShowLocationEvent.RequestPermissions)
|
||||||
@@ -271,14 +220,12 @@ class ShowLocationPresenterTest {
|
|||||||
val trackLocationState = awaitItem()
|
val trackLocationState = awaitItem()
|
||||||
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionDenied)
|
assertThat(trackLocationState.dialogState).isEqualTo(LocationConstraintsDialogState.PermissionDenied)
|
||||||
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
assertThat(trackLocationState.isTrackMyLocation).isFalse()
|
||||||
assertThat(trackLocationState.hasLocationPermission).isFalse()
|
|
||||||
|
|
||||||
// Dismiss the dialog
|
// Dismiss the dialog
|
||||||
initialState.eventSink(ShowLocationEvent.DismissDialog)
|
initialState.eventSink(ShowLocationEvent.DismissDialog)
|
||||||
val dialogDismissedState = awaitItem()
|
val dialogDismissedState = awaitItem()
|
||||||
assertThat(dialogDismissedState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
assertThat(dialogDismissedState.dialogState).isEqualTo(LocationConstraintsDialogState.None)
|
||||||
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
|
assertThat(dialogDismissedState.isTrackMyLocation).isFalse()
|
||||||
assertThat(dialogDismissedState.hasLocationPermission).isFalse()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +273,6 @@ class ShowLocationPresenterTest {
|
|||||||
val presenter = createShowLocationPresenter()
|
val presenter = createShowLocationPresenter()
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.hasLocationPermission).isTrue()
|
|
||||||
|
|
||||||
// Try to track location when location services are disabled
|
// Try to track location when location services are disabled
|
||||||
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
|
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
|
||||||
@@ -493,4 +439,25 @@ class ShowLocationPresenterTest {
|
|||||||
assertThat(state.isSheetDraggable).isFalse()
|
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