feat: update group creation composables and let contact parcelize
This commit is contained in:
parent
fabe2d44da
commit
6c3ac9bc5d
|
@ -7,15 +7,14 @@ import android.view.ViewGroup
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import com.ramcosta.composedestinations.navigation.dependency
|
import com.ramcosta.composedestinations.navigation.dependency
|
||||||
|
import com.ramcosta.composedestinations.result.NavResult
|
||||||
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
import com.ramcosta.composedestinations.result.ResultBackNavigator
|
||||||
import com.ramcosta.composedestinations.result.ResultRecipient
|
import com.ramcosta.composedestinations.result.ResultRecipient
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
@ -25,6 +24,7 @@ import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate
|
||||||
import org.thoughtcrime.securesms.groups.compose.CreateGroup
|
import org.thoughtcrime.securesms.groups.compose.CreateGroup
|
||||||
import org.thoughtcrime.securesms.groups.compose.CreateGroupNavGraph
|
import org.thoughtcrime.securesms.groups.compose.CreateGroupNavGraph
|
||||||
import org.thoughtcrime.securesms.groups.compose.SelectContacts
|
import org.thoughtcrime.securesms.groups.compose.SelectContacts
|
||||||
|
import org.thoughtcrime.securesms.groups.compose.StateUpdate
|
||||||
import org.thoughtcrime.securesms.groups.compose.ViewState
|
import org.thoughtcrime.securesms.groups.compose.ViewState
|
||||||
import org.thoughtcrime.securesms.groups.destinations.SelectContactScreenDestination
|
import org.thoughtcrime.securesms.groups.destinations.SelectContactScreenDestination
|
||||||
import org.thoughtcrime.securesms.ui.AppTheme
|
import org.thoughtcrime.securesms.ui.AppTheme
|
||||||
|
@ -64,11 +64,19 @@ fun CreateGroupScreen(
|
||||||
getDelegate: () -> NewConversationDelegate
|
getDelegate: () -> NewConversationDelegate
|
||||||
) {
|
) {
|
||||||
val viewState by viewModel.viewState.observeAsState(ViewState.DEFAULT)
|
val viewState by viewModel.viewState.observeAsState(ViewState.DEFAULT)
|
||||||
val lifecycleScope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
resultSelectContact.onNavResult { navResult ->
|
||||||
|
when (navResult) {
|
||||||
|
is NavResult.Value -> {
|
||||||
|
viewModel.updateState(StateUpdate.AddContact(navResult.value))
|
||||||
|
}
|
||||||
|
is NavResult.Canceled -> { /* do nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CreateGroup(
|
CreateGroup(
|
||||||
viewState,
|
viewState,
|
||||||
|
viewModel::updateState,
|
||||||
onClose = {
|
onClose = {
|
||||||
getDelegate().onDialogClosePressed()
|
getDelegate().onDialogClosePressed()
|
||||||
},
|
},
|
||||||
|
@ -90,11 +98,12 @@ fun SelectContactScreen(
|
||||||
|
|
||||||
val viewState by viewModel.viewState.observeAsState(ViewState.DEFAULT)
|
val viewState by viewModel.viewState.observeAsState(ViewState.DEFAULT)
|
||||||
val currentMembers = viewState.members
|
val currentMembers = viewState.members
|
||||||
val contacts by viewModel.contacts.observeAsState(initial = emptyList())
|
val contacts by viewModel.contacts.observeAsState(initial = emptySet())
|
||||||
|
|
||||||
SelectContacts(
|
SelectContacts(
|
||||||
contacts - currentMembers,
|
contacts - currentMembers,
|
||||||
onBack = { resultNavigator.navigateBack() },
|
onBack = { resultNavigator.navigateBack() },
|
||||||
onClose = { getDelegate().onDialogClosePressed() }
|
onClose = { getDelegate().onDialogClosePressed() },
|
||||||
|
onContactsSelected = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.database.Storage
|
import org.thoughtcrime.securesms.database.Storage
|
||||||
|
import org.thoughtcrime.securesms.groups.compose.StateUpdate
|
||||||
import org.thoughtcrime.securesms.groups.compose.ViewState
|
import org.thoughtcrime.securesms.groups.compose.ViewState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -16,10 +17,30 @@ class CreateGroupViewModel @Inject constructor(
|
||||||
private val storage: Storage,
|
private val storage: Storage,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _viewState = MutableLiveData(ViewState.DEFAULT)
|
private fun create() {
|
||||||
val viewState: LiveData<ViewState> = _viewState
|
tryCreateGroup()
|
||||||
|
}
|
||||||
|
|
||||||
val contacts = liveData { emit(storage.getAllContacts()) }
|
private inline fun <reified T> MutableLiveData<T>.update(body: T.() -> T) {
|
||||||
|
this.postValue(body(this.value!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _viewState = MutableLiveData(ViewState.DEFAULT.copy())
|
||||||
|
|
||||||
|
val viewState: LiveData<ViewState> = _viewState
|
||||||
|
|
||||||
|
fun updateState(stateUpdate: StateUpdate) {
|
||||||
|
when (stateUpdate) {
|
||||||
|
is StateUpdate.AddContact -> _viewState.update { copy(members = members + stateUpdate.value) }
|
||||||
|
is StateUpdate.Description -> _viewState.update { copy(description = stateUpdate.value) }
|
||||||
|
is StateUpdate.Name -> _viewState.update { copy(name = stateUpdate.value) }
|
||||||
|
is StateUpdate.RemoveContact -> _viewState.update { copy(members = members - stateUpdate.value) }
|
||||||
|
StateUpdate.Create -> { tryCreateGroup() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val contacts
|
||||||
|
get() = liveData { emit(storage.getAllContacts()) }
|
||||||
|
|
||||||
fun tryCreateGroup(): Recipient? {
|
fun tryCreateGroup(): Recipient? {
|
||||||
|
|
||||||
|
@ -46,7 +67,10 @@ class CreateGroupViewModel @Inject constructor(
|
||||||
|
|
||||||
if (members.size <= 1) {
|
if (members.size <= 1) {
|
||||||
_viewState.postValue(
|
_viewState.postValue(
|
||||||
currentState.copy(isLoading = false, error = R.string.activity_create_closed_group_not_enough_group_members_error)
|
currentState.copy(
|
||||||
|
isLoading = false,
|
||||||
|
error = R.string.activity_create_closed_group_not_enough_group_members_error
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.thoughtcrime.securesms.groups.compose.ViewState.StateUpdate
|
|
||||||
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
|
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
|
||||||
import org.thoughtcrime.securesms.ui.Divider
|
import org.thoughtcrime.securesms.ui.Divider
|
||||||
import org.thoughtcrime.securesms.ui.EditableAvatar
|
import org.thoughtcrime.securesms.ui.EditableAvatar
|
||||||
|
@ -49,6 +48,7 @@ data class CreateGroupState (
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateGroup(
|
fun CreateGroup(
|
||||||
viewState: ViewState,
|
viewState: ViewState,
|
||||||
|
updateState: (StateUpdate) -> Unit,
|
||||||
onSelectContact: () -> Unit,
|
onSelectContact: () -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
|
@ -80,7 +80,7 @@ fun CreateGroup(
|
||||||
val nameDescription = stringResource(id = R.string.AccessibilityId_closed_group_edit_group_name)
|
val nameDescription = stringResource(id = R.string.AccessibilityId_closed_group_edit_group_name)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewState.name,
|
value = viewState.name,
|
||||||
onValueChange = { viewState.updateState(StateUpdate.Name(it)) },
|
onValueChange = { updateState(StateUpdate.Name(it)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
|
@ -93,7 +93,7 @@ fun CreateGroup(
|
||||||
val descriptionDescription = stringResource(id = R.string.AccessibilityId_closed_group_edit_group_description)
|
val descriptionDescription = stringResource(id = R.string.AccessibilityId_closed_group_edit_group_description)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = viewState.description,
|
value = viewState.description,
|
||||||
onValueChange = { viewState.updateState(StateUpdate.Description(it)) },
|
onValueChange = { updateState(StateUpdate.Description(it)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
|
@ -150,13 +150,13 @@ fun CreateGroup(
|
||||||
}
|
}
|
||||||
// Group list
|
// Group list
|
||||||
memberList(contacts = viewState.members, modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp)) { deletedContact ->
|
memberList(contacts = viewState.members, modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp)) { deletedContact ->
|
||||||
viewState.updateState(StateUpdate.RemoveContact(deletedContact))
|
updateState(StateUpdate.RemoveContact(deletedContact))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create button
|
// Create button
|
||||||
val createDescription = stringResource(id = R.string.AccessibilityId_create_closed_group_create_button)
|
val createDescription = stringResource(id = R.string.AccessibilityId_create_closed_group_create_button)
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { viewState.create() },
|
onClick = { updateState(StateUpdate.Create) },
|
||||||
enabled = viewState.canCreate,
|
enabled = viewState.canCreate,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
|
@ -203,6 +203,7 @@ fun ClosedGroupPreview(
|
||||||
viewState = ViewState.DEFAULT.copy(
|
viewState = ViewState.DEFAULT.copy(
|
||||||
// override any preview parameters
|
// override any preview parameters
|
||||||
),
|
),
|
||||||
|
updateState = {},
|
||||||
onSelectContact = {},
|
onSelectContact = {},
|
||||||
onBack = {},
|
onBack = {},
|
||||||
onClose = {},
|
onClose = {},
|
||||||
|
@ -216,22 +217,21 @@ data class ViewState(
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
val description: String = "",
|
val description: String = "",
|
||||||
val members: List<Contact> = emptyList(),
|
val members: List<Contact> = emptyList(),
|
||||||
val updateState: (StateUpdate)->Unit,
|
|
||||||
val create: ()->Unit,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val canCreate
|
val canCreate
|
||||||
get() = name.isNotEmpty() && members.isNotEmpty()
|
get() = name.isNotEmpty() && members.isNotEmpty()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT = ViewState(false, null, updateState = {}, create = {})
|
val DEFAULT = ViewState(false, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class StateUpdate {
|
}
|
||||||
data class Name(val value: String): StateUpdate()
|
|
||||||
data class Description(val value: String): StateUpdate()
|
|
||||||
data class RemoveContact(val value: Contact): StateUpdate()
|
|
||||||
data class AddContact(val value: Contact): StateUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
sealed class StateUpdate {
|
||||||
|
data object Create: StateUpdate()
|
||||||
|
data class Name(val value: String): StateUpdate()
|
||||||
|
data class Description(val value: String): StateUpdate()
|
||||||
|
data class RemoveContact(val value: Contact): StateUpdate()
|
||||||
|
data class AddContact(val value: Contact): StateUpdate()
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.groups.compose
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -16,21 +17,23 @@ import org.thoughtcrime.securesms.ui.SearchBar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectContacts(
|
fun SelectContacts(
|
||||||
contactListState: List<Contact>,
|
contactListState: Set<Contact>,
|
||||||
onBack: ()->Unit,
|
onBack: ()->Unit,
|
||||||
onClose: ()->Unit,
|
onClose: ()->Unit,
|
||||||
|
onContactsSelected: (List<Contact>) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
var queryFilter by remember { mutableStateOf("") }
|
var queryFilter by remember { mutableStateOf("") }
|
||||||
|
|
||||||
// May introduce more advanced filters
|
// May introduce more advanced filters
|
||||||
val filtered = if (queryFilter.isEmpty()) contactListState
|
val filtered = if (queryFilter.isEmpty()) contactListState.toList()
|
||||||
else {
|
else {
|
||||||
contactListState
|
contactListState
|
||||||
.filter { contact ->
|
.filter { contact ->
|
||||||
contact.getSearchName()
|
contact.getSearchName()
|
||||||
.contains(queryFilter)
|
.contains(queryFilter)
|
||||||
}
|
}
|
||||||
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
@ -45,6 +48,10 @@ fun SelectContacts(
|
||||||
// Search Bar
|
// Search Bar
|
||||||
SearchBar(queryFilter, onValueChanged = { value -> queryFilter = value })
|
SearchBar(queryFilter, onValueChanged = { value -> queryFilter = value })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items(filtered) { contact ->
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,9 @@ class Contact(
|
||||||
*/
|
*/
|
||||||
var nickname: String? = null,
|
var nickname: String? = null,
|
||||||
): Parcelable {
|
): Parcelable {
|
||||||
|
|
||||||
|
constructor(id: String): this(sessionID = id)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name to display in the UI. For local use only.
|
* The name to display in the UI. For local use only.
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue