From a5f8641d50c903b3f4e425611029c952a84057aa Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 28 Jun 2023 15:00:42 +0300 Subject: [PATCH] android: search members (#2621) * android: search members * changed placement of search fields * less diff * remove unused function --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../views/chat/group/AddGroupMembersView.kt | 40 ++++++++++++++++--- .../app/views/chat/group/GroupChatInfoView.kt | 24 ++++++++++- .../app/views/helpers/DefaultTopAppBar.kt | 2 +- .../simplex/app/views/helpers/ModalView.kt | 6 +++ .../app/views/helpers/SearchTextField.kt | 21 ++++++---- .../app/views/usersettings/SettingsView.kt | 2 +- 6 files changed, 79 insertions(+), 16 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt index 096b19de0a..9d5284bbe7 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/AddGroupMembersView.kt @@ -11,12 +11,16 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -34,15 +38,17 @@ fun AddGroupMembersView(groupInfo: GroupInfo, creatingGroup: Boolean = false, ch val selectedContacts = remember { mutableStateListOf() } val selectedRole = remember { mutableStateOf(GroupMemberRole.Member) } var allowModifyMembers by remember { mutableStateOf(true) } + val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } BackHandler(onBack = close) AddGroupMembersLayout( chatModel.incognito.value, groupInfo = groupInfo, creatingGroup = creatingGroup, - contactsToAdd = getContactsToAdd(chatModel), + contactsToAdd = getContactsToAdd(chatModel, searchText.value.text), selectedContacts = selectedContacts, selectedRole = selectedRole, allowModifyMembers = allowModifyMembers, + searchText, openPreferences = { ModalManager.shared.showCustomModal { close -> GroupPreferencesView(chatModel, groupInfo.id, close) @@ -69,7 +75,8 @@ fun AddGroupMembersView(groupInfo: GroupInfo, creatingGroup: Boolean = false, ch ) } -fun getContactsToAdd(chatModel: ChatModel): List { +fun getContactsToAdd(chatModel: ChatModel, search: String): List { + val s = search.trim().lowercase() val memberContactIds = chatModel.groupMembers .filter { it.memberCurrent } .mapNotNull { it.memberContactId } @@ -78,7 +85,7 @@ fun getContactsToAdd(chatModel: ChatModel): List { .map { it.chatInfo } .filterIsInstance() .map { it.contact } - .filter { it.contactId !in memberContactIds } + .filter { it.contactId !in memberContactIds && it.chatViewName.lowercase().contains(s) } .sortedBy { it.displayName.lowercase() } .toList() } @@ -92,6 +99,7 @@ fun AddGroupMembersLayout( selectedContacts: List, selectedRole: MutableState, allowModifyMembers: Boolean, + searchText: MutableState, openPreferences: () -> Unit, inviteMembers: () -> Unit, clearSelection: () -> Unit, @@ -125,7 +133,7 @@ fun AddGroupMembersLayout( } SectionSpacer() - if (contactsToAdd.isEmpty()) { + if (contactsToAdd.isEmpty() && searchText.value.text.isEmpty()) { Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center @@ -154,8 +162,10 @@ fun AddGroupMembersLayout( InviteSectionFooter(selectedContactsCount = selectedContacts.size, allowModifyMembers, clearSelection) } SectionDividerSpaced(maxTopPadding = true) - SectionView(stringResource(R.string.select_contacts)) { + SectionItemView(padding = PaddingValues(start = DEFAULT_PADDING, end = DEFAULT_PADDING_HALF)) { + SearchRowView(searchText, selectedContacts.size) + } ContactList(contacts = contactsToAdd, selectedContacts, groupInfo, allowModifyMembers, addContact, removeContact) } } @@ -163,6 +173,25 @@ fun AddGroupMembersLayout( } } +@Composable +private fun SearchRowView( + searchText: MutableState = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) }, + selectedContactsSize: Int +) { + Box(Modifier.width(36.dp), contentAlignment = Alignment.Center) { + Icon(painterResource(R.drawable.ic_search), stringResource(android.R.string.search_go), tint = MaterialTheme.colors.secondary) + } + Spacer(Modifier.width(DEFAULT_SPACE_AFTER_ICON)) + SearchTextField(Modifier.fillMaxWidth(), searchText = searchText, alwaysVisible = true) { + searchText.value = searchText.value.copy(it) + } + val view = LocalView.current + LaunchedEffect(selectedContactsSize) { + searchText.value = searchText.value.copy("") + hideKeyboard(view) + } +} + @Composable private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState, enabled: Boolean) { Row( @@ -325,6 +354,7 @@ fun PreviewAddGroupMembersLayout() { selectedContacts = remember { mutableStateListOf() }, selectedRole = remember { mutableStateOf(GroupMemberRole.Admin) }, allowModifyMembers = true, + searchText = remember { mutableStateOf(TextFieldValue("")) }, openPreferences = {}, inviteMembers = {}, clearSelection = {}, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt index 4722820938..fdb94ac6fa 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/group/GroupChatInfoView.kt @@ -13,12 +13,14 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -196,10 +198,17 @@ fun GroupChatInfoLayout( val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary AddMembersButton(tint, onAddMembersClick) } + val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) } + val filteredMembers = derivedStateOf { members.filter { it.chatViewName.lowercase().contains(searchText.value.text.trim()) } } + if (members.size > 8) { + SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) { + SearchRowView(searchText) + } + } SectionItemView(minHeight = 54.dp) { MemberRow(groupInfo.membership, user = true) } - MembersList(members, showMemberInfo) + MembersList(filteredMembers.value, showMemberInfo) } SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) SectionView { @@ -393,6 +402,19 @@ private fun DeleteGroupButton(onClick: () -> Unit) { ) } +@Composable +private fun SearchRowView( + searchText: MutableState = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) } +) { + Box(Modifier.width(36.dp), contentAlignment = Alignment.Center) { + Icon(painterResource(R.drawable.ic_search), stringResource(android.R.string.search_go), tint = MaterialTheme.colors.secondary) + } + Spacer(Modifier.width(14.dp)) + SearchTextField(Modifier.fillMaxWidth(), searchText = searchText, alwaysVisible = true) { + searchText.value = searchText.value.copy(it) + } +} + @Preview @Composable fun PreviewGroupChatInfoLayout() { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt index 9896b0302e..81cad18f5c 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/DefaultTopAppBar.kt @@ -33,7 +33,7 @@ fun DefaultTopAppBar( if (!showSearch) { title?.invoke() } else { - SearchTextField(Modifier.fillMaxWidth(), stringResource(android.R.string.search_go), alwaysVisible = false, onSearchValueChanged) + SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = false, onValueChange = onSearchValueChanged) } }, backgroundColor = if (isInDarkTheme()) ToolbarDark else ToolbarLight, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt index d4b48bdb9d..64c2d7fc47 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/ModalView.kt @@ -1,5 +1,6 @@ package chat.simplex.app.views.helpers +import android.R import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.animation.* @@ -8,8 +9,13 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.intl.Locale import chat.simplex.app.TAG import chat.simplex.app.ui.theme.isInDarkTheme import chat.simplex.app.ui.theme.themedBackground diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt index 2655f75e31..5b299d3855 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SearchTextField.kt @@ -29,8 +29,13 @@ import kotlinx.coroutines.delay @OptIn(ExperimentalComposeUiApi::class) @Composable -fun SearchTextField(modifier: Modifier, placeholder: String, alwaysVisible: Boolean, onValueChange: (String) -> Unit) { - var searchText by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } +fun SearchTextField( + modifier: Modifier, + alwaysVisible: Boolean, + searchText: MutableState = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }, + placeholder: String = stringResource(android.R.string.search_go), + onValueChange: (String) -> Unit +) { val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current val keyboard = LocalSoftwareKeyboardController.current @@ -45,7 +50,7 @@ fun SearchTextField(modifier: Modifier, placeholder: String, alwaysVisible: Bool DisposableEffect(Unit) { onDispose { - if (searchText.text.isNotEmpty()) onValueChange("") + if (searchText.value.text.isNotEmpty()) onValueChange("") } } @@ -59,7 +64,7 @@ fun SearchTextField(modifier: Modifier, placeholder: String, alwaysVisible: Bool val shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize) val interactionSource = remember { MutableInteractionSource() } BasicTextField( - value = searchText, + value = searchText.value, modifier = modifier .background(colors.backgroundColor(enabled).value, shape) .indicatorLine(enabled, false, interactionSource, colors) @@ -69,7 +74,7 @@ fun SearchTextField(modifier: Modifier, placeholder: String, alwaysVisible: Bool minHeight = TextFieldDefaults.MinHeight ), onValueChange = { - searchText = it + searchText.value = it onValueChange(it.text) }, cursorBrush = SolidColor(colors.cursorColor(false).value), @@ -84,18 +89,18 @@ fun SearchTextField(modifier: Modifier, placeholder: String, alwaysVisible: Bool interactionSource = interactionSource, decorationBox = @Composable { innerTextField -> TextFieldDefaults.TextFieldDecorationBox( - value = searchText.text, + value = searchText.value.text, innerTextField = innerTextField, placeholder = { Text(placeholder) }, - trailingIcon = if (searchText.text.isNotEmpty()) {{ + trailingIcon = if (searchText.value.text.isNotEmpty()) {{ IconButton({ if (alwaysVisible) { keyboard?.hide() focusManager.clearFocus() } - searchText = TextFieldValue(""); + searchText.value = TextFieldValue(""); onValueChange("") }) { Icon(painterResource(R.drawable.ic_close), stringResource(R.string.icon_descr_close_button), tint = MaterialTheme.colors.primary,) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt index 2966a65709..682c44a12a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt @@ -62,7 +62,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean, FragmentActivity) ModalView( { close() }, endButtons = { - SearchTextField(Modifier.fillMaxWidth(), stringResource(android.R.string.search_go), alwaysVisible = true) { search.value = it } + SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = true) { search.value = it } }, content = { modalView(chatModel, search) }) }