Skip to content

Commit d5b375e

Browse files
authored
Merge pull request #6414 from vector-im/feature/mna/reply-to-lls
[Location sharing] - Reply action on a live message (PSG-343)
2 parents e326188 + f5e33ca commit d5b375e

File tree

12 files changed

+211
-28
lines changed

12 files changed

+211
-28
lines changed

changelog.d/6401.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Location sharing] - Reply action on a live message

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ interface LocationSharingService {
4848
/**
4949
* Starts sharing live location in the room.
5050
* @param timeoutMillis timeout of the live in milliseconds
51+
* @param description description of the live for text fallback
5152
* @return the result of the update of the live
5253
*/
53-
suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult
54+
suspend fun startLiveLocationShare(timeoutMillis: Long, description: String): UpdateLiveLocationShareResult
5455

5556
/**
5657
* Stops sharing live location in the room.

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
7272
return sendLiveLocationTask.execute(params)
7373
}
7474

75-
override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult {
75+
override suspend fun startLiveLocationShare(timeoutMillis: Long, description: String): UpdateLiveLocationShareResult {
7676
// Ensure to stop any active live before starting a new one
7777
if (checkIfExistingActiveLive()) {
7878
val result = stopLiveLocationShare()
@@ -82,7 +82,8 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
8282
}
8383
val params = StartLiveLocationShareTask.Params(
8484
roomId = roomId,
85-
timeoutMillis = timeoutMillis
85+
timeoutMillis = timeoutMillis,
86+
description = description
8687
)
8788
return startLiveLocationShareTask.execute(params)
8889
}

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.
3030
data class Params(
3131
val roomId: String,
3232
val timeoutMillis: Long,
33+
val description: String,
3334
)
3435
}
3536

@@ -41,6 +42,7 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor(
4142

4243
override suspend fun execute(params: StartLiveLocationShareTask.Params): UpdateLiveLocationShareResult {
4344
val beaconContent = MessageBeaconInfoContent(
45+
body = params.description,
4446
timeout = params.timeoutMillis,
4547
isLive = true,
4648
unstableTimestampMillis = clock.epochMillis()

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.content.Context
2020
import android.graphics.Bitmap
2121
import android.media.MediaMetadataRetriever
2222
import androidx.exifinterface.media.ExifInterface
23+
import org.matrix.android.sdk.api.extensions.ensureNotEmpty
2324
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
2425
import org.matrix.android.sdk.api.session.events.model.Content
2526
import org.matrix.android.sdk.api.session.events.model.Event
@@ -700,6 +701,7 @@ internal class LocalEchoEventFactory @Inject constructor(
700701
MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.")
701702
MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.")
702703
MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.")
704+
MessageType.MSGTYPE_BEACON_INFO -> return TextContent(content.body.ensureNotEmpty() ?: "shared live location.")
703705
MessageType.MSGTYPE_POLL_START -> {
704706
return TextContent((content as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "")
705707
}

matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ private const val A_LATITUDE = 1.4
5151
private const val A_LONGITUDE = 40.0
5252
private const val AN_UNCERTAINTY = 5.0
5353
private const val A_TIMEOUT = 15_000L
54+
private const val A_DESCRIPTION = "description"
5455

5556
@ExperimentalCoroutinesApi
5657
internal class DefaultLocationSharingServiceTest {
@@ -137,7 +138,7 @@ internal class DefaultLocationSharingServiceTest {
137138
coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success("stopped-event-id")
138139
coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
139140

140-
val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT)
141+
val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION)
141142

142143
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
143144
val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params(
@@ -150,7 +151,8 @@ internal class DefaultLocationSharingServiceTest {
150151
coVerify { stopLiveLocationShareTask.execute(expectedStopParams) }
151152
val expectedStartParams = StartLiveLocationShareTask.Params(
152153
roomId = A_ROOM_ID,
153-
timeoutMillis = A_TIMEOUT
154+
timeoutMillis = A_TIMEOUT,
155+
description = A_DESCRIPTION
154156
)
155157
coVerify { startLiveLocationShareTask.execute(expectedStartParams) }
156158
}
@@ -161,7 +163,7 @@ internal class DefaultLocationSharingServiceTest {
161163
val error = Throwable()
162164
coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Failure(error)
163165

164-
val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT)
166+
val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION)
165167

166168
result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error)
167169
val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params(
@@ -179,7 +181,7 @@ internal class DefaultLocationSharingServiceTest {
179181
coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns false
180182
coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
181183

182-
val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT)
184+
val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT, A_DESCRIPTION)
183185

184186
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
185187
val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params(
@@ -188,7 +190,8 @@ internal class DefaultLocationSharingServiceTest {
188190
coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) }
189191
val expectedStartParams = StartLiveLocationShareTask.Params(
190192
roomId = A_ROOM_ID,
191-
timeoutMillis = A_TIMEOUT
193+
timeoutMillis = A_TIMEOUT,
194+
description = A_DESCRIPTION
192195
)
193196
coVerify { startLiveLocationShareTask.execute(expectedStartParams) }
194197
}

matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.test.fakes.FakeSendStateTask
3434
private const val A_USER_ID = "user-id"
3535
private const val A_ROOM_ID = "room-id"
3636
private const val AN_EVENT_ID = "event-id"
37+
private const val A_DESCRIPTION = "description"
3738
private const val A_TIMEOUT = 15_000L
3839
private const val AN_EPOCH = 1655210176L
3940

@@ -58,7 +59,8 @@ internal class DefaultStartLiveLocationShareTaskTest {
5859
fun `given parameters and no error when calling the task then result is success`() = runTest {
5960
val params = StartLiveLocationShareTask.Params(
6061
roomId = A_ROOM_ID,
61-
timeoutMillis = A_TIMEOUT
62+
timeoutMillis = A_TIMEOUT,
63+
description = A_DESCRIPTION
6264
)
6365
fakeClock.givenEpoch(AN_EPOCH)
6466
fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID)
@@ -67,6 +69,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
6769

6870
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
6971
val expectedBeaconContent = MessageBeaconInfoContent(
72+
body = A_DESCRIPTION,
7073
timeout = params.timeoutMillis,
7174
isLive = true,
7275
unstableTimestampMillis = AN_EPOCH
@@ -87,7 +90,8 @@ internal class DefaultStartLiveLocationShareTaskTest {
8790
fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest {
8891
val params = StartLiveLocationShareTask.Params(
8992
roomId = A_ROOM_ID,
90-
timeoutMillis = A_TIMEOUT
93+
timeoutMillis = A_TIMEOUT,
94+
description = A_DESCRIPTION
9195
)
9296
fakeClock.givenEpoch(AN_EPOCH)
9397
fakeSendStateTask.givenExecuteRetryReturns("")
@@ -101,7 +105,8 @@ internal class DefaultStartLiveLocationShareTaskTest {
101105
fun `given parameters and error during event sending when calling the task then result is failure`() = runTest {
102106
val params = StartLiveLocationShareTask.Params(
103107
roomId = A_ROOM_ID,
104-
timeoutMillis = A_TIMEOUT
108+
timeoutMillis = A_TIMEOUT,
109+
description = A_DESCRIPTION
105110
)
106111
fakeClock.givenEpoch(AN_EPOCH)
107112
val error = Throwable()

vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ class TimelineFragment @Inject constructor(
12591259
val nonFormattedBody = when (messageContent) {
12601260
is MessageAudioContent -> getAudioContentBodyText(messageContent)
12611261
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
1262+
is MessageBeaconInfoContent -> getString(R.string.sent_live_location)
12621263
else -> messageContent?.body.orEmpty()
12631264
}
12641265
var formattedBody: CharSequence? = null
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.home.room.detail.timeline.action
18+
19+
import org.matrix.android.sdk.api.session.events.model.EventType
20+
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
21+
import org.matrix.android.sdk.api.session.room.model.message.MessageType
22+
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
23+
import javax.inject.Inject
24+
25+
class CheckIfCanReplyEventUseCase @Inject constructor() {
26+
27+
fun execute(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
28+
// Only EventType.MESSAGE, EventType.POLL_START and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment
29+
if (event.root.getClearType() !in EventType.STATE_ROOM_BEACON_INFO + EventType.POLL_START + EventType.MESSAGE) return false
30+
if (!actionPermissions.canSendMessage) return false
31+
return when (messageContent?.msgType) {
32+
MessageType.MSGTYPE_TEXT,
33+
MessageType.MSGTYPE_NOTICE,
34+
MessageType.MSGTYPE_EMOTE,
35+
MessageType.MSGTYPE_IMAGE,
36+
MessageType.MSGTYPE_VIDEO,
37+
MessageType.MSGTYPE_AUDIO,
38+
MessageType.MSGTYPE_FILE,
39+
MessageType.MSGTYPE_POLL_START,
40+
MessageType.MSGTYPE_BEACON_INFO,
41+
MessageType.MSGTYPE_LOCATION -> true
42+
else -> false
43+
}
44+
}
45+
}

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ class MessageActionsViewModel @AssistedInject constructor(
8080
private val errorFormatter: ErrorFormatter,
8181
private val stringProvider: StringProvider,
8282
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
83-
private val vectorPreferences: VectorPreferences
83+
private val vectorPreferences: VectorPreferences,
84+
private val checkIfCanReplyEventUseCase: CheckIfCanReplyEventUseCase,
8485
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
8586

8687
private val informationData = initialState.informationData
@@ -436,21 +437,7 @@ class MessageActionsViewModel @AssistedInject constructor(
436437
}
437438

438439
private fun canReply(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
439-
// Only EventType.MESSAGE and EventType.POLL_START event types are supported for the moment
440-
if (event.root.getClearType() !in EventType.POLL_START + EventType.MESSAGE) return false
441-
if (!actionPermissions.canSendMessage) return false
442-
return when (messageContent?.msgType) {
443-
MessageType.MSGTYPE_TEXT,
444-
MessageType.MSGTYPE_NOTICE,
445-
MessageType.MSGTYPE_EMOTE,
446-
MessageType.MSGTYPE_IMAGE,
447-
MessageType.MSGTYPE_VIDEO,
448-
MessageType.MSGTYPE_AUDIO,
449-
MessageType.MSGTYPE_FILE,
450-
MessageType.MSGTYPE_POLL_START,
451-
MessageType.MSGTYPE_LOCATION -> true
452-
else -> false
453-
}
440+
return checkIfCanReplyEventUseCase.execute(event, messageContent, actionPermissions)
454441
}
455442

456443
/**

0 commit comments

Comments
 (0)