From 8b1e379785e1936b9d7d355d9a81bf4656b06597 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Thu, 5 Jun 2025 16:00:00 +0200 Subject: [PATCH] DROID-3210 Chats | Enhancement | Add search field to chat reaction picker (#2497) --- .../ui/chats/SelectChatReactionFragment.kt | 4 +- .../SelectChatReactionViewModel.kt | 53 +++++++++++++++++-- .../feature_chats/ui/ChatReactionPicker.kt | 22 ++++++-- .../anytype/feature_chats/ui/ChatScreen.kt | 16 ------ 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/anytypeio/anytype/ui/chats/SelectChatReactionFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/chats/SelectChatReactionFragment.kt index 0d4e226397..e0a9ebd700 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/chats/SelectChatReactionFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/chats/SelectChatReactionFragment.kt @@ -20,7 +20,6 @@ import com.anytypeio.anytype.feature_chats.presentation.SelectChatReactionViewMo import com.anytypeio.anytype.feature_chats.ui.SelectChatReactionScreen import com.anytypeio.anytype.ui.settings.typography import javax.inject.Inject -import kotlin.getValue class SelectChatReactionFragment : BaseBottomSheetComposeFragment() { @@ -44,7 +43,8 @@ class SelectChatReactionFragment : BaseBottomSheetComposeFragment() { ) { SelectChatReactionScreen( views = vm.views.collectAsStateWithLifecycle(initialValue = emptyList()).value, - onEmojiClicked = vm::onEmojiClicked + onEmojiClicked = vm::onEmojiClicked, + onQueryChanged = vm::onQueryChanged ) LaunchedEffect(Unit) { vm.isDismissed.collect { isDismissed -> diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/SelectChatReactionViewModel.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/SelectChatReactionViewModel.kt index ca616329e0..f17cbb5108 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/SelectChatReactionViewModel.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/presentation/SelectChatReactionViewModel.kt @@ -16,9 +16,17 @@ import com.anytypeio.anytype.emojifier.data.EmojiProvider import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester import com.anytypeio.anytype.presentation.common.BaseViewModel import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber @@ -42,9 +50,36 @@ class SelectChatReactionViewModel @Inject constructor( private val recentlyUsed = MutableStateFlow>(emptyList()) - val views = combine(default, recentlyUsed) { default, recentlyUsed -> - buildList { - if (recentlyUsed.isNotEmpty()) { + private val rawQuery = MutableStateFlow("") + + @OptIn(FlowPreview::class) + private val debouncedQuery = rawQuery + .debounce(DEBOUNCE_DURATION) + .distinctUntilChanged() + .onStart { emit(rawQuery.value) } + + @OptIn(ExperimentalCoroutinesApi::class) + private val queries = debouncedQuery.flatMapLatest { query -> + flow { + val emojis = if (query.isEmpty()) { + emptyList() + } else { + suggester.search(query).map { result -> + ReactionPickerView.Emoji( + unicode = result.emoji, + page = -1, + index = -1, + emojified = Emojifier.safeUri(result.emoji) + ) + } + } + emit(query to emojis) + } + }.flowOn(dispatchers.io) + + val views = combine(default, recentlyUsed, queries) { default, recentlyUsed, (query, results) -> + buildList { + if (query.isEmpty() && recentlyUsed.isNotEmpty()) { add(ReactionPickerView.RecentUsedSection) addAll( recentlyUsed.map { unicode -> @@ -57,11 +92,16 @@ class SelectChatReactionViewModel @Inject constructor( } ) } - addAll(default) + if (query.isEmpty()) { + addAll(default) + } else { + addAll(results) + } } } init { + viewModelScope.launch { observeRecentlyUsedChatReactions .flow() @@ -126,6 +166,10 @@ class SelectChatReactionViewModel @Inject constructor( } } + fun onQueryChanged(input: String) { + rawQuery.value = input + } + class Factory @Inject constructor( private val params: Params, private val emojiProvider: EmojiProvider, @@ -165,5 +209,6 @@ class SelectChatReactionViewModel @Inject constructor( companion object { const val MAX_RECENTLY_USED_COUNT = 20 + const val DEBOUNCE_DURATION = 300L } } \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionPicker.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionPicker.kt index 6350fed19e..bfe2cbcc6f 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionPicker.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatReactionPicker.kt @@ -2,6 +2,8 @@ package com.anytypeio.anytype.feature_chats.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -25,6 +27,7 @@ import com.anytypeio.anytype.core_ui.common.DefaultPreviews import com.anytypeio.anytype.core_ui.foundation.Dragger import com.anytypeio.anytype.core_ui.foundation.noRippleClickable import com.anytypeio.anytype.core_ui.views.Caption1Medium +import com.anytypeio.anytype.core_ui.widgets.SearchField import com.anytypeio.anytype.emojifier.data.Emoji import com.anytypeio.anytype.feature_chats.R import com.anytypeio.anytype.feature_chats.presentation.SelectChatReactionViewModel.ReactionPickerView @@ -35,9 +38,10 @@ import com.bumptech.glide.integration.compose.GlideImage @Composable fun SelectChatReactionScreen( views: List = emptyList(), - onEmojiClicked: (String) -> Unit + onEmojiClicked: (String) -> Unit, + onQueryChanged: (String) -> Unit ) { - Box( + Column( modifier = Modifier .fillMaxSize() .nestedScroll(rememberNestedScrollInteropConnection()) @@ -45,14 +49,21 @@ fun SelectChatReactionScreen( Dragger( modifier = Modifier .padding(vertical = 6.dp) - .align(Alignment.TopCenter) + .align(Alignment.CenterHorizontally) ) + Spacer(modifier = Modifier.height(6.dp)) + SearchField( + onQueryChanged = onQueryChanged, + onFocused = { + // Do nothing + } + ) + Spacer(modifier = Modifier.height(12.dp)) LazyVerticalGrid( columns = GridCells.Fixed(6), modifier = Modifier .fillMaxSize() .padding( - top = 16.dp, start = 16.dp, end = 16.dp ), @@ -149,6 +160,7 @@ fun PickerPreview() { ) ) }, - onEmojiClicked = {} + onEmojiClicked = {}, + onQueryChanged = {} ) } \ No newline at end of file diff --git a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt index 3fea041ed0..0976271c05 100644 --- a/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt +++ b/feature-chats/src/main/java/com/anytypeio/anytype/feature_chats/ui/ChatScreen.kt @@ -114,7 +114,6 @@ fun ChatScreenWrapper( onViewChatReaction: (Id, String) -> Unit ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false) - var showReactionSheet by remember { mutableStateOf(false) } var showSendRateLimitWarning by remember { mutableStateOf(false) } val context = LocalContext.current Box( @@ -262,21 +261,6 @@ fun ChatScreenWrapper( } } } - if (showReactionSheet) { - ModalBottomSheet( - onDismissRequest = { - showReactionSheet = false - }, - sheetState = sheetState, - containerColor = colorResource(id = R.color.background_secondary), - shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), - dragHandle = null - ) { - SelectChatReactionScreen( - onEmojiClicked = {} - ) - } - } if (showSendRateLimitWarning) { ModalBottomSheet(