Live location : start collecting live location
This commit is contained in:
parent
969460d3da
commit
22bfb7feb6
8 changed files with 295 additions and 49 deletions
|
|
@ -13,11 +13,13 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
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.api.ShowLocationMode
|
||||||
import io.element.android.features.location.impl.common.LocationConstraintsCheck
|
import io.element.android.features.location.impl.common.LocationConstraintsCheck
|
||||||
import io.element.android.features.location.impl.common.MapDefaults
|
import io.element.android.features.location.impl.common.MapDefaults
|
||||||
|
|
@ -29,14 +31,21 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS
|
||||||
import io.element.android.features.location.impl.common.toDialogState
|
import io.element.android.features.location.impl.common.toDialogState
|
||||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
import io.element.android.libraries.core.coroutine.mapState
|
||||||
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.DateFormatter
|
||||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||||
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
|
||||||
|
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.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class ShowLocationPresenter(
|
class ShowLocationPresenter(
|
||||||
|
|
@ -46,6 +55,7 @@ class ShowLocationPresenter(
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
private val dateFormatter: DateFormatter,
|
private val dateFormatter: DateFormatter,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val joinedRoom: JoinedRoom,
|
||||||
) : Presenter<ShowLocationState> {
|
) : Presenter<ShowLocationState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
fun interface Factory {
|
fun interface Factory {
|
||||||
|
|
@ -96,9 +106,9 @@ class ShowLocationPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val locationShares = remember {
|
val locationShares = when (mode) {
|
||||||
when (mode) {
|
is ShowLocationMode.Static -> {
|
||||||
is ShowLocationMode.Static -> {
|
remember {
|
||||||
val relativeTime = dateFormatter.format(timestamp = mode.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
val relativeTime = dateFormatter.format(timestamp = mode.timestamp, mode = DateFormatterMode.Full, useRelative = true)
|
||||||
val formattedTimestamp = stringProvider.getString(
|
val formattedTimestamp = stringProvider.getString(
|
||||||
CommonStrings.screen_static_location_sheet_timestamp_description,
|
CommonStrings.screen_static_location_sheet_timestamp_description,
|
||||||
|
|
@ -121,7 +131,35 @@ class ShowLocationPresenter(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ShowLocationMode.Live -> persistentListOf()
|
}
|
||||||
|
ShowLocationMode.Live -> {
|
||||||
|
val liveShares by produceState(persistentListOf()) {
|
||||||
|
val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares()
|
||||||
|
val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() }
|
||||||
|
combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members ->
|
||||||
|
liveShares.mapNotNull { share ->
|
||||||
|
val location = Location.fromGeoUri(share.lastGeoUri) ?: return@mapNotNull null
|
||||||
|
val member = members.find { it.userId == share.userId }
|
||||||
|
val displayName = member?.getBestName() ?: share.userId.value
|
||||||
|
val avatarUrl = member?.avatarUrl
|
||||||
|
LocationShareItem(
|
||||||
|
userId = share.userId,
|
||||||
|
displayName = displayName,
|
||||||
|
avatarData = AvatarData(
|
||||||
|
id = share.userId.value,
|
||||||
|
name = displayName,
|
||||||
|
url = avatarUrl,
|
||||||
|
size = AvatarSize.UserListItem,
|
||||||
|
),
|
||||||
|
formattedTimestamp = "Sharing live location",
|
||||||
|
location = location,
|
||||||
|
isLive = true,
|
||||||
|
assetType = AssetType.SENDER,
|
||||||
|
)
|
||||||
|
}.toPersistentList()
|
||||||
|
}.collect { value = it }
|
||||||
|
}
|
||||||
|
liveShares
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import io.element.android.features.location.impl.common.permissions.FakePermissi
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
import io.element.android.tests.testutils.node.TestParentNode
|
import io.element.android.tests.testutils.node.TestParentNode
|
||||||
|
|
@ -43,7 +44,8 @@ class DefaultShowLocationEntryPointTest {
|
||||||
locationActions = FakeLocationActions(),
|
locationActions = FakeLocationActions(),
|
||||||
buildMeta = aBuildMeta(),
|
buildMeta = aBuildMeta(),
|
||||||
dateFormatter = FakeDateFormatter(),
|
dateFormatter = FakeDateFormatter(),
|
||||||
stringProvider = FakeStringProvider()
|
stringProvider = FakeStringProvider(),
|
||||||
|
joinedRoom = FakeJoinedRoom(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
analyticsService = FakeAnalyticsService(),
|
analyticsService = FakeAnalyticsService(),
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,17 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS
|
||||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
|
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||||
|
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
|
import io.element.android.libraries.matrix.test.room.FakeLiveLocationShareService
|
||||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -51,13 +57,15 @@ class ShowLocationPresenterTest {
|
||||||
assetType = null,
|
assetType = null,
|
||||||
),
|
),
|
||||||
locationActions: FakeLocationActions = fakeLocationActions,
|
locationActions: FakeLocationActions = fakeLocationActions,
|
||||||
|
joinedRoom: JoinedRoom = FakeJoinedRoom(),
|
||||||
) = ShowLocationPresenter(
|
) = ShowLocationPresenter(
|
||||||
mode = mode,
|
mode = mode,
|
||||||
permissionsPresenterFactory = { fakePermissionsPresenter },
|
permissionsPresenterFactory = { fakePermissionsPresenter },
|
||||||
locationActions = locationActions,
|
locationActions = locationActions,
|
||||||
buildMeta = fakeBuildMeta,
|
buildMeta = fakeBuildMeta,
|
||||||
dateFormatter = fakeDateFormatter,
|
dateFormatter = fakeDateFormatter,
|
||||||
stringProvider = FakeStringProvider()
|
stringProvider = FakeStringProvider(),
|
||||||
|
joinedRoom = joinedRoom,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -318,4 +326,171 @@ class ShowLocationPresenterTest {
|
||||||
assertThat(fakeLocationActions.openLocationSettingsInvocationsCount).isEqualTo(1)
|
assertThat(fakeLocationActions.openLocationSettingsInvocationsCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `live mode emits empty location shares initially`() = runTest {
|
||||||
|
val presenter = createShowLocationPresenter(
|
||||||
|
mode = ShowLocationMode.Live,
|
||||||
|
joinedRoom = FakeJoinedRoom(),
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.locationShares).isEmpty()
|
||||||
|
assertThat(initialState.isSheetDraggable).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `live mode collects live shares from room`() = runTest {
|
||||||
|
val userId = UserId("@bob:matrix.org")
|
||||||
|
val liveSharesFlow = MutableStateFlow(
|
||||||
|
listOf(
|
||||||
|
LiveLocationShare(
|
||||||
|
userId = userId,
|
||||||
|
lastGeoUri = "geo:48.8584,2.2945",
|
||||||
|
lastTimestamp = 1234567890L,
|
||||||
|
isLive = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val fakeRoom = FakeJoinedRoom(
|
||||||
|
liveLocationShareService = FakeLiveLocationShareService(
|
||||||
|
liveLocationSharesFlow = liveSharesFlow
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val presenter = createShowLocationPresenter(
|
||||||
|
mode = ShowLocationMode.Live,
|
||||||
|
joinedRoom = fakeRoom,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Skip initial empty state from collectAsState(initial = emptyList())
|
||||||
|
skipItems(1)
|
||||||
|
val state = awaitItem()
|
||||||
|
|
||||||
|
assertThat(state.locationShares).hasSize(1)
|
||||||
|
val item = state.locationShares.first()
|
||||||
|
assertThat(item.userId).isEqualTo(userId)
|
||||||
|
assertThat(item.location.lat).isEqualTo(48.8584)
|
||||||
|
assertThat(item.location.lon).isEqualTo(2.2945)
|
||||||
|
assertThat(item.isLive).isTrue()
|
||||||
|
assertThat(state.isSheetDraggable).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `live mode handles invalid geo uri gracefully`() = runTest {
|
||||||
|
val validUserId = UserId("@alice:matrix.org")
|
||||||
|
val invalidUserId = UserId("@bob:matrix.org")
|
||||||
|
val liveSharesFlow = MutableStateFlow(
|
||||||
|
listOf(
|
||||||
|
LiveLocationShare(
|
||||||
|
userId = validUserId,
|
||||||
|
lastGeoUri = "geo:48.8584,2.2945",
|
||||||
|
lastTimestamp = 1234567890L,
|
||||||
|
isLive = true,
|
||||||
|
),
|
||||||
|
LiveLocationShare(
|
||||||
|
userId = invalidUserId,
|
||||||
|
lastGeoUri = "invalid-geo-uri",
|
||||||
|
lastTimestamp = 1234567890L,
|
||||||
|
isLive = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val fakeRoom = FakeJoinedRoom(
|
||||||
|
liveLocationShareService = FakeLiveLocationShareService(
|
||||||
|
liveLocationSharesFlow = liveSharesFlow
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val presenter = createShowLocationPresenter(
|
||||||
|
mode = ShowLocationMode.Live,
|
||||||
|
joinedRoom = fakeRoom,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Skip initial empty state from collectAsState(initial = emptyList())
|
||||||
|
skipItems(1)
|
||||||
|
val state = awaitItem()
|
||||||
|
|
||||||
|
// Only the valid location share should be present
|
||||||
|
assertThat(state.locationShares).hasSize(1)
|
||||||
|
assertThat(state.locationShares.first().userId).isEqualTo(validUserId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `live mode updates when shares change`() = runTest {
|
||||||
|
val userId = UserId("@bob:matrix.org")
|
||||||
|
val liveSharesFlow = MutableStateFlow(emptyList<LiveLocationShare>())
|
||||||
|
val fakeRoom = FakeJoinedRoom(
|
||||||
|
liveLocationShareService = FakeLiveLocationShareService(
|
||||||
|
liveLocationSharesFlow = liveSharesFlow
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val presenter = createShowLocationPresenter(
|
||||||
|
mode = ShowLocationMode.Live,
|
||||||
|
joinedRoom = fakeRoom,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Initial state is empty
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.locationShares).isEmpty()
|
||||||
|
|
||||||
|
// Emit a new live share
|
||||||
|
liveSharesFlow.value = listOf(
|
||||||
|
LiveLocationShare(
|
||||||
|
userId = userId,
|
||||||
|
lastGeoUri = "geo:48.8584,2.2945",
|
||||||
|
lastTimestamp = 1234567890L,
|
||||||
|
isLive = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val updatedState = awaitItem()
|
||||||
|
assertThat(updatedState.locationShares).hasSize(1)
|
||||||
|
assertThat(updatedState.locationShares.first().userId).isEqualTo(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `static mode emits location share with correct data`() = runTest {
|
||||||
|
val senderId = UserId("@alice:matrix.org")
|
||||||
|
val senderName = "Alice"
|
||||||
|
val avatarUrl = "https://example.com/avatar.png"
|
||||||
|
val mode = ShowLocationMode.Static(
|
||||||
|
location = location,
|
||||||
|
senderName = senderName,
|
||||||
|
senderId = senderId,
|
||||||
|
senderAvatarUrl = avatarUrl,
|
||||||
|
timestamp = 1234567890L,
|
||||||
|
assetType = AssetType.SENDER,
|
||||||
|
)
|
||||||
|
|
||||||
|
val presenter = createShowLocationPresenter(mode = mode)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.locationShares).hasSize(1)
|
||||||
|
|
||||||
|
val item = state.locationShares.first()
|
||||||
|
assertThat(item.userId).isEqualTo(senderId)
|
||||||
|
assertThat(item.displayName).isEqualTo(senderName)
|
||||||
|
assertThat(item.location).isEqualTo(location)
|
||||||
|
assertThat(item.isLive).isFalse()
|
||||||
|
assertThat(item.assetType).isEqualTo(AssetType.SENDER)
|
||||||
|
assertThat(item.avatarData.id).isEqualTo(senderId.value)
|
||||||
|
assertThat(item.avatarData.name).isEqualTo(senderName)
|
||||||
|
assertThat(item.avatarData.url).isEqualTo(avatarUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `static mode has non-draggable sheet`() = runTest {
|
||||||
|
val presenter = createShowLocationPresenter()
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state.isSheetDraggable).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -558,17 +558,18 @@ class MessagesFlowNode(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is TimelineItemLocationContent -> {
|
is TimelineItemLocationContent -> {
|
||||||
val mode = ShowLocationMode.Static(
|
val mode = when(event.content.mode){
|
||||||
location = event.content.location,
|
is TimelineItemLocationContent.Mode.Live -> ShowLocationMode.Live
|
||||||
senderName = event.safeSenderName,
|
is TimelineItemLocationContent.Mode.Static -> ShowLocationMode.Static(
|
||||||
senderId = event.senderId,
|
location = event.content.mode.location,
|
||||||
senderAvatarUrl = event.senderAvatar.url,
|
senderName = event.safeSenderName,
|
||||||
timestamp = event.sentTimeMillis,
|
senderId = event.senderId,
|
||||||
assetType = event.content.assetType,
|
senderAvatarUrl = event.senderAvatar.url,
|
||||||
)
|
timestamp = event.sentTimeMillis,
|
||||||
NavTarget.LocationViewer(
|
assetType = event.content.assetType,
|
||||||
mode = mode
|
)
|
||||||
).takeIf { locationService.isServiceAvailable() }
|
}
|
||||||
|
NavTarget.LocationViewer(mode = mode).takeIf { locationService.isServiceAvailable() }
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,4 @@ data class LiveLocationShare(
|
||||||
val lastGeoUri: String,
|
val lastGeoUri: String,
|
||||||
/** The timestamp of the last location update. */
|
/** The timestamp of the last location update. */
|
||||||
val lastTimestamp: Long,
|
val lastTimestamp: Long,
|
||||||
/** Whether the live location share is still active. */
|
|
||||||
val isLive: Boolean,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import io.element.android.libraries.matrix.impl.mapper.map
|
||||||
import io.element.android.libraries.matrix.impl.room.history.map
|
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.join.map
|
||||||
import io.element.android.libraries.matrix.impl.room.knock.RustKnockRequest
|
import io.element.android.libraries.matrix.impl.room.knock.RustKnockRequest
|
||||||
import io.element.android.libraries.matrix.impl.room.location.map
|
import io.element.android.libraries.matrix.impl.room.location.liveLocationSharesFlow
|
||||||
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
|
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.roomdirectory.map
|
||||||
import io.element.android.libraries.matrix.impl.timeline.RustTimeline
|
import io.element.android.libraries.matrix.impl.timeline.RustTimeline
|
||||||
|
|
@ -68,7 +68,6 @@ import kotlinx.coroutines.withContext
|
||||||
import org.matrix.rustcomponents.sdk.DateDividerMode
|
import org.matrix.rustcomponents.sdk.DateDividerMode
|
||||||
import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener
|
import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener
|
||||||
import org.matrix.rustcomponents.sdk.KnockRequestsListener
|
import org.matrix.rustcomponents.sdk.KnockRequestsListener
|
||||||
import org.matrix.rustcomponents.sdk.LiveLocationShareListener
|
|
||||||
import org.matrix.rustcomponents.sdk.RoomMessageEventMessageType
|
import org.matrix.rustcomponents.sdk.RoomMessageEventMessageType
|
||||||
import org.matrix.rustcomponents.sdk.RoomSendQueueUpdate
|
import org.matrix.rustcomponents.sdk.RoomSendQueueUpdate
|
||||||
import org.matrix.rustcomponents.sdk.SendQueueListener
|
import org.matrix.rustcomponents.sdk.SendQueueListener
|
||||||
|
|
@ -504,13 +503,7 @@ class JoinedRustRoom(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun subscribeToLiveLocationShares(): Flow<List<LiveLocationShare>> {
|
override fun subscribeToLiveLocationShares(): Flow<List<LiveLocationShare>> {
|
||||||
return mxCallbackFlow {
|
return innerRoom.liveLocationSharesFlow()
|
||||||
innerRoom.subscribeToLiveLocationShares(object : LiveLocationShareListener {
|
|
||||||
override fun call(liveLocationShares: List<org.matrix.rustcomponents.sdk.LiveLocationShare>) {
|
|
||||||
trySend(liveLocationShares.map { it.map() })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startLiveLocationShare(durationMillis: Long): Result<Unit> = withContext(roomDispatcher) {
|
override suspend fun startLiveLocationShare(durationMillis: Long): Result<Unit> = withContext(roomDispatcher) {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 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.core.UserId
|
|
||||||
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
|
|
||||||
import org.matrix.rustcomponents.sdk.LiveLocationShare as RustLiveLocationShare
|
|
||||||
|
|
||||||
fun RustLiveLocationShare.map(): LiveLocationShare {
|
|
||||||
return LiveLocationShare(
|
|
||||||
userId = UserId(userId),
|
|
||||||
lastGeoUri = lastLocation.location.geoUri,
|
|
||||||
lastTimestamp = lastLocation.ts.toLong(),
|
|
||||||
isLive = isLive,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
|
||||||
|
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import org.matrix.rustcomponents.sdk.LiveLocationShare as RustLiveLocationShare
|
||||||
|
import org.matrix.rustcomponents.sdk.LiveLocationShareListener
|
||||||
|
import org.matrix.rustcomponents.sdk.LiveLocationShareUpdate
|
||||||
|
import org.matrix.rustcomponents.sdk.RoomInterface
|
||||||
|
|
||||||
|
fun RoomInterface.liveLocationSharesFlow(): Flow<List<LiveLocationShare>> {
|
||||||
|
fun MutableList<LiveLocationShare>.applyUpdate(update: LiveLocationShareUpdate) {
|
||||||
|
when (update) {
|
||||||
|
is LiveLocationShareUpdate.Append -> addAll(update.values.map { it.into() })
|
||||||
|
is LiveLocationShareUpdate.Clear -> clear()
|
||||||
|
is LiveLocationShareUpdate.Insert -> add(update.index.toInt(), update.value.into())
|
||||||
|
is LiveLocationShareUpdate.PopBack -> if (isNotEmpty()) removeAt(lastIndex)
|
||||||
|
is LiveLocationShareUpdate.PopFront -> if (isNotEmpty()) removeAt(0)
|
||||||
|
is LiveLocationShareUpdate.PushBack -> add(update.value.into())
|
||||||
|
is LiveLocationShareUpdate.PushFront -> add(0, update.value.into())
|
||||||
|
is LiveLocationShareUpdate.Remove -> removeAt(update.index.toInt())
|
||||||
|
is LiveLocationShareUpdate.Reset -> {
|
||||||
|
clear()
|
||||||
|
addAll(update.values.map { it.into() })
|
||||||
|
}
|
||||||
|
is LiveLocationShareUpdate.Set -> set(update.index.toInt(), update.value.into())
|
||||||
|
is LiveLocationShareUpdate.Truncate -> subList(update.length.toInt(), size).clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mxCallbackFlow {
|
||||||
|
val shares: MutableList<LiveLocationShare> = ArrayList()
|
||||||
|
subscribeToLiveLocationShares(object : LiveLocationShareListener {
|
||||||
|
override fun onUpdate(updates: List<LiveLocationShareUpdate>) {
|
||||||
|
for (update in updates) {
|
||||||
|
shares.applyUpdate(update)
|
||||||
|
}
|
||||||
|
trySend(shares)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}.buffer(Channel.UNLIMITED)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun RustLiveLocationShare.into(): LiveLocationShare {
|
||||||
|
return LiveLocationShare(
|
||||||
|
userId = UserId(userId),
|
||||||
|
lastGeoUri = lastLocation?.location?.geoUri.orEmpty(),
|
||||||
|
lastTimestamp = lastLocation?.ts?.toLong() ?: 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue