diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt index 4a2dbbdc37..00cc6d8028 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/BaseGroupMembersViewModel.kt @@ -44,11 +44,12 @@ abstract class BaseGroupMembersViewModel( private val storage: StorageProtocol, private val configFactory: ConfigFactoryProtocol, private val avatarUtils: AvatarUtils, - private val recipientRepository: RecipientRepository, + private val recipientRepository: RecipientRepository ) : ViewModel() { private val groupId = groupAddress.accountId // Output: the source-of-truth group information. Other states are derived from this. + @OptIn(FlowPreview::class) protected val groupInfo: StateFlow>?> = (configFactory.configUpdateNotifications .filter { @@ -65,21 +66,26 @@ abstract class BaseGroupMembersViewModel( val displayInfo = storage.getClosedGroupDisplayInfo(groupId.hexString) ?: return@withContext null - val rawMembers = configFactory.withGroupConfigs(groupId) { it.groupMembers.allWithStatus() } + val rawMembers = + configFactory.withGroupConfigs(groupId) { it.groupMembers.allWithStatus() } val memberState = mutableListOf() for ((member, status) in rawMembers) { - memberState.add(createGroupMember( - member = member, status = status, - shouldShowProBadge = recipientRepository.getRecipient(member.accountId().toAddress()).shouldShowProBadge, - myAccountId = currentUserId, - amIAdmin = displayInfo.isUserAdmin - )) + memberState.add( + createGroupMember( + member = member, status = status, + shouldShowProBadge = recipientRepository.getRecipient( + member.accountId().toAddress() + ).shouldShowProBadge, + myAccountId = currentUserId, + amIAdmin = displayInfo.isUserAdmin + ) + ) } displayInfo to sortMembers(memberState, currentUserId) } - }.stateIn(viewModelScope, SharingStarted.Eagerly, null) + }.stateIn(viewModelScope, SharingStarted.Eagerly, null) // Current group name (for header / text, if needed) val groupName: StateFlow = groupInfo @@ -174,7 +180,7 @@ abstract class BaseGroupMembersViewModel( canResendInvite = amIAdmin && memberAccountId != myAccountId && !member.isRemoved(status) && (status == GroupMember.Status.INVITE_SENT || status == GroupMember.Status.INVITE_FAILED), - status = status.takeIf { !isMyself }, // Status is only meant for other members + status = status.takeIf { !isMyself }, // status is only for other members highlightStatus = highlightStatus, showAsAdmin = member.isAdminOrBeingPromoted(status), showProBadge = shouldShowProBadge, diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt index aa28e2c98e..b1805de9e7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManagerV2Impl.kt @@ -6,11 +6,13 @@ import com.squareup.phrase.Phrase import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull import network.loki.messenger.R import network.loki.messenger.libsession_util.ED25519 import network.loki.messenger.libsession_util.Namespace @@ -44,6 +46,7 @@ import org.session.libsession.snode.OwnedSwarmAuth import org.session.libsession.snode.SnodeMessage import org.session.libsession.snode.model.BatchResponse import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Address.Companion.toAddress import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY import org.session.libsession.utilities.getGroup import org.session.libsession.utilities.recipients.Recipient @@ -75,6 +78,7 @@ import org.thoughtcrime.securesms.util.SessionMetaProtocol import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton +import kotlin.time.Duration.Companion.seconds private const val TAG = "GroupManagerV2Impl" @@ -520,12 +524,17 @@ class GroupManagerV2Impl @Inject constructor( // Update the group member's promotion status members.asSequence() .mapNotNull { configs.groupMembers.get(it.hexString) } - .onEach(GroupMember::setPromoted) + .onEach(GroupMember::setPromotionSent) .forEach(configs.groupMembers::set) configs.groupInfo.getName() } + // Ensure this push is complete before promotion messages go out + withTimeoutOrNull(10.seconds) { + configFactory.waitUntilGroupConfigsPushed(group) + } + // Build a group update message to the group telling members someone has been promoted val timestamp = clock.currentTimeMillis() val signature = ED25519.sign( @@ -589,10 +598,11 @@ class GroupManagerV2Impl @Inject constructor( promotedByMemberIDs.asSequence() .mapNotNull { (member, result) -> configs.groupMembers.get(member.hexString)?.apply { - if (result.isSuccess) { - setPromotionSent() - } else { - setPromotionFailed() + if (result.isFailure) { + configs.groupMembers.get(member.hexString)?.let { member -> + member.setPromotionFailed() + configs.groupMembers.set(member) + } } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt index 976092aa0c..c4ba642cc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMembersViewModel.kt @@ -18,7 +18,6 @@ import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsignal.utilities.AccountId import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.RecipientRepository -import org.thoughtcrime.securesms.pro.ProStatusManager import org.thoughtcrime.securesms.util.AvatarUtils @@ -29,7 +28,7 @@ class GroupMembersViewModel @AssistedInject constructor( storage: StorageProtocol, configFactory: ConfigFactoryProtocol, avatarUtils: AvatarUtils, - recipientRepository: RecipientRepository, + recipientRepository: RecipientRepository ) : BaseGroupMembersViewModel(address, context, storage, configFactory, avatarUtils, recipientRepository) { private val _navigationActions = Channel() diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ManageGroupAdminsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/ManageGroupAdminsViewModel.kt index 46394ebd10..39dcfe406c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ManageGroupAdminsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ManageGroupAdminsViewModel.kt @@ -10,10 +10,8 @@ import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R @@ -84,7 +82,7 @@ class ManageGroupAdminsViewModel @AssistedInject constructor( init { // Build footer from selected admins + collapsed state viewModelScope.launch { - kotlinx.coroutines.flow.combine( + combine( selectedAdmins, footerCollapsed, ::buildFooterState diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/PromoteMembersViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/PromoteMembersViewModel.kt index 9cd0f408bd..fafdb4c9a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/PromoteMembersViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/PromoteMembersViewModel.kt @@ -9,16 +9,13 @@ import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import network.loki.messenger.R import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.utilities.Address import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY @@ -34,7 +31,6 @@ class PromoteMembersViewModel @AssistedInject constructor( @ApplicationContext private val context: Context, storage: StorageProtocol, private val configFactory: ConfigFactoryProtocol, - private val groupManager: GroupManagerV2, private val recipientRepository: RecipientRepository, avatarUtils: AvatarUtils, ) : BaseGroupMembersViewModel( @@ -45,7 +41,6 @@ class PromoteMembersViewModel @AssistedInject constructor( avatarUtils = avatarUtils, recipientRepository = recipientRepository ) { - private val groupId = groupAddress.accountId private val _mutableSelectedMembers = MutableStateFlow(emptySet()) val selectedMembers: StateFlow> = _mutableSelectedMembers