Show Group V2 invited member dialog explaining invites on new group and add to group.

master
Alan Evans 2020-07-16 17:50:46 -03:00 committed by Greyson Parrelli
parent ae2b6e4d7a
commit a59e214317
16 changed files with 293 additions and 89 deletions

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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)

View File

@ -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(),

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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.
*/

View File

@ -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>

View File

@ -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” cant be automatically added to this group by you.\n\nTheyve been invited to join, and wont see any group messages until they accept.</string>
<string name="GroupManagement_learn_more">Learn more</string>
<string name="GroupManagement_invite_multiple_users">These users cant be automatically added to this group by you.\n\nTheyve been invited to join the group, and wont 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>