Improve live location UI with empty state

This commit is contained in:
ganfra 2026-04-10 20:44:05 +02:00
parent 9ba8798175
commit 7c3b9523df
5 changed files with 56 additions and 33 deletions

View file

@ -91,7 +91,7 @@ fun LocationShareRow(
)
}
Text(
text = item.formattedTimestamp,
text = item.description,
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
maxLines = 1,
@ -123,7 +123,7 @@ internal fun LocationShareRowPreview() = ElementPreview {
url = null,
size = AvatarSize.UserListItem,
),
formattedTimestamp = "Shared 1 min ago",
description = "Shared 1 min ago",
isLive = true,
assetType = AssetType.SENDER,
location = Location(0.0, 0.0)
@ -142,7 +142,7 @@ internal fun LocationShareRowPreview() = ElementPreview {
),
isLive = false,
assetType = AssetType.PIN,
formattedTimestamp = "Shared 5 hours ago",
description = "Shared 5 hours ago",
location = Location(0.0, 0.0)
),
onShareClick = {},

View file

@ -40,7 +40,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import kotlinx.collections.immutable.persistentListOf
@ -124,7 +123,7 @@ class ShowLocationPresenter(
url = mode.senderAvatarUrl,
size = AvatarSize.UserListItem,
),
formattedTimestamp = formattedTimestamp,
description = formattedTimestamp,
location = mode.location,
isLive = false,
assetType = mode.assetType,
@ -152,7 +151,7 @@ class ShowLocationPresenter(
url = avatarUrl,
size = AvatarSize.UserListItem,
),
formattedTimestamp = "Sharing live location",
description = "Sharing live location",
location = location,
isLive = true,
assetType = lastLocation.assetType,
@ -169,6 +168,7 @@ class ShowLocationPresenter(
locationShares = locationShares,
hasLocationPermission = permissionsState.isAnyGranted,
isTrackMyLocation = isTrackMyLocation,
isLive = mode is ShowLocationMode.Live,
appName = appName,
eventSink = ::handleEvent,
)

View file

@ -9,6 +9,7 @@
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.impl.common.ui.LocationConstraintsDialogState
import io.element.android.features.location.impl.common.ui.LocationMarkerData
import io.element.android.libraries.designsystem.components.PinVariant
@ -18,6 +19,7 @@ import io.element.android.libraries.matrix.api.room.location.AssetType
import kotlinx.collections.immutable.ImmutableList
data class ShowLocationState(
val isLive: Boolean,
val dialogState: LocationConstraintsDialogState,
val locationShares: ImmutableList<LocationShareItem>,
val hasLocationPermission: Boolean,
@ -25,14 +27,14 @@ data class ShowLocationState(
val appName: String,
val eventSink: (ShowLocationEvent) -> Unit,
) {
val isSheetDraggable = locationShares.any { item -> item.isLive }
val isSheetDraggable = isLive && locationShares.isNotEmpty()
}
data class LocationShareItem(
val userId: UserId,
val displayName: String,
val avatarData: AvatarData,
val formattedTimestamp: String,
val description: String,
val location: Location,
val isLive: Boolean,
val assetType: AssetType?,

View file

@ -10,6 +10,7 @@ package io.element.android.features.location.impl.show
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
@ -21,6 +22,8 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
override val values: Sequence<ShowLocationState>
get() = sequenceOf(
aShowLocationState(),
aShowLocationState(isLive = true),
aShowLocationState(isLive = true, locationShares = emptyList()),
aShowLocationState(
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
),
@ -44,8 +47,9 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
private const val APP_NAME = "ApplicationName"
fun aShowLocationState(
isLive: Boolean = false,
constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None,
locationShares: List<LocationShareItem> = listOf(aLocationShareItem()),
locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)),
hasLocationPermission: Boolean = false,
isTrackMyLocation: Boolean = false,
appName: String = APP_NAME,
@ -57,6 +61,7 @@ fun aShowLocationState(
hasLocationPermission = hasLocationPermission,
isTrackMyLocation = isTrackMyLocation,
appName = appName,
isLive = isLive,
eventSink = eventSink,
)
}
@ -78,7 +83,7 @@ fun aLocationShareItem(
userId = userId,
displayName = displayName,
avatarData = avatarData,
formattedTimestamp = formattedTimestamp,
description = formattedTimestamp,
location = location,
isLive = isLive,
assetType = assetType,

View file

@ -12,6 +12,7 @@ package io.element.android.features.location.impl.show
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.BottomSheetDefaults
@ -26,6 +27,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
@ -88,7 +90,7 @@ fun ShowLocationView(
bottomSheetState = rememberStandardBottomSheetState(
initialValue =
if (state.isSheetDraggable) {
SheetValue.PartiallyExpanded
SheetValue.Expanded
} else {
SheetValue.Expanded
}
@ -116,29 +118,43 @@ fun ShowLocationView(
},
sheetContent = { sheetPaddings ->
val coroutineScope = rememberCoroutineScope()
Spacer(Modifier.height(20.dp))
Text(
text = stringResource(CommonStrings.screen_static_location_sheet_title),
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(ShowLocationEvent.Share(locationShare.location)) },
modifier = Modifier.clickable {
state.eventSink(ShowLocationEvent.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)
}
}
if (!state.isSheetDraggable) {
Spacer(Modifier.height(20.dp))
}
if (state.locationShares.isEmpty()) {
Spacer(Modifier.height(16.dp))
Text(
text = "Nobody is sharing their location",
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textPrimary,
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
textAlign = TextAlign.Center,
)
Spacer(Modifier.height(16.dp))
} else {
Text(
text = stringResource(CommonStrings.screen_static_location_sheet_title),
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(ShowLocationEvent.Share(locationShare.location)) },
modifier = Modifier.clickable {
state.eventSink(ShowLocationEvent.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 = {