diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt index 878f944d7..8ca393d08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.groups -import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -20,12 +19,9 @@ import com.ramcosta.composedestinations.navigation.dependency import com.ramcosta.composedestinations.result.ResultBackNavigator import com.ramcosta.composedestinations.result.ResultRecipient import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import network.loki.messenger.databinding.FragmentCreateGroupBinding import org.session.libsession.messaging.contacts.Contact import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate -import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.groups.compose.CreateGroup import org.thoughtcrime.securesms.groups.compose.CreateGroupNavGraph import org.thoughtcrime.securesms.groups.compose.SelectContacts @@ -63,38 +59,20 @@ class CreateGroupFragment : Fragment() { @Destination fun CreateGroupScreen( navigator: DestinationsNavigator, - resultSelectContact: ResultRecipient, + resultSelectContact: ResultRecipient, viewModel: CreateGroupViewModel = hiltViewModel(), getDelegate: () -> NewConversationDelegate ) { val viewState by viewModel.viewState.observeAsState(ViewState.DEFAULT) val lifecycleScope = rememberCoroutineScope() val context = LocalContext.current - val currentGroupState = viewModel.createGroupState CreateGroup( viewState, - currentGroupState, - onCreate = { newGroup -> - // launch something to create here - // dunno if we want to key this here as a launched effect on some property :thinking: - lifecycleScope.launch(Dispatchers.IO) { - val groupRecipient = viewModel.tryCreateGroup(newGroup) - groupRecipient?.let { recipient -> - // launch conversation with this new group - val intent = Intent(context, ConversationActivityV2::class.java) - intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) - context.startActivity(intent) - getDelegate().onDialogClosePressed() - } - } - }, - onSelectContact = { - navigator.navigate(SelectContactScreenDestination) - }, onClose = { getDelegate().onDialogClosePressed() }, + onSelectContact = { navigator.navigate(SelectContactScreenDestination) }, onBack = { getDelegate().onDialogBackPressed() } @@ -105,17 +83,18 @@ fun CreateGroupScreen( @Composable @Destination fun SelectContactScreen( - resultNavigator: ResultBackNavigator, + resultNavigator: ResultBackNavigator, viewModel: CreateGroupViewModel = hiltViewModel(), getDelegate: () -> NewConversationDelegate ) { + val viewState by viewModel.viewState.observeAsState(ViewState.DEFAULT) + val currentMembers = viewState.members val contacts by viewModel.contacts.observeAsState(initial = emptyList()) - val currentMembers by viewModel.createGroupState.observeAsState() SelectContacts( - contacts - currentMembers?.members.orEmpty(), - onBack = { resultNavigator.navigateBack(null) }, + contacts - currentMembers, + onBack = { resultNavigator.navigateBack() }, onClose = { getDelegate().onDialogClosePressed() } ) } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt index 9c3e33740..abc174ca1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt @@ -6,56 +6,36 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData import dagger.hilt.android.lifecycle.HiltViewModel import network.loki.messenger.R -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.Storage -import org.thoughtcrime.securesms.groups.compose.CreateGroupState import org.thoughtcrime.securesms.groups.compose.ViewState import javax.inject.Inject @HiltViewModel class CreateGroupViewModel @Inject constructor( - private val textSecurePreferences: TextSecurePreferences, private val storage: Storage, ) : ViewModel() { private val _viewState = MutableLiveData(ViewState.DEFAULT) val viewState: LiveData = _viewState - val createGroupState: MutableLiveData = MutableLiveData(CreateGroupState("","", emptySet())) + val contacts = liveData { emit(storage.getAllContacts()) } - val contacts = liveData { - emit(storage.getAllContacts().toList()) - } + fun tryCreateGroup(): Recipient? { - init { -// viewModelScope.launch { -// threadDb.approvedConversationList.use { openCursor -> -// val reader = threadDb.readerFor(openCursor) -// val recipients = mutableListOf() -// while (true) { -// recipients += reader.next?.recipient ?: break -// } -// withContext(Dispatchers.Main) { -// _recipients.value = recipients -// .filter { !it.isGroupRecipient && it.hasApprovedMe() && it.address.serialize() != textSecurePreferences.getLocalNumber() } -// } -// } -// } - } + val currentState = _viewState.value!! - fun tryCreateGroup(createGroupState: CreateGroupState): Recipient? { - _viewState.postValue(ViewState(true, null)) + _viewState.postValue(currentState.copy(isLoading = true, error = null)) - val name = createGroupState.groupName - val description = createGroupState.groupDescription - val members = createGroupState.members.toMutableSet() + val name = currentState.name + val description = currentState.description + val members = currentState.members.toMutableSet() // do some validation // need a name if (name.isEmpty()) { _viewState.postValue( - ViewState(false, R.string.error) + currentState.copy(isLoading = false, error = R.string.error) ) return null } @@ -66,14 +46,14 @@ class CreateGroupViewModel @Inject constructor( if (members.size <= 1) { _viewState.postValue( - ViewState(false, 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) ) } // make a group val newGroup = storage.createNewGroup(name, description, members) if (!newGroup.isPresent) { - _viewState.postValue(ViewState(isLoading = false, null)) + _viewState.postValue(currentState.copy(isLoading = false, error = null)) } return newGroup.orNull() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt index 9d402dfc8..171e29133 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/CreateGroup.kt @@ -20,10 +20,6 @@ import androidx.compose.material.OutlinedButton import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -34,9 +30,9 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.lifecycle.MutableLiveData import network.loki.messenger.R 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.Divider import org.thoughtcrime.securesms.ui.EditableAvatar @@ -53,24 +49,14 @@ data class CreateGroupState ( @Composable fun CreateGroup( viewState: ViewState, - createGroupState: MutableLiveData, - onCreate: (CreateGroupState) -> Unit, onSelectContact: () -> Unit, onBack: () -> Unit, onClose: () -> Unit, modifier: Modifier = Modifier ) { - var name by createGroupState - var description by remember { mutableStateOf(createGroupState.groupDescription) } - var members by remember { mutableStateOf(createGroupState.members) } - val lazyState = rememberLazyListState() - val onDeleteMember = { contact: Contact -> - members -= contact - } - Box { Column( modifier @@ -93,8 +79,8 @@ fun CreateGroup( // Title val nameDescription = stringResource(id = R.string.AccessibilityId_closed_group_edit_group_name) OutlinedTextField( - value = name, - onValueChange = { name = it }, + value = viewState.name, + onValueChange = { viewState.updateState(StateUpdate.Name(it)) }, modifier = Modifier .fillMaxWidth() .align(Alignment.CenterHorizontally) @@ -106,8 +92,8 @@ fun CreateGroup( // Description val descriptionDescription = stringResource(id = R.string.AccessibilityId_closed_group_edit_group_description) OutlinedTextField( - value = description, - onValueChange = { description = it }, + value = viewState.description, + onValueChange = { viewState.updateState(StateUpdate.Description(it)) }, modifier = Modifier .fillMaxWidth() .align(Alignment.CenterHorizontally) @@ -163,13 +149,15 @@ fun CreateGroup( } } // Group list - memberList(contacts = members.toList(), modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp), onDeleteMember) + memberList(contacts = viewState.members, modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp)) { deletedContact -> + viewState.updateState(StateUpdate.RemoveContact(deletedContact)) + } } // Create button val createDescription = stringResource(id = R.string.AccessibilityId_create_closed_group_create_button) OutlinedButton( - onClick = { onCreate(CreateGroupState(name, description, members)) }, - enabled = name.isNotBlank() && !viewState.isLoading, + onClick = { viewState.create() }, + enabled = viewState.canCreate, modifier = Modifier .align(Alignment.CenterHorizontally) .padding(16.dp) @@ -212,21 +200,38 @@ fun ClosedGroupPreview( ) PreviewTheme(R.style.Theme_Session_DayNight_NoActionBar_Test) { CreateGroup( - viewState = ViewState(false, null), - createGroupState = CreateGroupState("Group Name", "Test Group Description", previewMembers), - onCreate = {}, - onClose = {}, + viewState = ViewState.DEFAULT.copy( + // override any preview parameters + ), onSelectContact = {}, onBack = {}, + onClose = {}, ) } } data class ViewState( val isLoading: Boolean, - @StringRes val error: Int? -) { + @StringRes val error: Int?, + val name: String = "", + val description: String = "", + val members: List = emptyList(), + val updateState: (StateUpdate)->Unit, + val create: ()->Unit, + ) { + + val canCreate + get() = name.isNotEmpty() && members.isNotEmpty() + companion object { - val DEFAULT = ViewState(false, null) + val DEFAULT = ViewState(false, null, updateState = {}, create = {}) } + + 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() + } + } \ No newline at end of file diff --git a/libsession/build.gradle b/libsession/build.gradle index f01b00fc0..44a43d2c3 100644 --- a/libsession/build.gradle +++ b/libsession/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.library' id 'kotlin-android' id 'kotlinx-serialization' + id 'kotlin-parcelize' } android { diff --git a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index 680460b46..c7981fa9f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -1,34 +1,37 @@ package org.session.libsession.messaging.contacts +import android.os.Parcelable +import kotlinx.parcelize.Parcelize import org.session.libsession.utilities.recipients.Recipient -class Contact(val sessionID: String) { +@Parcelize +class Contact( + val sessionID: String, /** * The URL from which to fetch the contact's profile picture. */ - var profilePictureURL: String? = null + var profilePictureURL: String? = null, /** * The file name of the contact's profile picture on local storage. */ - var profilePictureFileName: String? = null + var profilePictureFileName: String? = null, /** * The key with which the profile picture is encrypted. */ - var profilePictureEncryptionKey: ByteArray? = null + var profilePictureEncryptionKey: ByteArray? = null, /** * The ID of the thread associated with this contact. */ - var threadID: Long? = null - - // region Name + var threadID: Long? = null, /** * The name of the contact. Use this whenever you need the "real", underlying name of a user (e.g. when sending a message). */ - var name: String? = null + var name: String? = null, /** * The contact's nickname, if the user set one. */ - var nickname: String? = null + var nickname: String? = null, +): Parcelable { /** * The name to display in the UI. For local use only. */