Integrate the group member status change (#874)

pull/1709/head
SessionHero01 4 months ago committed by GitHub
parent 46653d9229
commit c04e4559b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.libsession_util.allWithStatus
import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
@ -114,7 +115,9 @@ class MentionViewModel(
} }
} else if (recipient.isGroupV2Recipient) { } else if (recipient.isGroupV2Recipient) {
configFactory.withGroupConfigs(AccountId(recipient.address.serialize())) { configFactory.withGroupConfigs(AccountId(recipient.address.serialize())) {
it.groupMembers.all().filterTo(hashSetOf()) { it.isAdminOrBeingPromoted } it.groupMembers.allWithStatus()
.filter { (member, status) -> member.isAdminOrBeingPromoted(status) }
.mapTo(hashSetOf()) { (member, _) -> member.accountId.toString() }
} }
} else { } else {
emptySet() emptySet()

@ -5,7 +5,6 @@ import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.libsession_util.ConfigBase import network.loki.messenger.libsession_util.ConfigBase
@ -49,7 +48,6 @@ import org.session.libsignal.crypto.ecc.DjbECPublicKey
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.configs.ConfigToDatabaseSync import org.thoughtcrime.securesms.configs.ConfigToDatabaseSync
import org.thoughtcrime.securesms.database.ConfigDatabase import org.thoughtcrime.securesms.database.ConfigDatabase

@ -6,7 +6,6 @@ import androidx.lifecycle.viewModelScope
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -17,6 +16,7 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.libsession_util.allWithStatus
import network.loki.messenger.libsession_util.util.GroupDisplayInfo import network.loki.messenger.libsession_util.util.GroupDisplayInfo
import network.loki.messenger.libsession_util.util.GroupMember import network.loki.messenger.libsession_util.util.GroupMember
import org.session.libsession.database.StorageProtocol import org.session.libsession.database.StorageProtocol
@ -50,14 +50,16 @@ abstract class BaseGroupMembersViewModel (
val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString) val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString)
?: return@withContext null ?: return@withContext null
val memberState = storage.getMembers(groupId.hexString) val memberState = configFactory.withGroupConfigs(groupId) { it.groupMembers.allWithStatus() }
.map { member -> .map { (member, status) ->
createGroupMember( createGroupMember(
member = member, member = member,
status = status,
myAccountId = currentUserId, myAccountId = currentUserId,
amIAdmin = displayInfo.isUserAdmin, amIAdmin = displayInfo.isUserAdmin,
) )
} }
.toList()
displayInfo to sortMembers(memberState, currentUserId) displayInfo to sortMembers(memberState, currentUserId)
} }
@ -70,6 +72,7 @@ abstract class BaseGroupMembersViewModel (
private fun createGroupMember( private fun createGroupMember(
member: GroupMember, member: GroupMember,
status: GroupMember.Status,
myAccountId: AccountId, myAccountId: AccountId,
amIAdmin: Boolean, amIAdmin: Boolean,
): GroupMemberState { ): GroupMemberState {
@ -80,7 +83,7 @@ abstract class BaseGroupMembersViewModel (
member.getMemberName(configFactory) member.getMemberName(configFactory)
} }
val highlightStatus = member.status in EnumSet.of( val highlightStatus = status in EnumSet.of(
GroupMember.Status.INVITE_FAILED, GroupMember.Status.INVITE_FAILED,
GroupMember.Status.PROMOTION_FAILED GroupMember.Status.PROMOTION_FAILED
) )
@ -89,17 +92,17 @@ abstract class BaseGroupMembersViewModel (
accountId = member.accountId, accountId = member.accountId,
name = name, name = name,
canRemove = amIAdmin && member.accountId != myAccountId canRemove = amIAdmin && member.accountId != myAccountId
&& !member.isAdminOrBeingPromoted && !member.removed, && !member.isAdminOrBeingPromoted(status) && !member.isRemoved(status),
canPromote = amIAdmin && member.accountId != myAccountId canPromote = amIAdmin && member.accountId != myAccountId
&& !member.isAdminOrBeingPromoted && !member.removed, && !member.isAdminOrBeingPromoted(status) && !member.isRemoved(status),
canResendPromotion = amIAdmin && member.accountId != myAccountId canResendPromotion = amIAdmin && member.accountId != myAccountId
&& member.status == GroupMember.Status.PROMOTION_FAILED && !member.removed, && status == GroupMember.Status.PROMOTION_FAILED && !member.isRemoved(status),
canResendInvite = amIAdmin && member.accountId != myAccountId canResendInvite = amIAdmin && member.accountId != myAccountId
&& !member.removed && !member.isRemoved(status)
&& (member.status == GroupMember.Status.INVITE_SENT || member.status == GroupMember.Status.INVITE_FAILED), && (status == GroupMember.Status.INVITE_SENT || status == GroupMember.Status.INVITE_FAILED),
status = member.status?.takeIf { !isMyself }, // Status is only meant for other members status = status.takeIf { !isMyself }, // Status is only meant for other members
highlightStatus = highlightStatus, highlightStatus = highlightStatus,
showAsAdmin = member.isAdminOrBeingPromoted, showAsAdmin = member.isAdminOrBeingPromoted(status),
clickable = !isMyself clickable = !isMyself
) )
} }
@ -147,10 +150,10 @@ data class GroupMemberState(
fun GroupMember.Status.getLabel(context: Context): String { fun GroupMember.Status.getLabel(context: Context): String {
return when (this) { return when (this) {
GroupMember.Status.INVITE_FAILED -> context.getString(R.string.groupInviteFailed) GroupMember.Status.INVITE_FAILED -> context.getString(R.string.groupInviteFailed)
GroupMember.Status.INVITE_NOT_SENT -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1) GroupMember.Status.INVENT_SENDING -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1)
GroupMember.Status.INVITE_SENT -> context.getString(R.string.groupInviteSent) GroupMember.Status.INVITE_SENT -> context.getString(R.string.groupInviteSent)
GroupMember.Status.PROMOTION_FAILED -> context.getString(R.string.adminPromotionFailed) GroupMember.Status.PROMOTION_FAILED -> context.getString(R.string.adminPromotionFailed)
GroupMember.Status.PROMOTION_NOT_SENT -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1) GroupMember.Status.PROMOTION_SENDING -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1)
GroupMember.Status.PROMOTION_SENT -> context.getString(R.string.adminPromotionSent) GroupMember.Status.PROMOTION_SENT -> context.getString(R.string.adminPromotionSent)
GroupMember.Status.REMOVED, GroupMember.Status.REMOVED,
GroupMember.Status.REMOVED_UNKNOWN, GroupMember.Status.REMOVED_UNKNOWN,
@ -158,6 +161,8 @@ fun GroupMember.Status.getLabel(context: Context): String {
GroupMember.Status.INVITE_UNKNOWN, GroupMember.Status.INVITE_UNKNOWN,
GroupMember.Status.INVITE_ACCEPTED, GroupMember.Status.INVITE_ACCEPTED,
GroupMember.Status.INVITE_NOT_SENT,
GroupMember.Status.PROMOTION_NOT_SENT,
GroupMember.Status.PROMOTION_UNKNOWN, GroupMember.Status.PROMOTION_UNKNOWN,
GroupMember.Status.PROMOTION_ACCEPTED -> "" GroupMember.Status.PROMOTION_ACCEPTED -> ""
} }

@ -218,7 +218,8 @@ class GroupManagerV2Impl @Inject constructor(
for (newMember in newMembers) { for (newMember in newMembers) {
val toSet = configs.groupMembers.get(newMember.hexString) val toSet = configs.groupMembers.get(newMember.hexString)
?.also { existing -> ?.also { existing ->
if (existing.status == GroupMember.Status.INVITE_FAILED || existing.status == GroupMember.Status.INVITE_SENT) { val status = configs.groupMembers.status(existing)
if (status == GroupMember.Status.INVITE_FAILED || status == GroupMember.Status.INVITE_SENT) {
existing.setSupplement(shareHistory) existing.setSupplement(shareHistory)
} }
} }

@ -3,14 +3,12 @@ package org.thoughtcrime.securesms.groups.handler
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.GroupInfo
import network.loki.messenger.libsession_util.util.GroupMember import network.loki.messenger.libsession_util.util.GroupMember
import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.ConfigUpdateNotification
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.getGroup
import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.AccountId
import java.util.EnumSet import java.util.EnumSet
import javax.inject.Inject import javax.inject.Inject
@ -71,7 +69,7 @@ class AdminStateSync @Inject constructor(
private fun isMemberPromotionPending(groupId: AccountId, localNumber: String): Boolean { private fun isMemberPromotionPending(groupId: AccountId, localNumber: String): Boolean {
return configFactory.withGroupConfigs(groupId) { groupConfigs -> return configFactory.withGroupConfigs(groupId) { groupConfigs ->
val status = groupConfigs.groupMembers.get(localNumber)?.status val status = groupConfigs.groupMembers.get(localNumber)?.let(groupConfigs.groupMembers::status)
status != null && status in EnumSet.of( status != null && status in EnumSet.of(
GroupMember.Status.PROMOTION_SENT, GroupMember.Status.PROMOTION_SENT,
GroupMember.Status.PROMOTION_FAILED, GroupMember.Status.PROMOTION_FAILED,

@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.libsession_util.ReadableGroupKeysConfig import network.loki.messenger.libsession_util.ReadableGroupKeysConfig
import network.loki.messenger.libsession_util.allWithStatus
import network.loki.messenger.libsession_util.util.GroupMember import network.loki.messenger.libsession_util.util.GroupMember
import network.loki.messenger.libsession_util.util.Sodium import network.loki.messenger.libsession_util.util.Sodium
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
@ -105,7 +106,10 @@ class RemoveGroupMemberHandler @Inject constructor(
val groupAuth = OwnedSwarmAuth.ofClosedGroup(groupAccountId, adminKey) val groupAuth = OwnedSwarmAuth.ofClosedGroup(groupAccountId, adminKey)
val (pendingRemovals, batchCalls) = configFactory.withGroupConfigs(groupAccountId) { configs -> val (pendingRemovals, batchCalls) = configFactory.withGroupConfigs(groupAccountId) { configs ->
val pendingRemovals = configs.groupMembers.all().filter { it.removed } val pendingRemovals = configs.groupMembers.allWithStatus()
.filter { (member, status) -> member.isRemoved(status) }
.toList()
if (pendingRemovals.isEmpty()) { if (pendingRemovals.isEmpty()) {
// Skip if there are no pending removals // Skip if there are no pending removals
return@withGroupConfigs pendingRemovals to emptyList() return@withGroupConfigs pendingRemovals to emptyList()
@ -124,8 +128,8 @@ class RemoveGroupMemberHandler @Inject constructor(
calls += checkNotNull( calls += checkNotNull(
SnodeAPI.buildAuthenticatedRevokeSubKeyBatchRequest( SnodeAPI.buildAuthenticatedRevokeSubKeyBatchRequest(
groupAdminAuth = groupAuth, groupAdminAuth = groupAuth,
subAccountTokens = pendingRemovals.map { subAccountTokens = pendingRemovals.map { (member, _) ->
configs.groupKeys.getSubAccountToken(it.accountId) configs.groupKeys.getSubAccountToken(member.accountId)
} }
) )
) { "Fail to create a revoke request" } ) { "Fail to create a revoke request" }
@ -135,7 +139,7 @@ class RemoveGroupMemberHandler @Inject constructor(
namespace = Namespace.REVOKED_GROUP_MESSAGES(), namespace = Namespace.REVOKED_GROUP_MESSAGES(),
message = buildGroupKickMessage( message = buildGroupKickMessage(
groupAccountId.hexString, groupAccountId.hexString,
pendingRemovals, pendingRemovals.map { it.first },
configs.groupKeys, configs.groupKeys,
adminKey adminKey
), ),
@ -143,7 +147,7 @@ class RemoveGroupMemberHandler @Inject constructor(
) )
// Call No 3. Conditionally send the `GroupUpdateDeleteMemberContent` // Call No 3. Conditionally send the `GroupUpdateDeleteMemberContent`
if (pendingRemovals.any { it.shouldRemoveMessages }) { if (pendingRemovals.any { (member, status) -> member.shouldRemoveMessages(status) }) {
calls += SnodeAPI.buildAuthenticatedStoreBatchInfo( calls += SnodeAPI.buildAuthenticatedStoreBatchInfo(
namespace = Namespace.CLOSED_GROUP_MESSAGES(), namespace = Namespace.CLOSED_GROUP_MESSAGES(),
message = buildDeleteGroupMemberContentMessage( message = buildDeleteGroupMemberContentMessage(
@ -151,8 +155,8 @@ class RemoveGroupMemberHandler @Inject constructor(
groupAccountId = groupAccountId.hexString, groupAccountId = groupAccountId.hexString,
memberSessionIDs = pendingRemovals memberSessionIDs = pendingRemovals
.asSequence() .asSequence()
.filter { it.shouldRemoveMessages } .filter { (member, status) -> member.shouldRemoveMessages(status) }
.map { it.accountIdString() }, .map { (member, _) -> member.accountIdString() },
), ),
auth = groupAuth, auth = groupAuth,
) )
@ -179,8 +183,8 @@ class RemoveGroupMemberHandler @Inject constructor(
// The essential part of the operation has been successful once we get to this point, // The essential part of the operation has been successful once we get to this point,
// now we can go ahead and update the configs // now we can go ahead and update the configs
configFactory.withMutableGroupConfigs(groupAccountId) { configs -> configFactory.withMutableGroupConfigs(groupAccountId) { configs ->
pendingRemovals.forEach { pendingRemovals.forEach { (member, _) ->
configs.groupMembers.erase(it.accountIdString()) configs.groupMembers.erase(member.accountIdString())
} }
configs.rekey() configs.rekey()
} }
@ -191,12 +195,12 @@ class RemoveGroupMemberHandler @Inject constructor(
// Try to delete members' message. It's ok to fail as they will be re-tried in different // Try to delete members' message. It's ok to fail as they will be re-tried in different
// cases (a.k.a the GroupUpdateDeleteMemberContent message handling) and could be by different admins. // cases (a.k.a the GroupUpdateDeleteMemberContent message handling) and could be by different admins.
val deletingMessagesForMembers = pendingRemovals.filter { it.shouldRemoveMessages } val deletingMessagesForMembers = pendingRemovals.filter { (member, status) -> member.shouldRemoveMessages(status) }
if (deletingMessagesForMembers.isNotEmpty()) { if (deletingMessagesForMembers.isNotEmpty()) {
val threadId = storage.getThreadId(Address.fromSerialized(groupAccountId.hexString)) val threadId = storage.getThreadId(Address.fromSerialized(groupAccountId.hexString))
if (threadId != null) { if (threadId != null) {
val until = clock.currentTimeMills() val until = clock.currentTimeMills()
for (member in deletingMessagesForMembers) { for ((member, _) in deletingMessagesForMembers) {
try { try {
messageDataProvider.markUserMessagesAsDeleted( messageDataProvider.markUserMessagesAsDeleted(
threadId = threadId, threadId = threadId,

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

@ -21,33 +21,35 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_00024Companion_newI
jbyteArray initial_dump, jbyteArray initial_dump,
jlong info_pointer, jlong info_pointer,
jlong members_pointer) { jlong members_pointer) {
std::lock_guard lock{util::util_mutex_}; return jni_utils::run_catching_cxx_exception_or_throws<jlong>(env, [=] {
auto user_key_bytes = util::ustring_from_bytes(env, user_secret_key); std::lock_guard lock{util::util_mutex_};
auto pub_key_bytes = util::ustring_from_bytes(env, group_public_key); auto user_key_bytes = util::ustring_from_bytes(env, user_secret_key);
std::optional<session::ustring> secret_key_optional{std::nullopt}; auto pub_key_bytes = util::ustring_from_bytes(env, group_public_key);
std::optional<session::ustring> initial_dump_optional{std::nullopt}; std::optional<session::ustring> secret_key_optional{std::nullopt};
std::optional<session::ustring> initial_dump_optional{std::nullopt};
if (group_secret_key && env->GetArrayLength(group_secret_key) > 0) { if (group_secret_key && env->GetArrayLength(group_secret_key) > 0) {
auto secret_key_bytes = util::ustring_from_bytes(env, group_secret_key); auto secret_key_bytes = util::ustring_from_bytes(env, group_secret_key);
secret_key_optional = secret_key_bytes; secret_key_optional = secret_key_bytes;
} }
if (initial_dump && env->GetArrayLength(initial_dump) > 0) { if (initial_dump && env->GetArrayLength(initial_dump) > 0) {
auto initial_dump_bytes = util::ustring_from_bytes(env, initial_dump); auto initial_dump_bytes = util::ustring_from_bytes(env, initial_dump);
initial_dump_optional = initial_dump_bytes; initial_dump_optional = initial_dump_bytes;
} }
auto info = reinterpret_cast<session::config::groups::Info*>(info_pointer); auto info = reinterpret_cast<session::config::groups::Info*>(info_pointer);
auto members = reinterpret_cast<session::config::groups::Members*>(members_pointer); auto members = reinterpret_cast<session::config::groups::Members*>(members_pointer);
auto* keys = new session::config::groups::Keys(user_key_bytes, auto* keys = new session::config::groups::Keys(user_key_bytes,
pub_key_bytes, pub_key_bytes,
secret_key_optional, secret_key_optional,
initial_dump_optional, initial_dump_optional,
*info, *info,
*members); *members);
return reinterpret_cast<jlong>(keys); return reinterpret_cast<jlong>(keys);
});
} }
extern "C" extern "C"

@ -149,12 +149,6 @@ Java_network_loki_messenger_libsession_1util_util_GroupMember_setRemoved(JNIEnv
ptrToMember(env, thiz)->set_removed(also_remove_messages); ptrToMember(env, thiz)->set_removed(also_remove_messages);
} }
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_util_GroupMember_statusInt(JNIEnv *env, jobject thiz) {
return static_cast<jint>(ptrToMember(env, thiz)->status());
}
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_util_GroupMember_setName(JNIEnv *env, jobject thiz, Java_network_loki_messenger_libsession_1util_util_GroupMember_setName(JNIEnv *env, jobject thiz,
@ -225,3 +219,18 @@ Java_network_loki_messenger_libsession_1util_util_GroupMember_setSupplement(JNIE
ptrToMember(env, thiz)->supplement = supplement; ptrToMember(env, thiz)->supplement = supplement;
} }
extern "C"
JNIEXPORT jint JNICALL
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_statusInt(JNIEnv *env, jobject thiz,
jobject group_member) {
return static_cast<jint>(ptrToMembers(env, thiz)->get_status(*ptrToMember(env, group_member)));
}
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_setPendingSend(JNIEnv *env,
jobject thiz,
jstring pub_key_hex,
jboolean pending) {
ptrToMembers(env, thiz)->set_pending_send(util::string_from_jstring(env, pub_key_hex), pending);
}

@ -384,12 +384,19 @@ class GroupInfoConfig private constructor(pointer: Long): ConfigBase(pointer), M
interface ReadableGroupMembersConfig: ReadableConfig { interface ReadableGroupMembersConfig: ReadableConfig {
fun all(): List<GroupMember> fun all(): List<GroupMember>
fun get(pubKeyHex: String): GroupMember? fun get(pubKeyHex: String): GroupMember?
fun status(groupMember: GroupMember): GroupMember.Status
}
fun ReadableGroupMembersConfig.allWithStatus(): Sequence<Pair<GroupMember, GroupMember.Status>> {
return all().asSequence().map { it to status(it) }
} }
interface MutableGroupMembersConfig : ReadableGroupMembersConfig, MutableConfig { interface MutableGroupMembersConfig : ReadableGroupMembersConfig, MutableConfig {
fun getOrConstruct(pubKeyHex: String): GroupMember fun getOrConstruct(pubKeyHex: String): GroupMember
fun set(groupMember: GroupMember) fun set(groupMember: GroupMember)
fun erase(pubKeyHex: String): Boolean fun erase(pubKeyHex: String): Boolean
fun setPendingSend(pubKeyHex: String, pending: Boolean)
} }
class GroupMembersConfig private constructor(pointer: Long): ConfigBase(pointer), MutableGroupMembersConfig { class GroupMembersConfig private constructor(pointer: Long): ConfigBase(pointer), MutableGroupMembersConfig {
@ -411,6 +418,13 @@ class GroupMembersConfig private constructor(pointer: Long): ConfigBase(pointer)
external override fun get(pubKeyHex: String): GroupMember? external override fun get(pubKeyHex: String): GroupMember?
external override fun getOrConstruct(pubKeyHex: String): GroupMember external override fun getOrConstruct(pubKeyHex: String): GroupMember
external override fun set(groupMember: GroupMember) external override fun set(groupMember: GroupMember)
external override fun setPendingSend(pubKeyHex: String, pending: Boolean)
private external fun statusInt(groupMember: GroupMember): Int
override fun status(groupMember: GroupMember): GroupMember.Status {
val statusInt = statusInt(groupMember)
return GroupMember.Status.entries.first { it.nativeValue == statusInt }
}
} }
sealed class ConfigSig(pointer: Long) : Config(pointer) sealed class ConfigSig(pointer: Long) : Config(pointer)

@ -31,9 +31,6 @@ class GroupMember private constructor(
external fun setRemoved(alsoRemoveMessages: Boolean) external fun setRemoved(alsoRemoveMessages: Boolean)
private external fun statusInt(): Int
val status: Status? get() = Status.entries.firstOrNull { it.nativeValue == statusInt() }
external fun profilePic(): UserPic? external fun profilePic(): UserPic?
external fun setProfilePic(pic: UserPic) external fun setProfilePic(pic: UserPic)
@ -60,37 +57,43 @@ class GroupMember private constructor(
destroy() destroy()
} }
val removed: Boolean fun isRemoved(status: Status): Boolean {
get() = status in EnumSet.of(Status.REMOVED, Status.REMOVED_UNKNOWN, Status.REMOVED_INCLUDING_MESSAGES) return status in EnumSet.of(Status.REMOVED, Status.REMOVED_UNKNOWN, Status.REMOVED_INCLUDING_MESSAGES)
}
val isAdminOrBeingPromoted: Boolean fun isAdminOrBeingPromoted(status: Status): Boolean {
get() = admin || status in EnumSet.of(Status.PROMOTION_SENT, Status.PROMOTION_ACCEPTED) return admin || status in EnumSet.of(Status.PROMOTION_SENT, Status.PROMOTION_ACCEPTED)
}
val inviteFailed: Boolean fun inviteFailed(status: Status): Boolean {
get() = status == Status.INVITE_FAILED return status == Status.INVITE_FAILED
}
val shouldRemoveMessages: Boolean fun shouldRemoveMessages(status: Status): Boolean {
get() = status == Status.REMOVED_INCLUDING_MESSAGES return status == Status.REMOVED_INCLUDING_MESSAGES
}
enum class Status(val nativeValue: Int) { enum class Status(val nativeValue: Int) {
INVITE_UNKNOWN(0), INVITE_UNKNOWN(0),
INVITE_NOT_SENT(1), INVITE_NOT_SENT(1),
INVITE_FAILED(2), INVENT_SENDING(2),
INVITE_SENT(3), INVITE_FAILED(3),
INVITE_ACCEPTED(4), INVITE_SENT(4),
INVITE_ACCEPTED(5),
PROMOTION_UNKNOWN(5),
PROMOTION_NOT_SENT(6), PROMOTION_UNKNOWN(6),
PROMOTION_FAILED(7), PROMOTION_NOT_SENT(7),
PROMOTION_SENT(8), PROMOTION_SENDING(8),
PROMOTION_ACCEPTED(9), PROMOTION_FAILED(9),
PROMOTION_SENT(10),
REMOVED_UNKNOWN(10), PROMOTION_ACCEPTED(11),
REMOVED(11),
REMOVED_INCLUDING_MESSAGES(12); REMOVED_UNKNOWN(12),
REMOVED(13),
REMOVED_INCLUDING_MESSAGES(14);
} }
override fun toString(): String { override fun toString(): String {
return "GroupMember(name=$name, admin=$admin, supplement=$supplement, status=$status)" return "GroupMember(name=$name, admin=$admin, supplement=$supplement)"
} }
} }
Loading…
Cancel
Save