diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 9e2ecd2a1..9f0a38749 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -241,7 +241,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe private val viewModel: ConversationViewModel by viewModels { var threadId = intent.getLongExtra(THREAD_ID, -1L) if (threadId == -1L) { - intent.getParcelableExtra
(ADDRESS)?.let { it -> + intent.getParcelableExtra(ADDRESS, Address::class.java)?.let { it -> threadId = threadDb.getThreadIdIfExistsFor(it.serialize()) if (threadId == -1L) { val sessionId = SessionId(it.serialize()) 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 4401b4c3c..36f4a9585 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupFragment.kt @@ -1,14 +1,17 @@ package org.thoughtcrime.securesms.groups +import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext import androidx.fragment.app.Fragment import androidx.hilt.navigation.compose.hiltViewModel import com.ramcosta.composedestinations.DestinationsNavHost @@ -23,6 +26,7 @@ import kotlinx.parcelize.Parcelize 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 @@ -59,7 +63,7 @@ class CreateGroupFragment : Fragment() { } @Parcelize -data class ContactList(val contacts: List) : Parcelable +data class ContactList(val contacts: Set) : Parcelable @CreateGroupNavGraph(start = true) @Composable @@ -83,6 +87,18 @@ fun CreateGroupScreen( } } + val context = LocalContext.current + + viewState.createdGroup?.let { group -> + SideEffect { + getDelegate().onDialogClosePressed() + val intent = Intent(context, ConversationActivityV2::class.java).apply { + putExtra(ConversationActivityV2.ADDRESS, group.address) + } + context.startActivity(intent) + } + } + CreateGroup( viewState, viewModel::updateState, 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 8acf79621..72a96a65c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/CreateGroupViewModel.kt @@ -4,9 +4,10 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import network.loki.messenger.R -import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.groups.compose.StateUpdate import org.thoughtcrime.securesms.groups.compose.ViewState @@ -17,10 +18,6 @@ class CreateGroupViewModel @Inject constructor( private val storage: Storage, ) : ViewModel() { - private fun create() { - tryCreateGroup() - } - private inline fun MutableLiveData.update(body: T.() -> T) { this.postValue(body(this.value!!)) } @@ -35,14 +32,14 @@ class CreateGroupViewModel @Inject constructor( 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() } + StateUpdate.Create -> { viewModelScope.launch { tryCreateGroup() } } } } val contacts get() = liveData { emit(storage.getAllContacts()) } - fun tryCreateGroup(): Recipient? { + fun tryCreateGroup() { val currentState = _viewState.value!! @@ -55,10 +52,9 @@ class CreateGroupViewModel @Inject constructor( // do some validation // need a name if (name.isEmpty()) { - _viewState.postValue( + return _viewState.postValue( currentState.copy(isLoading = false, error = R.string.error) ) - return null } storage.getAllContacts().forEach { contact -> @@ -77,8 +73,14 @@ class CreateGroupViewModel @Inject constructor( // make a group val newGroup = storage.createNewGroup(name, description, members) if (!newGroup.isPresent) { - _viewState.postValue(currentState.copy(isLoading = false, error = null)) + // show a generic couldn't create or something? + return _viewState.postValue(currentState.copy(isLoading = false, error = null)) + } else { + return _viewState.postValue(currentState.copy( + isLoading = false, + error = null, + createdGroup = newGroup.get()) + ) } - return newGroup.orNull() } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt index ea58ba56c..c46d5b2e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.MaterialTheme +import androidx.compose.material.RadioButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -54,10 +55,54 @@ fun EmptyPlaceholder(modifier: Modifier = Modifier) { } } -fun LazyListScope.memberList( +fun LazyListScope.multiSelectMemberList( contacts: List, modifier: Modifier = Modifier, - onDelete: (Contact) -> Unit + selectedContacts: Set = emptySet(), + onListUpdated: (Set)->Unit = {}, +) { + items(contacts) { contact -> + val isSelected = selectedContacts.contains(contact) + + val update = { + val newList = + if (isSelected) selectedContacts - contact + else selectedContacts + contact + onListUpdated(newList) + } + + Row(modifier = modifier.fillMaxWidth() + .clickable(onClick = update) + .padding(vertical = 8.dp, horizontal = 24.dp), + verticalAlignment = CenterVertically + ) { + ContactPhoto( + contact = contact, + modifier = Modifier + .size(48.dp) + ) + MemberName(name = contact.getSearchName()) + RadioButton(selected = isSelected, onClick = update) + } + } +} + +@Composable +fun RowScope.MemberName( + name: String +) = Text( + text = name, + fontWeight = FontWeight.Bold, + modifier = Modifier + .weight(1f) + .padding(16.dp) + .align(CenterVertically) +) + +fun LazyListScope.deleteMemberList( + contacts: List, + modifier: Modifier = Modifier, + onDelete: (Contact) -> Unit, ) { item { Text( @@ -80,23 +125,16 @@ fun LazyListScope.memberList( .size(48.dp) .align(CenterVertically) ) - Text( - text = contact.getSearchName(), - fontWeight = FontWeight.Bold, - modifier = Modifier - .weight(1f) - .padding(16.dp) - .align(CenterVertically) - ) + MemberName(name = contact.getSearchName()) Image( painterResource(id = R.drawable.ic_baseline_close_24), null, - Modifier + modifier = Modifier .size(32.dp) .align(CenterVertically) .clickable { onDelete(contact) - } + }, ) } } @@ -141,12 +179,9 @@ fun PreviewMemberList( ) PreviewTheme(themeResId = themeResId) { LazyColumn { - memberList( - previewMembers.toList(), - Modifier.padding(vertical = 8.dp, horizontal = 24.dp) - ) { deleted -> - - } + multiSelectMemberList( + contacts = previewMembers.toList(), + ) } } } \ No newline at end of file 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 946fc43eb..663917e2a 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 @@ -32,6 +32,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin import org.thoughtcrime.securesms.ui.Divider import org.thoughtcrime.securesms.ui.EditableAvatar @@ -149,7 +150,7 @@ fun CreateGroup( } } // Group list - memberList(contacts = viewState.members, modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp)) { deletedContact -> + deleteMemberList(contacts = viewState.members, modifier = Modifier.padding(vertical = 8.dp, horizontal = 24.dp)) { deletedContact -> updateState(StateUpdate.RemoveContact(deletedContact)) } } @@ -218,6 +219,7 @@ data class ViewState( val name: String = "", val description: String = "", val members: List = emptyList(), + val createdGroup: Recipient? = null, ) { val canCreate @@ -234,5 +236,5 @@ 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 AddContacts(val value: List): StateUpdate() + data class AddContacts(val value: Set): StateUpdate() } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt index 3e407124f..3b717ddf4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/SelectContacts.kt @@ -1,26 +1,36 @@ package org.thoughtcrime.securesms.groups.compose +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.OutlinedButton +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.res.stringResource +import androidx.compose.ui.unit.dp import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact import org.thoughtcrime.securesms.home.search.getSearchName import org.thoughtcrime.securesms.ui.NavigationBar import org.thoughtcrime.securesms.ui.SearchBar +@OptIn(ExperimentalFoundationApi::class) @Composable fun SelectContacts( contactListState: Set, onBack: ()->Unit, onClose: ()->Unit, - onContactsSelected: (List) -> Unit, + onContactsSelected: (Set) -> Unit, ) { var queryFilter by remember { mutableStateOf("") } @@ -30,12 +40,16 @@ fun SelectContacts( else { contactListState .filter { contact -> - contact.getSearchName() + contact.getSearchName().lowercase() .contains(queryFilter) } .toList() } + var selected by remember { + mutableStateOf(emptySet()) + } + Column { NavigationBar( title = stringResource(id = R.string.activity_create_closed_group_select_contacts), @@ -43,14 +57,24 @@ fun SelectContacts( onClose = onClose ) - LazyColumn { - item { + LazyColumn(modifier = Modifier.weight(1f)) { + stickyHeader { // Search Bar SearchBar(queryFilter, onValueChanged = { value -> queryFilter = value }) } - items(filtered) { contact -> - + multiSelectMemberList( + contacts = filtered.toList(), + selectedContacts = selected, + onListUpdated = { selected = it }, + ) + } + Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth()) { + OutlinedButton( + onClick = { onContactsSelected(selected) }, + shape = CircleShape, + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp)) { + Text(stringResource(id = R.string.ok)) } } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt index 0835b2800..946d3cc64 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/ConfigurationSyncJob.kt @@ -118,7 +118,7 @@ data class ConfigurationSyncJob(val destination: Destination) : Job { ) return ConfigMessageInformation( - if (signingKey != null && ed25519PubKey != null) { + if (signingKey != null && ed25519PubKey == null) { SnodeAPI.buildAuthenticatedStoreBatchInfo( namespace(), message,