Use consolidated member state and add pending removal support (#813)

* Use consolidated member state and add pending removal support

* Updated libsession-util
pull/1706/head
SessionHero01 5 months ago committed by GitHub
parent 819b6e1056
commit bd2cbeb8e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -24,6 +24,7 @@ import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.ConfigUpdateNotification
import org.session.libsession.utilities.getMemberName import org.session.libsession.utilities.getMemberName
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import java.util.EnumSet
abstract class BaseGroupMembersViewModel ( abstract class BaseGroupMembersViewModel (
@ -32,48 +33,35 @@ abstract class BaseGroupMembersViewModel (
private val storage: StorageProtocol, private val storage: StorageProtocol,
private val configFactory: ConfigFactoryProtocol private val configFactory: ConfigFactoryProtocol
) : ViewModel() { ) : ViewModel() {
// 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.
// The intermediate states are needed to show the user that the operation is in progress, and the
// states are limited to the view model (i.e. lost if the user navigates away). This is a trade-off
// between the complexity of the config system and the user experience.
protected val memberPendingState = MutableStateFlow<Map<AccountId, MemberPendingState>>(emptyMap())
// Output: the source-of-truth group information. Other states are derived from this. // Output: the source-of-truth group information. Other states are derived from this.
protected val groupInfo: StateFlow<Pair<GroupDisplayInfo, List<GroupMemberState>>?> = protected val groupInfo: StateFlow<Pair<GroupDisplayInfo, List<GroupMemberState>>?> =
combine( configFactory.configUpdateNotifications
configFactory.configUpdateNotifications .filter {
.filter { it is ConfigUpdateNotification.GroupConfigsUpdated && it.groupId == groupId ||
it is ConfigUpdateNotification.GroupConfigsUpdated && it.groupId == groupId || it is ConfigUpdateNotification.UserConfigsMerged
it is ConfigUpdateNotification.UserConfigsMerged
}
.onStart { emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId)) },
memberPendingState
) { _, pending ->
withContext(Dispatchers.Default) {
val currentUserId = AccountId(checkNotNull(storage.getUserPublicKey()) {
"User public key is null"
})
val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString)
?: return@withContext null
val members = storage.getMembers(groupId.hexString)
.filterTo(mutableListOf()) { !it.removed }
val memberState = members.map { member ->
createGroupMember(
member = member,
myAccountId = currentUserId,
amIAdmin = displayInfo.isUserAdmin,
pendingState = pending[member.accountId]
)
}
displayInfo to sortMembers(memberState, currentUserId)
} }
}.stateIn(viewModelScope, SharingStarted.Eagerly, null) .onStart { emit(ConfigUpdateNotification.GroupConfigsUpdated(groupId)) }
.map { _ ->
withContext(Dispatchers.Default) {
val currentUserId = AccountId(checkNotNull(storage.getUserPublicKey()) {
"User public key is null"
})
val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString)
?: return@withContext null
val memberState = storage.getMembers(groupId.hexString)
.map { member ->
createGroupMember(
member = member,
myAccountId = currentUserId,
amIAdmin = displayInfo.isUserAdmin,
)
}
displayInfo to sortMembers(memberState, currentUserId)
}
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
// Output: the list of the members and their state in the group. // Output: the list of the members and their state in the group.
val members: StateFlow<List<GroupMemberState>> = groupInfo val members: StateFlow<List<GroupMemberState>> = groupInfo
@ -84,54 +72,14 @@ abstract class BaseGroupMembersViewModel (
member: GroupMember, member: GroupMember,
myAccountId: AccountId, myAccountId: AccountId,
amIAdmin: Boolean, amIAdmin: Boolean,
pendingState: MemberPendingState?
): GroupMemberState { ): GroupMemberState {
var status = GroupMemberStatus.MEMBER val name = member.getMemberName(configFactory)
var highlightStatus = false val isMyself = member.accountId == myAccountId
var name = member.getMemberName(configFactory)
var isMyself = false
when {
member.accountIdString() == myAccountId.hexString -> {
name = context.getString(R.string.you)
isMyself = true
}
member.removed -> {
status = GroupMemberStatus.REMOVED
}
pendingState == MemberPendingState.Inviting -> {
status = GroupMemberStatus.INVITE_SENDING
}
pendingState == MemberPendingState.Promoting -> { val highlightStatus = member.status in EnumSet.of(
status = GroupMemberStatus.PROMOTION_SENDING GroupMember.Status.INVITE_FAILED,
} GroupMember.Status.PROMOTION_FAILED
)
member.status == GroupMember.Status.PROMOTION_SENT -> {
status = GroupMemberStatus.PROMOTION_SENT
}
member.status == GroupMember.Status.INVITE_SENT -> {
status = GroupMemberStatus.INVITE_SENT
}
member.status == GroupMember.Status.INVITE_FAILED -> {
status = GroupMemberStatus.INVITE_FAILED
highlightStatus = true
}
member.status == GroupMember.Status.REMOVED ||
member.status == GroupMember.Status.REMOVED_INCLUDING_MESSAGES -> {
status = GroupMemberStatus.REMOVAL_PENDING
}
member.status == GroupMember.Status.PROMOTION_FAILED -> {
status = GroupMemberStatus.PROMOTION_FAILED
highlightStatus = true
}
}
return GroupMemberState( return GroupMemberState(
accountId = member.accountId, accountId = member.accountId,
@ -145,7 +93,7 @@ abstract class BaseGroupMembersViewModel (
canResendInvite = amIAdmin && member.accountId != myAccountId canResendInvite = amIAdmin && member.accountId != myAccountId
&& !member.removed && !member.removed
&& (member.status == GroupMember.Status.INVITE_SENT || member.status == GroupMember.Status.INVITE_FAILED), && (member.status == GroupMember.Status.INVITE_SENT || member.status == GroupMember.Status.INVITE_FAILED),
status = status, status = member.status?.takeIf { !isMyself }, // Status is only meant for other members
highlightStatus = highlightStatus, highlightStatus = highlightStatus,
showAsAdmin = member.isAdminOrBeingPromoted, showAsAdmin = member.isAdminOrBeingPromoted,
clickable = !isMyself clickable = !isMyself
@ -156,11 +104,11 @@ abstract class BaseGroupMembersViewModel (
members.sortedWith( members.sortedWith(
compareBy<GroupMemberState>{ compareBy<GroupMemberState>{
when (it.status) { when (it.status) {
GroupMemberStatus.INVITE_FAILED -> 0 // Failed invite comes first GroupMember.Status.INVITE_FAILED -> 0 // Failed invite comes first
GroupMemberStatus.INVITE_SENDING -> 1 // then "Sending invite" GroupMember.Status.INVITE_NOT_SENT -> 1 // then "Sending invite"
GroupMemberStatus.INVITE_SENT -> 2 // then "Invite sent" GroupMember.Status.INVITE_SENT -> 2 // then "Invite sent"
GroupMemberStatus.PROMOTION_SENDING -> 3 // then "Sending promotion" GroupMember.Status.PROMOTION_NOT_SENT -> 3 // then "Sending promotion"
GroupMemberStatus.PROMOTION_SENT -> 4 // then "Promotion sent" GroupMember.Status.PROMOTION_SENT -> 4 // then "Promotion sent"
else -> 5 else -> 5
} }
} }
@ -174,17 +122,12 @@ abstract class BaseGroupMembersViewModel (
interface Factory { interface Factory {
fun create(groupId: AccountId): EditGroupViewModel fun create(groupId: AccountId): EditGroupViewModel
} }
protected enum class MemberPendingState {
Inviting,
Promoting,
}
} }
data class GroupMemberState( data class GroupMemberState(
val accountId: AccountId, val accountId: AccountId,
val name: String, val name: String,
val status: GroupMemberStatus, val status: GroupMember.Status?,
val highlightStatus: Boolean, val highlightStatus: Boolean,
val showAsAdmin: Boolean, val showAsAdmin: Boolean,
val canResendInvite: Boolean, val canResendInvite: Boolean,
@ -196,29 +139,22 @@ data class GroupMemberState(
val canEdit: Boolean get() = canRemove || canPromote || canResendInvite || canResendPromotion val canEdit: Boolean get() = canRemove || canPromote || canResendInvite || canResendPromotion
} }
// Function to get the label dynamically using the context
enum class GroupMemberStatus{ fun GroupMember.Status.getLabel(context: Context): String {
INVITE_FAILED, return when (this) {
INVITE_SENDING, GroupMember.Status.INVITE_FAILED -> context.getString(R.string.groupInviteFailed)
INVITE_SENT, GroupMember.Status.INVITE_NOT_SENT -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1)
PROMOTION_FAILED, GroupMember.Status.INVITE_SENT -> context.getString(R.string.groupInviteSent)
PROMOTION_SENDING, GroupMember.Status.PROMOTION_FAILED -> context.getString(R.string.adminPromotionFailed)
PROMOTION_SENT, GroupMember.Status.PROMOTION_NOT_SENT -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1)
REMOVAL_PENDING, GroupMember.Status.PROMOTION_SENT -> context.getString(R.string.adminPromotionSent)
REMOVED, GroupMember.Status.REMOVED,
MEMBER; GroupMember.Status.REMOVED_UNKNOWN,
GroupMember.Status.REMOVED_INCLUDING_MESSAGES -> context.getString(R.string.groupPendingRemoval)
// Function to get the label dynamically using the context
fun getLabel(context: Context): String { GroupMember.Status.INVITE_UNKNOWN,
return when (this) { GroupMember.Status.INVITE_ACCEPTED,
INVITE_FAILED -> context.getString(R.string.groupInviteFailed) GroupMember.Status.PROMOTION_UNKNOWN,
INVITE_SENDING -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1) GroupMember.Status.PROMOTION_ACCEPTED -> ""
INVITE_SENT -> context.getString(R.string.groupInviteSent)
PROMOTION_FAILED -> context.getString(R.string.adminPromotionFailed)
PROMOTION_SENDING -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1)
PROMOTION_SENT -> context.getString(R.string.adminPromotionSent)
REMOVAL_PENDING -> context.getString(R.string.groupPendingRemoval)
REMOVED, MEMBER -> ""
}
} }
} }

@ -80,21 +80,11 @@ class EditGroupViewModel @AssistedInject constructor(
fun onContactSelected(contacts: Set<AccountId>) { fun onContactSelected(contacts: Set<AccountId>) {
performGroupOperation { performGroupOperation {
try { groupManager.inviteMembers(
// Mark the contacts as pending groupId,
memberPendingState.update { states -> contacts.toList(),
states + contacts.associateWith { MemberPendingState.Inviting } shareHistory = false
} )
groupManager.inviteMembers(
groupId,
contacts.toList(),
shareHistory = false
)
} finally {
// Remove pending state (so the real state will be revealed)
memberPendingState.update { states -> states - contacts }
}
} }
} }
@ -104,15 +94,7 @@ class EditGroupViewModel @AssistedInject constructor(
fun onPromoteContact(memberSessionId: AccountId) { fun onPromoteContact(memberSessionId: AccountId) {
performGroupOperation { performGroupOperation {
try { groupManager.promoteMember(groupId, listOf(memberSessionId))
memberPendingState.update { states ->
states + (memberSessionId to MemberPendingState.Promoting)
}
groupManager.promoteMember(groupId, listOf(memberSessionId))
} finally {
memberPendingState.update { states -> states - memberSessionId }
}
} }
} }

@ -227,6 +227,7 @@ class GroupManagerV2Impl @Inject constructor(
member.setSupplement(shareHistory) member.setSupplement(shareHistory)
} }
toSet.setInvited()
configs.groupMembers.set(toSet) configs.groupMembers.set(toSet)
} }
@ -270,7 +271,7 @@ class GroupManagerV2Impl @Inject constructor(
configFactory.withMutableGroupConfigs(group) { configs -> configFactory.withMutableGroupConfigs(group) { configs ->
for (newMember in newMembers) { for (newMember in newMembers) {
configs.groupMembers.get(newMember.hexString)?.apply { configs.groupMembers.get(newMember.hexString)?.apply {
setInvited(failed = true) setInviteFailed()
configs.groupMembers.set(this) configs.groupMembers.set(this)
} }
} }
@ -478,7 +479,15 @@ class GroupManagerV2Impl @Inject constructor(
members: List<AccountId> members: List<AccountId>
): Unit = withContext(dispatcher + SupervisorJob()) { ): Unit = withContext(dispatcher + SupervisorJob()) {
val adminKey = requireAdminAccess(group) val adminKey = requireAdminAccess(group)
val groupName = configFactory.withGroupConfigs(group) { it.groupInfo.getName() } val groupName = configFactory.withMutableGroupConfigs(group) { configs ->
// Update the group member's promotion status
members.asSequence()
.mapNotNull { configs.groupMembers.get(it.hexString) }
.onEach(GroupMember::setPromoted)
.forEach(configs.groupMembers::set)
configs.groupInfo.getName()
}
// Send out the promote message to the members concurrently // Send out the promote message to the members concurrently
val promoteMessage = GroupUpdated( val promoteMessage = GroupUpdated(
@ -800,7 +809,7 @@ class GroupManagerV2Impl @Inject constructor(
val member = configs.groupMembers.get(sender.hexString) val member = configs.groupMembers.get(sender.hexString)
if (member != null) { if (member != null) {
configs.groupMembers.set(member.apply { configs.groupMembers.set(member.apply {
setAccepted() setInviteAccepted()
}) })
} else { } else {
Log.e(TAG, "User wasn't in the group membership to add!") Log.e(TAG, "User wasn't in the group membership to add!")

@ -42,12 +42,13 @@ import com.squareup.phrase.Phrase
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import network.loki.messenger.BuildConfig import network.loki.messenger.BuildConfig
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.libsession_util.util.GroupMember
import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import org.thoughtcrime.securesms.groups.EditGroupViewModel import org.thoughtcrime.securesms.groups.EditGroupViewModel
import org.thoughtcrime.securesms.groups.GroupMemberState import org.thoughtcrime.securesms.groups.GroupMemberState
import org.thoughtcrime.securesms.groups.GroupMemberStatus import org.thoughtcrime.securesms.groups.getLabel
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
@ -425,7 +426,7 @@ fun EditMemberItem(
MemberItem( MemberItem(
accountId = member.accountId, accountId = member.accountId,
title = member.name, title = member.name,
subtitle = member.status.getLabel(LocalContext.current), subtitle = member.status?.getLabel(LocalContext.current),
subtitleColor = if (member.highlightStatus) { subtitleColor = if (member.highlightStatus) {
LocalColors.current.danger LocalColors.current.danger
} else { } else {
@ -451,7 +452,7 @@ private fun EditGroupPreview3() {
val oneMember = GroupMemberState( val oneMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"),
name = "Test User", name = "Test User",
status = GroupMemberStatus.INVITE_SENT, status = GroupMember.Status.INVITE_SENT,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -463,7 +464,7 @@ private fun EditGroupPreview3() {
val twoMember = GroupMemberState( val twoMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"),
name = "Test User 2", name = "Test User 2",
status = GroupMemberStatus.PROMOTION_FAILED, status = GroupMember.Status.PROMOTION_FAILED,
highlightStatus = true, highlightStatus = true,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -475,7 +476,7 @@ private fun EditGroupPreview3() {
val threeMember = GroupMemberState( val threeMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"),
name = "Test User 3", name = "Test User 3",
status = GroupMemberStatus.MEMBER, status = null,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -525,7 +526,7 @@ private fun EditGroupPreview() {
val oneMember = GroupMemberState( val oneMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"),
name = "Test User", name = "Test User",
status = GroupMemberStatus.INVITE_SENT, status = GroupMember.Status.INVITE_SENT,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -537,7 +538,7 @@ private fun EditGroupPreview() {
val twoMember = GroupMemberState( val twoMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"),
name = "Test User 2", name = "Test User 2",
status = GroupMemberStatus.PROMOTION_FAILED, status = GroupMember.Status.PROMOTION_FAILED,
highlightStatus = true, highlightStatus = true,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -549,7 +550,7 @@ private fun EditGroupPreview() {
val threeMember = GroupMemberState( val threeMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"),
name = "Test User 3", name = "Test User 3",
status = GroupMemberStatus.MEMBER, status = null,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -599,7 +600,7 @@ private fun EditGroupEditNamePreview() {
val oneMember = GroupMemberState( val oneMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"),
name = "Test User", name = "Test User",
status = GroupMemberStatus.INVITE_SENT, status = GroupMember.Status.INVITE_SENT,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -611,7 +612,7 @@ private fun EditGroupEditNamePreview() {
val twoMember = GroupMemberState( val twoMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"),
name = "Test User 2", name = "Test User 2",
status = GroupMemberStatus.PROMOTION_FAILED, status = GroupMember.Status.PROMOTION_FAILED,
highlightStatus = true, highlightStatus = true,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -623,7 +624,7 @@ private fun EditGroupEditNamePreview() {
val threeMember = GroupMemberState( val threeMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"),
name = "Test User 3", name = "Test User 3",
status = GroupMemberStatus.MEMBER, status = null,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,

@ -1,62 +1,26 @@
package org.thoughtcrime.securesms.groups.compose package org.thoughtcrime.securesms.groups.compose
import android.widget.Toast
import androidx.compose.animation.Crossfade
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.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items 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.IconButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
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.remember
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Alignment.Companion.CenterVertically
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.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.squareup.phrase.Phrase
import network.loki.messenger.BuildConfig
import network.loki.messenger.R import network.loki.messenger.R
import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import network.loki.messenger.libsession_util.util.GroupMember
import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import org.thoughtcrime.securesms.groups.GroupMemberState import org.thoughtcrime.securesms.groups.GroupMemberState
import org.thoughtcrime.securesms.groups.GroupMemberStatus
import org.thoughtcrime.securesms.groups.GroupMembersViewModel import org.thoughtcrime.securesms.groups.GroupMembersViewModel
import org.thoughtcrime.securesms.ui.AlertDialog import org.thoughtcrime.securesms.groups.getLabel
import org.thoughtcrime.securesms.ui.DialogButtonModel
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.components.ActionSheet
import org.thoughtcrime.securesms.ui.components.ActionSheetItemData
import org.thoughtcrime.securesms.ui.components.BackAppBar import org.thoughtcrime.securesms.ui.components.BackAppBar
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
import org.thoughtcrime.securesms.ui.qaTag
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.PreviewTheme import org.thoughtcrime.securesms.ui.theme.PreviewTheme
@Composable @Composable
@ -98,7 +62,7 @@ fun GroupMembers(
MemberItem( MemberItem(
accountId = member.accountId, accountId = member.accountId,
title = member.name, title = member.name,
subtitle = member.status.getLabel(LocalContext.current), subtitle = member.status?.getLabel(LocalContext.current),
subtitleColor = if (member.highlightStatus) { subtitleColor = if (member.highlightStatus) {
LocalColors.current.danger LocalColors.current.danger
} else { } else {
@ -120,7 +84,7 @@ private fun EditGroupPreview() {
val oneMember = GroupMemberState( val oneMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234"),
name = "Test User", name = "Test User",
status = GroupMemberStatus.INVITE_SENT, status = GroupMember.Status.INVITE_SENT,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -132,7 +96,7 @@ private fun EditGroupPreview() {
val twoMember = GroupMemberState( val twoMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1235"),
name = "Test User 2", name = "Test User 2",
status = GroupMemberStatus.PROMOTION_FAILED, status = GroupMember.Status.PROMOTION_FAILED,
highlightStatus = true, highlightStatus = true,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,
@ -144,7 +108,7 @@ private fun EditGroupPreview() {
val threeMember = GroupMemberState( val threeMember = GroupMemberState(
accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"), accountId = AccountId("05abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1236"),
name = "Test User 3", name = "Test User 3",
status = GroupMemberStatus.MEMBER, status = null,
highlightStatus = false, highlightStatus = false,
canPromote = true, canPromote = true,
canRemove = true, canRemove = true,

@ -1 +1 @@
Subproject commit 1ed9170c7329512f78b25cb2bcb7ecaa9a416d39 Subproject commit 43b1c6c341ee8739a8678c631d0713136dbfd05f

@ -89,16 +89,29 @@ Java_network_loki_messenger_libsession_1util_GroupMembersConfig_set(JNIEnv *env,
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_util_GroupMember_setInvited(JNIEnv *env, Java_network_loki_messenger_libsession_1util_util_GroupMember_setInvited(JNIEnv *env,
jobject thiz, jobject thiz) {
jboolean failed) { ptrToMember(env, thiz)->invite_status = session::config::groups::STATUS_NOT_SENT;
ptrToMember(env, thiz)->set_invited(failed); }
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_util_GroupMember_setInviteSent(JNIEnv *env,
jobject thiz) {
ptrToMember(env, thiz)->set_invite_sent();
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_util_GroupMember_setInviteFailed(JNIEnv *env,
jobject thiz) {
ptrToMember(env, thiz)->set_invite_failed();
} }
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_util_GroupMember_setAccepted(JNIEnv *env, Java_network_loki_messenger_libsession_1util_util_GroupMember_setInviteAccepted(JNIEnv *env,
jobject thiz) { jobject thiz) {
ptrToMember(env, thiz)->set_accepted(); ptrToMember(env, thiz)->set_invite_accepted();
} }
extern "C" extern "C"
@ -210,4 +223,5 @@ Java_network_loki_messenger_libsession_1util_util_GroupMember_setSupplement(JNIE
jobject thiz, jobject thiz,
jboolean supplement) { jboolean supplement) {
ptrToMember(env, thiz)->supplement = supplement; ptrToMember(env, thiz)->supplement = supplement;
} }

@ -135,7 +135,7 @@ inline jobject serialize_closed_group_info(JNIEnv* env, session::config::group_i
jclass group_info_class = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$ClosedGroupInfo"); jclass group_info_class = env->FindClass("network/loki/messenger/libsession_util/util/GroupInfo$ClosedGroupInfo");
jmethodID constructor = env->GetMethodID(group_info_class, "<init>","(Lorg/session/libsignal/utilities/AccountId;[B[BJZLjava/lang/String;Z)V"); jmethodID constructor = env->GetMethodID(group_info_class, "<init>","(Lorg/session/libsignal/utilities/AccountId;[B[BJZLjava/lang/String;Z)V");
jobject return_object = env->NewObject(group_info_class,constructor, jobject return_object = env->NewObject(group_info_class,constructor,
session_id, admin_bytes, auth_bytes, (jlong)info.priority, info.invited, name, info.isDestroyed()); session_id, admin_bytes, auth_bytes, (jlong)info.priority, info.invited, name, info.is_destroyed());
return return_object; return return_object;
} }
@ -167,7 +167,7 @@ inline session::config::group_info deserialize_closed_group_info(JNIEnv* env, jo
group_info.invited = env->GetBooleanField(info_serialized, invited_field); group_info.invited = env->GetBooleanField(info_serialized, invited_field);
group_info.name = name; group_info.name = name;
if (env->GetBooleanField(info_serialized, destroy_field)) { if (env->GetBooleanField(info_serialized, destroy_field)) {
group_info.markDestroyed(); group_info.mark_destroyed();
} }
return group_info; return group_info;

@ -9,19 +9,26 @@ import java.util.EnumSet
* Note: unlike a read-only data class, this class is mutable and it is not thread-safe * Note: unlike a read-only data class, this class is mutable and it is not thread-safe
* in general. You have to synchronize access to it if you are going to use it in multiple threads. * in general. You have to synchronize access to it if you are going to use it in multiple threads.
*/ */
class GroupMember private constructor(@Suppress("CanBeParameter") private val nativePtr: Long) { class GroupMember private constructor(
// Constructed and used by native code.
@Suppress("CanBeParameter") private val nativePtr: Long
) {
init { init {
if (nativePtr == 0L) { if (nativePtr == 0L) {
throw NullPointerException("Native pointer is null") throw NullPointerException("Native pointer is null")
} }
} }
external fun setInvited(failed: Boolean = false) external fun setInvited()
external fun setAccepted() external fun setInviteSent()
external fun setInviteFailed()
external fun setInviteAccepted()
external fun setPromoted() external fun setPromoted()
external fun setPromotionSent() external fun setPromotionSent()
external fun setPromotionFailed() external fun setPromotionFailed()
external fun setPromotionAccepted() external fun setPromotionAccepted()
external fun setRemoved(alsoRemoveMessages: Boolean) external fun setRemoved(alsoRemoveMessages: Boolean)
private external fun statusInt(): Int private external fun statusInt(): Int

@ -91,7 +91,11 @@ class InviteContactsJob(val groupSessionId: String, val memberSessionIds: Array<
configs.withMutableGroupConfigs(sessionId) { configs -> configs.withMutableGroupConfigs(sessionId) { configs ->
results.forEach { (memberSessionId, result) -> results.forEach { (memberSessionId, result) ->
configs.groupMembers.get(memberSessionId)?.let { member -> configs.groupMembers.get(memberSessionId)?.let { member ->
member.setInvited(failed = result.isFailure) if (result.isFailure) {
member.setInviteFailed()
} else {
member.setInviteSent()
}
configs.groupMembers.set(member) configs.groupMembers.set(member)
} }
} }

Loading…
Cancel
Save