mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-29 04:55:15 +00:00
UI adjustment
This commit is contained in:
parent
74f7bbb6d5
commit
6e1fa1b257
@ -30,6 +30,7 @@ import org.session.libsession.database.StorageProtocol
|
|||||||
import org.session.libsession.messaging.groups.GroupManagerV2
|
import org.session.libsession.messaging.groups.GroupManagerV2
|
||||||
import org.session.libsession.utilities.ConfigUpdateNotification
|
import org.session.libsession.utilities.ConfigUpdateNotification
|
||||||
import org.session.libsignal.utilities.AccountId
|
import org.session.libsignal.utilities.AccountId
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
|
||||||
const val MAX_GROUP_NAME_LENGTH = 100
|
const val MAX_GROUP_NAME_LENGTH = 100
|
||||||
@ -45,6 +46,11 @@ class EditGroupViewModel @AssistedInject constructor(
|
|||||||
// Input/Output state
|
// Input/Output state
|
||||||
private val mutableEditingName = MutableStateFlow<String?>(null)
|
private val mutableEditingName = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
|
// Input/Output: the name that has been written and submitted for change to push to the server,
|
||||||
|
// but not yet confirmed by the server. When this state is present, it takes precedence over
|
||||||
|
// the group name in the group info.
|
||||||
|
private val mutablePendingEditedName = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
// Input: invite/promote member's intermediate states. This is needed because we don't have
|
// Input: invite/promote member's intermediate states. This is needed because we don't have
|
||||||
// a state that we can map into in the config system. The config system only provides "sent", "failed", etc.
|
// a state that we can map into in the config system. The config system only provides "sent", "failed", etc.
|
||||||
// The intermediate states are needed to show the user that the operation is in progress, and the
|
// The intermediate states are needed to show the user that the operation is in progress, and the
|
||||||
@ -94,8 +100,8 @@ class EditGroupViewModel @AssistedInject constructor(
|
|||||||
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
.stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
||||||
|
|
||||||
// Output: The name of the group. This is the current name of the group, not the name being edited.
|
// Output: The name of the group. This is the current name of the group, not the name being edited.
|
||||||
val groupName: StateFlow<String> = groupInfo
|
val groupName: StateFlow<String> = combine(groupInfo
|
||||||
.map { it?.first?.name.orEmpty() }
|
.map { it?.first?.name.orEmpty() }, mutablePendingEditedName) { name, pendingName -> pendingName ?: name }
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
|
||||||
// Output: the list of the members and their state in the group.
|
// Output: the list of the members and their state in the group.
|
||||||
@ -260,11 +266,22 @@ class EditGroupViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
fun onEditNameConfirmClicked() {
|
fun onEditNameConfirmClicked() {
|
||||||
val newName = mutableEditingName.value
|
val newName = mutableEditingName.value
|
||||||
|
if (newName.isNullOrBlank()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the edited name into the pending state
|
||||||
|
mutableEditingName.value = null
|
||||||
|
mutablePendingEditedName.value = newName
|
||||||
|
|
||||||
performGroupOperation {
|
performGroupOperation {
|
||||||
if (!newName.isNullOrBlank()) {
|
try {
|
||||||
groupManager.setName(groupId, newName)
|
groupManager.setName(groupId, newName)
|
||||||
mutableEditingName.value = null
|
} finally {
|
||||||
|
// As soon as the operation is done, clear the pending state,
|
||||||
|
// no matter if it's successful or not. So that we update the UI to reflect the
|
||||||
|
// real state.
|
||||||
|
mutablePendingEditedName.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import kotlinx.coroutines.awaitAll
|
|||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.supervisorScope
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE
|
import network.loki.messenger.libsession_util.ConfigBase.Companion.PRIORITY_VISIBLE
|
||||||
import network.loki.messenger.libsession_util.util.Conversation
|
import network.loki.messenger.libsession_util.util.Conversation
|
||||||
@ -323,12 +322,39 @@ class GroupManagerV2Impl @Inject constructor(
|
|||||||
removedMembers: List<AccountId>,
|
removedMembers: List<AccountId>,
|
||||||
removeMessages: Boolean
|
removeMessages: Boolean
|
||||||
) {
|
) {
|
||||||
|
val adminKey = requireAdminAccess(groupAccountId)
|
||||||
|
|
||||||
|
// Update the config to mark this member as "removed"
|
||||||
flagMembersForRemoval(
|
flagMembersForRemoval(
|
||||||
group = groupAccountId,
|
group = groupAccountId,
|
||||||
|
groupAdminKey = adminKey,
|
||||||
members = removedMembers,
|
members = removedMembers,
|
||||||
alsoRemoveMembersMessage = removeMessages,
|
alsoRemoveMembersMessage = removeMessages,
|
||||||
sendMemberChangeMessage = true
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val timestamp = clock.currentTimeMills()
|
||||||
|
val signature = SodiumUtilities.sign(
|
||||||
|
buildMemberChangeSignature(
|
||||||
|
GroupUpdateMemberChangeMessage.Type.REMOVED,
|
||||||
|
timestamp
|
||||||
|
),
|
||||||
|
adminKey
|
||||||
|
)
|
||||||
|
|
||||||
|
val updateMessage = GroupUpdateMessage.newBuilder()
|
||||||
|
.setMemberChangeMessage(
|
||||||
|
GroupUpdateMemberChangeMessage.newBuilder()
|
||||||
|
.addAllMemberSessionIds(removedMembers.map { it.hexString })
|
||||||
|
.setType(GroupUpdateMemberChangeMessage.Type.REMOVED)
|
||||||
|
.setAdminSignature(ByteString.copyFrom(signature))
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
val message = GroupUpdated(
|
||||||
|
updateMessage
|
||||||
|
).apply { sentTimestamp = timestamp }
|
||||||
|
|
||||||
|
MessageSender.send(message, Destination.ClosedGroup(groupAccountId.hexString), false).await()
|
||||||
|
storage.insertGroupInfoChange(message, groupAccountId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun removeMemberMessages(
|
override suspend fun removeMemberMessages(
|
||||||
@ -362,32 +388,17 @@ class GroupManagerV2Impl @Inject constructor(
|
|||||||
SnodeAPI.deleteMessage(groupAccountId.hexString, groupAdminAuth, messagesToDelete)
|
SnodeAPI.deleteMessage(groupAccountId.hexString, groupAdminAuth, messagesToDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun handleMemberLeft(message: GroupUpdated, group: AccountId) {
|
override suspend fun handleMemberLeftMessage(memberId: AccountId, group: AccountId) {
|
||||||
val closedGroup = configFactory.getClosedGroup(group) ?: return
|
val closedGroup = configFactory.getClosedGroup(group) ?: return
|
||||||
|
val groupAdminKey = closedGroup.adminKey
|
||||||
|
|
||||||
if (closedGroup.hasAdminKey()) {
|
if (groupAdminKey != null) {
|
||||||
flagMembersForRemoval(
|
flagMembersForRemoval(
|
||||||
group = group,
|
group = group,
|
||||||
members = listOf(AccountId(message.sender!!)),
|
groupAdminKey = groupAdminKey,
|
||||||
|
members = listOf(memberId),
|
||||||
alsoRemoveMembersMessage = false,
|
alsoRemoveMembersMessage = false,
|
||||||
sendMemberChangeMessage = false
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
val hasAnyAdminRemaining = configFactory.withGroupConfigs(group) { configs ->
|
|
||||||
configs.groupMembers.all()
|
|
||||||
.asSequence()
|
|
||||||
.filterNot { it.sessionId == message.sender }
|
|
||||||
.any { it.admin && !it.removed }
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the leaving member is last admin, disable the group and remove it
|
|
||||||
// This is just to emulate the "existing" group behaviour, this will probably be removed in future
|
|
||||||
if (!hasAnyAdminRemaining) {
|
|
||||||
pollerFactory.pollerFor(group)?.stop()
|
|
||||||
storage.getThreadId(Address.fromSerialized(group.hexString))
|
|
||||||
?.let(storage::deleteConversation)
|
|
||||||
configFactory.removeGroup(group)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,15 +541,17 @@ class GroupManagerV2Impl @Inject constructor(
|
|||||||
storage.insertGroupInfoChange(message, group)
|
storage.insertGroupInfoChange(message, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun flagMembersForRemoval(
|
/**
|
||||||
|
* Mark this member as "removed" in the group config.
|
||||||
|
*
|
||||||
|
* [RemoveGroupMemberHandler] should be able to pick up the config changes and remove the member from the group.
|
||||||
|
*/
|
||||||
|
private fun flagMembersForRemoval(
|
||||||
group: AccountId,
|
group: AccountId,
|
||||||
|
groupAdminKey: ByteArray, // Not used ATM required here for verification purpose
|
||||||
members: List<AccountId>,
|
members: List<AccountId>,
|
||||||
alsoRemoveMembersMessage: Boolean,
|
alsoRemoveMembersMessage: Boolean,
|
||||||
sendMemberChangeMessage: Boolean
|
|
||||||
) {
|
) {
|
||||||
val adminKey = requireAdminAccess(group)
|
|
||||||
|
|
||||||
// 1. Mark the members as removed in the group configs
|
|
||||||
configFactory.withMutableGroupConfigs(group) { configs ->
|
configFactory.withMutableGroupConfigs(group) { configs ->
|
||||||
for (member in members) {
|
for (member in members) {
|
||||||
val memberConfig = configs.groupMembers.get(member.hexString)
|
val memberConfig = configs.groupMembers.get(member.hexString)
|
||||||
@ -547,33 +560,6 @@ class GroupManagerV2Impl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Send a member change message
|
|
||||||
if (sendMemberChangeMessage) {
|
|
||||||
val timestamp = clock.currentTimeMills()
|
|
||||||
val signature = SodiumUtilities.sign(
|
|
||||||
buildMemberChangeSignature(
|
|
||||||
GroupUpdateMemberChangeMessage.Type.REMOVED,
|
|
||||||
timestamp
|
|
||||||
),
|
|
||||||
adminKey
|
|
||||||
)
|
|
||||||
|
|
||||||
val updateMessage = GroupUpdateMessage.newBuilder()
|
|
||||||
.setMemberChangeMessage(
|
|
||||||
GroupUpdateMemberChangeMessage.newBuilder()
|
|
||||||
.addAllMemberSessionIds(members.map { it.hexString })
|
|
||||||
.setType(GroupUpdateMemberChangeMessage.Type.REMOVED)
|
|
||||||
.setAdminSignature(ByteString.copyFrom(signature))
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
val message = GroupUpdated(
|
|
||||||
updateMessage
|
|
||||||
).apply { sentTimestamp = timestamp }
|
|
||||||
|
|
||||||
MessageSender.send(message, Destination.ClosedGroup(group.hexString), false).await()
|
|
||||||
storage.insertGroupInfoChange(message, group)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun respondToInvitation(groupId: AccountId, approved: Boolean) =
|
override suspend fun respondToInvitation(groupId: AccountId, approved: Boolean) =
|
||||||
|
@ -6,8 +6,10 @@ import androidx.compose.foundation.border
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
@ -44,7 +46,9 @@ import org.session.libsession.utilities.recipients.Recipient
|
|||||||
import org.session.libsignal.utilities.AccountId
|
import org.session.libsignal.utilities.AccountId
|
||||||
import org.thoughtcrime.securesms.groups.ContactItem
|
import org.thoughtcrime.securesms.groups.ContactItem
|
||||||
import org.thoughtcrime.securesms.ui.Avatar
|
import org.thoughtcrime.securesms.ui.Avatar
|
||||||
|
import org.thoughtcrime.securesms.ui.components.RadioButton
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalType
|
import org.thoughtcrime.securesms.ui.theme.LocalType
|
||||||
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
||||||
|
|
||||||
@ -73,31 +77,26 @@ fun LazyListScope.multiSelectMemberList(
|
|||||||
onContactItemClicked: (accountId: AccountId) -> Unit,
|
onContactItemClicked: (accountId: AccountId) -> Unit,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
items(contacts) { contact ->
|
items(contacts.size) { index ->
|
||||||
Column {
|
val contact = contacts[index]
|
||||||
Row(
|
Column(modifier = modifier) {
|
||||||
modifier = modifier
|
if (index == 0) {
|
||||||
.fillMaxWidth()
|
// Show top divider for the first item only
|
||||||
.toggleable(
|
HorizontalDivider(color = LocalColors.current.borders)
|
||||||
|
}
|
||||||
|
|
||||||
|
RadioButton(
|
||||||
|
onClick = { onContactItemClicked(contact.accountID) },
|
||||||
|
selected = contact.selected,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
value = contact.selected,
|
contentPadding = PaddingValues(
|
||||||
onValueChange = { onContactItemClicked(contact.accountID) },
|
vertical = LocalDimensions.current.xxsSpacing,
|
||||||
role = Role.Checkbox
|
horizontal = LocalDimensions.current.smallSpacing
|
||||||
)
|
)
|
||||||
.padding(vertical = 8.dp, horizontal = 24.dp),
|
|
||||||
verticalAlignment = CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
) {
|
||||||
ContactPhoto(
|
ContactPhoto(contact.accountID)
|
||||||
contact.accountID,
|
Spacer(modifier = Modifier.size(LocalDimensions.current.smallSpacing))
|
||||||
)
|
|
||||||
MemberName(name = contact.name)
|
MemberName(name = contact.name)
|
||||||
Checkbox(
|
|
||||||
checked = contact.selected,
|
|
||||||
onCheckedChange = null,
|
|
||||||
colors = CheckboxDefaults.colors(checkedColor = LocalColors.current.primary),
|
|
||||||
enabled = enabled,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(color = LocalColors.current.borders)
|
HorizontalDivider(color = LocalColors.current.borders)
|
||||||
@ -105,15 +104,14 @@ fun LazyListScope.multiSelectMemberList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val MemberNameStyle = TextStyle(fontWeight = FontWeight.Bold)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RowScope.MemberName(
|
fun RowScope.MemberName(
|
||||||
name: String,
|
name: String,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) = Text(
|
) = Text(
|
||||||
text = name,
|
text = name,
|
||||||
style = MemberNameStyle,
|
style = LocalType.current.h8,
|
||||||
|
color = LocalColors.current.text,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.align(CenterVertically)
|
.align(CenterVertically)
|
||||||
@ -121,7 +119,7 @@ fun RowScope.MemberName(
|
|||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RowScope.ContactPhoto(sessionId: AccountId) {
|
fun ContactPhoto(sessionId: AccountId) {
|
||||||
return if (LocalInspectionMode.current) {
|
return if (LocalInspectionMode.current) {
|
||||||
Image(
|
Image(
|
||||||
painterResource(id = R.drawable.ic_profile_default),
|
painterResource(id = R.drawable.ic_profile_default),
|
||||||
|
@ -3,11 +3,14 @@ package org.thoughtcrime.securesms.groups.compose
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -71,7 +74,6 @@ fun CreateGroupScreen(
|
|||||||
items = viewModel.selectContactsViewModel.contacts.collectAsState().value,
|
items = viewModel.selectContactsViewModel.contacts.collectAsState().value,
|
||||||
onCreateClicked = viewModel::onCreateClicked,
|
onCreateClicked = viewModel::onCreateClicked,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onClose = onClose,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,29 +90,35 @@ fun CreateGroup(
|
|||||||
items: List<ContactItem>,
|
items: List<ContactItem>,
|
||||||
onCreateClicked: () -> Unit,
|
onCreateClicked: () -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onClose: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
|
|
||||||
Column(
|
Scaffold(
|
||||||
modifier = modifier.padding(bottom = LocalDimensions.current.mediumSpacing),
|
containerColor = LocalColors.current.backgroundSecondary,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
topBar = {
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
BackAppBar(
|
BackAppBar(
|
||||||
title = stringResource(id = R.string.groupCreate),
|
title = stringResource(id = R.string.groupCreate),
|
||||||
|
backgroundColor = LocalColors.current.backgroundSecondary,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
) { paddings ->
|
||||||
|
Box(modifier = modifier.padding(paddings),) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.padding(vertical = LocalDimensions.current.spacing),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.spacing)
|
||||||
|
) {
|
||||||
SessionOutlinedTextField(
|
SessionOutlinedTextField(
|
||||||
text = groupName,
|
text = groupName,
|
||||||
onChange = onGroupNameChanged,
|
onChange = onGroupNameChanged,
|
||||||
placeholder = stringResource(R.string.groupNameEnter),
|
placeholder = stringResource(R.string.groupNameEnter),
|
||||||
textStyle = LocalType.current.base,
|
textStyle = LocalType.current.base,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing),
|
||||||
error = groupNameError.takeIf { it.isNotBlank() },
|
error = groupNameError.takeIf { it.isNotBlank() },
|
||||||
enabled = !showLoading,
|
enabled = !showLoading,
|
||||||
|
innerPadding = PaddingValues(LocalDimensions.current.smallSpacing),
|
||||||
onContinue = focusManager::clearFocus
|
onContinue = focusManager::clearFocus
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -118,7 +126,7 @@ fun CreateGroup(
|
|||||||
query = contactSearchQuery,
|
query = contactSearchQuery,
|
||||||
onValueChanged = onContactSearchQueryChanged,
|
onValueChanged = onContactSearchQueryChanged,
|
||||||
placeholder = stringResource(R.string.searchContacts),
|
placeholder = stringResource(R.string.searchContacts),
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = LocalDimensions.current.spacing),
|
||||||
enabled = !showLoading
|
enabled = !showLoading
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -130,12 +138,19 @@ fun CreateGroup(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimaryOutlineButton(onClick = onCreateClicked, modifier = Modifier.widthIn(min = 120.dp)) {
|
PrimaryOutlineButton(
|
||||||
|
onClick = onCreateClicked,
|
||||||
|
modifier = Modifier.widthIn(min = 120.dp)
|
||||||
|
) {
|
||||||
LoadingArcOr(loading = showLoading) {
|
LoadingArcOr(loading = showLoading) {
|
||||||
Text(stringResource(R.string.create))
|
Text(stringResource(R.string.create))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@ -150,18 +165,17 @@ private fun CreateGroupPreview(
|
|||||||
|
|
||||||
PreviewTheme {
|
PreviewTheme {
|
||||||
CreateGroup(
|
CreateGroup(
|
||||||
modifier = Modifier.background(LocalColors.current.backgroundSecondary),
|
groupName = "",
|
||||||
groupName = "Group Name",
|
|
||||||
onGroupNameChanged = {},
|
onGroupNameChanged = {},
|
||||||
|
groupNameError = "",
|
||||||
contactSearchQuery = "",
|
contactSearchQuery = "",
|
||||||
onContactSearchQueryChanged = {},
|
onContactSearchQueryChanged = {},
|
||||||
onContactItemClicked = {},
|
onContactItemClicked = {},
|
||||||
items = previewMembers,
|
|
||||||
onBack = {},
|
|
||||||
onClose = {},
|
|
||||||
onCreateClicked = {},
|
|
||||||
showLoading = false,
|
showLoading = false,
|
||||||
groupNameError = "",
|
items = previewMembers,
|
||||||
|
onCreateClicked = {},
|
||||||
|
onBack = {},
|
||||||
|
modifier = Modifier.background(LocalColors.current.backgroundSecondary),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package org.thoughtcrime.securesms.groups.compose
|
package org.thoughtcrime.securesms.groups.compose
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@ -15,14 +15,10 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
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.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SheetState
|
|
||||||
import androidx.compose.material3.Snackbar
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -50,14 +46,15 @@ import org.thoughtcrime.securesms.groups.GroupMemberState
|
|||||||
import org.thoughtcrime.securesms.ui.AlertDialog
|
import org.thoughtcrime.securesms.ui.AlertDialog
|
||||||
import org.thoughtcrime.securesms.ui.DialogButtonModel
|
import org.thoughtcrime.securesms.ui.DialogButtonModel
|
||||||
import org.thoughtcrime.securesms.ui.GetString
|
import org.thoughtcrime.securesms.ui.GetString
|
||||||
import org.thoughtcrime.securesms.ui.components.ActionAppBar
|
import org.thoughtcrime.securesms.ui.components.BackAppBar
|
||||||
import org.thoughtcrime.securesms.ui.components.AppBarBackIcon
|
import org.thoughtcrime.securesms.ui.components.BottomOptionsDialog
|
||||||
|
import org.thoughtcrime.securesms.ui.components.BottomOptionsDialogItem
|
||||||
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalType
|
import org.thoughtcrime.securesms.ui.theme.LocalType
|
||||||
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
||||||
import org.thoughtcrime.securesms.ui.theme.bold
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EditGroupScreen(
|
fun EditGroupScreen(
|
||||||
@ -130,9 +127,7 @@ fun EditGroup(
|
|||||||
showingError: String?,
|
showingError: String?,
|
||||||
onErrorDismissed: () -> Unit,
|
onErrorDismissed: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val sheetState = rememberModalBottomSheetState()
|
val (showingOptionsDialogForMember, setShowingBottomModelForMember) = remember {
|
||||||
|
|
||||||
val (showingBottomModelForMember, setShowingBottomModelForMember) = remember {
|
|
||||||
mutableStateOf<GroupMemberState?>(null)
|
mutableStateOf<GroupMemberState?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,20 +137,9 @@ fun EditGroup(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ActionAppBar(
|
BackAppBar(
|
||||||
title = stringResource(id = R.string.groupEdit),
|
title = stringResource(id = R.string.groupEdit),
|
||||||
navigationIcon = {
|
onBack = onBackClick,
|
||||||
AppBarBackIcon(onBack = onBackClick)
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
TextButton(onClick = onBackClick) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.done),
|
|
||||||
color = LocalColors.current.text,
|
|
||||||
style = LocalType.current.large.bold()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
@ -168,8 +152,11 @@ fun EditGroup(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateContentSize()
|
.animateContentSize()
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(LocalDimensions.current.smallSpacing),
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
|
LocalDimensions.current.xxxsSpacing,
|
||||||
|
Alignment.CenterHorizontally
|
||||||
|
),
|
||||||
verticalAlignment = CenterVertically,
|
verticalAlignment = CenterVertically,
|
||||||
) {
|
) {
|
||||||
if (editingName != null) {
|
if (editingName != null) {
|
||||||
@ -185,7 +172,11 @@ fun EditGroup(
|
|||||||
modifier = Modifier.width(180.dp),
|
modifier = Modifier.width(180.dp),
|
||||||
text = editingName,
|
text = editingName,
|
||||||
onChange = onEditingNameValueChanged,
|
onChange = onEditingNameValueChanged,
|
||||||
textStyle = LocalType.current.large
|
textStyle = LocalType.current.h8,
|
||||||
|
innerPadding = PaddingValues(
|
||||||
|
horizontal = LocalDimensions.current.spacing,
|
||||||
|
vertical = LocalDimensions.current.smallSpacing
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
IconButton(onClick = onEditNameConfirmed) {
|
IconButton(onClick = onEditNameConfirmed) {
|
||||||
@ -198,7 +189,7 @@ fun EditGroup(
|
|||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = groupName,
|
text = groupName,
|
||||||
style = LocalType.current.h3,
|
style = LocalType.current.h4,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -242,6 +233,7 @@ fun EditGroup(
|
|||||||
MemberItem(
|
MemberItem(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
member = member,
|
member = member,
|
||||||
|
clickable = member.canEdit,
|
||||||
onClick = { setShowingBottomModelForMember(member) }
|
onClick = { setShowingBottomModelForMember(member) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -249,27 +241,26 @@ fun EditGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showingBottomModelForMember != null) {
|
if (showingOptionsDialogForMember != null) {
|
||||||
MemberModalBottomSheetOptions(
|
MemberOptionsDialog(
|
||||||
onDismissRequest = { setShowingBottomModelForMember(null) },
|
onDismissRequest = { setShowingBottomModelForMember(null) },
|
||||||
sheetState = sheetState,
|
|
||||||
onRemove = {
|
onRemove = {
|
||||||
setShowingConfirmRemovingMember(showingBottomModelForMember)
|
setShowingConfirmRemovingMember(showingOptionsDialogForMember)
|
||||||
setShowingBottomModelForMember(null)
|
setShowingBottomModelForMember(null)
|
||||||
},
|
},
|
||||||
onPromote = {
|
onPromote = {
|
||||||
setShowingBottomModelForMember(null)
|
setShowingBottomModelForMember(null)
|
||||||
onPromoteClick(showingBottomModelForMember.accountId)
|
onPromoteClick(showingOptionsDialogForMember.accountId)
|
||||||
},
|
},
|
||||||
onResendInvite = {
|
onResendInvite = {
|
||||||
setShowingBottomModelForMember(null)
|
setShowingBottomModelForMember(null)
|
||||||
onResendInviteClick(showingBottomModelForMember.accountId)
|
onResendInviteClick(showingOptionsDialogForMember.accountId)
|
||||||
},
|
},
|
||||||
onResendPromotion = {
|
onResendPromotion = {
|
||||||
setShowingBottomModelForMember(null)
|
setShowingBottomModelForMember(null)
|
||||||
onResendPromotionClick(showingBottomModelForMember.accountId)
|
onResendPromotionClick(showingOptionsDialogForMember.accountId)
|
||||||
},
|
},
|
||||||
member = showingBottomModelForMember,
|
member = showingOptionsDialogForMember,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,17 +275,13 @@ fun EditGroup(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!showingError.isNullOrEmpty()) {
|
val context = LocalContext.current
|
||||||
Snackbar(
|
|
||||||
dismissAction = {
|
LaunchedEffect(showingError) {
|
||||||
TextButton(onClick = onErrorDismissed) {
|
if (showingError != null) {
|
||||||
Text(text = stringResource(id = R.string.dismiss))
|
Toast.makeText(context, showingError, Toast.LENGTH_SHORT).show()
|
||||||
|
onErrorDismissed()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
content = {
|
|
||||||
Text(text = showingError)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,80 +315,81 @@ private fun ConfirmRemovingMemberDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MemberModalBottomSheetOptions(
|
private fun MemberOptionsDialog(
|
||||||
member: GroupMemberState,
|
member: GroupMemberState,
|
||||||
onRemove: () -> Unit,
|
onRemove: () -> Unit,
|
||||||
onPromote: () -> Unit,
|
onPromote: () -> Unit,
|
||||||
onResendInvite: () -> Unit,
|
onResendInvite: () -> Unit,
|
||||||
onResendPromotion: () -> Unit,
|
onResendPromotion: () -> Unit,
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
sheetState: SheetState,
|
|
||||||
) {
|
) {
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
sheetState = sheetState,
|
|
||||||
) {
|
|
||||||
if (member.canRemove) {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
MemberModalBottomSheetOptionItem(
|
|
||||||
onClick = onRemove,
|
val options = remember(member) {
|
||||||
text = context.resources.getQuantityString(R.plurals.groupRemoveUserOnly, 1)
|
buildList {
|
||||||
|
if (member.canRemove) {
|
||||||
|
this += BottomOptionsDialogItem(
|
||||||
|
title = context.resources.getQuantityString(R.plurals.groupRemoveUserOnly, 1),
|
||||||
|
iconRes = R.drawable.ic_delete,
|
||||||
|
onClick = onRemove
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (member.canPromote) {
|
if (member.canPromote) {
|
||||||
MemberModalBottomSheetOptionItem(
|
this += BottomOptionsDialogItem(
|
||||||
onClick = onPromote,
|
title = context.getString(R.string.adminPromoteToAdmin),
|
||||||
text = stringResource(R.string.adminPromoteToAdmin)
|
iconRes = R.drawable.ic_profile_default,
|
||||||
|
onClick = onPromote
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (member.canResendInvite) {
|
if (member.canResendInvite) {
|
||||||
MemberModalBottomSheetOptionItem(onClick = onResendInvite, text = "Resend invite")
|
this += BottomOptionsDialogItem(
|
||||||
|
title = "Resend invite",
|
||||||
|
iconRes = R.drawable.ic_arrow_left,
|
||||||
|
onClick = onResendInvite
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (member.canResendPromotion) {
|
if (member.canResendPromotion) {
|
||||||
MemberModalBottomSheetOptionItem(onClick = onResendPromotion, text = "Resend promotion")
|
this += BottomOptionsDialogItem(
|
||||||
|
title = "Resend promotion",
|
||||||
|
iconRes = R.drawable.ic_arrow_left,
|
||||||
|
onClick = onResendPromotion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
BottomOptionsDialog(
|
||||||
}
|
items = options,
|
||||||
}
|
onDismissRequest = onDismissRequest
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun MemberModalBottomSheetOptionItem(
|
|
||||||
text: String,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = onClick)
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
style = LocalType.current.base,
|
|
||||||
text = text,
|
|
||||||
color = LocalColors.current.text,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MemberItem(
|
private fun MemberItem(
|
||||||
|
clickable: Boolean,
|
||||||
onClick: (accountId: AccountId) -> Unit,
|
onClick: (accountId: AccountId) -> Unit,
|
||||||
member: GroupMemberState,
|
member: GroupMemberState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
modifier = modifier
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
.clickable(enabled = clickable, onClick = { onClick(member.accountId) })
|
||||||
|
.padding(
|
||||||
|
horizontal = LocalDimensions.current.smallSpacing,
|
||||||
|
vertical = LocalDimensions.current.xsSpacing
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing),
|
||||||
verticalAlignment = CenterVertically,
|
verticalAlignment = CenterVertically,
|
||||||
) {
|
) {
|
||||||
ContactPhoto(member.accountId)
|
ContactPhoto(member.accountId)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.xxxsSpacing)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@ -424,14 +412,12 @@ private fun MemberItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (member.canEdit) {
|
if (member.canEdit) {
|
||||||
IconButton(onClick = { onClick(member.accountId) }) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_circle_dot_dot_dot),
|
painter = painterResource(R.drawable.ic_circle_dot_dot_dot),
|
||||||
contentDescription = stringResource(R.string.AccessibilityId_sessionSettings)
|
contentDescription = stringResource(R.string.AccessibilityId_sessionSettings)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -544,7 +544,7 @@ fun SearchBar(
|
|||||||
painterResource(id = R.drawable.ic_search_24),
|
painterResource(id = R.drawable.ic_search_24),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(
|
colorFilter = ColorFilter.tint(
|
||||||
LocalColors.current.text
|
LocalColors.current.textSecondary
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
@ -557,7 +557,7 @@ fun SearchBar(
|
|||||||
Text(
|
Text(
|
||||||
text = placeholder,
|
text = placeholder,
|
||||||
color = LocalColors.current.textSecondary,
|
color = LocalColors.current.textSecondary,
|
||||||
style = LocalType.current.base
|
style = LocalType.current.xl
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@ -91,6 +92,7 @@ fun SessionOutlinedTextField(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onChange: (String) -> Unit = {},
|
onChange: (String) -> Unit = {},
|
||||||
textStyle: TextStyle = LocalType.current.base,
|
textStyle: TextStyle = LocalType.current.base,
|
||||||
|
innerPadding: PaddingValues = PaddingValues(LocalDimensions.current.spacing),
|
||||||
placeholder: String = "",
|
placeholder: String = "",
|
||||||
onContinue: () -> Unit = {},
|
onContinue: () -> Unit = {},
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
@ -122,7 +124,7 @@ fun SessionOutlinedTextField(
|
|||||||
)
|
)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(LocalDimensions.current.spacing)
|
.padding(innerPadding)
|
||||||
) {
|
) {
|
||||||
innerTextField()
|
innerTextField()
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ interface GroupManagerV2 {
|
|||||||
members: List<AccountId>
|
members: List<AccountId>
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun handleMemberLeft(message: GroupUpdated, group: AccountId)
|
suspend fun handleMemberLeftMessage(memberId: AccountId, group: AccountId)
|
||||||
|
|
||||||
suspend fun leaveGroup(groupId:
|
suspend fun leaveGroup(groupId:
|
||||||
AccountId, deleteOnLeave: Boolean)
|
AccountId, deleteOnLeave: Boolean)
|
||||||
|
@ -57,7 +57,6 @@ import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
|||||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||||
import org.session.libsignal.messages.SignalServiceGroup
|
import org.session.libsignal.messages.SignalServiceGroup
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMemberChangeMessage
|
|
||||||
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
|
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
|
||||||
import org.session.libsignal.utilities.AccountId
|
import org.session.libsignal.utilities.AccountId
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
@ -675,8 +674,12 @@ private fun handleMemberChange(message: GroupUpdated, closedGroup: AccountId) {
|
|||||||
|
|
||||||
private fun handleMemberLeft(message: GroupUpdated, closedGroup: AccountId) {
|
private fun handleMemberLeft(message: GroupUpdated, closedGroup: AccountId) {
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
runCatching {
|
try {
|
||||||
MessagingModuleConfiguration.shared.groupManagerV2.handleMemberLeft(message, closedGroup)
|
MessagingModuleConfiguration.shared.groupManagerV2.handleMemberLeftMessage(
|
||||||
|
AccountId(message.sender!!), closedGroup
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("GroupUpdated", "Failed to handle member left message", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,11 +209,12 @@ object UpdateMessageBuilder {
|
|||||||
if (historyShared) R.string.groupMemberNewYouHistoryMultiple else R.string.groupInviteYouAndMoreNew)
|
if (historyShared) R.string.groupMemberNewYouHistoryMultiple else R.string.groupInviteYouAndMoreNew)
|
||||||
.put(COUNT_KEY, updateData.sessionIds.size - 1)
|
.put(COUNT_KEY, updateData.sessionIds.size - 1)
|
||||||
.format()
|
.format()
|
||||||
else -> Phrase.from(context,
|
number > 0 -> Phrase.from(context,
|
||||||
if (historyShared) R.string.groupMemberNewHistoryMultiple else R.string.groupMemberNewMultiple)
|
if (historyShared) R.string.groupMemberNewHistoryMultiple else R.string.groupMemberNewMultiple)
|
||||||
.put(NAME_KEY, context.youOrSender(updateData.sessionIds.first()))
|
.put(NAME_KEY, context.youOrSender(updateData.sessionIds.first()))
|
||||||
.put(COUNT_KEY, updateData.sessionIds.size - 1)
|
.put(COUNT_KEY, updateData.sessionIds.size - 1)
|
||||||
.format()
|
.format()
|
||||||
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user