Skip to content

Commit db1e2f3

Browse files
committed
Use shared recent emoji reactions from account data
- Add `AddRecentEmoji` and `GetRecentEmojis` use cases to avoid injecting the whole `MatrixClient` for just one of these operations. - Update the UI and logic of the emoji picker and message context menu to include the recent emojis. - Add `CoroutineDispatchers.Default` with the defaults coroutines to use in the app for ease of use.
1 parent d391be7 commit db1e2f3

File tree

25 files changed

+532
-168
lines changed

25 files changed

+532
-168
lines changed

app/src/main/kotlin/io/element/android/x/di/AppModule.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import io.element.android.x.BuildConfig
3333
import io.element.android.x.R
3434
import kotlinx.coroutines.CoroutineName
3535
import kotlinx.coroutines.CoroutineScope
36-
import kotlinx.coroutines.Dispatchers
3736
import kotlinx.coroutines.MainScope
3837
import kotlinx.coroutines.plus
3938
import java.io.File
@@ -107,11 +106,7 @@ object AppModule {
107106
@Provides
108107
@SingleIn(AppScope::class)
109108
fun providesCoroutineDispatchers(): CoroutineDispatchers {
110-
return CoroutineDispatchers(
111-
io = Dispatchers.IO,
112-
computation = Dispatchers.Default,
113-
main = Dispatchers.Main,
114-
)
109+
return CoroutineDispatchers.Default
115110
}
116111

117112
@Provides

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
5757
import io.element.android.libraries.architecture.AsyncData
5858
import io.element.android.libraries.architecture.Presenter
5959
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
60+
import io.element.android.libraries.core.extensions.flatMap
6061
import io.element.android.libraries.core.extensions.runCatchingExceptions
6162
import io.element.android.libraries.core.meta.BuildMeta
6263
import io.element.android.libraries.designsystem.components.avatar.AvatarData
@@ -70,6 +71,7 @@ import io.element.android.libraries.matrix.api.core.toThreadId
7071
import io.element.android.libraries.matrix.api.encryption.EncryptionService
7172
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
7273
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
74+
import io.element.android.libraries.matrix.api.recentemojis.AddRecentEmoji
7375
import io.element.android.libraries.matrix.api.room.JoinedRoom
7476
import io.element.android.libraries.matrix.api.room.MessageEventType
7577
import io.element.android.libraries.matrix.api.room.RoomInfo
@@ -121,6 +123,7 @@ class MessagesPresenter(
121123
private val analyticsService: AnalyticsService,
122124
private val encryptionService: EncryptionService,
123125
private val featureFlagService: FeatureFlagService,
126+
private val addRecentEmoji: AddRecentEmoji,
124127
) : Presenter<MessagesState> {
125128
@AssistedFactory
126129
interface Factory {
@@ -398,6 +401,7 @@ class MessagesPresenter(
398401
) = launch(dispatchers.io) {
399402
timelineController.invokeOnCurrentTimeline {
400403
toggleReaction(emoji, eventOrTransactionId)
404+
.flatMap { added -> if (added) addRecentEmoji(emoji) else Result.success(Unit) }
401405
.onFailure { Timber.e(it) }
402406
}
403407
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
4949
import io.element.android.libraries.matrix.api.timeline.Timeline
5050
import io.element.android.libraries.textcomposer.model.MessageComposerMode
5151
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
52+
import kotlinx.collections.immutable.ImmutableList
5253
import kotlinx.collections.immutable.persistentListOf
5354
import kotlinx.collections.immutable.persistentSetOf
5455

@@ -186,9 +187,11 @@ fun aReactionSummaryState(
186187

187188
fun aCustomReactionState(
188189
target: CustomReactionState.Target = CustomReactionState.Target.None,
190+
recentEmojis: ImmutableList<String> = persistentListOf(),
189191
eventSink: (CustomReactionEvents) -> Unit = {},
190192
) = CustomReactionState(
191193
target = target,
194+
recentEmojis = recentEmojis,
192195
selectedEmoji = persistentSetOf(),
193196
eventSink = eventSink,
194197
)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import io.element.android.libraries.di.RoomScope
4343
import io.element.android.libraries.featureflag.api.FeatureFlagService
4444
import io.element.android.libraries.featureflag.api.FeatureFlags
4545
import io.element.android.libraries.matrix.api.core.EventId
46+
import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis
4647
import io.element.android.libraries.matrix.api.room.BaseRoom
4748
import io.element.android.libraries.matrix.api.timeline.Timeline
4849
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@@ -73,6 +74,7 @@ class DefaultActionListPresenter(
7374
private val userSendFailureFactory: VerifiedUserSendFailureFactory,
7475
private val dateFormatter: DateFormatter,
7576
private val featureFlagService: FeatureFlagService,
77+
private val getRecentEmojis: GetRecentEmojis,
7678
) : ActionListPresenter {
7779
@AssistedFactory
7880
@ContributesBinding(RoomScope::class)
@@ -153,14 +155,15 @@ class DefaultActionListPresenter(
153155
),
154156
displayEmojiReactions = displayEmojiReactions,
155157
verifiedUserSendFailure = verifiedUserSendFailure,
156-
actions = actions.toImmutableList()
158+
actions = actions.toImmutableList(),
159+
recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf()
157160
)
158161
} else {
159162
target.value = ActionListState.Target.None
160163
}
161164
}
162165

163-
private suspend fun buildActions(
166+
private fun buildActions(
164167
timelineItem: TimelineItem.Event,
165168
usersEventPermissions: UserEventPermissions,
166169
isDeveloperModeEnabled: Boolean,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ data class ActionListState(
2626
val event: TimelineItem.Event,
2727
val sentTimeFull: String,
2828
val displayEmojiReactions: Boolean,
29+
val recentEmojis: ImmutableList<String>,
2930
val verifiedUserSendFailure: VerifiedUserSendFailure,
3031
val actions: ImmutableList<TimelineItemAction>,
3132
) : Target

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
2323
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
2424
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
2525
import kotlinx.collections.immutable.ImmutableList
26+
import kotlinx.collections.immutable.persistentListOf
2627
import kotlinx.collections.immutable.toPersistentList
2728

2829
open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
@@ -41,6 +42,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
4142
displayEmojiReactions = true,
4243
verifiedUserSendFailure = VerifiedUserSendFailure.None,
4344
actions = aTimelineItemActionList(),
45+
recentEmojis = persistentListOf(),
4446
)
4547
),
4648
anActionListState(
@@ -56,6 +58,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
5658
actions = aTimelineItemActionList(
5759
copyAction = TimelineItemAction.CopyCaption,
5860
),
61+
recentEmojis = persistentListOf(),
5962
)
6063
),
6164
anActionListState(
@@ -70,6 +73,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
7073
actions = aTimelineItemActionList(
7174
copyAction = TimelineItemAction.CopyCaption,
7275
),
76+
recentEmojis = persistentListOf(),
7377
)
7478
),
7579
anActionListState(
@@ -84,6 +88,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
8488
actions = aTimelineItemActionList(
8589
copyAction = null,
8690
),
91+
recentEmojis = persistentListOf(),
8792
)
8893
),
8994
anActionListState(
@@ -98,6 +103,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
98103
actions = aTimelineItemActionList(
99104
copyAction = TimelineItemAction.CopyCaption,
100105
),
106+
recentEmojis = persistentListOf(),
101107
)
102108
),
103109
anActionListState(
@@ -112,6 +118,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
112118
actions = aTimelineItemActionList(
113119
copyAction = null,
114120
),
121+
recentEmojis = persistentListOf(),
115122
)
116123
),
117124
anActionListState(
@@ -124,6 +131,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
124131
displayEmojiReactions = true,
125132
verifiedUserSendFailure = VerifiedUserSendFailure.None,
126133
actions = aTimelineItemActionList(),
134+
recentEmojis = persistentListOf(),
127135
)
128136
),
129137
anActionListState(
@@ -136,6 +144,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
136144
displayEmojiReactions = false,
137145
verifiedUserSendFailure = VerifiedUserSendFailure.None,
138146
actions = aTimelineItemActionList(),
147+
recentEmojis = persistentListOf(),
139148
),
140149
),
141150
anActionListState(
@@ -148,6 +157,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
148157
displayEmojiReactions = false,
149158
verifiedUserSendFailure = VerifiedUserSendFailure.None,
150159
actions = aTimelineItemPollActionList(),
160+
recentEmojis = persistentListOf(),
151161
),
152162
),
153163
anActionListState(
@@ -160,6 +170,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
160170
displayEmojiReactions = true,
161171
verifiedUserSendFailure = VerifiedUserSendFailure.None,
162172
actions = aTimelineItemActionList(),
173+
recentEmojis = persistentListOf(),
163174
)
164175
),
165176
anActionListState(
@@ -169,6 +180,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
169180
displayEmojiReactions = true,
170181
verifiedUserSendFailure = anUnsignedDeviceSendFailure(),
171182
actions = aTimelineItemActionList(),
183+
recentEmojis = persistentListOf(),
172184
)
173185
),
174186
)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
1313
import androidx.compose.foundation.layout.Arrangement
1414
import androidx.compose.foundation.layout.Box
1515
import androidx.compose.foundation.layout.Column
16+
import androidx.compose.foundation.layout.PaddingValues
1617
import androidx.compose.foundation.layout.Row
1718
import androidx.compose.foundation.layout.Spacer
1819
import androidx.compose.foundation.layout.fillMaxWidth
1920
import androidx.compose.foundation.layout.height
2021
import androidx.compose.foundation.layout.imePadding
2122
import androidx.compose.foundation.layout.navigationBarsPadding
2223
import androidx.compose.foundation.layout.padding
24+
import androidx.compose.foundation.layout.requiredSize
2325
import androidx.compose.foundation.layout.size
2426
import androidx.compose.foundation.layout.width
2527
import androidx.compose.foundation.lazy.LazyColumn
28+
import androidx.compose.foundation.lazy.LazyRow
2629
import androidx.compose.foundation.lazy.items
2730
import androidx.compose.foundation.shape.CircleShape
2831
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -35,6 +38,10 @@ import androidx.compose.runtime.remember
3538
import androidx.compose.runtime.rememberCoroutineScope
3639
import androidx.compose.ui.Alignment
3740
import androidx.compose.ui.Modifier
41+
import androidx.compose.ui.draw.drawWithContent
42+
import androidx.compose.ui.geometry.Offset
43+
import androidx.compose.ui.geometry.Size
44+
import androidx.compose.ui.graphics.Brush
3845
import androidx.compose.ui.graphics.Color
3946
import androidx.compose.ui.platform.LocalContext
4047
import androidx.compose.ui.res.stringResource
@@ -90,6 +97,7 @@ import io.element.android.libraries.matrix.ui.messages.sender.SenderName
9097
import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode
9198
import io.element.android.libraries.ui.strings.CommonStrings
9299
import kotlinx.collections.immutable.ImmutableList
100+
import kotlinx.collections.immutable.persistentListOf
93101

94102
@OptIn(ExperimentalMaterial3Api::class)
95103
@Composable
@@ -218,6 +226,7 @@ private fun ActionListViewContent(
218226
if (target.displayEmojiReactions) {
219227
item {
220228
EmojiReactionsRow(
229+
recentEmojis = target.recentEmojis,
221230
highlightedEmojis = target.event.reactionsState.highlightedKeys,
222231
onEmojiReactionClick = onEmojiReactionClick,
223232
onCustomReactionClick = onCustomReactionClick,
@@ -338,40 +347,65 @@ private val emojiRippleRadius = 24.dp
338347

339348
@Composable
340349
private fun EmojiReactionsRow(
350+
recentEmojis: ImmutableList<String>,
341351
highlightedEmojis: ImmutableList<String>,
342352
onEmojiReactionClick: (String) -> Unit,
343353
onCustomReactionClick: () -> Unit,
344354
modifier: Modifier = Modifier,
345355
) {
346356
Row(
347-
horizontalArrangement = Arrangement.SpaceBetween,
348-
modifier = modifier.padding(horizontal = 24.dp, vertical = 16.dp)
357+
modifier = modifier.padding(end = 16.dp, top = 16.dp, bottom = 16.dp),
349358
) {
350-
// TODO use most recently used emojis here when available from the Rust SDK
351-
val defaultEmojis = sequenceOf(
352-
"👍️",
353-
"👎️",
354-
"🔥",
355-
"❤️",
356-
"👏"
357-
)
358-
for (emoji in defaultEmojis) {
359-
val isHighlighted = highlightedEmojis.contains(emoji)
360-
EmojiButton(
361-
modifier = Modifier
362-
// Make it appear after the more useful actions for the accessibility service
363-
.semantics {
364-
traversalIndex = 1f
365-
},
366-
emoji = emoji,
367-
isHighlighted = isHighlighted,
368-
onClick = onEmojiReactionClick
369-
)
359+
val backgroundColor = ElementTheme.colors.bgCanvasDefault
360+
361+
val emojis = remember(recentEmojis.isEmpty()) {
362+
if (recentEmojis.isEmpty()) {
363+
persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
364+
} else {
365+
recentEmojis.take(50)
366+
}
370367
}
371-
Box(
368+
369+
LazyRow(
372370
modifier = Modifier
373-
.size(48.dp),
374-
contentAlignment = Alignment.Center,
371+
.weight(1f, fill = true)
372+
.drawWithContent {
373+
val gradientWidth = 24.dp.toPx()
374+
val width = size.width
375+
drawContent()
376+
377+
drawRect(
378+
brush = Brush.horizontalGradient(
379+
0.0f to Color.Transparent,
380+
1.0f to backgroundColor,
381+
startX = width - gradientWidth,
382+
endX = width,
383+
),
384+
topLeft = Offset(width - gradientWidth, 0f),
385+
size = Size(gradientWidth, size.height)
386+
)
387+
},
388+
contentPadding = PaddingValues(horizontal = 16.dp),
389+
horizontalArrangement = Arrangement.spacedBy(8.dp),
390+
) {
391+
items(emojis) { emoji ->
392+
val isHighlighted = highlightedEmojis.contains(emoji)
393+
EmojiButton(
394+
modifier = Modifier
395+
// Make it appear after the more useful actions for the accessibility service
396+
.semantics {
397+
traversalIndex = 1f
398+
},
399+
emoji = emoji,
400+
isHighlighted = isHighlighted,
401+
onClick = onEmojiReactionClick
402+
)
403+
}
404+
}
405+
406+
Box(
407+
modifier = Modifier.padding(end = 10.dp).requiredSize(48.dp),
408+
contentAlignment = Alignment.CenterEnd,
375409
) {
376410
Icon(
377411
imageVector = CompoundIcons.ReactionAdd(),

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
1717
import io.element.android.emojibasebindings.Emoji
1818
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.EmojiPicker
1919
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.EmojiPickerPresenter
20+
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
2021
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
2122
import io.element.android.libraries.designsystem.theme.components.hide
2223
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
@@ -50,7 +51,13 @@ fun CustomReactionBottomSheet(
5051
sheetState = sheetState,
5152
modifier = modifier
5253
) {
53-
val presenter = remember { EmojiPickerPresenter(target.emojibaseStore) }
54+
val presenter = remember {
55+
EmojiPickerPresenter(
56+
emojibaseStore = target.emojibaseStore,
57+
recentEmojis = state.recentEmojis,
58+
coroutineDispatchers = CoroutineDispatchers.Default,
59+
)
60+
}
5461
EmojiPicker(
5562
onSelectEmoji = ::onEmojiSelectedDismiss,
5663
state = presenter.present(),

0 commit comments

Comments
 (0)