Show Group V2 invited member dialog explaining invites on new group and add to group.
parent
ae2b6e4d7a
commit
a59e214317
|
@ -54,22 +54,12 @@ public final class GroupManager {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull GroupActionResult createGroupV1(@NonNull Context context,
|
||||
@NonNull Set<Recipient> members,
|
||||
@Nullable byte[] avatar,
|
||||
@Nullable String name,
|
||||
boolean mms)
|
||||
{
|
||||
return GroupManagerV1.createGroup(context, getMemberIds(members), avatar, name, mms);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static GroupActionResult updateGroup(@NonNull Context context,
|
||||
@NonNull GroupId groupId,
|
||||
@Nullable byte[] avatar,
|
||||
boolean avatarChanged,
|
||||
@NonNull String name,
|
||||
boolean nameChanged)
|
||||
public static GroupActionResult updateGroupDetails(@NonNull Context context,
|
||||
@NonNull GroupId groupId,
|
||||
@Nullable byte[] avatar,
|
||||
boolean avatarChanged,
|
||||
@NonNull String name,
|
||||
boolean nameChanged)
|
||||
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException
|
||||
{
|
||||
if (groupId.isV2()) {
|
||||
|
@ -80,23 +70,15 @@ public final class GroupManager {
|
|||
List<Recipient> members = DatabaseFactory.getGroupDatabase(context)
|
||||
.getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
|
||||
|
||||
return updateGroup(context, groupId.requireV1(), new HashSet<>(members), avatar, name);
|
||||
Set<RecipientId> recipientIds = getMemberIds(new HashSet<>(members));
|
||||
|
||||
return GroupManagerV1.updateGroup(context, groupId.requireV1(), recipientIds, avatar, name, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable GroupActionResult updateGroup(@NonNull Context context,
|
||||
@NonNull GroupId.V1 groupId,
|
||||
@NonNull Set<Recipient> members,
|
||||
@Nullable byte[] avatar,
|
||||
@Nullable String name)
|
||||
{
|
||||
Set<RecipientId> addresses = getMemberIds(members);
|
||||
|
||||
return GroupManagerV1.updateGroup(context, groupId, addresses, avatar, name);
|
||||
}
|
||||
|
||||
private static Set<RecipientId> getMemberIds(Collection<Recipient> recipients) {
|
||||
final Set<RecipientId> results = new HashSet<>();
|
||||
Set<RecipientId> results = new HashSet<>(recipients.size());
|
||||
|
||||
for (Recipient recipient : recipients) {
|
||||
results.add(recipient.getId());
|
||||
}
|
||||
|
@ -250,41 +232,59 @@ public final class GroupManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static void addMembers(@NonNull Context context,
|
||||
@NonNull GroupId.Push groupId,
|
||||
@NonNull Collection<RecipientId> newMembers)
|
||||
@WorkerThread
|
||||
public static @NonNull GroupActionResult addMembers(@NonNull Context context,
|
||||
@NonNull GroupId.Push groupId,
|
||||
@NonNull Collection<RecipientId> newMembers)
|
||||
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException, GroupChangeBusyException, MembershipNotSuitableForV2Exception
|
||||
{
|
||||
if (groupId.isV2()) {
|
||||
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
|
||||
editor.addMembers(newMembers);
|
||||
return editor.addMembers(newMembers);
|
||||
}
|
||||
} else {
|
||||
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).requireGroup(groupId);
|
||||
List<RecipientId> members = groupRecord.getMembers();
|
||||
byte[] avatar = groupRecord.hasAvatar() ? Util.readFully(AvatarHelper.getAvatar(context, groupRecord.getRecipientId())) : null;
|
||||
Set<RecipientId> addresses = new HashSet<>(members);
|
||||
GroupDatabase.GroupRecord groupRecord = DatabaseFactory.getGroupDatabase(context).requireGroup(groupId);
|
||||
List<RecipientId> members = groupRecord.getMembers();
|
||||
byte[] avatar = groupRecord.hasAvatar() ? Util.readFully(AvatarHelper.getAvatar(context, groupRecord.getRecipientId())) : null;
|
||||
Set<RecipientId> recipientIds = new HashSet<>(members);
|
||||
int originalSize = recipientIds.size();
|
||||
|
||||
addresses.addAll(newMembers);
|
||||
GroupManagerV1.updateGroup(context, groupId, addresses, avatar, groupRecord.getTitle());
|
||||
recipientIds.addAll(newMembers);
|
||||
return GroupManagerV1.updateGroup(context, groupId, recipientIds, avatar, groupRecord.getTitle(), recipientIds.size() - originalSize);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GroupActionResult {
|
||||
private final Recipient groupRecipient;
|
||||
private final long threadId;
|
||||
private final Recipient groupRecipient;
|
||||
private final long threadId;
|
||||
private final int addedMemberCount;
|
||||
private final List<RecipientId> invitedMembers;
|
||||
|
||||
public GroupActionResult(Recipient groupRecipient, long threadId) {
|
||||
this.groupRecipient = groupRecipient;
|
||||
this.threadId = threadId;
|
||||
public GroupActionResult(@NonNull Recipient groupRecipient,
|
||||
long threadId,
|
||||
int addedMemberCount,
|
||||
@NonNull List<RecipientId> invitedMembers)
|
||||
{
|
||||
this.groupRecipient = groupRecipient;
|
||||
this.threadId = threadId;
|
||||
this.addedMemberCount = addedMemberCount;
|
||||
this.invitedMembers = invitedMembers;
|
||||
}
|
||||
|
||||
public Recipient getGroupRecipient() {
|
||||
public @NonNull Recipient getGroupRecipient() {
|
||||
return groupRecipient;
|
||||
}
|
||||
|
||||
public long getThreadId() {
|
||||
return threadId;
|
||||
}
|
||||
|
||||
public int getAddedMemberCount() {
|
||||
return addedMemberCount;
|
||||
}
|
||||
|
||||
public @NonNull List<RecipientId> getInvitedMembers() {
|
||||
return invitedMembers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,11 +71,11 @@ final class GroupManagerV1 {
|
|||
}
|
||||
groupDatabase.onAvatarUpdated(groupIdV1, avatarBytes != null);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true);
|
||||
return sendGroupUpdate(context, groupIdV1, memberIds, name, avatarBytes);
|
||||
return sendGroupUpdate(context, groupIdV1, memberIds, name, avatarBytes, memberIds.size() - 1);
|
||||
} else {
|
||||
groupDatabase.create(groupId.requireMms(), memberIds);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION);
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
return new GroupActionResult(groupRecipient, threadId, memberIds.size() - 1, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,8 @@ final class GroupManagerV1 {
|
|||
@NonNull GroupId groupId,
|
||||
@NonNull Set<RecipientId> memberAddresses,
|
||||
@Nullable byte[] avatarBytes,
|
||||
@Nullable String name)
|
||||
@Nullable String name,
|
||||
int newMemberCount)
|
||||
{
|
||||
final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
final RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
|
||||
|
@ -102,19 +103,20 @@ final class GroupManagerV1 {
|
|||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to save avatar!", e);
|
||||
}
|
||||
return sendGroupUpdate(context, groupIdV1, memberAddresses, name, avatarBytes);
|
||||
return sendGroupUpdate(context, groupIdV1, memberAddresses, name, avatarBytes, newMemberCount);
|
||||
} else {
|
||||
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
return new GroupActionResult(groupRecipient, threadId, newMemberCount, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
private static GroupActionResult sendGroupUpdate(@NonNull Context context,
|
||||
@NonNull GroupId.V1 groupId,
|
||||
@NonNull Set<RecipientId> members,
|
||||
@Nullable String groupName,
|
||||
@Nullable byte[] avatar)
|
||||
private static GroupActionResult sendGroupUpdate(@NonNull Context context,
|
||||
@NonNull GroupId.V1 groupId,
|
||||
@NonNull Set<RecipientId> members,
|
||||
@Nullable String groupName,
|
||||
@Nullable byte[] avatar,
|
||||
int newMemberCount)
|
||||
{
|
||||
Attachment avatarAttachment = null;
|
||||
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
|
||||
|
@ -144,7 +146,7 @@ final class GroupManagerV1 {
|
|||
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList());
|
||||
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
||||
|
||||
return new GroupActionResult(groupRecipient, threadId);
|
||||
return new GroupActionResult(groupRecipient, threadId, newMemberCount, Collections.emptyList());
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
|
|
@ -156,7 +156,12 @@ final class GroupManagerV2 {
|
|||
groupDatabase.onAvatarUpdated(groupId, avatar != null);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true);
|
||||
|
||||
return sendGroupUpdate(masterKey, decryptedGroup, null, null);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, decryptedGroup, null, null);
|
||||
|
||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient,
|
||||
recipientAndThread.threadId,
|
||||
decryptedGroup.getMembersCount() - 1,
|
||||
getPendingMemberRecipientIds(decryptedGroup.getPendingMembersList()));
|
||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
|
@ -378,7 +383,7 @@ final class GroupManagerV2 {
|
|||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
|
||||
return new GroupManager.GroupActionResult(groupRecipient, threadId);
|
||||
return new GroupManager.GroupActionResult(groupRecipient, threadId, 0, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -429,7 +434,11 @@ final class GroupManagerV2 {
|
|||
GroupChange signedGroupChange = commitToServer(changeActions);
|
||||
groupDatabase.update(groupId, decryptedGroupState);
|
||||
|
||||
return sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange, signedGroupChange);
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange, signedGroupChange);
|
||||
int newMembersCount = decryptedChange.getNewMembersCount();
|
||||
List<RecipientId> newPendingMembers = getPendingMemberRecipientIds(decryptedChange.getNewPendingMembersList());
|
||||
|
||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient, recipientAndThread.threadId, newMembersCount, newPendingMembers);
|
||||
}
|
||||
|
||||
private GroupChange commitToServer(GroupChange.Actions change)
|
||||
|
@ -494,10 +503,10 @@ final class GroupManagerV2 {
|
|||
}
|
||||
}
|
||||
|
||||
private @NonNull GroupManager.GroupActionResult sendGroupUpdate(@NonNull GroupMasterKey masterKey,
|
||||
@NonNull DecryptedGroup decryptedGroup,
|
||||
@Nullable DecryptedGroupChange plainGroupChange,
|
||||
@Nullable GroupChange signedGroupChange)
|
||||
private @NonNull RecipientAndThread sendGroupUpdate(@NonNull GroupMasterKey masterKey,
|
||||
@NonNull DecryptedGroup decryptedGroup,
|
||||
@Nullable DecryptedGroupChange plainGroupChange,
|
||||
@Nullable GroupChange signedGroupChange)
|
||||
{
|
||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
|
@ -514,13 +523,19 @@ final class GroupManagerV2 {
|
|||
|
||||
if (plainGroupChange != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(plainGroupChange)) {
|
||||
ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, decryptedGroup, outgoingMessage));
|
||||
return new GroupManager.GroupActionResult(groupRecipient, -1);
|
||||
return new RecipientAndThread(groupRecipient, -1);
|
||||
} else {
|
||||
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
||||
return new GroupManager.GroupActionResult(groupRecipient, threadId);
|
||||
return new RecipientAndThread(groupRecipient, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull List<RecipientId> getPendingMemberRecipientIds(@NonNull List<DecryptedPendingMember> newPendingMembersList) {
|
||||
return Stream.of(DecryptedGroupUtil.pendingToUuidList(newPendingMembersList))
|
||||
.map(uuid-> RecipientId.from(uuid,null))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static @NonNull AccessControl.AccessRequired rightsToAccessControl(@NonNull GroupAccessControl rights) {
|
||||
switch (rights){
|
||||
case ALL_MEMBERS:
|
||||
|
@ -531,4 +546,14 @@ final class GroupManagerV2 {
|
|||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
static class RecipientAndThread {
|
||||
private final Recipient groupRecipient;
|
||||
private final long threadId;
|
||||
|
||||
RecipientAndThread(@NonNull Recipient groupRecipient, long threadId) {
|
||||
this.groupRecipient = groupRecipient;
|
||||
this.threadId = threadId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
package org.thoughtcrime.securesms.groups.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface AddMembersResultCallback {
|
||||
void onMembersAdded(int numberOfMembersAdded);
|
||||
void onMembersAdded(int numberOfMembersAdded, @NonNull List<RecipientId> invitedMembers);
|
||||
}
|
||||
|
|
|
@ -111,10 +111,17 @@ public abstract class GroupMemberEntry {
|
|||
private final UuidCiphertext inviteeCipherText;
|
||||
private final boolean cancellable;
|
||||
|
||||
public PendingMember(@NonNull Recipient invitee, @NonNull UuidCiphertext inviteeCipherText, boolean cancellable) {
|
||||
public PendingMember(@NonNull Recipient invitee, @Nullable UuidCiphertext inviteeCipherText, boolean cancellable) {
|
||||
this.invitee = invitee;
|
||||
this.inviteeCipherText = inviteeCipherText;
|
||||
this.cancellable = cancellable;
|
||||
if (cancellable && inviteeCipherText == null) {
|
||||
throw new IllegalArgumentException("inviteeCipherText must be supplied to enable cancellation");
|
||||
}
|
||||
}
|
||||
|
||||
public PendingMember(@NonNull Recipient invitee) {
|
||||
this(invitee, null, false);
|
||||
}
|
||||
|
||||
public Recipient getInvitee() {
|
||||
|
@ -122,6 +129,9 @@ public abstract class GroupMemberEntry {
|
|||
}
|
||||
|
||||
public UuidCiphertext getInviteeCipherText() {
|
||||
if (!cancellable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
return inviteeCipherText;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.creategroup.details;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
@ -14,10 +15,14 @@ import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
|||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupInviteSentDialog;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AddGroupDetailsActivity extends PassphraseRequiredActivity implements AddGroupDetailsFragment.Callback {
|
||||
|
||||
private static final String EXTRA_RECIPIENTS = "recipient_ids";
|
||||
|
@ -58,7 +63,19 @@ public class AddGroupDetailsActivity extends PassphraseRequiredActivity implemen
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onGroupCreated(@NonNull RecipientId recipientId, long threadId) {
|
||||
public void onGroupCreated(@NonNull RecipientId recipientId,
|
||||
long threadId,
|
||||
@NonNull List<Recipient> invitedMembers)
|
||||
{
|
||||
Dialog dialog = GroupInviteSentDialog.showInvitesSent(this, invitedMembers);
|
||||
if (dialog != null) {
|
||||
dialog.setOnDismissListener((d) -> goToConversation(recipientId, threadId));
|
||||
} else {
|
||||
goToConversation(recipientId, threadId);
|
||||
}
|
||||
}
|
||||
|
||||
void goToConversation(@NonNull RecipientId recipientId, long threadId) {
|
||||
Intent intent = ConversationActivity.buildIntent(this,
|
||||
recipientId,
|
||||
threadId,
|
||||
|
|
|
@ -20,7 +20,6 @@ import androidx.annotation.Nullable;
|
|||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
|
@ -45,6 +44,7 @@ import org.thoughtcrime.securesms.util.BitmapUtil;
|
|||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AddGroupDetailsFragment extends LoggingFragment {
|
||||
|
@ -202,7 +202,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
|||
}
|
||||
|
||||
private void handleGroupCreateResultSuccess(@NonNull GroupCreateResult.Success success) {
|
||||
callback.onGroupCreated(success.getGroupRecipient().getId(), success.getThreadId());
|
||||
callback.onGroupCreated(success.getGroupRecipient().getId(), success.getThreadId(), success.getInvitedMembers());
|
||||
}
|
||||
|
||||
private void handleGroupCreateResultError(@NonNull GroupCreateResult.Error error) {
|
||||
|
@ -252,7 +252,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
|
|||
}
|
||||
|
||||
public interface Callback {
|
||||
void onGroupCreated(@NonNull RecipientId recipientId, long threadId);
|
||||
void onGroupCreated(@NonNull RecipientId recipientId, long threadId, @NonNull List<Recipient> invitedMembers);
|
||||
void onNavigationButtonPressed();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.creategroup.details;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
abstract class GroupCreateResult {
|
||||
|
||||
@WorkerThread
|
||||
static GroupCreateResult success(@NonNull GroupManager.GroupActionResult result) {
|
||||
return new GroupCreateResult.Success(result.getThreadId(), result.getGroupRecipient());
|
||||
return new GroupCreateResult.Success(result.getThreadId(), result.getGroupRecipient(), result.getAddedMemberCount(), Recipient.resolvedList(result.getInvitedMembers()));
|
||||
}
|
||||
|
||||
static GroupCreateResult error(@NonNull GroupCreateResult.Error.Type errorType) {
|
||||
|
@ -20,12 +24,20 @@ abstract class GroupCreateResult {
|
|||
}
|
||||
|
||||
static final class Success extends GroupCreateResult {
|
||||
private final long threadId;
|
||||
private final Recipient groupRecipient;
|
||||
private final long threadId;
|
||||
private final Recipient groupRecipient;
|
||||
private final int addedMemberCount;
|
||||
private final List<Recipient> invitedMembers;
|
||||
|
||||
private Success(long threadId, @NonNull Recipient groupRecipient) {
|
||||
this.threadId = threadId;
|
||||
this.groupRecipient = groupRecipient;
|
||||
private Success(long threadId,
|
||||
@NonNull Recipient groupRecipient,
|
||||
int addedMemberCount,
|
||||
@NonNull List<Recipient> invitedMembers)
|
||||
{
|
||||
this.threadId = threadId;
|
||||
this.groupRecipient = groupRecipient;
|
||||
this.addedMemberCount = addedMemberCount;
|
||||
this.invitedMembers = invitedMembers;
|
||||
}
|
||||
|
||||
long getThreadId() {
|
||||
|
@ -36,6 +48,14 @@ abstract class GroupCreateResult {
|
|||
return groupRecipient;
|
||||
}
|
||||
|
||||
int getAddedMemberCount() {
|
||||
return addedMemberCount;
|
||||
}
|
||||
|
||||
List<Recipient> getInvitedMembers() {
|
||||
return invitedMembers;
|
||||
}
|
||||
|
||||
@Override
|
||||
void consume(@NonNull Consumer<Success> successConsumer,
|
||||
@NonNull Consumer<Error> errorConsumer)
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
|
|||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
import org.thoughtcrime.securesms.groups.ui.LeaveGroupDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupInviteSentDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupRightsDialog;
|
||||
import org.thoughtcrime.securesms.groups.ui.pendingmemberinvites.PendingMemberInvitesActivity;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
@ -317,6 +318,7 @@ public class ManageGroupFragment extends LoggingFragment {
|
|||
}
|
||||
|
||||
viewModel.getSnackbarEvents().observe(getViewLifecycleOwner(), this::handleSnackbarEvent);
|
||||
viewModel.getInvitedDialogEvents().observe(getViewLifecycleOwner(), this::handleInvitedDialogEvent);
|
||||
|
||||
viewModel.getCanLeaveGroup().observe(getViewLifecycleOwner(), canLeave -> leaveGroup.setVisibility(canLeave ? View.VISIBLE : View.GONE));
|
||||
viewModel.getCanBlockGroup().observe(getViewLifecycleOwner(), canBlock -> {
|
||||
|
@ -364,6 +366,10 @@ public class ManageGroupFragment extends LoggingFragment {
|
|||
Snackbar.make(requireView(), buildSnackbarString(snackbarEvent), Snackbar.LENGTH_SHORT).setTextColor(Color.WHITE).show();
|
||||
}
|
||||
|
||||
private void handleInvitedDialogEvent(@NonNull ManageGroupViewModel.InvitedDialogEvent invitedDialogEvent) {
|
||||
GroupInviteSentDialog.showInvitesSent(requireContext(), invitedDialogEvent.getNewInvitedMembers());
|
||||
}
|
||||
|
||||
private @NonNull String buildSnackbarString(@NonNull ManageGroupViewModel.SnackbarEvent snackbarEvent) {
|
||||
return getResources().getQuantityString(R.plurals.ManageGroupActivity_added,
|
||||
snackbarEvent.getNumberOfMembersAdded(),
|
||||
|
|
|
@ -146,8 +146,8 @@ final class ManageGroupRepository {
|
|||
void addMembers(@NonNull List<RecipientId> selected, @NonNull AddMembersResultCallback addMembersResultCallback, @NonNull GroupChangeErrorCallback error) {
|
||||
SignalExecutors.UNBOUNDED.execute(() -> {
|
||||
try {
|
||||
GroupManager.addMembers(context, groupId.requirePush(), selected);
|
||||
addMembersResultCallback.onMembersAdded(selected.size());
|
||||
GroupManager.GroupActionResult groupActionResult = GroupManager.addMembers(context, groupId.requirePush(), selected);
|
||||
addMembersResultCallback.onMembersAdded(groupActionResult.getAddedMemberCount(), groupActionResult.getInvitedMembers());
|
||||
} catch (GroupInsufficientRightsException | GroupNotAMemberException e) {
|
||||
Log.w(TAG, e);
|
||||
error.onError(GroupChangeFailureReason.NO_RIGHTS);
|
||||
|
|
|
@ -25,8 +25,6 @@ import org.thoughtcrime.securesms.database.MediaDatabase;
|
|||
import org.thoughtcrime.securesms.database.loaders.MediaLoader;
|
||||
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
|
||||
import org.thoughtcrime.securesms.groups.GroupAccessControl;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
|
||||
import org.thoughtcrime.securesms.groups.GroupChangeFailedException;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.LiveGroup;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||
|
@ -43,7 +41,6 @@ import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
|||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -54,6 +51,7 @@ public class ManageGroupViewModel extends ViewModel {
|
|||
private final Context context;
|
||||
private final ManageGroupRepository manageGroupRepository;
|
||||
private final SingleLiveEvent<SnackbarEvent> snackbarEvents = new SingleLiveEvent<>();
|
||||
private final SingleLiveEvent<InvitedDialogEvent> invitedDialogEvents = new SingleLiveEvent<>();
|
||||
private final LiveData<String> title;
|
||||
private final LiveData<Boolean> isAdmin;
|
||||
private final LiveData<Boolean> canEditGroupAttributes;
|
||||
|
@ -91,7 +89,7 @@ public class ManageGroupViewModel extends ViewModel {
|
|||
(state, hasEnoughMembers) -> state != CollapseState.OPEN && hasEnoughMembers);
|
||||
this.members = LiveDataUtil.combineLatest(liveGroup.getFullMembers(),
|
||||
memberListCollapseState,
|
||||
this::filterMemberList);
|
||||
ManageGroupViewModel::filterMemberList);
|
||||
this.pendingMemberCount = liveGroup.getPendingMemberCount();
|
||||
this.memberCountSummary = liveGroup.getMembershipCountDescription(context.getResources());
|
||||
this.fullMemberCountSummary = liveGroup.getFullMembershipCountDescription(context.getResources());
|
||||
|
@ -180,6 +178,10 @@ public class ManageGroupViewModel extends ViewModel {
|
|||
return snackbarEvents;
|
||||
}
|
||||
|
||||
SingleLiveEvent<InvitedDialogEvent> getInvitedDialogEvents() {
|
||||
return invitedDialogEvents;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getCanCollapseMemberList() {
|
||||
return canCollapseMemberList;
|
||||
}
|
||||
|
@ -220,7 +222,7 @@ public class ManageGroupViewModel extends ViewModel {
|
|||
}
|
||||
|
||||
void onAddMembers(List<RecipientId> selected) {
|
||||
manageGroupRepository.addMembers(selected, this::showSuccessSnackbar, this::showErrorToast);
|
||||
manageGroupRepository.addMembers(selected, this::showAddSuccess, this::showErrorToast);
|
||||
}
|
||||
|
||||
void setMuteUntil(long muteUntil) {
|
||||
|
@ -235,8 +237,8 @@ public class ManageGroupViewModel extends ViewModel {
|
|||
memberListCollapseState.setValue(CollapseState.OPEN);
|
||||
}
|
||||
|
||||
private @NonNull List<GroupMemberEntry.FullMember> filterMemberList(@NonNull List<GroupMemberEntry.FullMember> members,
|
||||
@NonNull CollapseState collapseState)
|
||||
private static @NonNull List<GroupMemberEntry.FullMember> filterMemberList(@NonNull List<GroupMemberEntry.FullMember> members,
|
||||
@NonNull CollapseState collapseState)
|
||||
{
|
||||
if (collapseState == CollapseState.COLLAPSED && members.size() > MAX_COLLAPSED_MEMBERS) {
|
||||
return members.subList(0, MAX_COLLAPSED_MEMBERS);
|
||||
|
@ -246,8 +248,14 @@ public class ManageGroupViewModel extends ViewModel {
|
|||
}
|
||||
|
||||
@WorkerThread
|
||||
private void showSuccessSnackbar(int numberOfMembersAdded) {
|
||||
snackbarEvents.postValue(new SnackbarEvent(numberOfMembersAdded));
|
||||
private void showAddSuccess(int numberOfMembersAdded, @NonNull List<RecipientId> newInvitedMembers) {
|
||||
if (!newInvitedMembers.isEmpty()) {
|
||||
invitedDialogEvents.postValue(new InvitedDialogEvent(Recipient.resolvedList(newInvitedMembers)));
|
||||
}
|
||||
|
||||
if (numberOfMembersAdded > 0) {
|
||||
snackbarEvents.postValue(new SnackbarEvent(numberOfMembersAdded));
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
@ -328,6 +336,19 @@ public class ManageGroupViewModel extends ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
static final class InvitedDialogEvent {
|
||||
|
||||
private final List<Recipient> newInvitedMembers;
|
||||
|
||||
private InvitedDialogEvent(@NonNull List<Recipient> newInvitedMembers) {
|
||||
this.newInvitedMembers = newInvitedMembers;
|
||||
}
|
||||
|
||||
public @NonNull List<Recipient> getNewInvitedMembers() {
|
||||
return newInvitedMembers;
|
||||
}
|
||||
}
|
||||
|
||||
private enum CollapseState {
|
||||
OPEN,
|
||||
COLLAPSED
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package org.thoughtcrime.securesms.groups.ui.managegroup.dialogs;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class GroupInviteSentDialog {
|
||||
|
||||
private GroupInviteSentDialog() {
|
||||
}
|
||||
|
||||
public static @Nullable Dialog showInvitesSent(@NonNull Context context, @NonNull List<Recipient> recipients) {
|
||||
int size = recipients.size();
|
||||
if (size == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context)
|
||||
.setTitle(context.getResources().getQuantityString(R.plurals.GroupManagement_invitation_sent, size, size))
|
||||
// TODO: GV2 Need a URL for learn more
|
||||
// .setNegativeButton(R.string.GroupManagement_learn_more, (dialog, which) -> {
|
||||
// })
|
||||
.setPositiveButton(android.R.string.ok, null);
|
||||
if (size == 1) {
|
||||
builder.setMessage(context.getString(R.string.GroupManagement_invite_single_user, recipients.get(0).getDisplayName(context)));
|
||||
} else {
|
||||
builder.setMessage(R.string.GroupManagement_invite_multiple_users)
|
||||
.setView(R.layout.dialog_multiple_group_invites_sent);
|
||||
}
|
||||
|
||||
Dialog dialog = builder.show();
|
||||
if (size > 1) {
|
||||
GroupMemberListView invitees = dialog.findViewById(R.id.list_invitees);
|
||||
|
||||
List<GroupMemberEntry.PendingMember> pendingMembers = new ArrayList<>(recipients.size());
|
||||
for (Recipient r : recipients) {
|
||||
pendingMembers.add(new GroupMemberEntry.PendingMember(r));
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
invitees.setMembers(pendingMembers);
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
}
|
|
@ -80,7 +80,7 @@ class EditPushGroupProfileRepository implements EditProfileRepository {
|
|||
{
|
||||
SimpleTask.run(() -> {
|
||||
try {
|
||||
GroupManager.updateGroup(context, groupId, avatar, avatarChanged, displayName, displayNameChanged);
|
||||
GroupManager.updateGroupDetails(context, groupId, avatar, avatarChanged, displayName, displayNameChanged);
|
||||
|
||||
return UploadResult.SUCCESS;
|
||||
} catch (GroupChangeFailedException | GroupInsufficientRightsException | IOException | GroupNotAMemberException | GroupChangeBusyException e) {
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
|||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
@ -125,6 +126,17 @@ public class Recipient {
|
|||
return live(id).resolve();
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public static @NonNull List<Recipient> resolvedList(@NonNull Collection<RecipientId> ids) {
|
||||
List<Recipient> recipients = new ArrayList<>(ids.size());
|
||||
|
||||
for (RecipientId recipientId : ids) {
|
||||
recipients.add(resolved(recipientId));
|
||||
}
|
||||
|
||||
return recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a fully-populated {@link Recipient} and associates it with the provided username.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.thoughtcrime.securesms.groups.ui.GroupMemberListView
|
||||
android:id="@+id/list_invitees"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="vertical"
|
||||
android:scrollIndicators="top|bottom"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:maxHeight="100dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -472,6 +472,15 @@
|
|||
<item>@string/GroupManagement_access_level_only_admins</item>
|
||||
</array>
|
||||
|
||||
<!-- GV2 invites sent -->
|
||||
<plurals name="GroupManagement_invitation_sent">
|
||||
<item quantity="one">Invitation sent</item>
|
||||
<item quantity="other">%d invitations sent</item>
|
||||
</plurals>
|
||||
<string name="GroupManagement_invite_single_user">“%1$s” can’t be automatically added to this group by you.\n\nThey’ve been invited to join, and won’t see any group messages until they accept.</string>
|
||||
<string name="GroupManagement_learn_more">Learn more</string>
|
||||
<string name="GroupManagement_invite_multiple_users">These users can’t be automatically added to this group by you.\n\nThey’ve been invited to join the group, and won’t see any group messages until they accept.</string>
|
||||
|
||||
<!-- PendingMembersActivity -->
|
||||
<string name="PendingMemberInvitesActivity_pending_group_invites">Pending group invites</string>
|
||||
<string name="PendingMembersActivity_people_you_invited">People you invited</string>
|
||||
|
|
Loading…
Reference in New Issue