Start implementing location shares sheet content
This commit is contained in:
parent
91626bd217
commit
cbec9dbe2c
9 changed files with 271 additions and 31 deletions
|
|
@ -41,9 +41,11 @@ dependencies {
|
||||||
implementation(libs.accompanist.permission)
|
implementation(libs.accompanist.permission)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
|
implementation(projects.libraries.dateformatter.api)
|
||||||
|
|
||||||
testCommonDependencies(libs, true)
|
testCommonDependencies(libs, true)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
implementation(projects.libraries.dateformatter.test)
|
||||||
testImplementation(projects.libraries.testtags)
|
testImplementation(projects.libraries.testtags)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(projects.features.messages.test)
|
testImplementation(projects.features.messages.test)
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@ package io.element.android.features.location.impl.common
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.map.GestureOptions
|
import org.maplibre.compose.map.GestureOptions
|
||||||
import org.maplibre.compose.map.MapOptions
|
import org.maplibre.compose.map.MapOptions
|
||||||
import org.maplibre.compose.map.OrnamentOptions
|
import org.maplibre.compose.map.OrnamentOptions
|
||||||
import org.maplibre.compose.map.RenderOptions
|
import org.maplibre.compose.map.RenderOptions
|
||||||
|
import org.maplibre.spatialk.geojson.Position
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common configuration values for the map.
|
* Common configuration values for the map.
|
||||||
|
|
@ -64,13 +66,12 @@ object MapDefaults {
|
||||||
pulseColor = Color.Black,
|
pulseColor = Color.Black,
|
||||||
)
|
)
|
||||||
|
|
||||||
val centerCameraPosition = CameraPosition.Builder()
|
|
||||||
.target(LatLng(49.843, 9.902056))
|
|
||||||
.zoom(2.7)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
val centerCameraPosition = CameraPosition(
|
||||||
|
target = Position(49.843, 9.902056),
|
||||||
|
zoom = 2.7,
|
||||||
|
)
|
||||||
const val DEFAULT_ZOOM = 15.0
|
const val DEFAULT_ZOOM = 15.0
|
||||||
|
|
||||||
val permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
|
val permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
|
import io.element.android.features.location.api.Location
|
||||||
|
import io.element.android.features.location.impl.show.LocationShareItem
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
|
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.AvatarType
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LocationShareRow(
|
||||||
|
item: LocationShareItem,
|
||||||
|
onShareClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Avatar(
|
||||||
|
avatarData = item.avatarData,
|
||||||
|
avatarType = AvatarType.User,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = item.displayName,
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
if (item.isLive) {
|
||||||
|
Icon(
|
||||||
|
imageVector = CompoundIcons.LocationPinSolid(),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = ElementTheme.colors.iconAccentPrimary,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
|
}else {
|
||||||
|
val icon = if(item.assetType == AssetType.PIN) CompoundIcons.LocationNavigator() else CompoundIcons.LocationNavigatorCentred()
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = ElementTheme.colors.iconSecondary,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = item.formattedTimestamp,
|
||||||
|
style = ElementTheme.typography.fontBodySmRegular,
|
||||||
|
color = ElementTheme.colors.textSecondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IconButton(onClick = onShareClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = CompoundIcons.ShareAndroid(),
|
||||||
|
contentDescription = stringResource(CommonStrings.action_share),
|
||||||
|
tint = ElementTheme.colors.iconPrimary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun LocationShareRowPreview() = ElementPreview {
|
||||||
|
Column {
|
||||||
|
LocationShareRow(
|
||||||
|
item = LocationShareItem(
|
||||||
|
userId = UserId("@alice:matrix.org"),
|
||||||
|
displayName = "Alice",
|
||||||
|
avatarData = AvatarData(
|
||||||
|
id = "@alice:matrix.org",
|
||||||
|
name = "Alice",
|
||||||
|
url = null,
|
||||||
|
size = AvatarSize.UserListItem,
|
||||||
|
),
|
||||||
|
formattedTimestamp = "Shared 1 min ago",
|
||||||
|
assetType = AssetType.SENDER,
|
||||||
|
isLive = true,
|
||||||
|
location = Location(0.0,0.0)
|
||||||
|
),
|
||||||
|
onShareClick = {},
|
||||||
|
)
|
||||||
|
LocationShareRow(
|
||||||
|
item = LocationShareItem(
|
||||||
|
userId = UserId("@bob:matrix.org"),
|
||||||
|
displayName = "Bob",
|
||||||
|
avatarData = AvatarData(
|
||||||
|
id = "@bob:matrix.org",
|
||||||
|
name = "Bob",
|
||||||
|
url = null,
|
||||||
|
size = AvatarSize.UserListItem,
|
||||||
|
),
|
||||||
|
assetType = AssetType.PIN,
|
||||||
|
formattedTimestamp = "Shared 5 hours ago",
|
||||||
|
isLive = false,
|
||||||
|
location = Location(0.0,0.0)
|
||||||
|
),
|
||||||
|
onShareClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -88,7 +88,7 @@ fun MapBottomSheetScaffold(
|
||||||
sheetSwipeEnabled: Boolean = true,
|
sheetSwipeEnabled: Boolean = true,
|
||||||
topBar: (@Composable () -> Unit)? = null,
|
topBar: (@Composable () -> Unit)? = null,
|
||||||
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
|
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
|
||||||
sheetContent: @Composable ColumnScope.() -> Unit = {},
|
sheetContent: @Composable ColumnScope.(PaddingValues) -> Unit = {},
|
||||||
mapContent: @Composable @MaplibreComposable () -> Unit = {},
|
mapContent: @Composable @MaplibreComposable () -> Unit = {},
|
||||||
overlayContent: @Composable BoxScope.(sheetPadding: PaddingValues) -> Unit = {},
|
overlayContent: @Composable BoxScope.(sheetPadding: PaddingValues) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
|
@ -113,7 +113,7 @@ fun MapBottomSheetScaffold(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
sheetPeekHeight = sheetPeekHeight,
|
sheetPeekHeight = sheetPeekHeight,
|
||||||
sheetContent = {
|
sheetContent = {
|
||||||
sheetContent()
|
sheetContent(sheetPadding)
|
||||||
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
||||||
},
|
},
|
||||||
scaffoldState = scaffoldState,
|
scaffoldState = scaffoldState,
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
|
|
||||||
package io.element.android.features.location.impl.show
|
package io.element.android.features.location.impl.show
|
||||||
|
|
||||||
|
import io.element.android.features.location.api.Location
|
||||||
|
|
||||||
sealed interface ShowLocationEvents {
|
sealed interface ShowLocationEvents {
|
||||||
data object Share : ShowLocationEvents
|
data class Share(val location: Location) : ShowLocationEvents
|
||||||
data class TrackMyLocation(val enabled: Boolean) : ShowLocationEvents
|
data class TrackMyLocation(val enabled: Boolean) : ShowLocationEvents
|
||||||
data object DismissDialog : ShowLocationEvents
|
data object DismissDialog : ShowLocationEvents
|
||||||
data object RequestPermissions : ShowLocationEvents
|
data object RequestPermissions : ShowLocationEvents
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS
|
||||||
import io.element.android.features.location.impl.common.ui.LocationMarkerData
|
import io.element.android.features.location.impl.common.ui.LocationMarkerData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||||
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
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
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
|
@ -38,6 +40,7 @@ class ShowLocationPresenter(
|
||||||
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||||
private val locationActions: LocationActions,
|
private val locationActions: LocationActions,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val dateFormatter: DateFormatter,
|
||||||
) : Presenter<ShowLocationState> {
|
) : Presenter<ShowLocationState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
fun interface Factory {
|
fun interface Factory {
|
||||||
|
|
@ -63,15 +66,8 @@ class ShowLocationPresenter(
|
||||||
|
|
||||||
fun handleEvent(event: ShowLocationEvents) {
|
fun handleEvent(event: ShowLocationEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
ShowLocationEvents.Share -> {
|
is ShowLocationEvents.Share -> {
|
||||||
when (mode) {
|
locationActions.share(event.location, null)
|
||||||
is ShowLocationMode.Static -> {
|
|
||||||
locationActions.share(mode.location, null)
|
|
||||||
}
|
|
||||||
ShowLocationMode.Live -> {
|
|
||||||
// TODO: Handle sharing for live locations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is ShowLocationEvents.TrackMyLocation -> {
|
is ShowLocationEvents.TrackMyLocation -> {
|
||||||
if (event.enabled) {
|
if (event.enabled) {
|
||||||
|
|
@ -121,10 +117,37 @@ class ShowLocationPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val locationShares = remember(mode) {
|
||||||
|
when (mode) {
|
||||||
|
is ShowLocationMode.Static -> {
|
||||||
|
val relativeTime = dateFormatter.format(timestamp = mode.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
||||||
|
val formattedTimestamp = "Shared $relativeTime"
|
||||||
|
listOf(
|
||||||
|
LocationShareItem(
|
||||||
|
userId = mode.senderId,
|
||||||
|
displayName = mode.senderName,
|
||||||
|
avatarData = AvatarData(
|
||||||
|
id = mode.senderId.value,
|
||||||
|
name = mode.senderName,
|
||||||
|
url = mode.senderAvatarUrl,
|
||||||
|
size = AvatarSize.UserListItem,
|
||||||
|
),
|
||||||
|
formattedTimestamp = formattedTimestamp,
|
||||||
|
isLive = false,
|
||||||
|
assetType = mode.assetType,
|
||||||
|
location = mode.location,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ShowLocationMode.Live -> emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ShowLocationState(
|
return ShowLocationState(
|
||||||
permissionDialog = permissionDialog,
|
permissionDialog = permissionDialog,
|
||||||
mode = mode,
|
mode = mode,
|
||||||
markers = markers,
|
markers = markers,
|
||||||
|
locationShares = locationShares,
|
||||||
hasLocationPermission = permissionsState.isAnyGranted,
|
hasLocationPermission = permissionsState.isAnyGranted,
|
||||||
isTrackMyLocation = isTrackMyLocation,
|
isTrackMyLocation = isTrackMyLocation,
|
||||||
appName = appName,
|
appName = appName,
|
||||||
|
|
|
||||||
|
|
@ -8,21 +8,39 @@
|
||||||
|
|
||||||
package io.element.android.features.location.impl.show
|
package io.element.android.features.location.impl.show
|
||||||
|
|
||||||
|
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.common.ui.LocationMarkerData
|
import io.element.android.features.location.impl.common.ui.LocationMarkerData
|
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||||
|
|
||||||
data class ShowLocationState(
|
data class ShowLocationState(
|
||||||
val permissionDialog: Dialog,
|
val permissionDialog: Dialog,
|
||||||
val mode: ShowLocationMode,
|
val mode: ShowLocationMode,
|
||||||
val markers: List<LocationMarkerData>,
|
val markers: List<LocationMarkerData>,
|
||||||
|
val locationShares: List<LocationShareItem>,
|
||||||
val hasLocationPermission: Boolean,
|
val hasLocationPermission: Boolean,
|
||||||
val isTrackMyLocation: Boolean,
|
val isTrackMyLocation: Boolean,
|
||||||
val appName: String,
|
val appName: String,
|
||||||
val eventSink: (ShowLocationEvents) -> Unit,
|
val eventSink: (ShowLocationEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val isSheetDraggable = locationShares.any { item -> item.isLive }
|
||||||
|
|
||||||
sealed interface Dialog {
|
sealed interface Dialog {
|
||||||
data object None : Dialog
|
data object None : Dialog
|
||||||
data object PermissionRationale : Dialog
|
data object PermissionRationale : Dialog
|
||||||
data object PermissionDenied : Dialog
|
data object PermissionDenied : Dialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class LocationShareItem(
|
||||||
|
val userId: UserId,
|
||||||
|
val displayName: String,
|
||||||
|
val avatarData: AvatarData,
|
||||||
|
val formattedTimestamp: String,
|
||||||
|
val location: Location,
|
||||||
|
val isLive: Boolean,
|
||||||
|
val assetType: AssetType?,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ fun aShowLocationState(
|
||||||
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
|
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
|
||||||
mode: ShowLocationMode = aStaticLocationMode(),
|
mode: ShowLocationMode = aStaticLocationMode(),
|
||||||
markers: List<LocationMarkerData>? = null,
|
markers: List<LocationMarkerData>? = null,
|
||||||
|
locationSharers: List<LocationShareItem>? = null,
|
||||||
hasLocationPermission: Boolean = false,
|
hasLocationPermission: Boolean = false,
|
||||||
isTrackMyLocation: Boolean = false,
|
isTrackMyLocation: Boolean = false,
|
||||||
appName: String = APP_NAME,
|
appName: String = APP_NAME,
|
||||||
|
|
@ -82,10 +83,30 @@ fun aShowLocationState(
|
||||||
)
|
)
|
||||||
ShowLocationMode.Live -> emptyList()
|
ShowLocationMode.Live -> emptyList()
|
||||||
}
|
}
|
||||||
|
val effectiveLocationSharers = locationSharers ?: when (mode) {
|
||||||
|
is ShowLocationMode.Static -> listOf(
|
||||||
|
LocationShareItem(
|
||||||
|
userId = mode.senderId,
|
||||||
|
displayName = mode.senderName,
|
||||||
|
avatarData = AvatarData(
|
||||||
|
id = mode.senderId.value,
|
||||||
|
name = mode.senderName,
|
||||||
|
url = mode.senderAvatarUrl,
|
||||||
|
size = AvatarSize.UserListItem,
|
||||||
|
),
|
||||||
|
formattedTimestamp = "Shared 1 min ago",
|
||||||
|
isLive = false,
|
||||||
|
assetType = mode.assetType,
|
||||||
|
location = mode.location,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ShowLocationMode.Live -> emptyList()
|
||||||
|
}
|
||||||
return ShowLocationState(
|
return ShowLocationState(
|
||||||
permissionDialog = permissionDialog,
|
permissionDialog = permissionDialog,
|
||||||
mode = mode,
|
mode = mode,
|
||||||
markers = effectiveMarkers,
|
markers = effectiveMarkers,
|
||||||
|
locationShares = effectiveLocationSharers,
|
||||||
hasLocationPermission = hasLocationPermission,
|
hasLocationPermission = hasLocationPermission,
|
||||||
isTrackMyLocation = isTrackMyLocation,
|
isTrackMyLocation = isTrackMyLocation,
|
||||||
appName = appName,
|
appName = appName,
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,16 @@
|
||||||
|
|
||||||
package io.element.android.features.location.impl.show
|
package io.element.android.features.location.impl.show
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.BottomSheetDefaults
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.SheetValue
|
import androidx.compose.material3.SheetValue
|
||||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
import androidx.compose.material3.rememberStandardBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -25,10 +28,13 @@ 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.MapDefaults
|
||||||
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
||||||
import io.element.android.features.location.impl.common.PermissionRationaleDialog
|
import io.element.android.features.location.impl.common.PermissionRationaleDialog
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
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.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.libraries.designsystem.theme.components.Text
|
||||||
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
|
||||||
|
|
@ -36,6 +42,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.maplibre.compose.camera.CameraMoveReason
|
import org.maplibre.compose.camera.CameraMoveReason
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
|
|
@ -72,9 +79,7 @@ fun ShowLocationView(
|
||||||
target = Position(latitude = mode.location.lat, longitude = mode.location.lon),
|
target = Position(latitude = mode.location.lat, longitude = mode.location.lon),
|
||||||
zoom = MapDefaults.DEFAULT_ZOOM
|
zoom = MapDefaults.DEFAULT_ZOOM
|
||||||
)
|
)
|
||||||
ShowLocationMode.Live -> CameraPosition(
|
ShowLocationMode.Live -> MapDefaults.centerCameraPosition
|
||||||
zoom = MapDefaults.DEFAULT_ZOOM
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
val cameraState = rememberCameraState(firstPosition = initialPosition)
|
val cameraState = rememberCameraState(firstPosition = initialPosition)
|
||||||
val locationProvider = if (state.hasLocationPermission) {
|
val locationProvider = if (state.hasLocationPermission) {
|
||||||
|
|
@ -94,9 +99,22 @@ fun ShowLocationView(
|
||||||
}
|
}
|
||||||
|
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||||
bottomSheetState = rememberStandardBottomSheetState(initialValue = SheetValue.PartiallyExpanded)
|
bottomSheetState = rememberStandardBottomSheetState(initialValue =
|
||||||
|
if(state.isSheetDraggable) {
|
||||||
|
SheetValue.PartiallyExpanded
|
||||||
|
}else {
|
||||||
|
SheetValue.Expanded
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
MapBottomSheetScaffold(
|
MapBottomSheetScaffold(
|
||||||
|
//sheetPeekHeight = 180.dp,
|
||||||
|
sheetDragHandle = if(state.isSheetDraggable) {
|
||||||
|
{BottomSheetDefaults.DragHandle()}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
sheetSwipeEnabled = state.isSheetDraggable,
|
||||||
scaffoldState = scaffoldState,
|
scaffoldState = scaffoldState,
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -108,18 +126,30 @@ fun ShowLocationView(
|
||||||
onClick = onBackClick,
|
onClick = onBackClick,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
|
||||||
IconButton(
|
|
||||||
onClick = { state.eventSink(ShowLocationEvents.Share) }
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = CompoundIcons.ShareAndroid(),
|
|
||||||
contentDescription = stringResource(CommonStrings.action_share),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
sheetContent = { sheetPaddings ->
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
Text(
|
||||||
|
text = "On the map",
|
||||||
|
style = ElementTheme.typography.fontBodyLgMedium,
|
||||||
|
color = ElementTheme.colors.textPrimary,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
)
|
||||||
|
state.locationShares.forEach { locationShare ->
|
||||||
|
LocationShareRow(
|
||||||
|
item = locationShare,
|
||||||
|
onShareClick = { state.eventSink(ShowLocationEvents.Share(locationShare.location)) },
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
state.eventSink(ShowLocationEvents.TrackMyLocation(false))
|
||||||
|
val position = CameraPosition(padding = sheetPaddings, target = Position(locationShare.location.lon, locationShare.location.lat), zoom = MapDefaults.DEFAULT_ZOOM)
|
||||||
|
coroutineScope.launch {
|
||||||
|
cameraState.animateTo(finalPosition = position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
mapContent = {
|
mapContent = {
|
||||||
UserLocationPuck(
|
UserLocationPuck(
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue