Feature : share live location (#6741)

* First live location sharing sending implementation

* Simplify logic around canStop sharing

* Add some debug logs around LiveLocationSharingService

* Add LiveLocationException

* Expose beaconId to identify the current share

* Throttle live location instead of debouncing

* Keep sync alive when sharing live location

* Improve LiveLocation sharing

* Show LiveLocationDisclaimer

* Read minDistanceUpdate in LiveLocationSharingService

* Set minDistanceUpdate in AdvancedSettings

* Display banner in room when sharing live location

* Fix tests around LiveLocationSharing

* Ensure shares are properly restarted/stopped when app is re-launched

* Ensure LLS data is cleared when session is removed

* Update and fix LLS tests

* Handle Start LLS in ui

* Add check LLS permissions

* Remove hardcoded strings

* Fix quality and format

* Create DeviceLocationProvider so we can share location data between sources (presenter/live location service)

* Update screenshots

* Fix warning

* Do not try to stop if it was not sharing

* Revert "Create DeviceLocationProvider so we can share location data between sources (presenter/live location service)"

This reverts commit ba12bd968e82941cc231bdbb449310b24c97c5b8.

* Tweak location provider config values

* Address PR review remarks

* Fix ktlint

* Update screenshots

* Fix some tests after merging develop

* Adjust TimelineItemLocationView ui to match figma

* Update screenshots

* Documentation and cleanup

* Remove temporary resource

---------

Co-authored-by: ElementBot <android@element.io>
Co-authored-by: Benoit Marty <benoit@matrix.org>
Co-authored-by: Benoit Marty <benoitm@matrix.org>
This commit is contained in:
ganfra 2026-05-11 10:19:28 +02:00 committed by GitHub
parent 0c657c258a
commit e49e183178
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
145 changed files with 2913 additions and 278 deletions

View file

@ -0,0 +1,100 @@
/*
* 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.api
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
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.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun LiveLocationSharingBanner(
onClick: () -> Unit,
onStopClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.background(ElementTheme.colors.bgCanvasDefault)
.drawBannerBorder(ElementTheme.colors.separatorPrimary)
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = CompoundIcons.LocationPinSolid(),
contentDescription = null,
tint = ElementTheme.colors.iconAccentPrimary,
modifier = Modifier.size(24.dp),
)
Text(
text = stringResource(CommonStrings.screen_room_live_location_banner),
style = ElementTheme.typography.fontBodyMdMedium,
color = ElementTheme.colors.textPrimary,
)
}
Button(
text = stringResource(CommonStrings.action_stop),
onClick = onStopClick,
destructive = true,
size = ButtonSize.Small,
)
}
}
private fun Modifier.drawBannerBorder(borderColor: Color): Modifier = drawBehind {
val strokeWidth = 1.dp.toPx()
val bottomY = size.height - strokeWidth / 2
drawLine(
color = borderColor,
start = Offset(0f, strokeWidth / 2),
end = Offset(size.width, strokeWidth / 2),
strokeWidth = strokeWidth,
)
drawLine(
color = borderColor,
start = Offset(0f, bottomY),
end = Offset(size.width, bottomY),
strokeWidth = strokeWidth,
)
}
@PreviewsDayNight
@Composable
internal fun LiveLocationSharingBannerPreview() = ElementPreview {
LiveLocationSharingBanner(
onClick = {},
onStopClick = {},
)
}

View file

@ -0,0 +1,43 @@
/*
* 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.api.live
import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration
interface ActiveLiveLocationShareManager {
/** All rooms currently sharing live location on this device. */
val sharingRoomIds: StateFlow<Set<RoomId>>
/**
* Initializes the manager.
* This will restart or stop current location sharing and set the listener on the SDK
* and the session manager.
*/
suspend fun setup()
/**
* Starts live location sharing in the given room.
* Calls room.startLiveLocationShare() on the SDK, registers the share,
* and starts the foreground GPS service if not already running.
*/
suspend fun startShare(roomId: RoomId, duration: Duration): Result<Unit>
/**
* Stops live location sharing in the given room.
* Calls room.stopLiveLocationShare() on the SDK, removes the share,
* and stops the foreground service if no shares remain.
*/
suspend fun stopShare(roomId: RoomId): Result<Unit>
}
fun ActiveLiveLocationShareManager.isCurrentlySharing(roomId: RoomId): StateFlow<Boolean> {
return sharingRoomIds.mapState { roomId in it }
}