Skip to content

Commit 6fa81b8

Browse files
ChelseaDHfrebib
authored andcommitted
Reaction emoji picker search/filtering
Fixes: element-hq#1611 Co-Authored-by: Chelsea Hilditch <[email protected]> Signed-off-by: Joe Groocock <[email protected]>
1 parent 7bda5fd commit 6fa81b8

File tree

18 files changed

+452
-37
lines changed

18 files changed

+452
-37
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.timeline.aTimelineItemList
2727
import io.element.android.features.messages.impl.timeline.aTimelineState
2828
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
2929
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
30+
import io.element.android.features.messages.impl.timeline.components.customreaction.EmojiPickerState
3031
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents
3132
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
3233
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents
@@ -42,6 +43,7 @@ import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMe
4243
import io.element.android.libraries.architecture.AsyncData
4344
import io.element.android.libraries.designsystem.components.avatar.AvatarData
4445
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
46+
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
4547
import io.element.android.libraries.matrix.api.core.RoomId
4648
import io.element.android.libraries.textcomposer.aRichTextEditorState
4749
import io.element.android.libraries.textcomposer.model.MessageComposerMode
@@ -163,6 +165,7 @@ fun aCustomReactionState(
163165
target = target,
164166
selectedEmoji = persistentSetOf(),
165167
eventSink = eventSink,
168+
searchState = EmojiPickerState(false, false, "", SearchBarResultState.Initial()) {}
166169
)
167170

168171
fun aReadReceiptBottomSheetState(

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
7272
import io.element.android.features.messages.impl.timeline.TimelineView
7373
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
7474
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
75+
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
7576
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents
7677
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView
7778
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheet
@@ -92,6 +93,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
9293
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
9394
import io.element.android.libraries.designsystem.components.button.BackButton
9495
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
96+
import io.element.android.libraries.designsystem.modifiers.applyIf
9597
import io.element.android.libraries.designsystem.preview.ElementPreview
9698
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
9799
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
@@ -332,7 +334,11 @@ private fun MessagesViewContent(
332334
modifier = modifier
333335
.fillMaxSize()
334336
.navigationBarsPadding()
335-
.imePadding(),
337+
.applyIf(
338+
// Disable imePadding() when reaction picker is open to prevent the chat moving behind the bottom sheet
339+
condition = state.customReactionState.target is CustomReactionState.Target.None,
340+
ifTrue = { imePadding() }
341+
)
336342
) {
337343
AttachmentsBottomSheet(
338344
state = state.composerState,

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@
1616

1717
package io.element.android.features.messages.impl.timeline.components.customreaction
1818

19+
import androidx.compose.foundation.gestures.awaitEachGesture
20+
import androidx.compose.foundation.gestures.awaitFirstDown
1921
import androidx.compose.foundation.layout.fillMaxSize
22+
import androidx.compose.foundation.layout.heightIn
2023
import androidx.compose.material3.ExperimentalMaterial3Api
2124
import androidx.compose.material3.rememberModalBottomSheetState
2225
import androidx.compose.runtime.Composable
2326
import androidx.compose.runtime.rememberCoroutineScope
2427
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.input.pointer.pointerInput
29+
import androidx.compose.ui.platform.LocalConfiguration
30+
import androidx.compose.ui.platform.LocalView
31+
import androidx.compose.ui.unit.Dp
32+
import androidx.compose.ui.unit.dp
2533
import io.element.android.emojibasebindings.Emoji
34+
import io.element.android.libraries.androidutils.ui.hideKeyboard
2635
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
2736
import io.element.android.libraries.designsystem.theme.components.hide
2837
import io.element.android.libraries.matrix.api.core.EventId
@@ -34,15 +43,18 @@ fun CustomReactionBottomSheet(
3443
onEmojiSelected: (EventId, Emoji) -> Unit,
3544
modifier: Modifier = Modifier,
3645
) {
37-
val sheetState = rememberModalBottomSheetState()
46+
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = state.searchState.startActive)
3847
val coroutineScope = rememberCoroutineScope()
3948
val target = state.target as? CustomReactionState.Target.Success
49+
val localView = LocalView.current
4050

4151
fun onDismiss() {
52+
localView.hideKeyboard()
4253
state.eventSink(CustomReactionEvents.DismissCustomReactionSheet)
4354
}
4455

4556
fun onEmojiSelectedDismiss(emoji: Emoji) {
57+
localView.hideKeyboard()
4658
if (target?.event?.eventId == null) return
4759
sheetState.hide(coroutineScope) {
4860
state.eventSink(CustomReactionEvents.DismissCustomReactionSheet)
@@ -55,11 +67,22 @@ fun CustomReactionBottomSheet(
5567
onDismissRequest = ::onDismiss,
5668
sheetState = sheetState,
5769
modifier = modifier
70+
.heightIn(min = if (state.searchState.isSearchActive) (LocalConfiguration.current.screenHeightDp).dp else Dp.Unspecified)
71+
.pointerInput(state.searchState.isSearchActive) {
72+
awaitEachGesture {
73+
// For any unconsumed pointer event in this sheet, deactivate the search field and hide the keyboard
74+
awaitFirstDown(requireUnconsumed = true)
75+
if (state.searchState.isSearchActive) {
76+
state.searchState.eventSink(EmojiPickerEvents.OnSearchActiveChanged(false))
77+
}
78+
}
79+
}
5880
) {
5981
EmojiPicker(
6082
onEmojiSelected = ::onEmojiSelectedDismiss,
6183
emojibaseStore = target.emojibaseStore,
6284
selectedEmojis = state.selectedEmoji,
85+
state = state.searchState,
6386
modifier = Modifier.fillMaxSize(),
6487
)
6588
}

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

+9-2
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,23 @@ import androidx.compose.runtime.mutableStateOf
2222
import androidx.compose.runtime.remember
2323
import androidx.compose.runtime.rememberCoroutineScope
2424
import io.element.android.features.messages.impl.timeline.model.TimelineItem
25+
import io.element.android.features.preferences.api.store.SessionPreferencesStore
26+
import io.element.android.libraries.androidutils.ui.hideKeyboard
2527
import io.element.android.libraries.architecture.Presenter
2628
import kotlinx.collections.immutable.toImmutableSet
2729
import kotlinx.coroutines.launch
2830
import javax.inject.Inject
2931

3032
class CustomReactionPresenter @Inject constructor(
31-
private val emojibaseProvider: EmojibaseProvider
33+
private val emojibaseProvider: EmojibaseProvider,
34+
private val emojiPickerStatePresenter: EmojiPickerStatePresenter,
3235
) : Presenter<CustomReactionState> {
3336
@Composable
3437
override fun present(): CustomReactionState {
3538
val target: MutableState<CustomReactionState.Target> = remember {
3639
mutableStateOf(CustomReactionState.Target.None)
3740
}
41+
val searchState = emojiPickerStatePresenter.present()
3842

3943
val localCoroutineScope = rememberCoroutineScope()
4044
fun handleShowCustomReactionSheet(event: TimelineItem.Event) {
@@ -49,6 +53,7 @@ class CustomReactionPresenter @Inject constructor(
4953

5054
fun handleDismissCustomReactionSheet() {
5155
target.value = CustomReactionState.Target.None
56+
searchState.eventSink(EmojiPickerEvents.Reset)
5257
}
5358

5459
fun handleEvents(event: CustomReactionEvents) {
@@ -57,6 +62,7 @@ class CustomReactionPresenter @Inject constructor(
5762
is CustomReactionEvents.DismissCustomReactionSheet -> handleDismissCustomReactionSheet()
5863
}
5964
}
65+
6066
val event = (target.value as? CustomReactionState.Target.Success)?.event
6167
val selectedEmoji = event
6268
?.reactionsState
@@ -67,7 +73,8 @@ class CustomReactionPresenter @Inject constructor(
6773
return CustomReactionState(
6874
target = target.value,
6975
selectedEmoji = selectedEmoji,
70-
eventSink = { handleEvents(it) }
76+
eventSink = { handleEvents(it) },
77+
searchState = searchState,
7178
)
7279
}
7380
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ data class CustomReactionState(
2424
val target: Target,
2525
val selectedEmoji: ImmutableSet<String>,
2626
val eventSink: (CustomReactionEvents) -> Unit,
27+
val searchState: EmojiPickerState,
2728
) {
2829
sealed interface Target {
2930
data object None : Target

0 commit comments

Comments
 (0)