Merge pull request #4898 from nextcloud/add_button_to_add_participants

"Add" button to add participants
This commit is contained in:
Marcel Hibbe 2025-05-30 12:07:39 +00:00 committed by GitHub
commit 89d51837b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 100 additions and 39 deletions

View File

@ -29,6 +29,7 @@ fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiStat
val isSearchActive by contactsViewModel.isSearchActive.collectAsStateWithLifecycle() val isSearchActive by contactsViewModel.isSearchActive.collectAsStateWithLifecycle()
val isAddParticipants by contactsViewModel.isAddParticipantsView.collectAsStateWithLifecycle() val isAddParticipants by contactsViewModel.isAddParticipantsView.collectAsStateWithLifecycle()
val autocompleteUsers by contactsViewModel.selectedParticipantsList.collectAsStateWithLifecycle() val autocompleteUsers by contactsViewModel.selectedParticipantsList.collectAsStateWithLifecycle()
val enableAddButton by contactsViewModel.enableAddButton.collectAsStateWithLifecycle()
Scaffold( Scaffold(
topBar = { topBar = {
@ -49,6 +50,10 @@ fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiStat
}, },
onUpdateAutocompleteUsers = { onUpdateAutocompleteUsers = {
contactsViewModel.getContactsFromSearchParams() contactsViewModel.getContactsFromSearchParams()
},
enableAddButton = enableAddButton,
clickAddButton = {
contactsViewModel.modifyClickAddButton(it)
} }
) )
}, },

View File

@ -36,6 +36,15 @@ class ContactsViewModel @Inject constructor(
private val _isAddParticipantsView = MutableStateFlow(false) private val _isAddParticipantsView = MutableStateFlow(false)
val isAddParticipantsView: StateFlow<Boolean> = _isAddParticipantsView val isAddParticipantsView: StateFlow<Boolean> = _isAddParticipantsView
private val _enableAddButton = MutableStateFlow(false)
val enableAddButton: StateFlow<Boolean> = _enableAddButton
@Suppress("PropertyName")
private val _selectedContacts = MutableStateFlow<List<AutocompleteUser>>(emptyList())
@Suppress("PropertyName")
private val _clickAddButton = MutableStateFlow(false)
private var hideAlreadyAddedParticipants: Boolean = false private var hideAlreadyAddedParticipants: Boolean = false
init { init {
@ -46,14 +55,28 @@ class ContactsViewModel @Inject constructor(
_searchQuery.value = query _searchQuery.value = query
} }
fun modifyClickAddButton(value: Boolean) {
_clickAddButton.value = value
}
fun selectContact(contact: AutocompleteUser) { fun selectContact(contact: AutocompleteUser) {
val updatedParticipants = selectedParticipants.value + contact val updatedParticipants = selectedParticipants.value + contact
selectedParticipants.value = updatedParticipants selectedParticipants.value = updatedParticipants
_selectedContacts.value = _selectedContacts.value + contact
}
fun updateAddButtonState() {
if (_selectedContacts.value.isEmpty()) {
_enableAddButton.value = false
} else {
_enableAddButton.value = true
}
} }
fun deselectContact(contact: AutocompleteUser) { fun deselectContact(contact: AutocompleteUser) {
val updatedParticipants = selectedParticipants.value - contact val updatedParticipants = selectedParticipants.value - contact
selectedParticipants.value = updatedParticipants selectedParticipants.value = updatedParticipants
_selectedContacts.value = _selectedContacts.value - contact
} }
fun updateSelectedParticipants(participants: List<AutocompleteUser>) { fun updateSelectedParticipants(participants: List<AutocompleteUser>) {
@ -86,10 +109,13 @@ class ContactsViewModel @Inject constructor(
) )
val contactsList: MutableList<AutocompleteUser>? = contacts.ocs!!.data?.toMutableList() val contactsList: MutableList<AutocompleteUser>? = contacts.ocs!!.data?.toMutableList()
if (hideAlreadyAddedParticipants) { if (hideAlreadyAddedParticipants && !_clickAddButton.value) {
contactsList?.removeAll(selectedParticipants.value) contactsList?.removeAll(selectedParticipants.value)
} }
if (_clickAddButton.value) {
contactsList?.removeAll(selectedParticipants.value)
contactsList?.addAll(_selectedContacts.value)
}
_contactsViewState.value = ContactsUiState.Success(contactsList) _contactsViewState.value = ContactsUiState.Success(contactsList)
} catch (exception: Exception) { } catch (exception: Exception) {
_contactsViewState.value = ContactsUiState.Error(exception.message ?: "") _contactsViewState.value = ContactsUiState.Error(exception.message ?: "")

View File

@ -13,6 +13,8 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
@ -20,8 +22,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -30,6 +34,7 @@ import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
@SuppressLint("UnrememberedMutableState") @SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongParameterList", "LongMethod")
@Composable @Composable
fun AppBar( fun AppBar(
title: String, title: String,
@ -40,12 +45,18 @@ fun AppBar(
onEnableSearch: () -> Unit, onEnableSearch: () -> Unit,
onDisableSearch: () -> Unit, onDisableSearch: () -> Unit,
onUpdateSearchQuery: (String) -> Unit, onUpdateSearchQuery: (String) -> Unit,
onUpdateAutocompleteUsers: () -> Unit onUpdateAutocompleteUsers: () -> Unit,
enableAddButton: Boolean,
clickAddButton: (Boolean) -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val appTitle = if (!isSearchActive) {
title
} else {
""
}
TopAppBar( TopAppBar(
title = { Text(text = title) }, title = { Text(text = appTitle) },
navigationIcon = { navigationIcon = {
IconButton(onClick = { IconButton(onClick = {
(context as? Activity)?.finish() (context as? Activity)?.finish()
@ -54,6 +65,7 @@ fun AppBar(
} }
}, },
actions = { actions = {
if (!isSearchActive) {
IconButton(onClick = onEnableSearch) { IconButton(onClick = onEnableSearch) {
Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search_icon)) Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search_icon))
} }
@ -73,17 +85,33 @@ fun AppBar(
) )
} }
} }
}
) )
if (isSearchActive) { if (isSearchActive) {
Row { Row(modifier = Modifier.fillMaxWidth()) {
SearchComponent( SearchComponent(
text = searchQuery, text = searchQuery,
onTextChange = { searchQuery -> onTextChange = { searchQuery ->
onUpdateSearchQuery(searchQuery) onUpdateSearchQuery(searchQuery)
onUpdateAutocompleteUsers() onUpdateAutocompleteUsers()
}, },
onDisableSearch = onDisableSearch onDisableSearch = onDisableSearch,
modifier = Modifier.weight(1f)
) )
if (isAddParticipants) {
TextButton(
modifier = Modifier.align(Alignment.CenterVertically).wrapContentWidth(),
onClick = {
onDisableSearch()
onUpdateSearchQuery("")
clickAddButton(true)
onUpdateAutocompleteUsers()
},
enabled = enableAddButton
) {
Text(text = context.getString(R.string.add_participants))
}
}
} }
} }
} }

View File

@ -65,8 +65,10 @@ fun ContactItemRow(contact: AutocompleteUser, contactsViewModel: ContactsViewMod
isSelected = !isSelected isSelected = !isSelected
if (isSelected) { if (isSelected) {
contactsViewModel.selectContact(contact) contactsViewModel.selectContact(contact)
contactsViewModel.updateAddButtonState()
} else { } else {
contactsViewModel.deselectContact(contact) contactsViewModel.deselectContact(contact)
contactsViewModel.updateAddButtonState()
} }
} }
} }

View File

@ -8,7 +8,6 @@
package com.nextcloud.talk.contacts.components package com.nextcloud.talk.contacts.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
@ -34,16 +33,19 @@ import androidx.compose.ui.unit.sp
import com.nextcloud.talk.R import com.nextcloud.talk.R
@Composable @Composable
fun SearchComponent(text: String, onTextChange: (String) -> Unit, onDisableSearch: () -> Unit) { fun SearchComponent(
text: String,
onTextChange: (String) -> Unit,
onDisableSearch: () -> Unit,
modifier: Modifier = Modifier
) {
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
TextField( TextField(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxWidth()
.height(60.dp),
value = text, value = text,
onValueChange = { onTextChange(it) }, onValueChange = { onTextChange(it) },
modifier = modifier
.background(MaterialTheme.colorScheme.background)
.height(60.dp),
placeholder = { Text(text = stringResource(R.string.nc_search)) }, placeholder = { Text(text = stringResource(R.string.nc_search)) },
textStyle = TextStyle(fontSize = 16.sp), textStyle = TextStyle(fontSize = 16.sp),
singleLine = true, singleLine = true,

View File

@ -859,9 +859,7 @@ class ConversationInfoActivity :
private fun selectParticipantsToAdd() { private fun selectParticipantsToAdd() {
val bundle = Bundle() val bundle = Bundle()
val existingParticipants = ArrayList<AutocompleteUser>() val existingParticipants = ArrayList<AutocompleteUser>()
for (userItem in userItems) { for (userItem in userItems) {
if (userItem.model.calculatedActorType == USERS) {
val user = AutocompleteUser( val user = AutocompleteUser(
userItem.model.calculatedActorId!!, userItem.model.calculatedActorId!!,
userItem.model.displayName, userItem.model.displayName,
@ -869,7 +867,6 @@ class ConversationInfoActivity :
) )
existingParticipants.add(user) existingParticipants.add(user)
} }
}
bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true) bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true)
bundle.putParcelableArrayList("selectedParticipants", existingParticipants) bundle.putParcelableArrayList("selectedParticipants", existingParticipants)

View File

@ -41,6 +41,7 @@ How to translate with transifex:
<!-- Bottom Navigation --> <!-- Bottom Navigation -->
<string name="nc_settings">Settings</string> <string name="nc_settings">Settings</string>
<string name="add_participants">Add</string>
<!-- Server selection --> <!-- Server selection -->
<string name="nc_server_connect">Test server connection</string> <string name="nc_server_connect">Test server connection</string>