Update live location shares when reaching timeout (before actual stop event)

This commit is contained in:
ganfra 2026-04-15 22:06:00 +02:00
parent 8cefebdc36
commit e162e667be
17 changed files with 331 additions and 64 deletions

View file

@ -44,6 +44,7 @@ import io.element.android.libraries.matrix.impl.room.history.map
import io.element.android.libraries.matrix.impl.room.join.map
import io.element.android.libraries.matrix.impl.room.knock.RustKnockRequest
import io.element.android.libraries.matrix.impl.room.location.liveLocationSharesFlow
import io.element.android.libraries.matrix.impl.room.location.timedByExpiry
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
import io.element.android.libraries.matrix.impl.roomdirectory.map
import io.element.android.libraries.matrix.impl.timeline.RustTimeline
@ -503,7 +504,7 @@ class JoinedRustRoom(
}
override fun subscribeToLiveLocationShares(): Flow<List<LiveLocationShare>> {
return innerRoom.liveLocationSharesFlow()
return innerRoom.liveLocationSharesFlow().timedByExpiry(systemClock::epochMillis)
}
override suspend fun startLiveLocationShare(durationMillis: Long): Result<Unit> = withContext(roomDispatcher) {

View file

@ -0,0 +1,56 @@
/*
* 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.libraries.matrix.impl.room.location
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
/**
* Makes sure to filter and emit live location based on the endTimestamp.
*/
internal fun Flow<List<LiveLocationShare>>.timedByExpiry(
currentTimeMillis: () -> Long = System::currentTimeMillis,
): Flow<List<LiveLocationShare>> = channelFlow {
var timerJob: Job? = null
fun List<LiveLocationShare>.nextExpiryAfter(timestamp: Long): Long? {
return this
.asSequence()
.map { it.endTimestamp }
.filter { it > timestamp }
.minOrNull()
}
fun List<LiveLocationShare>.filterLive(): List<LiveLocationShare> {
val currentTimeMillis = currentTimeMillis()
return filter { it.endTimestamp > currentTimeMillis }
}
fun reschedule(shares: List<LiveLocationShare>) {
timerJob?.cancel()
timerJob = launch {
val currentTimeMillis = currentTimeMillis()
val nextExpiry = shares.nextExpiryAfter(currentTimeMillis) ?: return@launch
delay((nextExpiry - currentTimeMillis).coerceAtLeast(0))
val liveShares = shares.filterLive()
send(liveShares)
reschedule(liveShares)
}
}
collect { shares ->
val liveShares = shares.filterLive()
send(liveShares)
reschedule(liveShares)
}
}

View file

@ -114,7 +114,7 @@ class TimelineEventContentMapper(
is MsgLikeKind.LiveLocation -> {
LiveLocationContent(
isLive = kind.content.isLive,
timestamp = kind.content.ts.toLong(),
startTimestamp = kind.content.ts.toLong(),
description = kind.content.description,
timeout = kind.content.timeoutMs.toLong(),
assetType = kind.content.assetType.into(),