feat: implement group members caching and enhance emoji picker functionality in GroupSetupScreen
This commit is contained in:
@@ -149,6 +149,40 @@ private data class GroupSharedStats(
|
||||
val linksCount: Int = 0
|
||||
)
|
||||
|
||||
private data class GroupMembersCacheEntry(
|
||||
val members: List<String>,
|
||||
val memberInfoByKey: Map<String, SearchUser>,
|
||||
val updatedAtMs: Long
|
||||
)
|
||||
|
||||
private object GroupMembersMemoryCache {
|
||||
private const val TTL_MS = 90_000L
|
||||
private val cache = mutableMapOf<String, GroupMembersCacheEntry>()
|
||||
|
||||
fun getAny(key: String): GroupMembersCacheEntry? = synchronized(cache) { cache[key] }
|
||||
|
||||
fun getFresh(key: String): GroupMembersCacheEntry? = synchronized(cache) {
|
||||
val entry = cache[key] ?: return@synchronized null
|
||||
if (System.currentTimeMillis() - entry.updatedAtMs <= TTL_MS) entry else null
|
||||
}
|
||||
|
||||
fun put(key: String, members: List<String>, memberInfoByKey: Map<String, SearchUser>) {
|
||||
if (key.isBlank()) return
|
||||
synchronized(cache) {
|
||||
cache[key] =
|
||||
GroupMembersCacheEntry(
|
||||
members = members,
|
||||
memberInfoByKey = memberInfoByKey,
|
||||
updatedAtMs = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(key: String) {
|
||||
synchronized(cache) { cache.remove(key) }
|
||||
}
|
||||
}
|
||||
|
||||
private data class GroupMediaItem(
|
||||
val key: String,
|
||||
val attachment: MessageAttachment,
|
||||
@@ -249,9 +283,13 @@ fun GroupInfoScreen(
|
||||
var isMuted by remember { mutableStateOf(false) }
|
||||
var showAddMembersPicker by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
|
||||
var pendingInviteText by rememberSaveable(dialogPublicKey) { mutableStateOf("") }
|
||||
var isRefreshingMembers by remember(dialogPublicKey) { mutableStateOf(false) }
|
||||
|
||||
var members by remember(dialogPublicKey) { mutableStateOf<List<String>>(emptyList()) }
|
||||
val memberInfoByKey = remember(dialogPublicKey) { mutableStateMapOf<String, SearchUser>() }
|
||||
val membersCacheKey = remember(currentUserPublicKey, normalizedGroupId) {
|
||||
"${currentUserPublicKey.trim().lowercase()}|${normalizedGroupId.trim().lowercase()}"
|
||||
}
|
||||
|
||||
val groupEntity by produceState<com.rosetta.messenger.database.GroupEntity?>(
|
||||
initialValue = null,
|
||||
@@ -341,41 +379,76 @@ fun GroupInfoScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshMembers() {
|
||||
fun refreshMembers(force: Boolean = false, showLoader: Boolean = true) {
|
||||
if (normalizedGroupId.isBlank()) return
|
||||
if (isRefreshingMembers && !force) return
|
||||
|
||||
scope.launch {
|
||||
membersLoading = true
|
||||
val fetchedMembers = withContext(Dispatchers.IO) {
|
||||
groupRepository.requestGroupMembers(normalizedGroupId).orEmpty()
|
||||
if (!force) {
|
||||
GroupMembersMemoryCache.getFresh(membersCacheKey)?.let { cached ->
|
||||
members = cached.members
|
||||
memberInfoByKey.clear()
|
||||
memberInfoByKey.putAll(cached.memberInfoByKey)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
members = fetchedMembers.distinct()
|
||||
membersLoading = false
|
||||
|
||||
if (members.isEmpty()) return@launch
|
||||
val hasAnyCache = GroupMembersMemoryCache.getAny(membersCacheKey) != null
|
||||
val shouldShowLoader = showLoader && members.isEmpty() && !hasAnyCache
|
||||
if (shouldShowLoader) membersLoading = true
|
||||
isRefreshingMembers = true
|
||||
try {
|
||||
val fetchedMembers = withContext(Dispatchers.IO) {
|
||||
groupRepository.requestGroupMembers(normalizedGroupId).orEmpty()
|
||||
}
|
||||
val distinctMembers = fetchedMembers.distinct()
|
||||
if (distinctMembers.isNotEmpty() || members.isEmpty()) {
|
||||
members = distinctMembers
|
||||
}
|
||||
|
||||
val resolvedUsers = withContext(Dispatchers.IO) {
|
||||
val resolvedMap = LinkedHashMap<String, SearchUser>()
|
||||
members.forEach { memberKey ->
|
||||
val cached = ProtocolManager.getCachedUserInfo(memberKey)
|
||||
if (cached != null) {
|
||||
resolvedMap[memberKey] = cached
|
||||
} else {
|
||||
ProtocolManager.resolveUserInfo(memberKey, timeoutMs = 2500L)?.let { resolvedUser ->
|
||||
resolvedMap[memberKey] = resolvedUser
|
||||
if (members.isEmpty()) return@launch
|
||||
|
||||
val resolvedUsers = withContext(Dispatchers.IO) {
|
||||
val resolvedMap = LinkedHashMap<String, SearchUser>()
|
||||
members.forEach { memberKey ->
|
||||
val cached = ProtocolManager.getCachedUserInfo(memberKey)
|
||||
if (cached != null) {
|
||||
resolvedMap[memberKey] = cached
|
||||
} else {
|
||||
ProtocolManager.resolveUserInfo(memberKey, timeoutMs = 2500L)?.let { resolvedUser ->
|
||||
resolvedMap[memberKey] = resolvedUser
|
||||
}
|
||||
}
|
||||
}
|
||||
resolvedMap
|
||||
}
|
||||
resolvedMap
|
||||
}
|
||||
if (resolvedUsers.isNotEmpty()) {
|
||||
memberInfoByKey.putAll(resolvedUsers)
|
||||
if (resolvedUsers.isNotEmpty()) {
|
||||
memberInfoByKey.putAll(resolvedUsers)
|
||||
}
|
||||
|
||||
GroupMembersMemoryCache.put(
|
||||
key = membersCacheKey,
|
||||
members = members,
|
||||
memberInfoByKey = memberInfoByKey.toMap()
|
||||
)
|
||||
} finally {
|
||||
if (shouldShowLoader) membersLoading = false
|
||||
isRefreshingMembers = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(normalizedGroupId) {
|
||||
refreshMembers()
|
||||
LaunchedEffect(membersCacheKey) {
|
||||
val cachedEntry = GroupMembersMemoryCache.getAny(membersCacheKey)
|
||||
cachedEntry?.let { cached ->
|
||||
members = cached.members
|
||||
memberInfoByKey.clear()
|
||||
memberInfoByKey.putAll(cached.memberInfoByKey)
|
||||
}
|
||||
|
||||
if (GroupMembersMemoryCache.getFresh(membersCacheKey) == null) {
|
||||
refreshMembers(force = true, showLoader = cachedEntry == null)
|
||||
}
|
||||
}
|
||||
|
||||
val onlineCount by remember(members, memberInfoByKey) {
|
||||
@@ -541,6 +614,7 @@ fun GroupInfoScreen(
|
||||
}
|
||||
isLeaving = false
|
||||
if (left) {
|
||||
GroupMembersMemoryCache.remove(membersCacheKey)
|
||||
onGroupLeft()
|
||||
} else {
|
||||
Toast.makeText(context, "Failed to leave group", Toast.LENGTH_SHORT).show()
|
||||
@@ -580,7 +654,12 @@ fun GroupInfoScreen(
|
||||
if (removed) {
|
||||
members = members.filterNot { it.trim().equals(memberKey, ignoreCase = true) }
|
||||
memberInfoByKey.remove(member.publicKey)
|
||||
refreshMembers()
|
||||
GroupMembersMemoryCache.put(
|
||||
key = membersCacheKey,
|
||||
members = members,
|
||||
memberInfoByKey = memberInfoByKey.toMap()
|
||||
)
|
||||
refreshMembers(force = true, showLoader = false)
|
||||
Toast.makeText(context, "Member removed", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "Failed to remove member", Toast.LENGTH_SHORT).show()
|
||||
@@ -1117,42 +1196,85 @@ fun GroupInfoScreen(
|
||||
|
||||
if (showEncryptionDialog) {
|
||||
val displayLines = remember(encryptionKey) { encodeGroupKeyForDisplay(encryptionKey) }
|
||||
val keyImagePalette = if (isDarkTheme) {
|
||||
listOf(
|
||||
Color(0xFF2B4F78),
|
||||
Color(0xFF2F5F90),
|
||||
Color(0xFF3D74A8),
|
||||
Color(0xFF4E89BE),
|
||||
Color(0xFF64A0D6)
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
Color(0xFFD5E8FF),
|
||||
Color(0xFFBBD9FF),
|
||||
Color(0xFFA1CAFF),
|
||||
Color(0xFF87BAFF),
|
||||
Color(0xFF6EA9F4)
|
||||
)
|
||||
}
|
||||
val keyCardColor = if (isDarkTheme) Color(0xFF1F1F22) else Color(0xFFF7F9FC)
|
||||
val keyCodeColor = if (isDarkTheme) Color(0xFFC7D6EA) else Color(0xFF34495E)
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { showEncryptionDialog = false },
|
||||
title = { Text("Encryption key") },
|
||||
containerColor = cardColor,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
title = {
|
||||
Text(
|
||||
text = "Encryption key",
|
||||
color = primaryText,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Box(
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
color = keyCardColor,
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
DesktopStyleKeyImage(
|
||||
keyRender = encryptionKey,
|
||||
size = 180.dp,
|
||||
radius = 14.dp
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
SelectionContainer {
|
||||
Column {
|
||||
if (displayLines.isNotEmpty()) {
|
||||
displayLines.forEach { line ->
|
||||
Text(
|
||||
text = line,
|
||||
color = secondaryText,
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
Text(
|
||||
text = "This key encrypts and decrypts group messages.",
|
||||
color = secondaryText,
|
||||
fontSize = 12.sp
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 14.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
DesktopStyleKeyImage(
|
||||
keyRender = encryptionKey,
|
||||
size = 180.dp,
|
||||
radius = 14.dp,
|
||||
palette = keyImagePalette
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
SelectionContainer {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = sectionColor,
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp)) {
|
||||
if (displayLines.isNotEmpty()) {
|
||||
displayLines.forEach { line ->
|
||||
Text(
|
||||
text = line,
|
||||
color = keyCodeColor,
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
Text(
|
||||
text = "This key encrypts and decrypts group messages.",
|
||||
color = secondaryText,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
@@ -1167,7 +1289,7 @@ fun GroupInfoScreen(
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showEncryptionDialog = false }) {
|
||||
Text("Close", color = secondaryText)
|
||||
Text("Close", color = primaryText)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1244,15 +1366,17 @@ fun GroupInfoScreen(
|
||||
private fun DesktopStyleKeyImage(
|
||||
keyRender: String,
|
||||
size: androidx.compose.ui.unit.Dp,
|
||||
radius: androidx.compose.ui.unit.Dp = 0.dp
|
||||
radius: androidx.compose.ui.unit.Dp = 0.dp,
|
||||
palette: List<Color> = KEY_IMAGE_COLORS
|
||||
) {
|
||||
val composition = remember(keyRender) {
|
||||
val colors = if (palette.isNotEmpty()) palette else KEY_IMAGE_COLORS
|
||||
val composition = remember(keyRender, colors) {
|
||||
buildList(64) {
|
||||
val source = if (keyRender.isBlank()) "rosetta" else keyRender
|
||||
for (i in 0 until 64) {
|
||||
val code = source[i % source.length].code
|
||||
val colorIndex = code % KEY_IMAGE_COLORS.size
|
||||
add(KEY_IMAGE_COLORS[colorIndex])
|
||||
val colorIndex = code % colors.size
|
||||
add(colors[colorIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1261,7 +1385,7 @@ private fun DesktopStyleKeyImage(
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.clip(RoundedCornerShape(radius))
|
||||
.background(KEY_IMAGE_COLORS.first())
|
||||
.background(colors.first())
|
||||
) {
|
||||
val cells = 8
|
||||
val cellSize = this.size.minDimension / cells.toFloat()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.rosetta.messenger.ui.chats
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.app.Activity
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -17,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -39,6 +40,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -75,12 +77,15 @@ import com.rosetta.messenger.data.GroupRepository
|
||||
import com.rosetta.messenger.network.GroupStatus
|
||||
import com.rosetta.messenger.network.SearchUser
|
||||
import com.rosetta.messenger.repository.AvatarRepository
|
||||
import com.rosetta.messenger.ui.components.KeyboardHeightProvider
|
||||
import com.rosetta.messenger.ui.components.OptimizedEmojiPicker
|
||||
import com.rosetta.messenger.ui.components.AvatarImage
|
||||
import com.rosetta.messenger.ui.icons.TelegramIcons
|
||||
import com.rosetta.messenger.utils.AvatarFileManager
|
||||
import com.rosetta.messenger.utils.ImageCropHelper
|
||||
import com.rosetta.messenger.ui.settings.ProfilePhotoPicker
|
||||
import app.rosette.android.ui.keyboard.AnimatedKeyboardTransition
|
||||
import app.rosette.android.ui.keyboard.rememberKeyboardTransitionCoordinator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -118,6 +123,9 @@ fun GroupSetupScreen(
|
||||
var errorText by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var showEmojiKeyboard by rememberSaveable { mutableStateOf(false) }
|
||||
var showPhotoPicker by rememberSaveable { mutableStateOf(false) }
|
||||
val coordinator = rememberKeyboardTransitionCoordinator()
|
||||
var lastToggleTime by remember { mutableLongStateOf(0L) }
|
||||
val toggleCooldownMs = 500L
|
||||
|
||||
val cropLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
@@ -217,21 +225,91 @@ fun GroupSetupScreen(
|
||||
val actionEnabled = if (step == GroupSetupStep.DETAILS) canGoNext else canCreate
|
||||
val density = LocalDensity.current
|
||||
val imeBottomPx = WindowInsets.ime.getBottom(density)
|
||||
val navBottomPx = WindowInsets.navigationBars.getBottom(density)
|
||||
val keyboardHeightPx = (imeBottomPx - navBottomPx).coerceAtLeast(0)
|
||||
val imeBottomDp = with(density) { imeBottomPx.toDp() }
|
||||
val keyboardOrEmojiHeight =
|
||||
if (coordinator.isEmojiBoxVisible) coordinator.emojiHeight else imeBottomDp
|
||||
val fabBottomPadding =
|
||||
if (keyboardHeightPx > 0) {
|
||||
with(density) { keyboardHeightPx.toDp() } + 14.dp
|
||||
if (keyboardOrEmojiHeight > 0.dp) {
|
||||
keyboardOrEmojiHeight + 14.dp
|
||||
} else {
|
||||
18.dp
|
||||
}
|
||||
|
||||
LaunchedEffect(step) {
|
||||
if (step != GroupSetupStep.DETAILS) {
|
||||
showEmojiKeyboard = false
|
||||
if (showEmojiKeyboard || coordinator.isEmojiVisible || coordinator.isEmojiBoxVisible) {
|
||||
coordinator.closeEmoji(hideEmoji = { showEmojiKeyboard = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val savedPx = KeyboardHeightProvider.getSavedKeyboardHeight(context)
|
||||
if (savedPx > 0) {
|
||||
coordinator.initializeEmojiHeight(with(density) { savedPx.toDp() })
|
||||
}
|
||||
}
|
||||
|
||||
var lastStableKeyboardHeight by remember { mutableStateOf(0.dp) }
|
||||
LaunchedEffect(imeBottomPx) {
|
||||
val currentImeHeight = with(density) { imeBottomPx.toDp() }
|
||||
coordinator.updateKeyboardHeight(currentImeHeight)
|
||||
if (currentImeHeight > 100.dp) {
|
||||
coordinator.syncHeights()
|
||||
lastStableKeyboardHeight = currentImeHeight
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleEmojiPicker() {
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastToggleTime < toggleCooldownMs || step != GroupSetupStep.DETAILS || isLoading) return
|
||||
lastToggleTime = now
|
||||
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
if (coordinator.isEmojiVisible) {
|
||||
coordinator.requestShowKeyboard(
|
||||
showKeyboard = {
|
||||
nameFocusRequester.requestFocus()
|
||||
keyboardController?.show()
|
||||
@Suppress("DEPRECATION")
|
||||
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
||||
},
|
||||
hideEmoji = { showEmojiKeyboard = false }
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (imeBottomPx > 0) {
|
||||
coordinator.requestShowEmoji(
|
||||
hideKeyboard = {
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
keyboardController?.hide()
|
||||
},
|
||||
showEmoji = {
|
||||
focusManager.clearFocus(force = true)
|
||||
showEmojiKeyboard = true
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (coordinator.emojiHeight == 0.dp) {
|
||||
val fallbackHeight = if (lastStableKeyboardHeight > 0.dp) {
|
||||
lastStableKeyboardHeight
|
||||
} else {
|
||||
with(density) { KeyboardHeightProvider.getSavedKeyboardHeight(context).toDp() }
|
||||
}
|
||||
coordinator.initializeEmojiHeight(fallbackHeight)
|
||||
}
|
||||
|
||||
coordinator.openEmojiOnly(
|
||||
showEmoji = {
|
||||
focusManager.clearFocus(force = true)
|
||||
showEmojiKeyboard = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (step == GroupSetupStep.DETAILS) {
|
||||
delay(120)
|
||||
@@ -270,16 +348,22 @@ fun GroupSetupScreen(
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
if (step == GroupSetupStep.DETAILS && showEmojiKeyboard) {
|
||||
OptimizedEmojiPicker(
|
||||
isVisible = true,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onEmojiSelected = { emojiCode ->
|
||||
val emoji = decodeEmojiCodeToUnicode(emojiCode)
|
||||
title = (title + emoji).take(80)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().navigationBarsPadding()
|
||||
)
|
||||
if (step == GroupSetupStep.DETAILS) {
|
||||
AnimatedKeyboardTransition(
|
||||
coordinator = coordinator,
|
||||
showEmojiPicker = showEmojiKeyboard
|
||||
) {
|
||||
OptimizedEmojiPicker(
|
||||
isVisible = true,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onEmojiSelected = { emojiCode ->
|
||||
val emoji = decodeEmojiCodeToUnicode(emojiCode)
|
||||
title = (title + emoji).take(80)
|
||||
},
|
||||
onClose = { toggleEmojiPicker() },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
@@ -366,8 +450,11 @@ fun GroupSetupScreen(
|
||||
.fillMaxWidth()
|
||||
.focusRequester(nameFocusRequester)
|
||||
.onFocusChanged { focusState ->
|
||||
if (focusState.isFocused) {
|
||||
showEmojiKeyboard = false
|
||||
if (focusState.isFocused &&
|
||||
showEmojiKeyboard &&
|
||||
!coordinator.isTransitioning
|
||||
) {
|
||||
coordinator.closeEmoji(hideEmoji = { showEmojiKeyboard = false })
|
||||
}
|
||||
}
|
||||
.padding(vertical = 2.dp),
|
||||
@@ -391,21 +478,15 @@ fun GroupSetupScreen(
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (showEmojiKeyboard) {
|
||||
showEmojiKeyboard = false
|
||||
nameFocusRequester.requestFocus()
|
||||
keyboardController?.show()
|
||||
} else {
|
||||
showEmojiKeyboard = true
|
||||
focusManager.clearFocus(force = true)
|
||||
keyboardController?.hide()
|
||||
}
|
||||
},
|
||||
onClick = { toggleEmojiPicker() },
|
||||
enabled = !isLoading
|
||||
) {
|
||||
Icon(
|
||||
painter = TelegramIcons.Smile,
|
||||
painter = if (showEmojiKeyboard || coordinator.isEmojiBoxVisible) {
|
||||
TelegramIcons.Keyboard
|
||||
} else {
|
||||
TelegramIcons.Smile
|
||||
},
|
||||
contentDescription = "Emoji",
|
||||
tint = secondaryTextColor,
|
||||
modifier = Modifier.size(22.dp)
|
||||
|
||||
Reference in New Issue
Block a user