Skip to content

Commit 2a3db7d

Browse files
Merge pull request #879 from session-foundation/merge-groups-to-dev
Merge latest changes to groups back to dev
2 parents 578d89a + cc769c0 commit 2a3db7d

File tree

22 files changed

+313
-241
lines changed

22 files changed

+313
-241
lines changed

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/mention/MentionViewModel.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.mapLatest
2626
import kotlinx.coroutines.flow.onStart
2727
import kotlinx.coroutines.flow.stateIn
2828
import kotlinx.coroutines.withContext
29+
import network.loki.messenger.libsession_util.allWithStatus
2930
import org.session.libsession.messaging.contacts.Contact
3031
import org.session.libsession.utilities.ConfigFactoryProtocol
3132
import org.session.libsignal.utilities.AccountId
@@ -114,7 +115,9 @@ class MentionViewModel(
114115
}
115116
} else if (recipient.isGroupV2Recipient) {
116117
configFactory.withGroupConfigs(AccountId(recipient.address.serialize())) {
117-
it.groupMembers.all().filterTo(hashSetOf()) { it.isAdminOrBeingPromoted }
118+
it.groupMembers.allWithStatus()
119+
.filter { (member, status) -> member.isAdminOrBeingPromoted(status) }
120+
.mapTo(hashSetOf()) { (member, _) -> member.accountId.toString() }
118121
}
119122
} else {
120123
emptySet()

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ object ConversationMenuHelper {
414414
doLeave = {
415415
try {
416416
channel.send(GroupLeavingStatus.Leaving)
417-
groupManager.leaveGroup(accountId, true)
417+
groupManager.leaveGroup(accountId)
418418
channel.send(GroupLeavingStatus.Left)
419419
} catch (e: Exception) {
420420
channel.send(GroupLeavingStatus.Error)

app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ open class Storage @Inject constructor(
11311131
return groupDatabase.getAllGroups(includeInactive)
11321132
}
11331133

1134-
override fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? {
1134+
override suspend fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? {
11351135
return OpenGroupManager.addOpenGroup(urlAsString, context)
11361136
}
11371137

app/src/main/java/org/thoughtcrime/securesms/dependencies/ConfigFactory.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import dagger.Lazy
55
import dagger.hilt.android.qualifiers.ApplicationContext
66
import kotlinx.coroutines.CoroutineScope
77
import kotlinx.coroutines.GlobalScope
8-
import kotlinx.coroutines.channels.BufferOverflow
98
import kotlinx.coroutines.flow.MutableSharedFlow
109
import kotlinx.coroutines.launch
1110
import network.loki.messenger.libsession_util.ConfigBase
@@ -49,7 +48,6 @@ import org.session.libsignal.crypto.ecc.DjbECPublicKey
4948
import org.session.libsignal.utilities.AccountId
5049
import org.session.libsignal.utilities.Hex
5150
import org.session.libsignal.utilities.IdPrefix
52-
import org.session.libsignal.utilities.Log
5351
import org.session.libsignal.utilities.toHexString
5452
import org.thoughtcrime.securesms.configs.ConfigToDatabaseSync
5553
import org.thoughtcrime.securesms.database.ConfigDatabase

app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.lifecycle.viewModelScope
66
import dagger.assisted.AssistedFactory
77
import dagger.hilt.android.qualifiers.ApplicationContext
88
import kotlinx.coroutines.Dispatchers
9-
import kotlinx.coroutines.flow.MutableStateFlow
109
import kotlinx.coroutines.flow.SharingStarted
1110
import kotlinx.coroutines.flow.StateFlow
1211
import kotlinx.coroutines.flow.combine
@@ -17,6 +16,7 @@ import kotlinx.coroutines.flow.onStart
1716
import kotlinx.coroutines.flow.stateIn
1817
import kotlinx.coroutines.withContext
1918
import network.loki.messenger.R
19+
import network.loki.messenger.libsession_util.allWithStatus
2020
import network.loki.messenger.libsession_util.util.GroupDisplayInfo
2121
import network.loki.messenger.libsession_util.util.GroupMember
2222
import org.session.libsession.database.StorageProtocol
@@ -50,14 +50,16 @@ abstract class BaseGroupMembersViewModel (
5050
val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString)
5151
?: return@withContext null
5252

53-
val memberState = storage.getMembers(groupId.hexString)
54-
.map { member ->
53+
val memberState = configFactory.withGroupConfigs(groupId) { it.groupMembers.allWithStatus() }
54+
.map { (member, status) ->
5555
createGroupMember(
5656
member = member,
57+
status = status,
5758
myAccountId = currentUserId,
5859
amIAdmin = displayInfo.isUserAdmin,
5960
)
6061
}
62+
.toList()
6163

6264
displayInfo to sortMembers(memberState, currentUserId)
6365
}
@@ -70,6 +72,7 @@ abstract class BaseGroupMembersViewModel (
7072

7173
private fun createGroupMember(
7274
member: GroupMember,
75+
status: GroupMember.Status,
7376
myAccountId: AccountId,
7477
amIAdmin: Boolean,
7578
): GroupMemberState {
@@ -80,7 +83,7 @@ abstract class BaseGroupMembersViewModel (
8083
member.getMemberName(configFactory)
8184
}
8285

83-
val highlightStatus = member.status in EnumSet.of(
86+
val highlightStatus = status in EnumSet.of(
8487
GroupMember.Status.INVITE_FAILED,
8588
GroupMember.Status.PROMOTION_FAILED
8689
)
@@ -89,17 +92,17 @@ abstract class BaseGroupMembersViewModel (
8992
accountId = member.accountId,
9093
name = name,
9194
canRemove = amIAdmin && member.accountId != myAccountId
92-
&& !member.isAdminOrBeingPromoted && !member.removed,
95+
&& !member.isAdminOrBeingPromoted(status) && !member.isRemoved(status),
9396
canPromote = amIAdmin && member.accountId != myAccountId
94-
&& !member.isAdminOrBeingPromoted && !member.removed,
97+
&& !member.isAdminOrBeingPromoted(status) && !member.isRemoved(status),
9598
canResendPromotion = amIAdmin && member.accountId != myAccountId
96-
&& member.status == GroupMember.Status.PROMOTION_FAILED && !member.removed,
99+
&& status == GroupMember.Status.PROMOTION_FAILED && !member.isRemoved(status),
97100
canResendInvite = amIAdmin && member.accountId != myAccountId
98-
&& !member.removed
99-
&& (member.status == GroupMember.Status.INVITE_SENT || member.status == GroupMember.Status.INVITE_FAILED),
100-
status = member.status?.takeIf { !isMyself }, // Status is only meant for other members
101+
&& !member.isRemoved(status)
102+
&& (status == GroupMember.Status.INVITE_SENT || status == GroupMember.Status.INVITE_FAILED),
103+
status = status.takeIf { !isMyself }, // Status is only meant for other members
101104
highlightStatus = highlightStatus,
102-
showAsAdmin = member.isAdminOrBeingPromoted,
105+
showAsAdmin = member.isAdminOrBeingPromoted(status),
103106
clickable = !isMyself
104107
)
105108
}
@@ -147,17 +150,19 @@ data class GroupMemberState(
147150
fun GroupMember.Status.getLabel(context: Context): String {
148151
return when (this) {
149152
GroupMember.Status.INVITE_FAILED -> context.getString(R.string.groupInviteFailed)
150-
GroupMember.Status.INVITE_NOT_SENT -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1)
153+
GroupMember.Status.INVENT_SENDING -> context.resources.getQuantityString(R.plurals.groupInviteSending, 1)
151154
GroupMember.Status.INVITE_SENT -> context.getString(R.string.groupInviteSent)
152155
GroupMember.Status.PROMOTION_FAILED -> context.getString(R.string.adminPromotionFailed)
153-
GroupMember.Status.PROMOTION_NOT_SENT -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1)
156+
GroupMember.Status.PROMOTION_SENDING -> context.resources.getQuantityString(R.plurals.adminSendingPromotion, 1)
154157
GroupMember.Status.PROMOTION_SENT -> context.getString(R.string.adminPromotionSent)
155158
GroupMember.Status.REMOVED,
156159
GroupMember.Status.REMOVED_UNKNOWN,
157160
GroupMember.Status.REMOVED_INCLUDING_MESSAGES -> context.getString(R.string.groupPendingRemoval)
158161

159162
GroupMember.Status.INVITE_UNKNOWN,
160163
GroupMember.Status.INVITE_ACCEPTED,
164+
GroupMember.Status.INVITE_NOT_SENT,
165+
GroupMember.Status.PROMOTION_NOT_SENT,
161166
GroupMember.Status.PROMOTION_UNKNOWN,
162167
GroupMember.Status.PROMOTION_ACCEPTED -> ""
163168
}

app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ class GroupManagerV2Impl @Inject constructor(
218218
for (newMember in newMembers) {
219219
val toSet = configs.groupMembers.get(newMember.hexString)
220220
?.also { existing ->
221-
if (existing.status == GroupMember.Status.INVITE_FAILED || existing.status == GroupMember.Status.INVITE_SENT) {
221+
val status = configs.groupMembers.status(existing)
222+
if (status == GroupMember.Status.INVITE_FAILED || status == GroupMember.Status.INVITE_SENT) {
222223
existing.setSupplement(shareHistory)
223224
}
224225
}
@@ -264,6 +265,9 @@ class GroupManagerV2Impl @Inject constructor(
264265
subAccountTokens = subAccountTokens
265266
)
266267

268+
// Before we send the invitation, we need to make sure the configs are pushed
269+
configFactory.waitUntilGroupConfigsPushed(group)
270+
267271
// Call the API
268272
try {
269273
val swarmNode = SnodeAPI.getSingleTargetSnode(group.hexString).await()
@@ -414,71 +418,73 @@ class GroupManagerV2Impl @Inject constructor(
414418
}
415419
}
416420

417-
override suspend fun leaveGroup(groupId: AccountId, deleteOnLeave: Boolean) = withContext(dispatcher + SupervisorJob()) {
421+
override suspend fun leaveGroup(groupId: AccountId) = withContext(dispatcher + SupervisorJob()) {
418422
val group = configFactory.getGroup(groupId)
419423

420-
// Only send the left/left notification group message when we are not kicked and we are not the only admin (only admin has a special treatment)
421-
val weAreTheOnlyAdmin = configFactory.withGroupConfigs(groupId) { config ->
422-
val allMembers = config.groupMembers.all()
423-
allMembers.count { it.admin } == 1 &&
424-
allMembers.first { it.admin }.accountIdString() == storage.getUserPublicKey()
425-
}
426-
427-
if (group != null && !group.kicked && !weAreTheOnlyAdmin) {
428-
val destination = Destination.ClosedGroup(groupId.hexString)
429-
val sendMessageTasks = mutableListOf<Deferred<*>>()
430-
431-
// Always send a "XXX left" message to the group if we can
432-
sendMessageTasks += async {
433-
MessageSender.send(
434-
GroupUpdated(
435-
GroupUpdateMessage.newBuilder()
436-
.setMemberLeftNotificationMessage(DataMessage.GroupUpdateMemberLeftNotificationMessage.getDefaultInstance())
437-
.build()
438-
),
439-
destination,
440-
isSyncMessage = false
441-
).await()
424+
if (group?.destroyed != true) {
425+
// Only send the left/left notification group message when we are not kicked and we are not the only admin (only admin has a special treatment)
426+
val weAreTheOnlyAdmin = configFactory.withGroupConfigs(groupId) { config ->
427+
val allMembers = config.groupMembers.all()
428+
allMembers.count { it.admin } == 1 &&
429+
allMembers.first { it.admin }
430+
.accountIdString() == storage.getUserPublicKey()
442431
}
443432

433+
if (group != null && !group.kicked && !weAreTheOnlyAdmin) {
434+
val destination = Destination.ClosedGroup(groupId.hexString)
435+
val sendMessageTasks = mutableListOf<Deferred<*>>()
436+
437+
// Always send a "XXX left" message to the group if we can
438+
sendMessageTasks += async {
439+
MessageSender.send(
440+
GroupUpdated(
441+
GroupUpdateMessage.newBuilder()
442+
.setMemberLeftNotificationMessage(DataMessage.GroupUpdateMemberLeftNotificationMessage.getDefaultInstance())
443+
.build()
444+
),
445+
destination,
446+
isSyncMessage = false
447+
).await()
448+
}
444449

445-
// If we are not the only admin, send a left message for other admin to handle the member removal
446-
sendMessageTasks += async {
447-
MessageSender.send(
448-
GroupUpdated(
449-
GroupUpdateMessage.newBuilder()
450-
.setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance())
451-
.build()
452-
),
453-
destination,
454-
isSyncMessage = false
455-
).await()
456-
}
457450

458-
sendMessageTasks.awaitAll()
459-
}
451+
// If we are not the only admin, send a left message for other admin to handle the member removal
452+
sendMessageTasks += async {
453+
MessageSender.send(
454+
GroupUpdated(
455+
GroupUpdateMessage.newBuilder()
456+
.setMemberLeftMessage(DataMessage.GroupUpdateMemberLeftMessage.getDefaultInstance())
457+
.build()
458+
),
459+
destination,
460+
isSyncMessage = false
461+
).await()
462+
}
460463

461-
// If we are the only admin, leaving this group will destroy the group
462-
if (weAreTheOnlyAdmin) {
463-
configFactory.withMutableGroupConfigs(groupId) { configs ->
464-
configs.groupInfo.destroyGroup()
464+
sendMessageTasks.awaitAll()
465465
}
466466

467-
// Must wait until the config is pushed, otherwise if we go through the rest
468-
// of the code it will destroy the conversation, destroying the necessary configs
469-
// along the way, we won't be able to push the "destroyed" state anymore.
470-
configFactory.waitUntilGroupConfigsPushed(groupId)
467+
// If we are the only admin, leaving this group will destroy the group
468+
if (weAreTheOnlyAdmin) {
469+
configFactory.withMutableGroupConfigs(groupId) { configs ->
470+
configs.groupInfo.destroyGroup()
471+
}
472+
473+
// Must wait until the config is pushed, otherwise if we go through the rest
474+
// of the code it will destroy the conversation, destroying the necessary configs
475+
// along the way, we won't be able to push the "destroyed" state anymore.
476+
configFactory.waitUntilGroupConfigsPushed(groupId)
477+
}
471478
}
472479

473480
pollerFactory.pollerFor(groupId)?.stop()
474481

475-
if (deleteOnLeave) {
476-
storage.getThreadId(Address.fromSerialized(groupId.hexString))
477-
?.let(storage::deleteConversation)
478-
configFactory.removeGroup(groupId)
479-
lokiAPIDatabase.clearLastMessageHashes(groupId.hexString)
480-
lokiAPIDatabase.clearReceivedMessageHashValues(groupId.hexString)
481-
}
482+
// Delete conversation and group configs
483+
storage.getThreadId(Address.fromSerialized(groupId.hexString))
484+
?.let(storage::deleteConversation)
485+
configFactory.removeGroup(groupId)
486+
lokiAPIDatabase.clearLastMessageHashes(groupId.hexString)
487+
lokiAPIDatabase.clearReceivedMessageHashValues(groupId.hexString)
482488
}
483489

484490
override suspend fun promoteMember(

app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
1414
import org.session.libsession.messaging.open_groups.OpenGroup
1515
import org.session.libsession.messaging.open_groups.OpenGroupApi
1616
import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPoller
17+
import org.session.libsession.snode.utilities.await
1718
import org.session.libsession.utilities.StringSubstitutionConstants.COMMUNITY_NAME_KEY
1819
import org.session.libsignal.utilities.Log
1920
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
@@ -74,7 +75,7 @@ object OpenGroupManager {
7475
fun getCommunitiesWriteAccessFlow() = _communityWriteAccess.asStateFlow()
7576

7677
@WorkerThread
77-
fun add(server: String, room: String, publicKey: String, context: Context): Pair<Long,OpenGroupApi.RoomInfo?> {
78+
suspend fun add(server: String, room: String, publicKey: String, context: Context): Pair<Long,OpenGroupApi.RoomInfo?> {
7879
val openGroupID = "$server.$room"
7980
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
8081
val storage = MessagingModuleConfiguration.shared.storage
@@ -90,7 +91,7 @@ object OpenGroupManager {
9091
// Store the public key
9192
storage.setOpenGroupPublicKey(server, publicKey)
9293
// Get capabilities & room info
93-
val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get()
94+
val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).await()
9495
storage.setServerCapabilities(server, capabilities.capabilities)
9596
// Create the group locally if not available already
9697
if (threadID < 0) {
@@ -161,7 +162,7 @@ object OpenGroupManager {
161162
}
162163

163164
@WorkerThread
164-
fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? {
165+
suspend fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? {
165166
val url = urlAsString.toHttpUrlOrNull() ?: return null
166167
val server = OpenGroup.getServer(urlAsString)
167168
val room = url.pathSegments.firstOrNull() ?: return null

app/src/main/java/org/thoughtcrime/securesms/groups/compose/Components.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.ui.theme.LocalColors
3131
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
3232
import org.thoughtcrime.securesms.ui.theme.LocalType
3333
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
34+
import org.thoughtcrime.securesms.ui.theme.primaryOrange
3435

3536

3637
@Composable
@@ -42,7 +43,7 @@ fun GroupMinimumVersionBanner(modifier: Modifier = Modifier) {
4243
maxLines = 2,
4344
textAlign = TextAlign.Center,
4445
modifier = Modifier
45-
.background(LocalColors.current.warning)
46+
.background(primaryOrange)
4647
.fillMaxWidth()
4748
.padding(
4849
horizontal = LocalDimensions.current.spacing,

0 commit comments

Comments
 (0)