Make sure we can display both Live and Static locations in ShowLocation

This commit is contained in:
ganfra 2026-02-26 22:07:30 +01:00
parent e1e7c264ed
commit ffad69b7b9
16 changed files with 168 additions and 67 deletions

View file

@ -40,7 +40,7 @@ class ShowLocationNode(
}
private val inputs: ShowLocationEntryPoint.Inputs = inputs()
private val presenter = presenterFactory.create(inputs.location, inputs.description)
private val presenter = presenterFactory.create(inputs.mode)
@Composable
override fun View(modifier: Modifier) {

View file

@ -18,7 +18,7 @@ import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
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.impl.common.MapDefaults
import io.element.android.features.location.impl.common.actions.LocationActions
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
@ -29,15 +29,14 @@ import io.element.android.libraries.core.meta.BuildMeta
@AssistedInject
class ShowLocationPresenter(
@Assisted private val location: Location,
@Assisted private val description: String?,
@Assisted private val mode: ShowLocationMode,
permissionsPresenterFactory: PermissionsPresenter.Factory,
private val locationActions: LocationActions,
private val buildMeta: BuildMeta,
) : Presenter<ShowLocationState> {
@AssistedFactory
fun interface Factory {
fun create(location: Location, description: String?): ShowLocationPresenter
fun create(mode: ShowLocationMode): ShowLocationPresenter
}
private val permissionsPresenter = permissionsPresenterFactory.create(MapDefaults.permissions)
@ -59,7 +58,16 @@ class ShowLocationPresenter(
fun handleEvent(event: ShowLocationEvents) {
when (event) {
ShowLocationEvents.Share -> locationActions.share(location, description)
ShowLocationEvents.Share -> {
when (mode) {
is ShowLocationMode.Static -> {
locationActions.share(mode.location, null)
}
ShowLocationMode.Live -> {
// TODO: Handle sharing for live locations
}
}
}
is ShowLocationEvents.TrackMyLocation -> {
if (event.enabled) {
when {
@ -82,8 +90,7 @@ class ShowLocationPresenter(
return ShowLocationState(
permissionDialog = permissionDialog,
location = location,
description = description,
mode = mode,
hasLocationPermission = permissionsState.isAnyGranted,
isTrackMyLocation = isTrackMyLocation,
appName = appName,

View file

@ -8,12 +8,11 @@
package io.element.android.features.location.impl.show
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationMode
data class ShowLocationState(
val permissionDialog: Dialog,
val location: Location,
val description: String?,
val mode: ShowLocationMode,
val hasLocationPermission: Boolean,
val isTrackMyLocation: Boolean,
val appName: String,

View file

@ -10,6 +10,9 @@ 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.api.ShowLocationMode
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.AssetType
private const val APP_NAME = "ApplicationName"
@ -31,32 +34,47 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
isTrackMyLocation = true,
),
aShowLocationState(
description = "My favourite place!",
mode = aStaticLocationMode(senderName = "My favourite place!"),
),
aShowLocationState(
description = "For some reason I decided to to write a small essay that wraps at just two lines!",
mode = aStaticLocationMode(
senderName = "For some reason I decided to write a small essay that wraps at just two lines!"
),
),
aShowLocationState(
description = "For some reason I decided to write a small essay in the location description. " +
"It is so long that it will wrap onto more than two lines!",
mode = ShowLocationMode.Live,
),
)
}
fun aShowLocationState(
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
location: Location = Location(1.23, 2.34, 4f),
description: String? = null,
mode: ShowLocationMode = aStaticLocationMode(),
hasLocationPermission: Boolean = false,
isTrackMyLocation: Boolean = false,
appName: String = APP_NAME,
eventSink: (ShowLocationEvents) -> Unit = {},
) = ShowLocationState(
permissionDialog = permissionDialog,
location = location,
description = description,
mode = mode,
hasLocationPermission = hasLocationPermission,
isTrackMyLocation = isTrackMyLocation,
appName = appName,
eventSink = eventSink,
)
fun aStaticLocationMode(
location: Location = Location(1.23, 2.34, 4f),
senderName: String = "Alice",
senderId: UserId = UserId("@alice:matrix.org"),
senderAvatarUrl: String? = null,
timestamp: Long = System.currentTimeMillis(),
assetType: AssetType? = null,
) = ShowLocationMode.Static(
location = location,
senderName = senderName,
senderId = senderId,
senderAvatarUrl = senderAvatarUrl,
timestamp = timestamp,
assetType = assetType,
)

View file

@ -11,6 +11,9 @@ package io.element.android.features.location.impl.show
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberStandardBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@ -23,6 +26,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.compound.tokens.generated.TypographyTokens
import io.element.android.features.location.api.ShowLocationMode
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.PermissionDeniedDialog
@ -74,12 +78,16 @@ fun ShowLocationView(
)
}
val cameraState = rememberCameraState(
firstPosition = CameraPosition(
target = Position(latitude = state.location.lat, longitude = state.location.lon),
val initialPosition = when (val mode = state.mode) {
is ShowLocationMode.Static -> CameraPosition(
target = Position(latitude = mode.location.lat, longitude = mode.location.lon),
zoom = MapDefaults.DEFAULT_ZOOM
)
)
ShowLocationMode.Live -> CameraPosition(
zoom = MapDefaults.DEFAULT_ZOOM
)
}
val cameraState = rememberCameraState(firstPosition = initialPosition)
val locationProvider = if (state.hasLocationPermission) {
rememberDefaultLocationProvider(
updateInterval = 1.minutes,
@ -96,10 +104,13 @@ fun ShowLocationView(
}
}
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberStandardBottomSheetState()
)
MapBottomSheetScaffold(
scaffoldState = scaffoldState,
cameraState = cameraState,
modifier = modifier,
sheetPeekHeight = 80.dp,
topBar = {
TopAppBar(
titleStr = stringResource(CommonStrings.screen_view_location_title),
@ -121,17 +132,22 @@ fun ShowLocationView(
)
},
sheetContent = {
state.description?.let {
Text(
text = it,
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = TypographyTokens.fontBodyMdRegular,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
)
when (val mode = state.mode) {
is ShowLocationMode.Static -> {
Text(
text = mode.senderName,
textAlign = TextAlign.Center,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
style = TypographyTokens.fontBodyMdRegular,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
)
}
ShowLocationMode.Live -> {
// TODO: Show list of active live location sharers
}
}
},
mapContent = {
@ -140,22 +156,29 @@ fun ShowLocationView(
locationState = userLocationState,
trackUserLocation = state.isTrackMyLocation
)
val senderLocation = rememberGeoJsonSource(
data = GeoJsonData.Features(
Point(
Position(
latitude = state.location.lat,
longitude = state.location.lon
when (val mode = state.mode) {
is ShowLocationMode.Static -> {
val senderLocation = rememberGeoJsonSource(
data = GeoJsonData.Features(
Point(
Position(
latitude = mode.location.lat,
longitude = mode.location.lon
)
)
)
)
)
)
val marker = painterResource(R.drawable.pin_small)
SymbolLayer(
id = "sender_location",
source = senderLocation,
iconImage = image(marker)
)
val marker = painterResource(R.drawable.pin_small)
SymbolLayer(
id = "sender_location",
source = senderLocation,
iconImage = image(marker)
)
}
ShowLocationMode.Live -> {
// TODO: Show pins for all active live location sharers
}
}
},
overlayContent = {
LocationFloatingActionButton(