1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-3210 Chats | Enhancement | Add search field to chat reaction picker (#2497)

This commit is contained in:
Evgenii Kozlov 2025-06-05 16:00:00 +02:00 committed by GitHub
parent f55144794a
commit 8b1e379785
Signed by: github
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 27 deletions

View file

@ -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<List<String>>(emptyList())
val views = combine(default, recentlyUsed) { default, recentlyUsed ->
buildList<ReactionPickerView> {
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
}
}

View file

@ -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<ReactionPickerView> = 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 = {}
)
}

View file

@ -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(